From aa69ec7f80fb3a29c16b97e5f67e86342ed0a727 Mon Sep 17 00:00:00 2001 From: Jeroen Bakker Date: Tue, 28 Mar 2023 11:51:32 +0200 Subject: [PATCH] Vulkan: Clearing Framebuffer + Scissors This PR adds support for clearing framebuffers and scissor testing. Tweaks had to be made to VKTexture to keep track of its layout on the device. Based on the actual call the layout can switch to a more optimum layout. For example during upload of a texture the texture will be converted to a transfer destination optimized layout. When reading from the texture it will be converted to a transfer source optimized layout. The order of the attachments in the framebuffer follows the next rules - When only color attachments are there the color attachments will be placed in the slot they are defined. This way it will match the ShaderCreateInfo binding location. - When a stencil/depth attachment is added it will be placed right after the color attachments. When there isn't a color attachment it will be the first attachment. Pull Request: https://projects.blender.org/blender/blender/pulls/106044 --- source/blender/gpu/CMakeLists.txt | 1 + source/blender/gpu/tests/framebuffer_test.cc | 200 ++++++++++++ .../blender/gpu/vulkan/vk_command_buffer.cc | 26 +- .../blender/gpu/vulkan/vk_command_buffer.hh | 15 +- source/blender/gpu/vulkan/vk_common.cc | 42 +++ source/blender/gpu/vulkan/vk_common.hh | 1 + source/blender/gpu/vulkan/vk_context.cc | 42 ++- source/blender/gpu/vulkan/vk_context.hh | 6 + source/blender/gpu/vulkan/vk_framebuffer.cc | 297 +++++++++++++++++- source/blender/gpu/vulkan/vk_framebuffer.hh | 43 ++- source/blender/gpu/vulkan/vk_texture.cc | 147 +++++---- source/blender/gpu/vulkan/vk_texture.hh | 43 ++- 12 files changed, 782 insertions(+), 81 deletions(-) create mode 100644 source/blender/gpu/tests/framebuffer_test.cc diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index aefa41ccdc5..b0eaf664025 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -832,6 +832,7 @@ if(WITH_GTESTS) set(TEST_SRC tests/gpu_testing.cc + tests/framebuffer_test.cc tests/index_buffer_test.cc tests/push_constants_test.cc tests/shader_test.cc diff --git a/source/blender/gpu/tests/framebuffer_test.cc b/source/blender/gpu/tests/framebuffer_test.cc new file mode 100644 index 00000000000..02d8ae82fb7 --- /dev/null +++ b/source/blender/gpu/tests/framebuffer_test.cc @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "testing/testing.h" + +#include "GPU_framebuffer.h" +#include "gpu_testing.hh" + +#include "BLI_math_vector.hh" + +namespace blender::gpu::tests { + +static void test_framebuffer_clear_color_single_attachment() +{ + const int2 size(10, 10); + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; + GPUTexture *texture = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr); + + GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__); + GPU_framebuffer_ensure_config(&framebuffer, + {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture)}); + GPU_framebuffer_bind(framebuffer); + + const float4 clear_color(0.1f, 0.2f, 0.5f, 1.0f); + GPU_framebuffer_clear_color(framebuffer, clear_color); + GPU_finish(); + + float4 *read_data = static_cast(GPU_texture_read(texture, GPU_DATA_FLOAT, 0)); + for (float4 pixel_color : Span(read_data, size.x * size.y)) { + EXPECT_EQ(pixel_color, clear_color); + } + MEM_freeN(read_data); + + GPU_framebuffer_free(framebuffer); + GPU_texture_free(texture); +} +GPU_TEST(framebuffer_clear_color_single_attachment); + +static void test_framebuffer_clear_color_multiple_attachments() +{ + const int2 size(10, 10); + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; + GPUTexture *texture1 = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr); + GPUTexture *texture2 = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_RGBA32UI, usage, nullptr); + + GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__); + GPU_framebuffer_ensure_config( + &framebuffer, + {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture1), GPU_ATTACHMENT_TEXTURE(texture2)}); + GPU_framebuffer_bind(framebuffer); + + const float4 clear_color(0.1f, 0.2f, 0.5f, 1.0f); + GPU_framebuffer_clear_color(framebuffer, clear_color); + GPU_finish(); + + float4 *read_data1 = static_cast(GPU_texture_read(texture1, GPU_DATA_FLOAT, 0)); + for (float4 pixel_color : Span(read_data1, size.x * size.y)) { + EXPECT_EQ(pixel_color, clear_color); + } + MEM_freeN(read_data1); + + uint4 *read_data2 = static_cast(GPU_texture_read(texture2, GPU_DATA_UINT, 0)); + uint4 clear_color_uint(1036831949, 1045220557, 1056964608, 1065353216); + for (uint4 pixel_color : Span(read_data2, size.x * size.y)) { + EXPECT_EQ(pixel_color, clear_color_uint); + } + MEM_freeN(read_data2); + + GPU_framebuffer_free(framebuffer); + GPU_texture_free(texture1); + GPU_texture_free(texture2); +} +GPU_TEST(framebuffer_clear_color_multiple_attachments); + +static void test_framebuffer_clear_multiple_color_multiple_attachments() +{ + const int2 size(10, 10); + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; + GPUTexture *texture1 = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr); + GPUTexture *texture2 = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr); + + GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__); + GPU_framebuffer_ensure_config( + &framebuffer, + {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture1), GPU_ATTACHMENT_TEXTURE(texture2)}); + GPU_framebuffer_bind(framebuffer); + + const float4 clear_color[2] = {float4(0.1f, 0.2f, 0.5f, 1.0f), float4(0.5f, 0.2f, 0.1f, 1.0f)}; + GPU_framebuffer_multi_clear( + framebuffer, static_cast(static_cast(clear_color))); + GPU_finish(); + + float4 *read_data1 = static_cast(GPU_texture_read(texture1, GPU_DATA_FLOAT, 0)); + for (float4 pixel_color : Span(read_data1, size.x * size.y)) { + EXPECT_EQ(pixel_color, clear_color[0]); + } + MEM_freeN(read_data1); + + float4 *read_data2 = static_cast(GPU_texture_read(texture2, GPU_DATA_FLOAT, 0)); + for (float4 pixel_color : Span(read_data1, size.x * size.y)) { + EXPECT_EQ(pixel_color, clear_color[1]); + } + MEM_freeN(read_data2); + + GPU_framebuffer_free(framebuffer); + GPU_texture_free(texture1); + GPU_texture_free(texture2); +} +GPU_TEST(framebuffer_clear_multiple_color_multiple_attachments); + +static void test_framebuffer_clear_depth() +{ + const int2 size(10, 10); + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; + GPUTexture *texture = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_DEPTH_COMPONENT32F, usage, nullptr); + + GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__); + GPU_framebuffer_ensure_config(&framebuffer, {GPU_ATTACHMENT_TEXTURE(texture)}); + GPU_framebuffer_bind(framebuffer); + + const float clear_depth = 0.5f; + GPU_framebuffer_clear_depth(framebuffer, clear_depth); + GPU_finish(); + + float *read_data = static_cast(GPU_texture_read(texture, GPU_DATA_FLOAT, 0)); + for (float pixel_depth : Span(read_data, size.x * size.y)) { + EXPECT_EQ(pixel_depth, clear_depth); + } + MEM_freeN(read_data); + + GPU_framebuffer_free(framebuffer); + GPU_texture_free(texture); +} +GPU_TEST(framebuffer_clear_depth); + +static void test_framebuffer_scissor_test() +{ + const int2 size(128, 128); + const int bar_size = 16; + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ; + GPUTexture *texture = GPU_texture_create_2d( + __func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr); + + GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__); + GPU_framebuffer_ensure_config(&framebuffer, + {GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture)}); + GPU_framebuffer_bind(framebuffer); + + const float4 color1(0.0f); + const float4 color2(0.5f); + const float4 color3(1.0f); + GPU_framebuffer_clear_color(framebuffer, color1); + + GPU_scissor_test(true); + for (int x = 0; x < size.x; x += 2 * bar_size) { + GPU_scissor(x, 0, bar_size, size.y); + GPU_framebuffer_clear_color(framebuffer, color2); + } + for (int y = 0; y < size.y; y += 2 * bar_size) { + GPU_scissor(0, y, size.x, bar_size); + GPU_framebuffer_clear_color(framebuffer, color3); + } + GPU_scissor_test(false); + GPU_finish(); + + float4 *read_data = static_cast(GPU_texture_read(texture, GPU_DATA_FLOAT, 0)); + int offset = 0; + for (float4 pixel_color : Span(read_data, size.x * size.y)) { + int x = offset % size.x; + int y = offset / size.x; + int bar_x = x / bar_size; + int bar_y = y / bar_size; + + if (bar_y % 2 == 0) { + EXPECT_EQ(pixel_color, color3); + } + else { + if (bar_x % 2 == 0) { + EXPECT_EQ(pixel_color, color2); + } + else { + EXPECT_EQ(pixel_color, color1); + } + } + + offset++; + } + MEM_freeN(read_data); + + GPU_framebuffer_free(framebuffer); + GPU_texture_free(texture); +} +GPU_TEST(framebuffer_scissor_test); + +} // namespace blender::gpu::tests diff --git a/source/blender/gpu/vulkan/vk_command_buffer.cc b/source/blender/gpu/vulkan/vk_command_buffer.cc index 4c2001cc1a1..e41dd71df0a 100644 --- a/source/blender/gpu/vulkan/vk_command_buffer.cc +++ b/source/blender/gpu/vulkan/vk_command_buffer.cc @@ -8,6 +8,7 @@ #include "vk_command_buffer.hh" #include "vk_buffer.hh" #include "vk_context.hh" +#include "vk_framebuffer.hh" #include "vk_memory.hh" #include "vk_pipeline.hh" #include "vk_texture.hh" @@ -73,6 +74,21 @@ void VKCommandBuffer::bind(const VKDescriptorSet &descriptor_set, vk_command_buffer_, bind_point, vk_pipeline_layout, 0, 1, &vk_descriptor_set, 0, 0); } +void VKCommandBuffer::begin_render_pass(const VKFrameBuffer &framebuffer) +{ + VkRenderPassBeginInfo render_pass_begin_info = {}; + render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin_info.renderPass = framebuffer.vk_render_pass_get(); + render_pass_begin_info.framebuffer = framebuffer.vk_framebuffer_get(); + render_pass_begin_info.renderArea = framebuffer.vk_render_area_get(); + vkCmdBeginRenderPass(vk_command_buffer_, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE); +} + +void VKCommandBuffer::end_render_pass(const VKFrameBuffer & /*framebuffer*/) +{ + vkCmdEndRenderPass(vk_command_buffer_); +} + void VKCommandBuffer::push_constants(const VKPushConstants &push_constants, const VkPipelineLayout vk_pipeline_layout, const VkShaderStageFlags vk_shader_stages) @@ -98,7 +114,7 @@ void VKCommandBuffer::copy(VKBuffer &dst_buffer, { vkCmdCopyImageToBuffer(vk_command_buffer_, src_texture.vk_image_handle(), - VK_IMAGE_LAYOUT_GENERAL, + src_texture.current_layout_get(), dst_buffer.vk_handle(), regions.size(), regions.data()); @@ -110,7 +126,7 @@ void VKCommandBuffer::copy(VKTexture &dst_texture, vkCmdCopyBufferToImage(vk_command_buffer_, src_buffer.vk_handle(), dst_texture.vk_image_handle(), - VK_IMAGE_LAYOUT_GENERAL, + dst_texture.current_layout_get(), regions.size(), regions.data()); } @@ -128,6 +144,12 @@ void VKCommandBuffer::clear(VkImage vk_image, ranges.data()); } +void VKCommandBuffer::clear(Span attachments, Span areas) +{ + vkCmdClearAttachments( + vk_command_buffer_, attachments.size(), attachments.data(), areas.size(), areas.data()); +} + void VKCommandBuffer::pipeline_barrier(VkPipelineStageFlags source_stages, VkPipelineStageFlags destination_stages) { diff --git a/source/blender/gpu/vulkan/vk_command_buffer.hh b/source/blender/gpu/vulkan/vk_command_buffer.hh index f06e554d049..4ea87af7b14 100644 --- a/source/blender/gpu/vulkan/vk_command_buffer.hh +++ b/source/blender/gpu/vulkan/vk_command_buffer.hh @@ -14,10 +14,11 @@ namespace blender::gpu { class VKBuffer; -class VKTexture; -class VKPushConstants; -class VKPipeline; class VKDescriptorSet; +class VKFrameBuffer; +class VKPipeline; +class VKPushConstants; +class VKTexture; /** Command buffer to keep track of the life-time of a command buffer. */ class VKCommandBuffer : NonCopyable, NonMovable { @@ -39,6 +40,9 @@ class VKCommandBuffer : NonCopyable, NonMovable { void bind(const VKDescriptorSet &descriptor_set, const VkPipelineLayout vk_pipeline_layout, VkPipelineBindPoint bind_point); + void begin_render_pass(const VKFrameBuffer &framebuffer); + void end_render_pass(const VKFrameBuffer &framebuffer); + /** * Add a push constant command to the command buffer. * @@ -61,6 +65,11 @@ class VKCommandBuffer : NonCopyable, NonMovable { VkImageLayout vk_image_layout, const VkClearColorValue &vk_clear_color, Span ranges); + + /** + * Clear attachments of the active framebuffer. + */ + void clear(Span attachments, Span areas); void fill(VKBuffer &buffer, uint32_t data); /** diff --git a/source/blender/gpu/vulkan/vk_common.cc b/source/blender/gpu/vulkan/vk_common.cc index 08d46962418..cfb16415646 100644 --- a/source/blender/gpu/vulkan/vk_common.cc +++ b/source/blender/gpu/vulkan/vk_common.cc @@ -307,4 +307,46 @@ VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat /*format*/) return component_mapping; } +template void copy_color(T dst[4], const T *src) +{ + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; +} + +VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const void *data) +{ + VkClearColorValue result = {0.0f}; + switch (format) { + case GPU_DATA_FLOAT: { + const float *float_data = static_cast(data); + copy_color(result.float32, float_data); + break; + } + + case GPU_DATA_INT: { + const int32_t *int_data = static_cast(data); + copy_color(result.int32, int_data); + break; + } + + case GPU_DATA_UINT: { + const uint32_t *uint_data = static_cast(data); + copy_color(result.uint32, uint_data); + break; + } + + case GPU_DATA_HALF_FLOAT: + case GPU_DATA_UBYTE: + case GPU_DATA_UINT_24_8: + case GPU_DATA_10_11_11_REV: + case GPU_DATA_2_10_10_10_REV: { + BLI_assert_unreachable(); + break; + } + } + return result; +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_common.hh b/source/blender/gpu/vulkan/vk_common.hh index af661342e57..10596a84989 100644 --- a/source/blender/gpu/vulkan/vk_common.hh +++ b/source/blender/gpu/vulkan/vk_common.hh @@ -24,5 +24,6 @@ VkFormat to_vk_format(const eGPUTextureFormat format); VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat format); VkImageViewType to_vk_image_view_type(const eGPUTextureType type); VkImageType to_vk_image_type(const eGPUTextureType type); +VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const void *data); } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index 6528a2b8aca..3812bf0b9a0 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -51,7 +51,7 @@ VKContext::VKContext(void *ghost_window, void *ghost_context) VKBackend::capabilities_init(*this); /* For off-screen contexts. Default frame-buffer is empty. */ - active_fb = back_left = new VKFrameBuffer("back_left"); + back_left = new VKFrameBuffer("back_left"); } VKContext::~VKContext() @@ -71,19 +71,24 @@ void VKContext::activate() { if (ghost_window_) { VkImage image; /* TODO will be used for reading later... */ - VkFramebuffer framebuffer; + VkFramebuffer vk_framebuffer; VkRenderPass render_pass; VkExtent2D extent; uint32_t fb_id; GHOST_GetVulkanBackbuffer( - (GHOST_WindowHandle)ghost_window_, &image, &framebuffer, &render_pass, &extent, &fb_id); + (GHOST_WindowHandle)ghost_window_, &image, &vk_framebuffer, &render_pass, &extent, &fb_id); /* Recreate the gpu::VKFrameBuffer wrapper after every swap. */ + if (has_active_framebuffer()) { + deactivate_framebuffer(); + } delete back_left; - back_left = new VKFrameBuffer("back_left", framebuffer, render_pass, extent); - active_fb = back_left; + VKFrameBuffer *framebuffer = new VKFrameBuffer( + "back_left", vk_framebuffer, render_pass, extent); + back_left = framebuffer; + framebuffer->bind(false); } } @@ -113,6 +118,9 @@ void VKContext::flush() void VKContext::finish() { + if (has_active_framebuffer()) { + deactivate_framebuffer(); + } command_buffer_.submit(); } @@ -120,4 +128,28 @@ void VKContext::memory_statistics_get(int * /*total_mem*/, int * /*free_mem*/) { } +void VKContext::activate_framebuffer(VKFrameBuffer &framebuffer) +{ + if (has_active_framebuffer()) { + deactivate_framebuffer(); + } + + BLI_assert(active_fb == nullptr); + active_fb = &framebuffer; + command_buffer_.begin_render_pass(framebuffer); +} + +bool VKContext::has_active_framebuffer() const +{ + return active_fb != nullptr; +} + +void VKContext::deactivate_framebuffer() +{ + BLI_assert(active_fb != nullptr); + VKFrameBuffer *framebuffer = unwrap(active_fb); + command_buffer_.end_render_pass(*framebuffer); + active_fb = nullptr; +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_context.hh b/source/blender/gpu/vulkan/vk_context.hh index b646492faec..d5c0a03dcba 100644 --- a/source/blender/gpu/vulkan/vk_context.hh +++ b/source/blender/gpu/vulkan/vk_context.hh @@ -13,6 +13,7 @@ #include "vk_descriptor_pools.hh" namespace blender::gpu { +class VKFrameBuffer; class VKContext : public Context { private: @@ -55,6 +56,9 @@ class VKContext : public Context { bool debug_capture_scope_begin(void *scope) override; void debug_capture_scope_end(void *scope) override; + void activate_framebuffer(VKFrameBuffer &framebuffer); + void deactivate_framebuffer(); + static VKContext *get(void) { return static_cast(Context::get()); @@ -102,6 +106,8 @@ class VKContext : public Context { private: void init_physical_device_limits(); + + bool has_active_framebuffer() const; }; } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_framebuffer.cc b/source/blender/gpu/vulkan/vk_framebuffer.cc index 70bd5f0fbaf..026373794df 100644 --- a/source/blender/gpu/vulkan/vk_framebuffer.cc +++ b/source/blender/gpu/vulkan/vk_framebuffer.cc @@ -6,6 +6,8 @@ */ #include "vk_framebuffer.hh" +#include "vk_memory.hh" +#include "vk_texture.hh" namespace blender::gpu { @@ -20,7 +22,7 @@ VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name) VKFrameBuffer::VKFrameBuffer(const char *name, VkFramebuffer vk_framebuffer, - VkRenderPass /*vk_render_pass*/, + VkRenderPass vk_render_pass, VkExtent2D vk_extent) : FrameBuffer(name) { @@ -30,6 +32,7 @@ VKFrameBuffer::VKFrameBuffer(const char *name, width_ = vk_extent.width; height_ = vk_extent.height; vk_framebuffer_ = vk_framebuffer; + vk_render_pass_ = vk_render_pass; viewport_[0] = scissor_[0] = 0; viewport_[1] = scissor_[1] = 0; @@ -39,8 +42,8 @@ VKFrameBuffer::VKFrameBuffer(const char *name, VKFrameBuffer::~VKFrameBuffer() { - if (!immutable_ && vk_framebuffer_ != VK_NULL_HANDLE) { - vkDestroyFramebuffer(vk_device_, vk_framebuffer_, NULL); + if (!immutable_) { + render_pass_free(); } } @@ -48,6 +51,32 @@ VKFrameBuffer::~VKFrameBuffer() void VKFrameBuffer::bind(bool /*enabled_srgb*/) { + update_attachments(); + + VKContext &context = *VKContext::get(); + context.activate_framebuffer(*this); +} + +VkRect2D VKFrameBuffer::vk_render_area_get() const +{ + VkRect2D render_area = {}; + + if (scissor_test_get()) { + int scissor_rect[4]; + scissor_get(scissor_rect); + render_area.offset.x = scissor_rect[0]; + render_area.offset.y = scissor_rect[1]; + render_area.extent.width = scissor_rect[2]; + render_area.extent.height = scissor_rect[3]; + } + else { + render_area.offset.x = 0; + render_area.offset.y = 0; + render_area.extent.width = width_; + render_area.extent.height = height_; + } + + return render_area; } bool VKFrameBuffer::check(char /*err_out*/[256]) @@ -55,29 +84,110 @@ bool VKFrameBuffer::check(char /*err_out*/[256]) return false; } -void VKFrameBuffer::clear(eGPUFrameBufferBits /*buffers*/, - const float /*clear_col*/[4], - float /*clear_depth*/, - uint /*clear_stencil*/) +void VKFrameBuffer::build_clear_attachments_depth_stencil( + const eGPUFrameBufferBits buffers, + float clear_depth, + uint32_t clear_stencil, + Vector &r_attachments) const { + VkClearAttachment clear_attachment = {}; + clear_attachment.aspectMask = (buffers & GPU_DEPTH_BIT ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) | + (buffers & GPU_STENCIL_BIT ? VK_IMAGE_ASPECT_STENCIL_BIT : 0); + clear_attachment.clearValue.depthStencil.depth = clear_depth; + clear_attachment.clearValue.depthStencil.stencil = clear_stencil; + r_attachments.append(clear_attachment); } -void VKFrameBuffer::clear_multi(const float (*/*clear_col*/)[4]) +void VKFrameBuffer::build_clear_attachments_color(const float (*clear_colors)[4], + const bool multi_clear_colors, + Vector &r_attachments) const { + int color_index = 0; + for (int color_slot = 0; color_slot < GPU_FB_MAX_COLOR_ATTACHMENT; color_slot++) { + const GPUAttachment &attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + color_slot]; + if (attachment.tex == nullptr) { + continue; + } + VkClearAttachment clear_attachment = {}; + clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + clear_attachment.colorAttachment = color_slot; + eGPUDataFormat data_format = to_data_format(GPU_texture_format(attachment.tex)); + clear_attachment.clearValue.color = to_vk_clear_color_value(data_format, + &clear_colors[color_index]); + r_attachments.append(clear_attachment); + + color_index += multi_clear_colors ? 1 : 0; + } +} + +/* -------------------------------------------------------------------- */ +/** \name Clear + * \{ */ + +void VKFrameBuffer::clear(const Vector &attachments) const +{ + VkClearRect clear_rect = {}; + clear_rect.rect = vk_render_area_get(); + clear_rect.baseArrayLayer = 0; + clear_rect.layerCount = 1; + + VKContext &context = *VKContext::get(); + VKCommandBuffer &command_buffer = context.command_buffer_get(); + command_buffer.clear(attachments, Span(&clear_rect, 1)); +} + +void VKFrameBuffer::clear(const eGPUFrameBufferBits buffers, + const float clear_color[4], + float clear_depth, + uint clear_stencil) +{ + Vector attachments; + if (buffers & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)) { + build_clear_attachments_depth_stencil(buffers, clear_depth, clear_stencil, attachments); + } + if (buffers & GPU_COLOR_BIT) { + float clear_color_single[4]; + copy_v4_v4(clear_color_single, clear_color); + build_clear_attachments_color(&clear_color_single, false, attachments); + } + clear(attachments); +} + +void VKFrameBuffer::clear_multi(const float (*clear_color)[4]) +{ + Vector attachments; + build_clear_attachments_color(clear_color, true, attachments); + clear(attachments); } void VKFrameBuffer::clear_attachment(GPUAttachmentType /*type*/, eGPUDataFormat /*data_format*/, const void * /*clear_value*/) { + /* Clearing of a single attachment was added to implement `clear_multi` in OpenGL. As + * `clear_multi` is supported in Vulkan it isn't needed to implement this method. + */ + BLI_assert_unreachable(); } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Load/Store operations + * \{ */ + void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType /*type*/, eGPULoadOp /*load_action*/, eGPUStoreOp /*store_action*/) { } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Read back + * \{ */ + void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/, eGPUDataFormat /*format*/, const int /*area*/[4], @@ -87,6 +197,12 @@ void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/, { } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Blit operations + * \{ */ + void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/, int /*src_slot*/, FrameBuffer * /*dst*/, @@ -96,4 +212,169 @@ void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/, { } +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Update attachments + * \{ */ + +void VKFrameBuffer::update_attachments() +{ + if (immutable_) { + return; + } + if (!dirty_attachments_) { + return; + } + + render_pass_free(); + render_pass_create(); + + dirty_attachments_ = false; +} + +void VKFrameBuffer::render_pass_create() +{ + BLI_assert(!immutable_); + BLI_assert(vk_render_pass_ == VK_NULL_HANDLE); + BLI_assert(vk_framebuffer_ == VK_NULL_HANDLE); + + VK_ALLOCATION_CALLBACKS + + /* Track first attachment for size.*/ + GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHMENT; + + std::array attachment_descriptions; + std::array image_views; + std::array attachment_references; + /*Vector color_attachments; + VkAttachmentReference depth_attachment = {}; + */ + bool has_depth_attachment = false; + bool found_attachment = false; + int depth_location = -1; + + for (int type = GPU_FB_MAX_ATTACHMENT - 1; type >= 0; type--) { + GPUAttachment &attachment = attachments_[type]; + if (attachment.tex == nullptr && !found_attachment) { + /* Move the depth texture to the next binding point after all color textures. The binding + * location of the color textures should be kept in sync between ShaderCreateInfos and the + * framebuffer attachments. The depth buffer should be the last slot. */ + depth_location = max_ii(type - GPU_FB_COLOR_ATTACHMENT0, 0); + continue; + } + found_attachment |= attachment.tex != nullptr; + + /* Keep the first attachment to the first color attachment, or to the depth buffer when there + * is no color attachment. */ + if (attachment.tex != nullptr && + (first_attachment == GPU_FB_MAX_ATTACHMENT || type >= GPU_FB_COLOR_ATTACHMENT0)) { + first_attachment = static_cast(type); + } + + int attachment_location = type >= GPU_FB_COLOR_ATTACHMENT0 ? type - GPU_FB_COLOR_ATTACHMENT0 : + depth_location; + + if (attachment.tex) { + /* Ensure texture is allocated to ensure the image view.*/ + VKTexture &texture = *static_cast(unwrap(attachment.tex)); + texture.ensure_allocated(); + image_views[attachment_location] = texture.vk_image_view_handle(); + + VkAttachmentDescription &attachment_description = + attachment_descriptions[attachment_location]; + attachment_description.flags = 0; + attachment_description.format = to_vk_format(texture.format_get()); + attachment_description.samples = VK_SAMPLE_COUNT_1_BIT; + attachment_description.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment_description.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment_description.initialLayout = VK_IMAGE_LAYOUT_GENERAL; + attachment_description.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + + /* Create the attachment reference. */ + const bool is_depth_attachment = ELEM( + type, GPU_FB_DEPTH_ATTACHMENT, GPU_FB_DEPTH_STENCIL_ATTACHMENT); + + BLI_assert_msg(!is_depth_attachment || !has_depth_attachment, + "There can only be one depth/stencil attachment."); + has_depth_attachment |= is_depth_attachment; + VkAttachmentReference &attachment_reference = attachment_references[attachment_location]; + attachment_reference.attachment = attachment_location; + attachment_reference.layout = is_depth_attachment ? + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : + VK_IMAGE_LAYOUT_GENERAL; + } + } + + /* Update the size, viewport & scissor based on the first attachment. */ + if (first_attachment != GPU_FB_MAX_ATTACHMENT) { + GPUAttachment &attachment = attachments_[first_attachment]; + BLI_assert(attachment.tex); + + int size[3]; + GPU_texture_get_mipmap_size(attachment.tex, attachment.mip, size); + size_set(size[0], size[1]); + } + else { + this->size_set(0, 0); + } + viewport_reset(); + scissor_reset(); + + /* Create render pass. */ + + const int attachment_len = has_depth_attachment ? depth_location + 1 : depth_location; + const int color_attachment_len = depth_location; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = color_attachment_len; + subpass.pColorAttachments = attachment_references.begin(); + if (has_depth_attachment) { + subpass.pDepthStencilAttachment = &attachment_references[depth_location]; + } + + VkRenderPassCreateInfo render_pass_info = {}; + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_info.attachmentCount = attachment_len; + render_pass_info.pAttachments = attachment_descriptions.data(); + render_pass_info.subpassCount = 1; + render_pass_info.pSubpasses = &subpass; + + VKContext &context = *VKContext::get(); + vkCreateRenderPass( + context.device_get(), &render_pass_info, vk_allocation_callbacks, &vk_render_pass_); + + /* We might want to split framebuffer and render pass....*/ + VkFramebufferCreateInfo framebuffer_create_info = {}; + framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebuffer_create_info.renderPass = vk_render_pass_; + framebuffer_create_info.attachmentCount = attachment_len; + framebuffer_create_info.pAttachments = image_views.begin(); + framebuffer_create_info.width = width_; + framebuffer_create_info.height = height_; + framebuffer_create_info.layers = 1; + + vkCreateFramebuffer( + context.device_get(), &framebuffer_create_info, vk_allocation_callbacks, &vk_framebuffer_); +} + +void VKFrameBuffer::render_pass_free() +{ + BLI_assert(!immutable_); + if (vk_render_pass_ == VK_NULL_HANDLE) { + return; + } + VK_ALLOCATION_CALLBACKS + + VKContext &context = *VKContext::get(); + vkDestroyRenderPass(context.device_get(), vk_render_pass_, vk_allocation_callbacks); + vkDestroyFramebuffer(context.device_get(), vk_framebuffer_, vk_allocation_callbacks); + vk_render_pass_ = VK_NULL_HANDLE; + vk_framebuffer_ = VK_NULL_HANDLE; +} + +/** \} */ + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_framebuffer.hh b/source/blender/gpu/vulkan/vk_framebuffer.hh index aed52304030..15ed5bb3f83 100644 --- a/source/blender/gpu/vulkan/vk_framebuffer.hh +++ b/source/blender/gpu/vulkan/vk_framebuffer.hh @@ -7,6 +7,10 @@ #pragma once +#include "BLI_math_vector.hh" +#include "BLI_span.hh" +#include "BLI_vector.hh" + #include "gpu_framebuffer_private.hh" #include "vk_common.hh" @@ -20,7 +24,7 @@ class VKFrameBuffer : public FrameBuffer { /* Vulkan device who created the handle. */ VkDevice vk_device_ = VK_NULL_HANDLE; /* Base render pass used for framebuffer creation. */ - VkRenderPass render_pass_ = VK_NULL_HANDLE; + VkRenderPass vk_render_pass_ = VK_NULL_HANDLE; /* Number of layers if the attachments are layered textures. */ int depth_ = 1; /** Internal frame-buffers are immutable. */ @@ -46,10 +50,10 @@ class VKFrameBuffer : public FrameBuffer { void bind(bool enabled_srgb) override; bool check(char err_out[256]) override; void clear(eGPUFrameBufferBits buffers, - const float clear_col[4], + const float clear_color[4], float clear_depth, uint clear_stencil) override; - void clear_multi(const float (*clear_col)[4]) override; + void clear_multi(const float (*clear_color)[4]) override; void clear_attachment(GPUAttachmentType type, eGPUDataFormat data_format, const void *clear_value) override; @@ -71,6 +75,39 @@ class VKFrameBuffer : public FrameBuffer { int dst_slot, int dst_offset_x, int dst_offset_y) override; + + VkFramebuffer vk_framebuffer_get() const + { + BLI_assert(vk_framebuffer_ != VK_NULL_HANDLE); + return vk_framebuffer_; + } + + VkRenderPass vk_render_pass_get() const + { + BLI_assert(vk_render_pass_ != VK_NULL_HANDLE); + return vk_render_pass_; + } + VkRect2D vk_render_area_get() const; + + private: + void update_attachments(); + void render_pass_free(); + void render_pass_create(); + + /* Clearing attachments */ + void build_clear_attachments_depth_stencil(eGPUFrameBufferBits buffers, + float clear_depth, + uint32_t clear_stencil, + Vector &r_attachments) const; + void build_clear_attachments_color(const float (*clear_colors)[4], + const bool multi_clear_colors, + Vector &r_attachments) const; + void clear(const Vector &attachments) const; }; +static inline VKFrameBuffer *unwrap(FrameBuffer *framebuffer) +{ + return static_cast(framebuffer); +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_texture.cc b/source/blender/gpu/vulkan/vk_texture.cc index 129a4d305f7..a57c9ff47f3 100644 --- a/source/blender/gpu/vulkan/vk_texture.cc +++ b/source/blender/gpu/vulkan/vk_texture.cc @@ -37,48 +37,6 @@ void VKTexture::copy_to(Texture * /*tex*/) { } -template void copy_color(T dst[4], const T *src) -{ - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = src[3]; -} - -static VkClearColorValue to_vk_clear_color_value(eGPUDataFormat format, const void *data) -{ - VkClearColorValue result = {{0.0f}}; - switch (format) { - case GPU_DATA_FLOAT: { - const float *float_data = static_cast(data); - copy_color(result.float32, float_data); - break; - } - - case GPU_DATA_INT: { - const int32_t *int_data = static_cast(data); - copy_color(result.int32, int_data); - break; - } - - case GPU_DATA_UINT: { - const uint32_t *uint_data = static_cast(data); - copy_color(result.uint32, uint_data); - break; - } - - case GPU_DATA_HALF_FLOAT: - case GPU_DATA_UBYTE: - case GPU_DATA_UINT_24_8: - case GPU_DATA_10_11_11_REV: - case GPU_DATA_2_10_10_10_REV: { - BLI_assert_unreachable(); - break; - } - } - return result; -} - void VKTexture::clear(eGPUDataFormat format, const void *data) { if (!is_allocated()) { @@ -92,9 +50,10 @@ void VKTexture::clear(eGPUDataFormat format, const void *data) range.aspectMask = to_vk_image_aspect_flag_bits(format_); range.levelCount = VK_REMAINING_MIP_LEVELS; range.layerCount = VK_REMAINING_ARRAY_LAYERS; + layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL); command_buffer.clear( - vk_image_, VK_IMAGE_LAYOUT_GENERAL, clear_color, Span(&range, 1)); + vk_image_, current_layout_get(), clear_color, Span(&range, 1)); } void VKTexture::swizzle_set(const char /*swizzle_mask*/[4]) @@ -111,8 +70,10 @@ void VKTexture::mip_range_set(int /*min*/, int /*max*/) void *VKTexture::read(int mip, eGPUDataFormat format) { - /* Vulkan images cannot be directly mapped to host memory and requires a staging buffer. */ VKContext &context = *VKContext::get(); + layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + /* Vulkan images cannot be directly mapped to host memory and requires a staging buffer. */ VKBuffer staging_buffer; /* NOTE: mip_size_get() won't override any dimension that is equal to 0. */ @@ -170,6 +131,7 @@ void VKTexture::update_sub( region.imageSubresource.mipLevel = mip; region.imageSubresource.layerCount = 1; + layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VKCommandBuffer &command_buffer = context.command_buffer_get(); command_buffer.copy(*this, staging_buffer, Span(®ion, 1)); command_buffer.submit(); @@ -208,11 +170,51 @@ bool VKTexture::init_internal(const GPUTexture * /*src*/, int /*mip_offset*/, in return false; } -bool VKTexture::is_allocated() +void VKTexture::ensure_allocated() +{ + if (!is_allocated()) { + allocate(); + } +} + +bool VKTexture::is_allocated() const { return vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE; } +static VkImageUsageFlagBits to_vk_image_usage(const eGPUTextureUsage usage, + const eGPUTextureFormatFlag format_flag) +{ + VkImageUsageFlagBits result = static_cast(VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT); + if (usage & GPU_TEXTURE_USAGE_SHADER_READ) { + result = static_cast(result | VK_IMAGE_USAGE_STORAGE_BIT); + } + if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) { + result = static_cast(result | VK_IMAGE_USAGE_STORAGE_BIT); + } + if (usage & GPU_TEXTURE_USAGE_ATTACHMENT) { + if (format_flag & (GPU_FORMAT_NORMALIZED_INTEGER | GPU_FORMAT_COMPRESSED)) { + /* These formats aren't supported as an attachment. When using GPU_TEXTURE_USAGE_DEFAULT they + * are still being evaluated to be attachable. So we need to skip them.*/ + } + else { + if (format_flag & (GPU_FORMAT_DEPTH | GPU_FORMAT_STENCIL)) { + result = static_cast(result | + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + } + else { + result = static_cast(result | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + } + } + } + if (usage & GPU_TEXTURE_USAGE_HOST_READ) { + result = static_cast(result | VK_IMAGE_USAGE_TRANSFER_SRC_BIT); + } + + return result; +} + bool VKTexture::allocate() { BLI_assert(!is_allocated()); @@ -230,10 +232,14 @@ bool VKTexture::allocate() image_info.mipLevels = 1; image_info.arrayLayers = 1; image_info.format = to_vk_format(format_); - image_info.tiling = VK_IMAGE_TILING_LINEAR; + /* Some platforms (NVIDIA) requires that attached textures are always tiled optimal. + * + * As image data are always accessed via an staging buffer we can enable optimal tiling for all + * texture. Tilings based on actual usages should be done in `VKFramebuffer`. + */ + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; + image_info.usage = to_vk_image_usage(gpu_image_usage_flags_, format_flag_); image_info.samples = VK_SAMPLE_COUNT_1_BIT; VkResult result; @@ -254,8 +260,6 @@ bool VKTexture::allocate() VmaAllocationCreateInfo allocCreateInfo = {}; allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; - allocCreateInfo.flags = static_cast( - VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT); allocCreateInfo.priority = 1.0f; result = vmaCreateImage(context.mem_allocator_get(), &image_info, @@ -268,15 +272,7 @@ bool VKTexture::allocate() } /* Promote image to the correct layout. */ - VkImageMemoryBarrier barrier{}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - barrier.image = vk_image_; - barrier.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_); - barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; - barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; - context.command_buffer_get().pipeline_barrier(Span(&barrier, 1)); + layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL); VK_ALLOCATION_CALLBACKS VkImageViewCreateInfo image_view_info = {}; @@ -307,4 +303,37 @@ void VKTexture::image_bind(int binding) shader->pipeline_get().descriptor_set_get().image_bind(*this, location); } +/* -------------------------------------------------------------------- */ +/** \name Image Layout + * \{ */ + +VkImageLayout VKTexture::current_layout_get() const +{ + return current_layout_; +} + +void VKTexture::current_layout_set(const VkImageLayout new_layout) +{ + current_layout_ = new_layout; +} + +void VKTexture::layout_ensure(VKContext &context, const VkImageLayout requested_layout) +{ + const VkImageLayout current_layout = current_layout_get(); + if (current_layout == requested_layout) { + return; + } + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = current_layout; + barrier.newLayout = requested_layout; + barrier.image = vk_image_; + barrier.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_); + barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + context.command_buffer_get().pipeline_barrier(Span(&barrier, 1)); + current_layout_set(requested_layout); +} +/** \} */ + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_texture.hh b/source/blender/gpu/vulkan/vk_texture.hh index 37a4b063139..29e5be95b47 100644 --- a/source/blender/gpu/vulkan/vk_texture.hh +++ b/source/blender/gpu/vulkan/vk_texture.hh @@ -17,6 +17,12 @@ class VKTexture : public Texture { VkImageView vk_image_view_ = VK_NULL_HANDLE; VmaAllocation allocation_ = VK_NULL_HANDLE; + /* Last image layout of the texture. Framebuffer and barriers can alter/require the actual layout + * to be changed. During this it requires to set the current layout in order to know which + * conversion should happen. #current_layout_ keep track of the layout so the correct conversion + * can be done.*/ + VkImageLayout current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED; + public: VKTexture(const char *name) : Texture(name) { @@ -43,13 +49,17 @@ class VKTexture : public Texture { void image_bind(int location); VkImage vk_image_handle() const { + BLI_assert(is_allocated()); return vk_image_; } VkImageView vk_image_view_handle() const { + BLI_assert(is_allocated()); return vk_image_view_; } + void ensure_allocated(); + protected: bool init_internal() override; bool init_internal(GPUVertBuf *vbo) override; @@ -57,7 +67,8 @@ class VKTexture : public Texture { private: /** Is this texture already allocated on device. */ - bool is_allocated(); + bool is_allocated() const; + /** * Allocate the texture of the device. Result is `true` when texture is successfully allocated * on the device. @@ -65,6 +76,36 @@ class VKTexture : public Texture { bool allocate(); VkImageViewType vk_image_view_type() const; + + /* -------------------------------------------------------------------- */ + /** \name Image Layout + * \{ */ + public: + /** + * Update the current layout attribute, without actually changing the layout. + * + * Vulkan can change the layout of an image, when a command is being executed. + * The start of a render pass or the end of a render pass can also alter the + * actual layout of the image. This method allows to change the last known layout + * that the image is using. + * + * NOTE: When we add command encoding, this should partly being done inside + * the command encoder, as there is more accurate determination of the transition + * of the layout. Only the final transition should then be stored inside the texture + * to be used by as initial layout for the next set of commands. + */ + void current_layout_set(VkImageLayout new_layout); + VkImageLayout current_layout_get() const; + + /** + * Ensure the layout of the texture. This also performs the conversion by adding a memory + * barrier to the active command buffer to perform the conversion. + * + * When texture is already in the requested layout, nothing will be done. + */ + void layout_ensure(VKContext &context, VkImageLayout requested_layout); + + /** \} */ }; static inline VKTexture *unwrap(Texture *tex)