Vulkan: Frame Buffer Flipping #107743

Merged
Jeroen Bakker merged 2 commits from Jeroen-Bakker/blender:vulkan-framebuffer-flipping into main 2023-05-09 09:22:34 +02:00
6 changed files with 239 additions and 48 deletions

View File

@ -31,22 +31,28 @@ VKContext::VKContext(void *ghost_window, void *ghost_context)
state_manager = new VKStateManager(); state_manager = new VKStateManager();
/* For off-screen contexts. Default frame-buffer is empty. */ /* 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() {} VKContext::~VKContext() {}
void VKContext::activate() void VKContext::sync_backbuffer()
{ {
if (ghost_window_) { if (ghost_window_) {
VkImage image; /* TODO will be used for reading later... */ VkImage vk_image;
VkFramebuffer vk_framebuffer; VkFramebuffer vk_framebuffer;
VkRenderPass render_pass; VkRenderPass render_pass;
VkExtent2D extent; VkExtent2D extent;
uint32_t fb_id; uint32_t fb_id;
GHOST_GetVulkanBackbuffer( GHOST_GetVulkanBackbuffer((GHOST_WindowHandle)ghost_window_,
(GHOST_WindowHandle)ghost_window_, &image, &vk_framebuffer, &render_pass, &extent, &fb_id); &vk_image,
&vk_framebuffer,
&render_pass,
&extent,
&fb_id);
/* Recreate the gpu::VKFrameBuffer wrapper after every swap. */ /* Recreate the gpu::VKFrameBuffer wrapper after every swap. */
if (has_active_framebuffer()) { if (has_active_framebuffer()) {
@ -55,22 +61,37 @@ void VKContext::activate()
delete back_left; delete back_left;
VKFrameBuffer *framebuffer = new VKFrameBuffer( VKFrameBuffer *framebuffer = new VKFrameBuffer(
"back_left", vk_framebuffer, render_pass, extent); "back_left", vk_image, vk_framebuffer, render_pass, extent);
back_left = framebuffer; back_left = framebuffer;
framebuffer->bind(false); back_left->bind(false);
} }
if (ghost_context_) {
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
GHOST_GetVulkanCommandBuffer(static_cast<GHOST_ContextHandle>(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::deactivate() {}
void VKContext::begin_frame() void VKContext::begin_frame()
{ {
VkCommandBuffer command_buffer = VK_NULL_HANDLE; sync_backbuffer();
GHOST_GetVulkanCommandBuffer(static_cast<GHOST_ContextHandle>(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::end_frame() void VKContext::end_frame()
@ -115,16 +136,23 @@ void VKContext::activate_framebuffer(VKFrameBuffer &framebuffer)
command_buffer_.begin_render_pass(framebuffer); command_buffer_.begin_render_pass(framebuffer);
} }
VKFrameBuffer *VKContext::active_framebuffer_get() const
{
return unwrap(active_fb);
}
bool VKContext::has_active_framebuffer() const bool VKContext::has_active_framebuffer() const
{ {
return active_fb != nullptr; return active_framebuffer_get() != nullptr;
} }
void VKContext::deactivate_framebuffer() void VKContext::deactivate_framebuffer()
{ {
BLI_assert(active_fb != nullptr); BLI_assert(active_fb != nullptr);
VKFrameBuffer *framebuffer = unwrap(active_fb); VKFrameBuffer *framebuffer = active_framebuffer_get();
command_buffer_.end_render_pass(*framebuffer); if (framebuffer->is_valid()) {
command_buffer_.end_render_pass(*framebuffer);
}
active_fb = nullptr; active_fb = nullptr;
} }

View File

@ -45,8 +45,12 @@ class VKContext : public Context, NonCopyable {
bool debug_capture_scope_begin(void *scope) override; bool debug_capture_scope_begin(void *scope) override;
void debug_capture_scope_end(void *scope) override; void debug_capture_scope_end(void *scope) override;
bool has_active_framebuffer() const;
void activate_framebuffer(VKFrameBuffer &framebuffer); void activate_framebuffer(VKFrameBuffer &framebuffer);
void deactivate_framebuffer(); void deactivate_framebuffer();
VKFrameBuffer *active_framebuffer_get() const;
void sync_backbuffer();
static VKContext *get(void) static VKContext *get(void)
{ {
@ -59,9 +63,6 @@ class VKContext : public Context, NonCopyable {
} }
const VKStateManager &state_manager_get() const; const VKStateManager &state_manager_get() const;
private:
bool has_active_framebuffer() const;
}; };
} // namespace blender::gpu } // namespace blender::gpu

View File

@ -7,6 +7,7 @@
#include "vk_framebuffer.hh" #include "vk_framebuffer.hh"
#include "vk_backend.hh" #include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_memory.hh" #include "vk_memory.hh"
#include "vk_texture.hh" #include "vk_texture.hh"
@ -19,26 +20,28 @@ namespace blender::gpu {
VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name) VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name)
{ {
immutable_ = false; immutable_ = false;
flip_viewport_ = false;
size_set(1, 1);
} }
VKFrameBuffer::VKFrameBuffer(const char *name, VKFrameBuffer::VKFrameBuffer(const char *name,
VkImage vk_image,
VkFramebuffer vk_framebuffer, VkFramebuffer vk_framebuffer,
VkRenderPass vk_render_pass, VkRenderPass vk_render_pass,
VkExtent2D vk_extent) VkExtent2D vk_extent)
: FrameBuffer(name) : FrameBuffer(name)
{ {
immutable_ = true; immutable_ = true;
flip_viewport_ = true;
/* Never update an internal frame-buffer. */ /* Never update an internal frame-buffer. */
dirty_attachments_ = false; dirty_attachments_ = false;
width_ = vk_extent.width; vk_image_ = vk_image;
height_ = vk_extent.height;
vk_framebuffer_ = vk_framebuffer; vk_framebuffer_ = vk_framebuffer;
vk_render_pass_ = vk_render_pass; vk_render_pass_ = vk_render_pass;
viewport_[0] = scissor_[0] = 0; size_set(vk_extent.width, vk_extent.height);
viewport_[1] = scissor_[1] = 0; viewport_reset();
viewport_[2] = scissor_[2] = width_; scissor_reset();
viewport_[3] = scissor_[3] = height_;
} }
VKFrameBuffer::~VKFrameBuffer() VKFrameBuffer::~VKFrameBuffer()
@ -52,12 +55,47 @@ VKFrameBuffer::~VKFrameBuffer()
void VKFrameBuffer::bind(bool /*enabled_srgb*/) 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(); update_attachments();
VKContext &context = *VKContext::get();
context.activate_framebuffer(*this); 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 VKFrameBuffer::vk_render_area_get() const
{ {
VkRect2D render_area = {}; VkRect2D render_area = {};
@ -82,7 +120,7 @@ VkRect2D VKFrameBuffer::vk_render_area_get() const
bool VKFrameBuffer::check(char /*err_out*/[256]) bool VKFrameBuffer::check(char /*err_out*/[256])
{ {
return false; return true;
} }
void VKFrameBuffer::build_clear_attachments_depth_stencil( 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<VkClearAttachment> &attachments) const void VKFrameBuffer::clear(const Vector<VkClearAttachment> &attachments) const
{ {
if (attachments.is_empty()) {
return;
}
VkClearRect clear_rect = {}; VkClearRect clear_rect = {};
clear_rect.rect = vk_render_area_get(); clear_rect.rect = vk_render_area_get();
clear_rect.baseArrayLayer = 0; clear_rect.baseArrayLayer = 0;
@ -189,13 +230,43 @@ void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType /*type*/,
/** \name Read back /** \name Read back
* \{ */ * \{ */
void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/, void VKFrameBuffer::read(eGPUFrameBufferBits plane,
eGPUDataFormat /*format*/, eGPUDataFormat format,
const int /*area*/[4], const int /*area*/[4],
int /*channel_len*/, int channel_len,
int /*slot*/, int slot,
void * /*r_data*/) 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 /** \name Blit operations
* \{ */ * \{ */
void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/, void VKFrameBuffer::blit_to(eGPUFrameBufferBits planes,
int /*src_slot*/, int src_slot,
FrameBuffer * /*dst*/, FrameBuffer *dst,
int /*dst_slot*/, int dst_slot,
int /*dst_offset_x*/, int dst_offset_x,
int /*dst_offset_y*/) 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<VkImageBlit>(&image_blit, 1));
} }
/** \} */ /** \} */
@ -248,10 +382,6 @@ void VKFrameBuffer::render_pass_create()
std::array<VkAttachmentDescription, GPU_FB_MAX_ATTACHMENT> attachment_descriptions; std::array<VkAttachmentDescription, GPU_FB_MAX_ATTACHMENT> attachment_descriptions;
std::array<VkImageView, GPU_FB_MAX_ATTACHMENT> image_views; std::array<VkImageView, GPU_FB_MAX_ATTACHMENT> image_views;
std::array<VkAttachmentReference, GPU_FB_MAX_ATTACHMENT> attachment_references; std::array<VkAttachmentReference, GPU_FB_MAX_ATTACHMENT> attachment_references;
#if 0
Vector<VkAttachmentReference> color_attachments;
VkAttachmentReference depth_attachment = {};
#endif
bool has_depth_attachment = false; bool has_depth_attachment = false;
bool found_attachment = false; bool found_attachment = false;
int depth_location = -1; int depth_location = -1;
@ -289,7 +419,7 @@ void VKFrameBuffer::render_pass_create()
attachment_description.flags = 0; attachment_description.flags = 0;
attachment_description.format = to_vk_format(texture.format_get()); attachment_description.format = to_vk_format(texture.format_get());
attachment_description.samples = VK_SAMPLE_COUNT_1_BIT; 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.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_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]); size_set(size[0], size[1]);
} }
else { else {
this->size_set(0, 0); /* A framebuffer should at least be 1 by 1.*/
this->size_set(1, 1);
} }
viewport_reset(); viewport_reset();
scissor_reset(); scissor_reset();

View File

@ -25,11 +25,21 @@ class VKFrameBuffer : public FrameBuffer {
VkDevice vk_device_ = VK_NULL_HANDLE; VkDevice vk_device_ = VK_NULL_HANDLE;
/* Base render pass used for framebuffer creation. */ /* Base render pass used for framebuffer creation. */
VkRenderPass vk_render_pass_ = VK_NULL_HANDLE; VkRenderPass vk_render_pass_ = VK_NULL_HANDLE;
VkImage vk_image_ = VK_NULL_HANDLE;
/* Number of layers if the attachments are layered textures. */ /* Number of layers if the attachments are layered textures. */
int depth_ = 1; int depth_ = 1;
/** Internal frame-buffers are immutable. */ /** Internal frame-buffers are immutable. */
bool 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: public:
/** /**
* Create a conventional framebuffer to attach texture to. * 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. * This just act as a wrapper, the actual allocations are done by GHOST_ContextVK.
**/ **/
VKFrameBuffer(const char *name, VKFrameBuffer(const char *name,
VkImage vk_image,
VkFramebuffer vk_framebuffer, VkFramebuffer vk_framebuffer,
VkRenderPass vk_render_pass, VkRenderPass vk_render_pass,
VkExtent2D vk_extent); VkExtent2D vk_extent);
@ -76,6 +87,11 @@ class VKFrameBuffer : public FrameBuffer {
int dst_offset_x, int dst_offset_x,
int dst_offset_y) override; int dst_offset_y) override;
bool is_valid() const
{
return vk_framebuffer_ != VK_NULL_HANDLE;
}
VkFramebuffer vk_framebuffer_get() const VkFramebuffer vk_framebuffer_get() const
{ {
BLI_assert(vk_framebuffer_ != VK_NULL_HANDLE); BLI_assert(vk_framebuffer_ != VK_NULL_HANDLE);
@ -87,7 +103,13 @@ class VKFrameBuffer : public FrameBuffer {
BLI_assert(vk_render_pass_ != VK_NULL_HANDLE); BLI_assert(vk_render_pass_ != VK_NULL_HANDLE);
return vk_render_pass_; return vk_render_pass_;
} }
VkViewport vk_viewport_get() const;
VkRect2D vk_render_area_get() const; VkRect2D vk_render_area_get() const;
VkImage vk_image_get() const
{
BLI_assert(vk_image_ != VK_NULL_HANDLE);
return vk_image_;
}
private: private:
void update_attachments(); void update_attachments();

View File

@ -24,10 +24,17 @@ namespace blender::gpu {
VKTexture::~VKTexture() VKTexture::~VKTexture()
{ {
VK_ALLOCATION_CALLBACKS 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(); void VKTexture::init(VkImage vk_image, VkImageLayout layout)
vmaDestroyImage(device.mem_allocator_get(), vk_image_, allocation_); {
vkDestroyImageView(device.device_get(), vk_image_view_, vk_allocation_callbacks); vk_image_ = vk_image;
current_layout_ = layout;
} }
void VKTexture::generate_mipmap() {} void VKTexture::generate_mipmap() {}

View File

@ -27,6 +27,8 @@ class VKTexture : public Texture {
VKTexture(const char *name) : Texture(name) {} VKTexture(const char *name) : Texture(name) {}
virtual ~VKTexture() override; virtual ~VKTexture() override;
void init(VkImage vk_image, VkImageLayout layout);
void generate_mipmap() override; void generate_mipmap() override;
void copy_to(Texture *tex) override; void copy_to(Texture *tex) override;
void clear(eGPUDataFormat format, const void *data) override; void clear(eGPUDataFormat format, const void *data) override;
@ -47,7 +49,7 @@ class VKTexture : public Texture {
void image_bind(int location); void image_bind(int location);
VkImage vk_image_handle() const VkImage vk_image_handle() const
{ {
BLI_assert(is_allocated()); BLI_assert(vk_image_ != VK_NULL_HANDLE);
return vk_image_; return vk_image_;
} }
VkImageView vk_image_view_handle() const VkImageView vk_image_view_handle() const