Vulkan: Frame Buffer Flipping #107743
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue