Vulkan: Clearing Framebuffer + Scissors #106044

Merged
Jeroen Bakker merged 49 commits from Jeroen-Bakker/blender:vulkan-framebuffer-clear into main 2023-03-28 11:51:45 +02:00
9 changed files with 194 additions and 69 deletions
Showing only changes of commit 393100d9e5 - Show all commits

View File

@ -14,7 +14,7 @@ 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_RGBA16F, usage, nullptr);
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(&framebuffer,
@ -41,9 +41,9 @@ 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_RGBA16F, usage, nullptr);
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
GPUTexture *texture2 = GPU_texture_create_2d(
__func__, UNPACK2(size), 1, GPU_RGBA16F, usage, nullptr);
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(
@ -78,9 +78,9 @@ 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_RGBA16F, usage, nullptr);
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
GPUTexture *texture2 = GPU_texture_create_2d(
__func__, UNPACK2(size), 1, GPU_RGBA16F, usage, nullptr);
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
Jeroen-Bakker marked this conversation as resolved Outdated

Would be best to test if clearing attachments with different formats work.
And also with integer format.

Would be best to test if clearing attachments with different formats work. And also with integer format.
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(

View File

@ -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"
@ -72,19 +73,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::bind(const VkRenderPass vk_render_pass,
const VkFramebuffer vk_framebuffer,
VkRect2D render_area)
void VKCommandBuffer::begin_render_pass(const VKFrameBuffer &framebuffer)
{
VkRenderPassBeginInfo render_pass_begin_info{};
VkRenderPassBeginInfo render_pass_begin_info = {};
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin_info.renderPass = vk_render_pass;
render_pass_begin_info.framebuffer = vk_framebuffer;
render_pass_begin_info.renderArea = render_area;
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)

View File

@ -13,10 +13,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 {
@ -37,6 +38,8 @@ 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);
void bind(const VkRenderPass vk_render_pass,
const VkFramebuffer vk_framebuffer,
VkRect2D render_area);

View File

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

View File

@ -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<VKContext *>(Context::get());
@ -102,6 +106,8 @@ class VKContext : public Context {
private:
void init_physical_device_limits();
bool has_active_framebuffer() const;
};
} // namespace blender::gpu

View File

