From: Mikko Rasa Date: Tue, 30 Nov 2021 21:30:27 +0000 (+0200) Subject: Refactor Synchronizer to deal with individual mipmap levels X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=8ab7589d65c4f8ca799f2a6886d128f4c36ff046 Refactor Synchronizer to deal with individual mipmap levels Some algorithms read from one mipmap level and write to another. On the other hand, there's currently no need for layer granularity. --- diff --git a/source/backends/vulkan/buffer_backend.cpp b/source/backends/vulkan/buffer_backend.cpp index 29dd15ef..9290b42a 100644 --- a/source/backends/vulkan/buffer_backend.cpp +++ b/source/backends/vulkan/buffer_backend.cpp @@ -52,7 +52,7 @@ void VulkanBuffer::allocate() void VulkanBuffer::sub_data(size_t off, size_t sz, const void *d) { - device.get_synchronizer().access(handle, off, sz); + device.get_synchronizer().write_buffer(handle, off, sz); void *staging = device.get_transfer_queue().prepare_transfer(sz, [this, off, sz](VkCommandBuffer cmd_buf, VkBuffer staging_buf, size_t src_off){ const VulkanFunctions &vk = device.get_functions(); diff --git a/source/backends/vulkan/commands_backend.cpp b/source/backends/vulkan/commands_backend.cpp index 6f5703f6..d55a4dfd 100644 --- a/source/backends/vulkan/commands_backend.cpp +++ b/source/backends/vulkan/commands_backend.cpp @@ -85,7 +85,7 @@ void VulkanCommands::begin_render_pass(bool clear, const ClearValue *clear_value if(dynamic_cast(framebuffer->get_attachment(i))) to_present = true; if(!to_present) - framebuffer->synchronize(clear); + framebuffer->prepare_image_layouts(clear); VkRenderPass render_pass = device.get_pipeline_cache().get_render_pass(framebuffer->get_format(), clear, !clear_values, to_present); framebuffer->refresh(); diff --git a/source/backends/vulkan/framebuffer_backend.cpp b/source/backends/vulkan/framebuffer_backend.cpp index 9320fffb..2ebd8b33 100644 --- a/source/backends/vulkan/framebuffer_backend.cpp +++ b/source/backends/vulkan/framebuffer_backend.cpp @@ -82,10 +82,10 @@ void VulkanFramebuffer::update(unsigned) const set_vulkan_object_name(); } -void VulkanFramebuffer::synchronize(bool discard) const +void VulkanFramebuffer::prepare_image_layouts(bool discard) const { for(const Framebuffer::Attachment &a: static_cast(this)->attachments) - a.tex->synchronize(a.layer, get_vulkan_attachment_layout(get_components(a.tex->get_format())), discard); + a.tex->change_layout(0, a.level, get_vulkan_attachment_layout(get_components(a.tex->get_format())), discard); } void VulkanFramebuffer::set_debug_name(const string &name) diff --git a/source/backends/vulkan/framebuffer_backend.h b/source/backends/vulkan/framebuffer_backend.h index 9a616875..b990d307 100644 --- a/source/backends/vulkan/framebuffer_backend.h +++ b/source/backends/vulkan/framebuffer_backend.h @@ -31,7 +31,7 @@ protected: void update(unsigned) const; void require_complete() const { } - void synchronize(bool = false) const; + void prepare_image_layouts(bool = false) const; void set_debug_name(const std::string &); void set_vulkan_object_name() const; diff --git a/source/backends/vulkan/synchronizer.cpp b/source/backends/vulkan/synchronizer.cpp index c2c245a5..3ec04026 100644 --- a/source/backends/vulkan/synchronizer.cpp +++ b/source/backends/vulkan/synchronizer.cpp @@ -1,6 +1,7 @@ #include #include "buffer.h" #include "device.h" +#include "error.h" #include "texture.h" #include "synchronizer.h" #include "vulkan.h" @@ -14,12 +15,12 @@ Synchronizer::Synchronizer(Device &d): device(d) { } -void Synchronizer::access(VkBuffer buffer, size_t offset, size_t size) +void Synchronizer::write_buffer(VkBuffer buffer, size_t offset, size_t size) { - auto i = find_member(buffer_accesses, buffer, &BufferAccess::buffer); - if(i==buffer_accesses.end()) + auto i = lower_bound_member(buffer_accesses, buffer, &BufferAccess::buffer); + if(i==buffer_accesses.end() || i->buffer!=buffer) { - i = buffer_accesses.emplace(buffer_accesses.end()); + i = buffer_accesses.emplace(i); i->buffer = buffer; i->offset = offset; i->size = size; @@ -35,23 +36,79 @@ void Synchronizer::access(VkBuffer buffer, size_t offset, size_t size) i->pending_write = true; } -void Synchronizer::access(VkImage image, unsigned aspect, int layer, unsigned layout, bool discard) +void Synchronizer::split_image_mipmap(VkImage image, unsigned aspect, unsigned n_levels) { - auto i = find_member(image_accesses, image, &ImageAccess::image); - if(i==image_accesses.end()) + if(!n_levels) + throw invalid_argument("Synchronizer::split_image_mipmap"); + + auto i = lower_bound_member(image_accesses, image, &ImageAccess::image); + if(i!=image_accesses.end() && i->image==image && i->level>=0) + return; + + if(i!=image_accesses.end() && i->image==image && i->level==-1) + { + i = image_accesses.insert(i, n_levels-1, *i); + for(unsigned j=0; jlevel = j; + } + else + { + ImageAccess access; + access.image = image; + access.aspect = aspect; + access.current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + access.pending_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + if(i->level==-2) + i = image_accesses.erase(i); + + for(unsigned j=0; jimage!=image || i->level>static_cast(j)) + { + i = image_accesses.insert(i, access); + i->level = j; + } + } +} + +void Synchronizer::change_image_layout(VkImage image, unsigned aspect, int level, unsigned layout, bool discard) +{ + auto i = lower_bound_member(image_accesses, image, &ImageAccess::image); + + if(level>=0) + { + if(i==image_accesses.end() || i->image!=image) + { + i = image_accesses.emplace(i); + i->image = image; + i->level = -2; + ++i; + } + else if(i->level==-1) + throw invalid_operation("Synchronizer::change_image_layout"); + else + { + for(; (i!=image_accesses.end() && i->image==image && i->levelimage==image && i->level==-2) + throw invalid_operation("Synchronizer::change_image_layout"); + + if(i==image_accesses.end() || i->image!=image || (level>=0 && i->level!=level)) { - i = image_accesses.emplace(image_accesses.end()); + i = image_accesses.emplace(i); i->image = image; i->aspect = aspect; - i->layer = layer; + i->level = (level<0 ? -1 : level); i->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } - if(discard) - i->current_layout = VK_IMAGE_LAYOUT_UNDEFINED; - if(layer!=i->layer) - i->layer = -1; - i->pending_layout = layout; + for(; (i!=image_accesses.end() && i->image==image && (level<0 || i->level==level)); ++i) + { + if(discard) + i->current_layout = VK_IMAGE_LAYOUT_UNDEFINED; + i->pending_layout = layout; + } } void Synchronizer::reset() @@ -108,7 +165,7 @@ void Synchronizer::barrier(VkCommandBuffer command_buffer) image_barriers.reserve(image_accesses.size()); for(const ImageAccess &i: image_accesses) { - if(i.pending_layout==i.current_layout) + if(i.level==-2 || i.pending_layout==i.current_layout) continue; image_barriers.emplace_back(VkImageMemoryBarrier{ }); @@ -123,18 +180,10 @@ void Synchronizer::barrier(VkCommandBuffer command_buffer) barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = handle_cast<::VkImage>(i.image); barrier.subresourceRange.aspectMask = i.aspect; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; - if(i.layer>=0) - { - barrier.subresourceRange.baseArrayLayer = i.layer; - barrier.subresourceRange.layerCount = 1; - } - else - { - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; - } + barrier.subresourceRange.baseMipLevel = max(i.level, 0); + barrier.subresourceRange.levelCount = (i.level<0 ? VK_REMAINING_MIP_LEVELS : 1); + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; if(i.current_layout!=VK_IMAGE_LAYOUT_UNDEFINED) src_stage |= (is_write_layout(i.current_layout) ? image_write_stages : image_read_stages); @@ -163,10 +212,29 @@ void Synchronizer::barrier(VkCommandBuffer command_buffer) } } + bool sparse_levels = false; for(auto i=image_accesses.begin(); i!=image_accesses.end(); ) { - if(i->pending_layout==VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + if(i->level==-2) + { + sparse_levels = true; + ++i; + } + else if(i->pending_layout==VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + VkImage image = i->image; i = image_accesses.erase(i); + if(i->image!=image) + { + if(sparse_levels) + { + auto j = prev(i); + if(j->level==-2) + i = image_accesses.erase(j); + } + sparse_levels = false; + } + } else { i->current_layout = i->pending_layout; diff --git a/source/backends/vulkan/synchronizer.h b/source/backends/vulkan/synchronizer.h index 48699540..78c3443e 100644 --- a/source/backends/vulkan/synchronizer.h +++ b/source/backends/vulkan/synchronizer.h @@ -15,7 +15,7 @@ private: { VkImage image = 0; unsigned aspect; - int layer = -1; + int level = -1; unsigned current_layout; unsigned pending_layout; }; @@ -36,8 +36,9 @@ private: public: Synchronizer(Device &); - void access(VkBuffer, std::size_t, std::size_t); - void access(VkImage, unsigned, int, unsigned, bool); + void write_buffer(VkBuffer, std::size_t, std::size_t); + void split_image_mipmap(VkImage, unsigned, unsigned); + void change_image_layout(VkImage, unsigned, int, unsigned, bool); void reset(); void barrier(VkCommandBuffer); diff --git a/source/backends/vulkan/texture1d_backend.cpp b/source/backends/vulkan/texture1d_backend.cpp index aaa3c385..91c6252a 100644 --- a/source/backends/vulkan/texture1d_backend.cpp +++ b/source/backends/vulkan/texture1d_backend.cpp @@ -27,7 +27,7 @@ void VulkanTexture1D::sub_image(unsigned level, int x, unsigned wd, const void * const Texture1D &self = *static_cast(this); unsigned level_size = self.get_level_size(level); - synchronize(-1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && wd==level_size)); + change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && wd==level_size)); size_t data_size = wd*get_pixel_size(storage_fmt); void *staging = device.get_transfer_queue().prepare_transfer(data_size, diff --git a/source/backends/vulkan/texture2d_backend.cpp b/source/backends/vulkan/texture2d_backend.cpp index 2d6ee131..d3db86d3 100644 --- a/source/backends/vulkan/texture2d_backend.cpp +++ b/source/backends/vulkan/texture2d_backend.cpp @@ -28,7 +28,7 @@ void VulkanTexture2D::sub_image(unsigned level, int x, int y, unsigned wd, unsig const Texture2D &self = *static_cast(this); auto level_size = self.get_level_size(level); - synchronize(-1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size.x && ht==level_size.y)); + change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size.x && ht==level_size.y)); size_t data_size = wd*ht*get_pixel_size(storage_fmt); void *staging = device.get_transfer_queue().prepare_transfer(data_size, diff --git a/source/backends/vulkan/texture3d_backend.cpp b/source/backends/vulkan/texture3d_backend.cpp index 7a11b35e..03a95734 100644 --- a/source/backends/vulkan/texture3d_backend.cpp +++ b/source/backends/vulkan/texture3d_backend.cpp @@ -33,8 +33,8 @@ void VulkanTexture3D::sub_image(unsigned level, int x, int y, int z, unsigned wd const Texture3D &self = *static_cast(this); auto level_size = self.get_level_size(level); - int layer = (is_array() && dp==1 ? z : -1); - synchronize(layer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && z==0 && wd==level_size.x && ht==level_size.y && dp==level_size.z)); + bool discard = (x==0 && y==0 && z==0 && wd==level_size.x && ht==level_size.y && dp==level_size.z); + change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, discard); size_t data_size = wd*ht*dp*get_pixel_size(storage_fmt); void *staging = device.get_transfer_queue().prepare_transfer(data_size, diff --git a/source/backends/vulkan/texture_backend.cpp b/source/backends/vulkan/texture_backend.cpp index 06c4a622..f1e8b85b 100644 --- a/source/backends/vulkan/texture_backend.cpp +++ b/source/backends/vulkan/texture_backend.cpp @@ -73,7 +73,7 @@ void VulkanTexture::allocate() memory_id = device.get_allocator().allocate(handle, DEVICE_MEMORY); // Trigger a layout transition if the image is used before uploading data. - synchronize(-1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, true); + change_layout(0, -1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, true); } VkImageViewCreateInfo view_info = { }; @@ -105,10 +105,12 @@ void VulkanTexture::generate_mipmap() throw logic_error("VulkanTexture::generate_mipmap is unimplemented"); } -void VulkanTexture::synchronize(int layer, unsigned layout, bool discard) const +void VulkanTexture::change_layout(unsigned n_levels, int level, unsigned layout, bool discard) const { unsigned aspect = get_vulkan_aspect(get_components(static_cast(this)->storage_fmt)); - device.get_synchronizer().access(handle, aspect, layer, layout, discard); + if(n_levels>0) + device.get_synchronizer().split_image_mipmap(handle, aspect, n_levels); + device.get_synchronizer().change_image_layout(handle, aspect, level, layout, discard); } void VulkanTexture::set_debug_name(const string &name) diff --git a/source/backends/vulkan/texture_backend.h b/source/backends/vulkan/texture_backend.h index 4784ae9a..123c6e4b 100644 --- a/source/backends/vulkan/texture_backend.h +++ b/source/backends/vulkan/texture_backend.h @@ -32,7 +32,7 @@ protected: void generate_mipmap(); - void synchronize(int, unsigned, bool = false) const; + void change_layout(unsigned, int, unsigned, bool) const; void set_debug_name(const std::string &); void set_vulkan_object_names() const; diff --git a/source/backends/vulkan/texturecube_backend.cpp b/source/backends/vulkan/texturecube_backend.cpp index fe528921..38348f34 100644 --- a/source/backends/vulkan/texturecube_backend.cpp +++ b/source/backends/vulkan/texturecube_backend.cpp @@ -30,7 +30,7 @@ void VulkanTextureCube::sub_image(unsigned face, unsigned level, int x, int y, u const TextureCube &self = *static_cast(this); unsigned level_size = self.get_level_size(level); - synchronize(face, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size && ht==level_size)); + change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size && ht==level_size)); size_t data_size = wd*ht*get_pixel_size(storage_fmt); void *staging = device.get_transfer_queue().prepare_transfer(data_size,