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
12 changed files with 782 additions and 81 deletions

View File

@ -832,6 +832,7 @@ if(WITH_GTESTS)
set(TEST_SRC
tests/gpu_testing.cc
tests/framebuffer_test.cc
tests/index_buffer_test.cc
tests/push_constants_test.cc
tests/shader_test.cc

View File

@ -0,0 +1,200 @@
/* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "GPU_framebuffer.h"
#include "gpu_testing.hh"
#include "BLI_math_vector.hh"
namespace blender::gpu::tests {
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_RGBA32F, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(&framebuffer,
{GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture)});
GPU_framebuffer_bind(framebuffer);
const float4 clear_color(0.1f, 0.2f, 0.5f, 1.0f);
GPU_framebuffer_clear_color(framebuffer, clear_color);
GPU_finish();
float4 *read_data = static_cast<float4 *>(GPU_texture_read(texture, GPU_DATA_FLOAT, 0));
for (float4 pixel_color : Span<float4>(read_data, size.x * size.y)) {
EXPECT_EQ(pixel_color, clear_color);
}
MEM_freeN(read_data);
GPU_framebuffer_free(framebuffer);
GPU_texture_free(texture);
}
GPU_TEST(framebuffer_clear_color_single_attachment);
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_RGBA32F, usage, nullptr);
GPUTexture *texture2 = GPU_texture_create_2d(
__func__, UNPACK2(size), 1, GPU_RGBA32UI, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(
&framebuffer,
{GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture1), GPU_ATTACHMENT_TEXTURE(texture2)});
GPU_framebuffer_bind(framebuffer);
const float4 clear_color(0.1f, 0.2f, 0.5f, 1.0f);
GPU_framebuffer_clear_color(framebuffer, clear_color);
GPU_finish();
float4 *read_data1 = static_cast<float4 *>(GPU_texture_read(texture1, GPU_DATA_FLOAT, 0));
for (float4 pixel_color : Span<float4>(read_data1, size.x * size.y)) {
EXPECT_EQ(pixel_color, clear_color);
}
MEM_freeN(read_data1);
uint4 *read_data2 = static_cast<uint4 *>(GPU_texture_read(texture2, GPU_DATA_UINT, 0));
uint4 clear_color_uint(1036831949, 1045220557, 1056964608, 1065353216);
for (uint4 pixel_color : Span<uint4>(read_data2, size.x * size.y)) {
EXPECT_EQ(pixel_color, clear_color_uint);
Jeroen-Bakker marked this conversation as resolved Outdated

This Check may result in an error.

Sometimes it clears correctly.


\blender\source\blender\gpu\tests\framebuffer_test.cc(66): error: Expected equality of these values:
pixel_color
Which is: (-1.9984e+18, -1.9984e+18, -1.9984e+18, -1.9984e+18)
clear_color
Which is: (0.1, 0.2, 0.5, 1)


A possible cause of this suspicious behavior is that the layout is only general.

This Check may result in an error. Sometimes it clears correctly. ----- \blender\source\blender\gpu\tests\framebuffer_test.cc(66): error: Expected equality of these values: pixel_color Which is: (-1.9984e+18, -1.9984e+18, -1.9984e+18, -1.9984e+18) clear_color Which is: (0.1, 0.2, 0.5, 1) ----- A possible cause of this suspicious behavior is that the layout is only general.

The issue was a read out of bounds. Can you recheck if it still fails for you.

The root cause was that read_data1 was read, but was already freed. It should have checked read_data2.

The issue was a read out of bounds. Can you recheck if it still fails for you. The root cause was that `read_data1` was read, but was already freed. It should have checked `read_data2`.
}
MEM_freeN(read_data2);
GPU_framebuffer_free(framebuffer);
GPU_texture_free(texture1);
GPU_texture_free(texture2);
}
GPU_TEST(framebuffer_clear_color_multiple_attachments);
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_RGBA32F, usage, nullptr);
GPUTexture *texture2 = GPU_texture_create_2d(
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.
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(
&framebuffer,
{GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture1), GPU_ATTACHMENT_TEXTURE(texture2)});
GPU_framebuffer_bind(framebuffer);
const float4 clear_color[2] = {float4(0.1f, 0.2f, 0.5f, 1.0f), float4(0.5f, 0.2f, 0.1f, 1.0f)};
GPU_framebuffer_multi_clear(
framebuffer, static_cast<const float(*)[4]>(static_cast<const void *>(clear_color)));
GPU_finish();
float4 *read_data1 = static_cast<float4 *>(GPU_texture_read(texture1, GPU_DATA_FLOAT, 0));
for (float4 pixel_color : Span<float4>(read_data1, size.x * size.y)) {
EXPECT_EQ(pixel_color, clear_color[0]);
}
MEM_freeN(read_data1);
float4 *read_data2 = static_cast<float4 *>(GPU_texture_read(texture2, GPU_DATA_FLOAT, 0));
for (float4 pixel_color : Span<float4>(read_data1, size.x * size.y)) {
EXPECT_EQ(pixel_color, clear_color[1]);
}
MEM_freeN(read_data2);
GPU_framebuffer_free(framebuffer);
GPU_texture_free(texture1);
GPU_texture_free(texture2);
}
GPU_TEST(framebuffer_clear_multiple_color_multiple_attachments);
static void test_framebuffer_clear_depth()
{
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_DEPTH_COMPONENT32F, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(&framebuffer, {GPU_ATTACHMENT_TEXTURE(texture)});
GPU_framebuffer_bind(framebuffer);
const float clear_depth = 0.5f;
GPU_framebuffer_clear_depth(framebuffer, clear_depth);
GPU_finish();
float *read_data = static_cast<float *>(GPU_texture_read(texture, GPU_DATA_FLOAT, 0));
for (float pixel_depth : Span<float>(read_data, size.x * size.y)) {
EXPECT_EQ(pixel_depth, clear_depth);
}
MEM_freeN(read_data);
GPU_framebuffer_free(framebuffer);
GPU_texture_free(texture);
}
GPU_TEST(framebuffer_clear_depth);
static void test_framebuffer_scissor_test()
{
const int2 size(128, 128);
const int bar_size = 16;
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ;
GPUTexture *texture = GPU_texture_create_2d(
__func__, UNPACK2(size), 1, GPU_RGBA32F, usage, nullptr);
GPUFrameBuffer *framebuffer = GPU_framebuffer_create(__func__);
GPU_framebuffer_ensure_config(&framebuffer,
{GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(texture)});
GPU_framebuffer_bind(framebuffer);
const float4 color1(0.0f);
const float4 color2(0.5f);
const float4 color3(1.0f);
GPU_framebuffer_clear_color(framebuffer, color1);
GPU_scissor_test(true);
for (int x = 0; x < size.x; x += 2 * bar_size) {
GPU_scissor(x, 0, bar_size, size.y);
GPU_framebuffer_clear_color(framebuffer, color2);
}
for (int y = 0; y < size.y; y += 2 * bar_size) {
GPU_scissor(0, y, size.x, bar_size);
GPU_framebuffer_clear_color(framebuffer, color3);
}
GPU_scissor_test(false);
GPU_finish();
float4 *read_data = static_cast<float4 *>(GPU_texture_read(texture, GPU_DATA_FLOAT, 0));
int offset = 0;
for (float4 pixel_color : Span<float4>(read_data, size.x * size.y)) {
int x = offset % size.x;
int y = offset / size.x;
int bar_x = x / bar_size;
int bar_y = y / bar_size;
if (bar_y % 2 == 0) {
EXPECT_EQ(pixel_color, color3);
}
else {
if (bar_x % 2 == 0) {
EXPECT_EQ(pixel_color, color2);
}
else {
EXPECT_EQ(pixel_color, color1);
}
}
offset++;
}
MEM_freeN(read_data);
GPU_framebuffer_free(framebuffer);
GPU_texture_free(texture);
}
GPU_TEST(framebuffer_scissor_test);
} // namespace blender::gpu::tests

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"
@ -73,6 +74,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::begin_render_pass(const VKFrameBuffer &framebuffer)
{
VkRenderPassBeginInfo render_pass_begin_info = {};
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
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)
@ -98,7 +114,7 @@ void VKCommandBuffer::copy(VKBuffer &dst_buffer,
{
vkCmdCopyImageToBuffer(vk_command_buffer_,
src_texture.vk_image_handle(),
VK_IMAGE_LAYOUT_GENERAL,
src_texture.current_layout_get(),
dst_buffer.vk_handle(),
regions.size(),
regions.data());
@ -110,7 +126,7 @@ void VKCommandBuffer::copy(VKTexture &dst_texture,
vkCmdCopyBufferToImage(vk_command_buffer_,
src_buffer.vk_handle(),
dst_texture.vk_image_handle(),
VK_IMAGE_LAYOUT_GENERAL,
dst_texture.current_layout_get(),
regions.size(),
regions.data());
}
@ -128,6 +144,12 @@ void VKCommandBuffer::clear(VkImage vk_image,
ranges.data());
}
void VKCommandBuffer::clear(Span<VkClearAttachment> attachments, Span<VkClearRect> areas)
{
vkCmdClearAttachments(
vk_command_buffer_, attachments.size(), attachments.data(), areas.size(), areas.data());
}
void VKCommandBuffer::pipeline_barrier(VkPipelineStageFlags source_stages,
VkPipelineStageFlags destination_stages)
{

View File

@ -14,10 +14,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 {
@ -39,6 +40,9 @@ 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);
/**
* Add a push constant command to the command buffer.
*
@ -61,6 +65,11 @@ class VKCommandBuffer : NonCopyable, NonMovable {
VkImageLayout vk_image_layout,
const VkClearColorValue &vk_clear_color,
Span<VkImageSubresourceRange> ranges);
/**
* Clear attachments of the active framebuffer.
*/
void clear(Span<VkClearAttachment> attachments, Span<VkClearRect> areas);
void fill(VKBuffer &buffer, uint32_t data);
/**

View File

@ -307,4 +307,46 @@ VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat /*format*/)
return component_mapping;
}
template<typename T> void copy_color(T dst[4], const T *src)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const void *data)
{
VkClearColorValue result = {0.0f};
switch (format) {
case GPU_DATA_FLOAT: {
const float *float_data = static_cast<const float *>(data);
copy_color<float>(result.float32, float_data);
break;
}
case GPU_DATA_INT: {
const int32_t *int_data = static_cast<const int32_t *>(data);
copy_color<int32_t>(result.int32, int_data);
break;
}
case GPU_DATA_UINT: {
const uint32_t *uint_data = static_cast<const uint32_t *>(data);
copy_color<uint32_t>(result.uint32, uint_data);
break;
}
case GPU_DATA_HALF_FLOAT:
case GPU_DATA_UBYTE:
case GPU_DATA_UINT_24_8:
case GPU_DATA_10_11_11_REV:
case GPU_DATA_2_10_10_10_REV: {
BLI_assert_unreachable();
break;
}
}
return result;
}
} // namespace blender::gpu

