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();
/* 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_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::begin_frame()
{
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();
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;
}

View File

@ -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

View File

@ -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<VkClearAttachment> &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<VkImageBlit>(&image_blit, 1));
}
/** \} */
@ -248,10 +382,6 @@ void VKFrameBuffer::render_pass_create()
std::array<VkAttachmentDescription, GPU_FB_MAX_ATTACHMENT> attachment_descriptions;
std::array<VkImageView, GPU_FB_MAX_ATTACHMENT> image_views;
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 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();

View File

@ -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();

View File

@ -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() {}

View File

@ -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