diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index f4d15d2078c..9529addc8ce 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -31,22 +31,28 @@ VKContext::VKContext(void *ghost_window, void *ghost_context) state_manager = new VKStateManager(); /* For off-screen contexts. Default frame-buffer is empty. */ - back_left = new VKFrameBuffer("back_left"); + VKFrameBuffer *framebuffer = new VKFrameBuffer("back_left"); + back_left = framebuffer; + active_fb = framebuffer; } VKContext::~VKContext() {} -void VKContext::activate() +void VKContext::sync_backbuffer() { if (ghost_window_) { - VkImage image; /* TODO will be used for reading later... */ + VkImage vk_image; VkFramebuffer vk_framebuffer; VkRenderPass render_pass; VkExtent2D extent; uint32_t fb_id; - GHOST_GetVulkanBackbuffer( - (GHOST_WindowHandle)ghost_window_, &image, &vk_framebuffer, &render_pass, &extent, &fb_id); + GHOST_GetVulkanBackbuffer((GHOST_WindowHandle)ghost_window_, + &vk_image, + &vk_framebuffer, + &render_pass, + &extent, + &fb_id); /* Recreate the gpu::VKFrameBuffer wrapper after every swap. */ if (has_active_framebuffer()) { @@ -55,22 +61,37 @@ void VKContext::activate() delete back_left; VKFrameBuffer *framebuffer = new VKFrameBuffer( - "back_left", vk_framebuffer, render_pass, extent); + "back_left", vk_image, vk_framebuffer, render_pass, extent); back_left = framebuffer; - framebuffer->bind(false); + back_left->bind(false); } + + if (ghost_context_) { + VkCommandBuffer command_buffer = VK_NULL_HANDLE; + GHOST_GetVulkanCommandBuffer(static_cast(ghost_context_), + &command_buffer); + VKDevice &device = VKBackend::get().device_; + command_buffer_.init(device.device_get(), device.queue_get(), command_buffer); + command_buffer_.begin_recording(); + device.descriptor_pools_get().reset(); + } +} + +void VKContext::activate() +{ + /* Make sure no other context is already bound to this thread. */ + BLI_assert(is_active_ == false); + + is_active_ = true; + + sync_backbuffer(); } void VKContext::deactivate() {} void VKContext::begin_frame() { - VkCommandBuffer command_buffer = VK_NULL_HANDLE; - GHOST_GetVulkanCommandBuffer(static_cast(ghost_context_), &command_buffer); - VKDevice &device = VKBackend::get().device_; - command_buffer_.init(device.device_get(), device.queue_get(), command_buffer); - command_buffer_.begin_recording(); - device.descriptor_pools_get().reset(); + sync_backbuffer(); } void VKContext::end_frame() @@ -115,16 +136,23 @@ void VKContext::activate_framebuffer(VKFrameBuffer &framebuffer) command_buffer_.begin_render_pass(framebuffer); } +VKFrameBuffer *VKContext::active_framebuffer_get() const +{ + return unwrap(active_fb); +} + bool VKContext::has_active_framebuffer() const { - return active_fb != nullptr; + return active_framebuffer_get() != nullptr; } void VKContext::deactivate_framebuffer() { BLI_assert(active_fb != nullptr); - VKFrameBuffer *framebuffer = unwrap(active_fb); - command_buffer_.end_render_pass(*framebuffer); + VKFrameBuffer *framebuffer = active_framebuffer_get(); + if (framebuffer->is_valid()) { + command_buffer_.end_render_pass(*framebuffer); + } active_fb = nullptr; } diff --git a/source/blender/gpu/vulkan/vk_context.hh b/source/blender/gpu/vulkan/vk_context.hh index d57d9377961..d6b9ff2a36d 100644 --- a/source/blender/gpu/vulkan/vk_context.hh +++ b/source/blender/gpu/vulkan/vk_context.hh @@ -45,8 +45,12 @@ class VKContext : public Context, NonCopyable { bool debug_capture_scope_begin(void *scope) override; void debug_capture_scope_end(void *scope) override; + bool has_active_framebuffer() const; void activate_framebuffer(VKFrameBuffer &framebuffer); void deactivate_framebuffer(); + VKFrameBuffer *active_framebuffer_get() const; + + void sync_backbuffer(); static VKContext *get(void) { @@ -59,9 +63,6 @@ class VKContext : public Context, NonCopyable { } const VKStateManager &state_manager_get() const; - - private: - 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 666427098e3..8ba46aa38e9 100644 --- a/source/blender/gpu/vulkan/vk_framebuffer.cc +++ b/source/blender/gpu/vulkan/vk_framebuffer.cc @@ -7,6 +7,7 @@ #include "vk_framebuffer.hh" #include "vk_backend.hh" +#include "vk_context.hh" #include "vk_memory.hh" #include "vk_texture.hh" @@ -19,26 +20,28 @@ namespace blender::gpu { VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name) { immutable_ = false; + flip_viewport_ = false; + size_set(1, 1); } VKFrameBuffer::VKFrameBuffer(const char *name, + VkImage vk_image, VkFramebuffer vk_framebuffer, VkRenderPass vk_render_pass, VkExtent2D vk_extent) : FrameBuffer(name) { immutable_ = true; + flip_viewport_ = true; /* Never update an internal frame-buffer. */ dirty_attachments_ = false; - width_ = vk_extent.width; - height_ = vk_extent.height; + vk_image_ = vk_image; vk_framebuffer_ = vk_framebuffer; vk_render_pass_ = vk_render_pass; - viewport_[0] = scissor_[0] = 0; - viewport_[1] = scissor_[1] = 0; - viewport_[2] = scissor_[2] = width_; - viewport_[3] = scissor_[3] = height_; + size_set(vk_extent.width, vk_extent.height); + viewport_reset(); + scissor_reset(); } VKFrameBuffer::~VKFrameBuffer() @@ -52,12 +55,47 @@ VKFrameBuffer::~VKFrameBuffer() void VKFrameBuffer::bind(bool /*enabled_srgb*/) { + VKContext &context = *VKContext::get(); + /* Updating attachments can issue pipeline barriers, this should be done outside the render pass. + * When done inside a render pass there should be a self-dependency between sub-passes on the + * active render pass. As the active render pass isn't aware of the new render pass (and should + * not) it is better to deactivate it before updating the attachments. For more information check + * `VkSubpassDependency`. */ + if (context.has_active_framebuffer()) { + context.deactivate_framebuffer(); + } + update_attachments(); - VKContext &context = *VKContext::get(); context.activate_framebuffer(*this); } +VkViewport VKFrameBuffer::vk_viewport_get() const +{ + VkViewport viewport; + int viewport_rect[4]; + viewport_get(viewport_rect); + + viewport.x = viewport_rect[0]; + viewport.y = viewport_rect[1]; + viewport.width = viewport_rect[2]; + viewport.height = viewport_rect[3]; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + /* + * Vulkan has origin to the top left, Blender bottom left. We counteract this by using a negative + * viewport when flip_viewport_ is set. This flips the viewport making any draw/blit use the + * correct orientation. + */ + if (flip_viewport_) { + viewport.y = height_ - viewport_rect[1]; + viewport.height = -viewport_rect[3]; + } + + return viewport; +} + VkRect2D VKFrameBuffer::vk_render_area_get() const { VkRect2D render_area = {}; @@ -82,7 +120,7 @@ VkRect2D VKFrameBuffer::vk_render_area_get() const bool VKFrameBuffer::check(char /*err_out*/[256]) { - return false; + return true; } void VKFrameBuffer::build_clear_attachments_depth_stencil( @@ -127,6 +165,9 @@ void VKFrameBuffer::build_clear_attachments_color(const float (*clear_colors)[4] void VKFrameBuffer::clear(const Vector &attachments) const { + if (attachments.is_empty()) { + return; + } VkClearRect clear_rect = {}; clear_rect.rect = vk_render_area_get(); clear_rect.baseArrayLayer = 0; @@ -189,13 +230,43 @@ void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType /*type*/, /** \name Read back * \{ */ -void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/, - eGPUDataFormat /*format*/, +void VKFrameBuffer::read(eGPUFrameBufferBits plane, + eGPUDataFormat format, const int /*area*/[4], - int /*channel_len*/, - int /*slot*/, - void * /*r_data*/) + int channel_len, + int slot, + void *r_data) { + VKTexture *texture = nullptr; + switch (plane) { + case GPU_COLOR_BIT: + texture = unwrap(unwrap(attachments_[GPU_FB_COLOR_ATTACHMENT0 + slot].tex)); + break; + + default: + BLI_assert_unreachable(); + return; + } + + BLI_assert_msg(texture, + "Trying to read back color texture from framebuffer, but no color texture is " + "available in requested slot."); + void *data = texture->read(0, format); + + /* + * TODO: + * - Add support for area. + * - Add support for channel_len. + * Best option would be to add this to VKTexture so we don't over-allocate and reduce number of + * times copies are made. + */ + BLI_assert(format == GPU_DATA_FLOAT); + BLI_assert(channel_len == 4); + int mip_size[3] = {1, 1, 1}; + texture->mip_size_get(0, mip_size); + const size_t mem_size = mip_size[0] * mip_size[1] * mip_size[2] * sizeof(float) * channel_len; + memcpy(r_data, data, mem_size); + MEM_freeN(data); } /** \} */ @@ -204,13 +275,76 @@ void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/, /** \name Blit operations * \{ */ -void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/, - int /*src_slot*/, - FrameBuffer * /*dst*/, - int /*dst_slot*/, - int /*dst_offset_x*/, - int /*dst_offset_y*/) +void VKFrameBuffer::blit_to(eGPUFrameBufferBits planes, + int src_slot, + FrameBuffer *dst, + int dst_slot, + int dst_offset_x, + int dst_offset_y) { + BLI_assert(dst); + BLI_assert(planes == GPU_COLOR_BIT); + UNUSED_VARS_NDEBUG(planes); + + VKContext &context = *VKContext::get(); + if (!context.has_active_framebuffer()) { + BLI_assert_unreachable(); + return; + } + + /* Retrieve source texture. */ + const GPUAttachment &src_attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + src_slot]; + if (src_attachment.tex == nullptr) { + return; + } + VKTexture &src_texture = *unwrap(unwrap(src_attachment.tex)); + src_texture.layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + /* Retrieve destination texture. */ + const VKFrameBuffer &dst_framebuffer = *unwrap(dst); + const GPUAttachment &dst_attachment = + dst_framebuffer.attachments_[GPU_FB_COLOR_ATTACHMENT0 + dst_slot]; + VKTexture *dst_texture = nullptr; + VKTexture tmp_texture("FramebufferTexture"); + if (dst_attachment.tex) { + dst_texture = unwrap(unwrap(dst_attachment.tex)); + dst_texture->layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + } + else { + tmp_texture.init(dst_framebuffer.vk_image_get(), VK_IMAGE_LAYOUT_GENERAL); + dst_texture = &tmp_texture; + } + + VkImageBlit image_blit = {}; + image_blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_blit.srcSubresource.mipLevel = 0; + image_blit.srcSubresource.baseArrayLayer = 0; + image_blit.srcSubresource.layerCount = 1; + image_blit.srcOffsets[0].x = 0; + image_blit.srcOffsets[0].y = 0; + image_blit.srcOffsets[0].z = 0; + image_blit.srcOffsets[1].x = src_texture.width_get(); + image_blit.srcOffsets[1].y = src_texture.height_get(); + image_blit.srcOffsets[1].z = 1; + + image_blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + image_blit.dstSubresource.mipLevel = 0; + image_blit.dstSubresource.baseArrayLayer = 0; + image_blit.dstSubresource.layerCount = 1; + image_blit.dstOffsets[0].x = dst_offset_x; + image_blit.dstOffsets[0].y = dst_offset_y; + image_blit.dstOffsets[0].z = 0; + image_blit.dstOffsets[1].x = dst_offset_x + src_texture.width_get(); + image_blit.dstOffsets[1].y = dst_offset_x + src_texture.height_get(); + image_blit.dstOffsets[1].z = 1; + + const bool should_flip = flip_viewport_ != dst_framebuffer.flip_viewport_; + if (should_flip) { + image_blit.dstOffsets[0].y = dst_framebuffer.height_ - dst_offset_y; + image_blit.dstOffsets[1].y = dst_framebuffer.height_ - dst_offset_y - src_texture.height_get(); + } + + context.command_buffer_get().blit(*dst_texture, src_texture, Span(&image_blit, 1)); } /** \} */ @@ -248,10 +382,6 @@ void VKFrameBuffer::render_pass_create() std::array attachment_descriptions; std::array image_views; std::array attachment_references; -#if 0 - Vector color_attachments; - VkAttachmentReference depth_attachment = {}; -#endif bool has_depth_attachment = false; bool found_attachment = false; int depth_location = -1; @@ -289,7 +419,7 @@ void VKFrameBuffer::render_pass_create() 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.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; 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; @@ -321,7 +451,8 @@ void VKFrameBuffer::render_pass_create() size_set(size[0], size[1]); } else { - this->size_set(0, 0); + /* A framebuffer should at least be 1 by 1.*/ + this->size_set(1, 1); } viewport_reset(); scissor_reset(); diff --git a/source/blender/gpu/vulkan/vk_framebuffer.hh b/source/blender/gpu/vulkan/vk_framebuffer.hh index abd9c9a56ec..e8fcbce657c 100644 --- a/source/blender/gpu/vulkan/vk_framebuffer.hh +++ b/source/blender/gpu/vulkan/vk_framebuffer.hh @@ -25,11 +25,21 @@ class VKFrameBuffer : public FrameBuffer { VkDevice vk_device_ = VK_NULL_HANDLE; /* Base render pass used for framebuffer creation. */ VkRenderPass vk_render_pass_ = VK_NULL_HANDLE; + VkImage vk_image_ = VK_NULL_HANDLE; /* Number of layers if the attachments are layered textures. */ int depth_ = 1; /** Internal frame-buffers are immutable. */ bool immutable_; + /** + * Should we flip the viewport to match Blenders coordinate system. We flip the viewport for + * offscreen framebuffers. + * + * When two framebuffers are blitted we also check if the coordinate system should be flipped + * during blitting. + */ + bool flip_viewport_ = false; + public: /** * Create a conventional framebuffer to attach texture to. @@ -41,6 +51,7 @@ class VKFrameBuffer : public FrameBuffer { * This just act as a wrapper, the actual allocations are done by GHOST_ContextVK. **/ VKFrameBuffer(const char *name, + VkImage vk_image, VkFramebuffer vk_framebuffer, VkRenderPass vk_render_pass, VkExtent2D vk_extent); @@ -76,6 +87,11 @@ class VKFrameBuffer : public FrameBuffer { int dst_offset_x, int dst_offset_y) override; + bool is_valid() const + { + return vk_framebuffer_ != VK_NULL_HANDLE; + } + VkFramebuffer vk_framebuffer_get() const { BLI_assert(vk_framebuffer_ != VK_NULL_HANDLE); @@ -87,7 +103,13 @@ class VKFrameBuffer : public FrameBuffer { BLI_assert(vk_render_pass_ != VK_NULL_HANDLE); return vk_render_pass_; } + VkViewport vk_viewport_get() const; VkRect2D vk_render_area_get() const; + VkImage vk_image_get() const + { + BLI_assert(vk_image_ != VK_NULL_HANDLE); + return vk_image_; + } private: void update_attachments(); diff --git a/source/blender/gpu/vulkan/vk_texture.cc b/source/blender/gpu/vulkan/vk_texture.cc index 7ed8a07f29b..19fde6c9a96 100644 --- a/source/blender/gpu/vulkan/vk_texture.cc +++ b/source/blender/gpu/vulkan/vk_texture.cc @@ -24,10 +24,17 @@ namespace blender::gpu { VKTexture::~VKTexture() { VK_ALLOCATION_CALLBACKS + if (is_allocated()) { + const VKDevice &device = VKBackend::get().device_get(); + vmaDestroyImage(device.mem_allocator_get(), vk_image_, allocation_); + vkDestroyImageView(device.device_get(), vk_image_view_, vk_allocation_callbacks); + } +} - const VKDevice &device = VKBackend::get().device_get(); - vmaDestroyImage(device.mem_allocator_get(), vk_image_, allocation_); - vkDestroyImageView(device.device_get(), vk_image_view_, vk_allocation_callbacks); +void VKTexture::init(VkImage vk_image, VkImageLayout layout) +{ + vk_image_ = vk_image; + current_layout_ = layout; } void VKTexture::generate_mipmap() {} diff --git a/source/blender/gpu/vulkan/vk_texture.hh b/source/blender/gpu/vulkan/vk_texture.hh index 6e026e0a4e6..adb65485825 100644 --- a/source/blender/gpu/vulkan/vk_texture.hh +++ b/source/blender/gpu/vulkan/vk_texture.hh @@ -27,6 +27,8 @@ class VKTexture : public Texture { VKTexture(const char *name) : Texture(name) {} virtual ~VKTexture() override; + void init(VkImage vk_image, VkImageLayout layout); + void generate_mipmap() override; void copy_to(Texture *tex) override; void clear(eGPUDataFormat format, const void *data) override; @@ -47,7 +49,7 @@ class VKTexture : public Texture { void image_bind(int location); VkImage vk_image_handle() const { - BLI_assert(is_allocated()); + BLI_assert(vk_image_ != VK_NULL_HANDLE); return vk_image_; } VkImageView vk_image_view_handle() const