View File

@ -24,5 +24,6 @@ VkFormat to_vk_format(const eGPUTextureFormat format);
VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat format);
VkImageViewType to_vk_image_view_type(const eGPUTextureType type);
VkImageType to_vk_image_type(const eGPUTextureType type);
VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const void *data);
} // namespace blender::gpu

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

@ -6,6 +6,8 @@
*/
#include "vk_framebuffer.hh"
#include "vk_memory.hh"
#include "vk_texture.hh"
namespace blender::gpu {
@ -20,7 +22,7 @@ VKFrameBuffer::VKFrameBuffer(const char *name) : FrameBuffer(name)
VKFrameBuffer::VKFrameBuffer(const char *name,
VkFramebuffer vk_framebuffer,
VkRenderPass /*vk_render_pass*/,
VkRenderPass vk_render_pass,
VkExtent2D vk_extent)
: FrameBuffer(name)
{
@ -30,6 +32,7 @@ VKFrameBuffer::VKFrameBuffer(const char *name,
width_ = vk_extent.width;
height_ = vk_extent.height;
vk_framebuffer_ = vk_framebuffer;
vk_render_pass_ = vk_render_pass;
viewport_[0] = scissor_[0] = 0;
viewport_[1] = scissor_[1] = 0;
@ -39,8 +42,8 @@ VKFrameBuffer::VKFrameBuffer(const char *name,
VKFrameBuffer::~VKFrameBuffer()
{
if (!immutable_ && vk_framebuffer_ != VK_NULL_HANDLE) {
vkDestroyFramebuffer(vk_device_, vk_framebuffer_, NULL);
if (!immutable_) {
render_pass_free();
}
}
@ -48,6 +51,32 @@ VKFrameBuffer::~VKFrameBuffer()
void VKFrameBuffer::bind(bool /*enabled_srgb*/)
{
update_attachments();
VKContext &context = *VKContext::get();
context.activate_framebuffer(*this);
}
VkRect2D VKFrameBuffer::vk_render_area_get() const
{
VkRect2D render_area = {};
if (scissor_test_get()) {
int scissor_rect[4];
scissor_get(scissor_rect);
render_area.offset.x = scissor_rect[0];
render_area.offset.y = scissor_rect[1];
render_area.extent.width = scissor_rect[2];
render_area.extent.height = scissor_rect[3];
}
else {
render_area.offset.x = 0;
render_area.offset.y = 0;
render_area.extent.width = width_;
render_area.extent.height = height_;
}
return render_area;
}
bool VKFrameBuffer::check(char /*err_out*/[256])
@ -55,29 +84,110 @@ 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
{
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);
}
void VKFrameBuffer::clear_multi(const float (*/*clear_col*/)[4])
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;
eGPUDataFormat data_format = to_data_format(GPU_texture_format(attachment.tex));
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.
clear_attachment.clearValue.color = to_vk_clear_color_value(data_format,
&clear_colors[color_index]);
r_attachments.append(clear_attachment);
color_index += multi_clear_colors ? 1 : 0;
}
}
/* -------------------------------------------------------------------- */
/** \name Clear
* \{ */
void VKFrameBuffer::clear(const Vector<VkClearAttachment> &attachments) const
{
VkClearRect clear_rect = {};
clear_rect.rect = vk_render_area_get();
clear_rect.baseArrayLayer = 0;
clear_rect.layerCount = 1;
VKContext &context = *VKContext::get();
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.clear(attachments, Span<VkClearRect>(&clear_rect, 1));
}
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*/,
eGPUDataFormat /*data_format*/,
const void * /*clear_value*/)
{
/* Clearing of a single attachment was added to implement `clear_multi` in OpenGL. As
* `clear_multi` is supported in Vulkan it isn't needed to implement this method.
*/
BLI_assert_unreachable();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Load/Store operations
* \{ */
void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType /*type*/,
eGPULoadOp /*load_action*/,
eGPUStoreOp /*store_action*/)
{
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Read back
* \{ */
void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/,
eGPUDataFormat /*format*/,
const int /*area*/[4],
@ -87,6 +197,12 @@ void VKFrameBuffer::read(eGPUFrameBufferBits /*planes*/,
{
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blit operations
* \{ */
void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/,
int /*src_slot*/,
FrameBuffer * /*dst*/,
@ -96,4 +212,169 @@ void VKFrameBuffer::blit_to(eGPUFrameBufferBits /*planes*/,
{
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Update attachments
* \{ */
void VKFrameBuffer::update_attachments()
{
if (immutable_) {
return;
}
if (!dirty_attachments_) {
return;
}
render_pass_free();
render_pass_create();
dirty_attachments_ = false;
}
void VKFrameBuffer::render_pass_create()
{
BLI_assert(!immutable_);
BLI_assert(vk_render_pass_ == VK_NULL_HANDLE);
BLI_assert(vk_framebuffer_ == VK_NULL_HANDLE);
VK_ALLOCATION_CALLBACKS
/* Track first attachment for size.*/
GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHMENT;
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;
/*Vector<VkAttachmentReference> color_attachments;
VkAttachmentReference depth_attachment = {};
*/
bool has_depth_attachment = false;
bool found_attachment = false;
int depth_location = -1;
for (int type = GPU_FB_MAX_ATTACHMENT - 1; type >= 0; type--) {
GPUAttachment &attachment = attachments_[type];
if (attachment.tex == nullptr && !found_attachment) {
/* Move the depth texture to the next binding point after all color textures. The binding
* location of the color textures should be kept in sync between ShaderCreateInfos and the
* framebuffer attachments. The depth buffer should be the last slot. */
depth_location = max_ii(type - GPU_FB_COLOR_ATTACHMENT0, 0);
continue;
}
found_attachment |= attachment.tex != nullptr;
/* Keep the first attachment to the first color attachment, or to the depth buffer when there
* is no color attachment. */
if (attachment.tex != nullptr &&
(first_attachment == GPU_FB_MAX_ATTACHMENT || type >= GPU_FB_COLOR_ATTACHMENT0)) {
first_attachment = static_cast<GPUAttachmentType>(type);
}
int attachment_location = type >= GPU_FB_COLOR_ATTACHMENT0 ? type - GPU_FB_COLOR_ATTACHMENT0 :
depth_location;
if (attachment.tex) {
/* Ensure texture is allocated to ensure the image view.*/
VKTexture &texture = *static_cast<VKTexture *>(unwrap(attachment.tex));
texture.ensure_allocated();
image_views[attachment_location] = texture.vk_image_view_handle();
VkAttachmentDescription &attachment_description =
attachment_descriptions[attachment_location];
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.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment_description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment_description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment_description.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
attachment_description.finalLayout = VK_IMAGE_LAYOUT_GENERAL;
/* Create the attachment reference. */
const bool is_depth_attachment = ELEM(
type, GPU_FB_DEPTH_ATTACHMENT, GPU_FB_DEPTH_STENCIL_ATTACHMENT);
BLI_assert_msg(!is_depth_attachment || !has_depth_attachment,
"There can only be one depth/stencil attachment.");
has_depth_attachment |= is_depth_attachment;
VkAttachmentReference &attachment_reference = attachment_references[attachment_location];
attachment_reference.attachment = attachment_location;
attachment_reference.layout = is_depth_attachment ?
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL :
VK_IMAGE_LAYOUT_GENERAL;
}
}
/* Update the size, viewport & scissor based on the first attachment. */
if (first_attachment != GPU_FB_MAX_ATTACHMENT) {
GPUAttachment &attachment = attachments_[first_attachment];
BLI_assert(attachment.tex);
int size[3];
GPU_texture_get_mipmap_size(attachment.tex, attachment.mip, size);
size_set(size[0], size[1]);
}
else {
this->size_set(0, 0);
}
viewport_reset();
scissor_reset();
/* Create render pass. */
const int attachment_len = has_depth_attachment ? depth_location + 1 : depth_location;
const int color_attachment_len = depth_location;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = color_attachment_len;
subpass.pColorAttachments = attachment_references.begin();
Review

In case of windows machine, this part does not pass.
attachment_references.data();
image_views.data();
No problem here.

In case of windows machine, this part does not pass. `attachment_references.data();` `image_views.data();` No problem here.
if (has_depth_attachment) {
subpass.pDepthStencilAttachment = &attachment_references[depth_location];
}
VkRenderPassCreateInfo render_pass_info = {};
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_info.attachmentCount = attachment_len;
render_pass_info.pAttachments = attachment_descriptions.data();
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
VKContext &context = *VKContext::get();
vkCreateRenderPass(
context.device_get(), &render_pass_info, vk_allocation_callbacks, &vk_render_pass_);
/* We might want to split framebuffer and render pass....*/
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 = attachment_len;
framebuffer_create_info.pAttachments = image_views.begin();
framebuffer_create_info.width = width_;
framebuffer_create_info.height = height_;
framebuffer_create_info.layers = 1;
vkCreateFramebuffer(
context.device_get(), &framebuffer_create_info, vk_allocation_callbacks, &vk_framebuffer_);
}
void VKFrameBuffer::render_pass_free()
{
BLI_assert(!immutable_);
if (vk_render_pass_ == VK_NULL_HANDLE) {
return;
}
VK_ALLOCATION_CALLBACKS
VKContext &context = *VKContext::get();
vkDestroyRenderPass(context.device_get(), vk_render_pass_, vk_allocation_callbacks);
vkDestroyFramebuffer(context.device_get(), vk_framebuffer_, vk_allocation_callbacks);
vk_render_pass_ = VK_NULL_HANDLE;
vk_framebuffer_ = VK_NULL_HANDLE;
}
/** \} */
} // namespace blender::gpu

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"
@ -20,7 +24,7 @@ class VKFrameBuffer : public FrameBuffer {
/* Vulkan device who created the handle. */
VkDevice vk_device_ = VK_NULL_HANDLE;
/* Base render pass used for framebuffer creation. */
VkRenderPass render_pass_ = VK_NULL_HANDLE;
VkRenderPass vk_render_pass_ = VK_NULL_HANDLE;
/* Number of layers if the attachments are layered textures. */
int depth_ = 1;
/** Internal frame-buffers are immutable. */
@ -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;
@ -71,6 +75,39 @@ class VKFrameBuffer : public FrameBuffer {
int dst_slot,
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

@ -37,48 +37,6 @@ void VKTexture::copy_to(Texture * /*tex*/)
{
}
template<typename T> void copy_color(T dst[4], const T *src)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
static VkClearColorValue to_vk_clear_color_value(eGPUDataFormat format, const void *data)
{
VkClearColorValue result = {{0.0f}};
switch (format) {
case GPU_DATA_FLOAT: {
const float *float_data = static_cast<const float *>(data);
copy_color<float>(result.float32, float_data);
break;
}
case GPU_DATA_INT: {
const int32_t *int_data = static_cast<const int32_t *>(data);
copy_color<int32_t>(result.int32, int_data);
break;
}
case GPU_DATA_UINT: {
const uint32_t *uint_data = static_cast<const uint32_t *>(data);
copy_color<uint32_t>(result.uint32, uint_data);
break;
}
case GPU_DATA_HALF_FLOAT:
case GPU_DATA_UBYTE:
case GPU_DATA_UINT_24_8:
case GPU_DATA_10_11_11_REV:
case GPU_DATA_2_10_10_10_REV: {
BLI_assert_unreachable();
break;
}
}
return result;
}
void VKTexture::clear(eGPUDataFormat format, const void *data)
{
if (!is_allocated()) {
@ -92,9 +50,10 @@ void VKTexture::clear(eGPUDataFormat format, const void *data)
range.aspectMask = to_vk_image_aspect_flag_bits(format_);
range.levelCount = VK_REMAINING_MIP_LEVELS;
range.layerCount = VK_REMAINING_ARRAY_LAYERS;
layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL);
command_buffer.clear(
vk_image_, VK_IMAGE_LAYOUT_GENERAL, clear_color, Span<VkImageSubresourceRange>(&range, 1));
vk_image_, current_layout_get(), clear_color, Span<VkImageSubresourceRange>(&range, 1));
}
void VKTexture::swizzle_set(const char /*swizzle_mask*/[4])
@ -111,8 +70,10 @@ void VKTexture::mip_range_set(int /*min*/, int /*max*/)
void *VKTexture::read(int mip, eGPUDataFormat format)
{
/* Vulkan images cannot be directly mapped to host memory and requires a staging buffer. */
VKContext &context = *VKContext::get();
layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
/* Vulkan images cannot be directly mapped to host memory and requires a staging buffer. */
VKBuffer staging_buffer;
/* NOTE: mip_size_get() won't override any dimension that is equal to 0. */
@ -170,6 +131,7 @@ void VKTexture::update_sub(
region.imageSubresource.mipLevel = mip;
region.imageSubresource.layerCount = 1;
layout_ensure(context, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.copy(*this, staging_buffer, Span<VkBufferImageCopy>(&region, 1));
command_buffer.submit();
@ -208,11 +170,51 @@ bool VKTexture::init_internal(const GPUTexture * /*src*/, int /*mip_offset*/, in
return false;
}
bool VKTexture::is_allocated()
void VKTexture::ensure_allocated()
{
if (!is_allocated()) {
allocate();
}
}
bool VKTexture::is_allocated() const
{
return vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE;
}
static VkImageUsageFlagBits to_vk_image_usage(const eGPUTextureUsage usage,
const eGPUTextureFormatFlag format_flag)
{
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) {
if (format_flag & (GPU_FORMAT_NORMALIZED_INTEGER | GPU_FORMAT_COMPRESSED)) {
/* These formats aren't supported as an attachment. When using GPU_TEXTURE_USAGE_DEFAULT they
* are still being evaluated to be attachable. So we need to skip them.*/
}
else {
if (format_flag & (GPU_FORMAT_DEPTH | GPU_FORMAT_STENCIL)) {
result = static_cast<VkImageUsageFlagBits>(result |
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
}
else {
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());
@ -230,10 +232,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;
image_info.usage = to_vk_image_usage(gpu_image_usage_flags_, format_flag_);
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
VkResult result;
@ -254,8 +260,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,
@ -268,15 +272,7 @@ bool VKTexture::allocate()
}
/* Promote image to the correct layout. */
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.image = vk_image_;
barrier.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_);
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
context.command_buffer_get().pipeline_barrier(Span<VkImageMemoryBarrier>(&barrier, 1));
layout_ensure(context, VK_IMAGE_LAYOUT_GENERAL);
VK_ALLOCATION_CALLBACKS
VkImageViewCreateInfo image_view_info = {};
@ -307,4 +303,37 @@ void VKTexture::image_bind(int binding)
shader->pipeline_get().descriptor_set_get().image_bind(*this, location);
}
/* -------------------------------------------------------------------- */
/** \name Image Layout
* \{ */
VkImageLayout VKTexture::current_layout_get() const
{
return current_layout_;
}
void VKTexture::current_layout_set(const VkImageLayout new_layout)
{
current_layout_ = new_layout;
}
void VKTexture::layout_ensure(VKContext &context, const VkImageLayout requested_layout)
{
const VkImageLayout current_layout = current_layout_get();
if (current_layout == requested_layout) {
return;
}
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = current_layout;
barrier.newLayout = requested_layout;
barrier.image = vk_image_;
barrier.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_);
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
context.command_buffer_get().pipeline_barrier(Span<VkImageMemoryBarrier>(&barrier, 1));
current_layout_set(requested_layout);
}
/** \} */
} // namespace blender::gpu

View File

@ -17,6 +17,12 @@ class VKTexture : public Texture {
VkImageView vk_image_view_ = VK_NULL_HANDLE;
VmaAllocation allocation_ = VK_NULL_HANDLE;
/* Last image layout of the texture. Framebuffer and barriers can alter/require the actual layout
* to be changed. During this it requires to set the current layout in order to know which
* conversion should happen. #current_layout_ keep track of the layout so the correct conversion
* can be done.*/
VkImageLayout current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;
public:
VKTexture(const char *name) : Texture(name)
{
@ -43,13 +49,17 @@ 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_;
}
void ensure_allocated();
protected:
bool init_internal() override;
bool init_internal(GPUVertBuf *vbo) override;
@ -57,7 +67,8 @@ 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.
@ -65,6 +76,36 @@ class VKTexture : public Texture {
bool allocate();
VkImageViewType vk_image_view_type() const;
/* -------------------------------------------------------------------- */
/** \name Image Layout
* \{ */
public:
/**
* Update the current layout attribute, without actually changing the layout.
Jeroen-Bakker marked this conversation as resolved Outdated

Update the current layout attribute, without actually changing the layout.

Update the current layout attribute, without actually changing the layout.
*
* Vulkan can change the layout of an image, when a command is being executed.
* The start of a render pass or the end of a render pass can also alter the
* actual layout of the image. This method allows to change the last known layout
* that the image is using.
*
* NOTE: When we add command encoding, this should partly being done inside
* the command encoder, as there is more accurate determination of the transition
* of the layout. Only the final transition should then be stored inside the texture
* to be used by as initial layout for the next set of commands.
*/
void current_layout_set(VkImageLayout new_layout);
VkImageLayout current_layout_get() const;
/**
* Ensure the layout of the texture. This also performs the conversion by adding a memory
* barrier to the active command buffer to perform the conversion.
*
* When texture is already in the requested layout, nothing will be done.
*/
void layout_ensure(VKContext &context, VkImageLayout requested_layout);
/** \} */
};
static inline VKTexture *unwrap(Texture *tex)