@ -54,13 +54,17 @@ void VKFrameBuffer::bind(bool /*enabled_srgb*/)
update_attachments();
VKContext &context = *VKContext::get();
VKCommandBuffer &command_buffer = context.command_buffer_get();
context.activate_framebuffer(*this);
}
VkRect2D VKFrameBuffer::vk_render_area_get() const
{
VkRect2D render_area{};
render_area.offset.x = 0;
render_area.offset.y = 0;
render_area.extent.width = width_;
render_area.extent.height = height_;
command_buffer.bind(vk_render_pass_, vk_framebuffer_, render_area);
return render_area;
}
bool VKFrameBuffer::check(char /*err_out*/[256])
@ -68,52 +72,74 @@ 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<VkClearAttachment> &r_attachments) const
{
Vector<VkClearAttachment> clear_attachments;
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);
}
if (buffers & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)) {
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;
clear_attachments.append(clear_attachment);
}
if (buffers & GPU_COLOR_BIT) {
for (int color_slot = 0; color_slot < GPU_FB_MAX_COLOR_ATTACHMENT; color_slot++) {
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;
copy_v4_v4(clear_attachment.clearValue.color.float32, clear_col);
clear_attachments.append(clear_attachment);
void VKFrameBuffer::build_clear_attachments_color(const float (*clear_colors)[4],
const bool multi_clear_colors,
Vector<VkClearAttachment> &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;
copy_v4_v4(clear_attachment.clearValue.color.float32, clear_colors[color_index]);
r_attachments.append(clear_attachment);
color_index += multi_clear_colors ? 1 : 0;
}
}
void VKFrameBuffer::clear(const Vector<VkClearAttachment> &attachments) const
{
VkClearRect clear_rect = {};
/* Extract to function? I expect I need this multiple times. */
clear_rect.rect.offset.x = 1;
clear_rect.rect.offset.y = 1;
clear_rect.rect.extent.width = width_;
clear_rect.rect.extent.height = height_;
clear_rect.rect = vk_render_area_get();
clear_rect.baseArrayLayer = 0;
clear_rect.layerCount = 1;
Jeroen-Bakker marked this conversation as resolved Outdated

I believe that doesn't work if the attachement is a integer texture. If I remember correctly we expect the data to be converted to (U)INT. So would still suggest to cast the floats to int so that 1.0f > 1.

After looking up the usage, it seem to be only used for object IDs and thoses are cleared to 0.

EDIT: In GL, we reinterpret the data:

    switch (data_format) {
      case GPU_DATA_FLOAT:
        glClearBufferfv(GL_COLOR, slot, (GLfloat *)clear_value);
        break;
      case GPU_DATA_UINT:
        glClearBufferuiv(GL_COLOR, slot, (GLuint *)clear_value);
        break;
      case GPU_DATA_INT:
        glClearBufferiv(GL_COLOR, slot, (GLint *)clear_value);
        break;
      default:
        BLI_assert_msg(0, "Unhandled data format");
        break;
    }
I believe that doesn't work if the attachement is a integer texture. If I remember correctly we expect the data to be converted to (U)INT. So would still suggest to cast the floats to int so that `1.0f > 1`. After looking up the usage, it seem to be only used for object IDs and thoses are cleared to 0. EDIT: In GL, we reinterpret the data: ``` switch (data_format) { case GPU_DATA_FLOAT: glClearBufferfv(GL_COLOR, slot, (GLfloat *)clear_value); break; case GPU_DATA_UINT: glClearBufferuiv(GL_COLOR, slot, (GLuint *)clear_value); break; case GPU_DATA_INT: glClearBufferiv(GL_COLOR, slot, (GLint *)clear_value); break; default: BLI_assert_msg(0, "Unhandled data format"); break; }

Yes you're right, I misread the specifications how they handled SINT/UINT texture formats. Althought VkClearColorValue is an union and would have the same effect as the OpenGL code. I cleaned the code up so this is more visible.

I think we should change the API as the API uses a float parameter here, which is confusing when clearing other type of buffers. In that case I can move to_vk_clear_color_value to vk_common.cc and use that one. Something to do outside this patch.

Yes you're right, I misread the specifications how they handled SINT/UINT texture formats. Althought [`VkClearColorValue`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkClearColorValue.html) is an union and would have the same effect as the OpenGL code. I cleaned the code up so this is more visible. I think we should change the API as the API uses a float parameter here, which is confusing when clearing other type of buffers. In that case I can move `to_vk_clear_color_value` to `vk_common.cc` and use that one. Something to do outside this patch.
VKContext &context = *VKContext::get();
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.clear(clear_attachments, Span<VkClearRect>(&clear_rect, 1));
command_buffer.clear(attachments, Span<VkClearRect>(&clear_rect, 1));
}
void VKFrameBuffer::clear_multi(const float (* /*clear_col*/)[4])
void VKFrameBuffer::clear(const eGPUFrameBufferBits buffers,
const float clear_color[4],
float clear_depth,
uint clear_stencil)
{
Vector<VkClearAttachment> 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<VkClearAttachment> attachments;
build_clear_attachments_color(clear_color, true, attachments);
clear(attachments);
}
void VKFrameBuffer::clear_attachment(GPUAttachmentType /*type*/,
@ -212,7 +238,7 @@ void VKFrameBuffer::render_pass_create()
subpass.colorAttachmentCount = color_attachments.size();
subpass.pColorAttachments = color_attachments.data();
VkRenderPassCreateInfo render_pass_info{};
VkRenderPassCreateInfo render_pass_info = {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.attachmentCount = attachment_descriptions.size();
render_pass_info.pAttachments = attachment_descriptions.data();
@ -235,7 +261,7 @@ void VKFrameBuffer::render_pass_create()
this->size_set(0, 0);
}
VkFramebufferCreateInfo framebuffer_create_info{};
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 = image_views.size();

View File

@ -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"
@ -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;
@ -72,10 +76,38 @@ class VKFrameBuffer : public FrameBuffer {
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<VkClearAttachment> &r_attachments) const;
void build_clear_attachments_color(const float (*clear_colors)[4],
const bool multi_clear_colors,
Vector<VkClearAttachment> &r_attachments) const;
void clear(const Vector<VkClearAttachment> &attachments) const;
};
static inline VKFrameBuffer *unwrap(FrameBuffer *framebuffer)
{
return static_cast<VKFrameBuffer *>(framebuffer);
}
} // namespace blender::gpu

View File

@ -215,11 +215,32 @@ void VKTexture::ensure_allocated()
}
}
bool VKTexture::is_allocated()
bool VKTexture::is_allocated() const
{
return vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE;
}
static VkImageUsageFlagBits to_vk_image_usage(const eGPUTextureUsage usage)
{
VkImageUsageFlagBits result = static_cast<VkImageUsageFlagBits>(VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT);
if (usage & GPU_TEXTURE_USAGE_SHADER_READ) {
result = static_cast<VkImageUsageFlagBits>(result | VK_IMAGE_USAGE_STORAGE_BIT);
}
if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) {
result = static_cast<VkImageUsageFlagBits>(result | VK_IMAGE_USAGE_STORAGE_BIT);
}
if (usage & GPU_TEXTURE_USAGE_ATTACHMENT) {
/* TODO add other types of attachments based on the format. */
result = static_cast<VkImageUsageFlagBits>(result | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
}
if (usage & GPU_TEXTURE_USAGE_HOST_READ) {
result = static_cast<VkImageUsageFlagBits>(result | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
}
return result;
}
bool VKTexture::allocate()
{
BLI_assert(!is_allocated());
@ -237,12 +258,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;
// TODO: this conflicts with other usages. | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
image_info.usage = to_vk_image_usage(gpu_image_usage_flags_);
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
VkResult result;
@ -263,8 +286,6 @@ bool VKTexture::allocate()
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = static_cast<VmaAllocationCreateFlagBits>(
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT);
allocCreateInfo.priority = 1.0f;
result = vmaCreateImage(context.mem_allocator_get(),
&image_info,

View File

@ -43,10 +43,12 @@ 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_;
}
@ -59,7 +61,7 @@ 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.