From 8ab7589d65c4f8ca799f2a6886d128f4c36ff046 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 30 Nov 2021 23:30:27 +0200 Subject: [PATCH] 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. --- source/backends/vulkan/buffer_backend.cpp | 2 +- source/backends/vulkan/commands_backend.cpp | 2 +- .../backends/vulkan/framebuffer_backend.cpp | 4 +- source/backends/vulkan/framebuffer_backend.h | 2 +- source/backends/vulkan/synchronizer.cpp | 124 ++++++++++++++---- source/backends/vulkan/synchronizer.h | 7 +- source/backends/vulkan/texture1d_backend.cpp | 2 +- source/backends/vulkan/texture2d_backend.cpp | 2 +- source/backends/vulkan/texture3d_backend.cpp | 4 +- source/backends/vulkan/texture_backend.cpp | 8 +- source/backends/vulkan/texture_backend.h | 2 +- .../backends/vulkan/texturecube_backend.cpp | 2 +- 12 files changed, 116 insertions(+), 45 deletions(-) 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, -- 2.43.0