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)