Vulkan: Initial Cube(Array) Support

This PR adds initial cube (array) support. Depending on how the texture
is used a different image view is created. When used as a framebuffer
attachment only a single side of the cubemap is attached. The image
view is attached as a 2d texture array. When used as a shader resource
the image view is a cubemap.

Also adds test cases to test both scenarios.

Pull Request: https://projects.blender.org/blender/blender/pulls/108794
This commit is contained in:
Jeroen Bakker 2023-06-09 16:09:54 +02:00
parent 5400fe941e
commit 2ee2ae93fb
11 changed files with 176 additions and 22 deletions

@ -215,6 +215,7 @@ class GHOST_DeviceVK {
device_features.geometryShader = VK_TRUE;
device_features.dualSrcBlend = VK_TRUE;
device_features.logicOp = VK_TRUE;
device_features.imageCubeArray = VK_TRUE;
#endif
VkDeviceCreateInfo device_create_info = {};
@ -309,7 +310,7 @@ static GHOST_TSuccess ensure_vulkan_device(VkInstance vk_instance,
#if STRICT_REQUIREMENTS
if (!device_vk.features.geometryShader || !device_vk.features.dualSrcBlend ||
!device_vk.features.logicOp)
!device_vk.features.logicOp || !device_vk.features.imageCubeArray)
{
continue;
}

@ -4,6 +4,7 @@
#include "testing/testing.h"
#include "GPU_context.h"
#include "GPU_framebuffer.h"
#include "gpu_testing.hh"
@ -199,4 +200,52 @@ static void test_framebuffer_scissor_test()
}
GPU_TEST(framebuffer_scissor_test);
/* Color each side of a cubemap with a different color. */
static void test_framebuffer_cube()
{
const int SIZE = 32;
GPU_render_begin();
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ;
GPUTexture *tex = GPU_texture_create_cube("tex", SIZE, 1, GPU_RGBA32F, usage, nullptr);
const float4 clear_colors[6] = {
{0.5f, 0.0f, 0.0f, 1.0f},
{1.0f, 0.0f, 0.0f, 1.0f},
{0.0f, 0.5f, 0.0f, 1.0f},
{0.0f, 1.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 0.5f, 1.0f},
{0.0f, 0.0f, 1.0f, 1.0f},
};
GPUFrameBuffer *framebuffers[6] = {nullptr};
for (int i : IndexRange(6)) {
GPU_framebuffer_ensure_config(&framebuffers[i],
{
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE_CUBEFACE(tex, i),
});
GPU_framebuffer_bind(framebuffers[i]);
GPU_framebuffer_clear_color(framebuffers[i], clear_colors[i]);
};
float4 *data = (float4 *)GPU_texture_read(tex, GPU_DATA_FLOAT, 0);
for (int side : IndexRange(6)) {
for (int pixel_index : IndexRange(SIZE * SIZE)) {
int index = pixel_index + (SIZE * SIZE) * side;
EXPECT_EQ(clear_colors[side], data[index]);
}
}
MEM_freeN(data);
GPU_texture_free(tex);
for (int i : IndexRange(6)) {
GPU_FRAMEBUFFER_FREE_SAFE(framebuffers[i]);
}
GPU_render_end();
}
GPU_TEST(framebuffer_cube)
} // namespace blender::gpu::tests

@ -63,6 +63,52 @@ static void test_texture_read()
}
GPU_TEST(texture_read)
static void test_texture_cube()
{
const int SIZE = 32;
GPU_render_begin();
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ;
GPUTexture *tex = GPU_texture_create_cube("tex", SIZE, 1, GPU_RGBA32F, usage, nullptr);
float4 clear_color(1.0f, 0.5f, 0.2f, 1.0f);
GPU_texture_clear(tex, GPU_DATA_FLOAT, clear_color);
float4 *data = (float4 *)GPU_texture_read(tex, GPU_DATA_FLOAT, 0);
for (int index : IndexRange(SIZE * SIZE * 6)) {
EXPECT_EQ(clear_color, data[index]);
}
MEM_freeN(data);
GPU_texture_free(tex);
GPU_render_end();
}
GPU_TEST(texture_cube)
static void test_texture_cube_array()
{
const int SIZE = 32;
const int ARRAY = 2;
GPU_render_begin();
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_HOST_READ;
GPUTexture *tex = GPU_texture_create_cube_array(
"tex", SIZE, ARRAY, 1, GPU_RGBA32F, usage, nullptr);
float4 clear_color(1.0f, 0.5f, 0.2f, 1.0f);
GPU_texture_clear(tex, GPU_DATA_FLOAT, clear_color);
float4 *data = (float4 *)GPU_texture_read(tex, GPU_DATA_FLOAT, 0);
for (int index : IndexRange(SIZE * SIZE * 6 * ARRAY)) {
EXPECT_EQ(clear_color, data[index]);
}
MEM_freeN(data);
GPU_texture_free(tex);
GPU_render_end();
}
GPU_TEST(texture_cube_array)
static void test_texture_copy()
{
const int SIZE = 128;

@ -108,7 +108,10 @@ void VKBackend::compute_dispatch(int groups_x_len, int groups_y_len, int groups_
command_buffer.dispatch(groups_x_len, groups_y_len, groups_z_len);
}
void VKBackend::compute_dispatch_indirect(StorageBuf * /*indirect_buf*/) {}
void VKBackend::compute_dispatch_indirect(StorageBuf * /*indirect_buf*/)
{
NOT_YET_IMPLEMENTED;
}
Context *VKBackend::context_alloc(void *ghost_window, void *ghost_context)
{

@ -585,17 +585,22 @@ VkFormat to_vk_format(const GPUVertCompType type, const uint32_t size, GPUVertFe
VkImageType to_vk_image_type(const eGPUTextureType type)
{
/* See
* https://vulkan.lunarg.com/doc/view/1.3.243.0/linux/1.3-extensions/vkspec.html#resources-image-views-compatibility
* for reference */
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_BUFFER:
case GPU_TEXTURE_1D_ARRAY:
return VK_IMAGE_TYPE_1D;
case GPU_TEXTURE_2D:
case GPU_TEXTURE_2D_ARRAY:
return VK_IMAGE_TYPE_2D;
case GPU_TEXTURE_3D:
case GPU_TEXTURE_CUBE:
case GPU_TEXTURE_CUBE_ARRAY:
return VK_IMAGE_TYPE_2D;
case GPU_TEXTURE_3D:
return VK_IMAGE_TYPE_3D;
case GPU_TEXTURE_ARRAY:
@ -607,7 +612,7 @@ VkImageType to_vk_image_type(const eGPUTextureType type)
return VK_IMAGE_TYPE_1D;
}
VkImageViewType to_vk_image_view_type(const eGPUTextureType type)
VkImageViewType to_vk_image_view_type(const eGPUTextureType type, const eImageViewUsage view_type)
{
switch (type) {
case GPU_TEXTURE_1D:
@ -618,13 +623,15 @@ VkImageViewType to_vk_image_view_type(const eGPUTextureType type)
case GPU_TEXTURE_3D:
return VK_IMAGE_VIEW_TYPE_3D;
case GPU_TEXTURE_CUBE:
return VK_IMAGE_VIEW_TYPE_CUBE;
return view_type == eImageViewUsage::Attachment ? VK_IMAGE_VIEW_TYPE_2D_ARRAY :
VK_IMAGE_VIEW_TYPE_CUBE;
case GPU_TEXTURE_1D_ARRAY:
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case GPU_TEXTURE_2D_ARRAY:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case GPU_TEXTURE_CUBE_ARRAY:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
return view_type == eImageViewUsage::Attachment ? VK_IMAGE_VIEW_TYPE_2D_ARRAY :
VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case GPU_TEXTURE_ARRAY:
/* GPU_TEXTURE_ARRAY should always be used together with 1D, 2D, or CUBE*/

@ -23,13 +23,27 @@
namespace blender::gpu {
/**
* Based on the usage of an Image View a different image view type should be created.
*
* When using a GPU_TEXTURE_CUBE as an frame buffer attachment it will be used as a
* GPU_TEXTURE_2D_ARRAY. eg only a single side of the cube map will be attached. But when bound as
* a shader resource the cubemap will be used.
*/
enum class eImageViewUsage {
/** Image View will be used as a bindable shader resource. */
ShaderBinding,
/** Image View will be used as an framebuffer attachment. */
Attachment,
};
VkImageAspectFlagBits to_vk_image_aspect_flag_bits(const eGPUTextureFormat format);
VkFormat to_vk_format(const eGPUTextureFormat format);
VkFormat to_vk_format(const GPUVertCompType type,
const uint32_t size,
const GPUVertFetchMode fetch_mode);
VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat format);
VkImageViewType to_vk_image_view_type(const eGPUTextureType type);
VkImageViewType to_vk_image_view_type(const eGPUTextureType type, eImageViewUsage view_type);
VkImageType to_vk_image_type(const eGPUTextureType type);
VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const void *data);
VkIndexType to_vk_index_type(const GPUIndexBufType index_type);

@ -404,7 +404,7 @@ void VKFrameBuffer::render_pass_create()
/* Ensure texture is allocated to ensure the image view. */
VKTexture &texture = *static_cast<VKTexture *>(unwrap(attachment.tex));
texture.ensure_allocated();
image_views_.append(VKImageView(texture, attachment.mip, name_));
image_views_.append(VKImageView(texture, attachment.layer, attachment.mip, name_));
image_views[attachment_location] = image_views_.last().vk_handle();
VkAttachmentDescription &attachment_description =

@ -15,8 +15,8 @@
namespace blender::gpu {
VKImageView::VKImageView(VKTexture &texture, int mip_level, StringRefNull name)
: vk_image_view_(create_vk_image_view(texture, mip_level, name))
VKImageView::VKImageView(VKTexture &texture, int layer, int mip_level, StringRefNull name)
: vk_image_view_(create_vk_image_view(texture, layer, mip_level, name))
{
BLI_assert(vk_image_view_ != VK_NULL_HANDLE);
}
@ -41,6 +41,7 @@ VKImageView::~VKImageView()
}
}
VkImageView VKImageView::create_vk_image_view(VKTexture &texture,
int layer,
int mip_level,
StringRefNull name)
{
@ -49,14 +50,15 @@ VkImageView VKImageView::create_vk_image_view(VKTexture &texture,
VkImageViewCreateInfo image_view_info = {};
image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_info.image = texture.vk_image_handle();
image_view_info.viewType = to_vk_image_view_type(texture.type_get());
image_view_info.viewType = to_vk_image_view_type(texture.type_get(),
eImageViewUsage::Attachment);
image_view_info.format = to_vk_format(texture.format_get());
image_view_info.components = to_vk_component_mapping(texture.format_get());
image_view_info.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(texture.format_get());
image_view_info.subresourceRange.baseMipLevel = mip_level;
image_view_info.subresourceRange.levelCount = 1;
image_view_info.subresourceRange.baseArrayLayer = 0;
image_view_info.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
image_view_info.subresourceRange.baseArrayLayer = layer == -1 ? 0 : layer;
image_view_info.subresourceRange.layerCount = 1;
const VKDevice &device = VKBackend::get().device_get();
VkImageView image_view = VK_NULL_HANDLE;

@ -20,7 +20,7 @@ class VKImageView : NonCopyable {
VkImageView vk_image_view_ = VK_NULL_HANDLE;
public:
VKImageView(VKTexture &texture, int mip_level, StringRefNull name);
VKImageView(VKTexture &texture, int layer, int mip_level, StringRefNull name);
/**
* Wrap the given vk_image_view handle. Note that the vk_image_view handle ownership is
@ -38,7 +38,10 @@ class VKImageView : NonCopyable {
}
private:
static VkImageView create_vk_image_view(VKTexture &texture, int mip_level, StringRefNull name);
static VkImageView create_vk_image_view(VKTexture &texture,
int layer,
int mip_level,
StringRefNull name);
};
} // namespace blender::gpu

@ -157,6 +157,15 @@ void VKTexture::mip_range_set(int min, int max)
flags_ |= IMAGE_VIEW_DIRTY;
}
int VKTexture::layer_count()
{
int layers = 1;
if (ELEM(type_, GPU_TEXTURE_CUBE, GPU_TEXTURE_CUBE_ARRAY)) {
layers = d_;
}
return layers;
}
void VKTexture::read_sub(int mip, eGPUDataFormat format, const int area[4], void *r_data)
{
VKContext &context = *VKContext::get();
@ -165,7 +174,7 @@ void VKTexture::read_sub(int mip, eGPUDataFormat format, const int area[4], void
/* Vulkan images cannot be directly mapped to host memory and requires a staging buffer. */
VKBuffer staging_buffer;
size_t sample_len = area[2] * area[3];
size_t sample_len = area[2] * area[3] * layer_count();
size_t device_memory_size = sample_len * to_bytesize(format_);
staging_buffer.create(
@ -179,7 +188,7 @@ void VKTexture::read_sub(int mip, eGPUDataFormat format, const int area[4], void
region.imageExtent.depth = 1;
region.imageSubresource.aspectMask = to_vk_image_aspect_flag_bits(format_);
region.imageSubresource.mipLevel = mip;
region.imageSubresource.layerCount = 1;
region.imageSubresource.layerCount = layer_count();
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.copy(staging_buffer, *this, Span<VkBufferImageCopy>(&region, 1));
@ -192,7 +201,7 @@ void *VKTexture::read(int mip, eGPUDataFormat format)
{
int mip_size[3] = {1, 1, 1};
mip_size_get(mip, mip_size);
size_t sample_len = mip_size[0] * mip_size[1];
size_t sample_len = mip_size[0] * mip_size[1] * layer_count();
size_t host_memory_size = sample_len * to_bytesize(format_, format);
void *data = MEM_mallocN(host_memory_size, __func__);
@ -372,6 +381,17 @@ static VkImageUsageFlagBits to_vk_image_usage(const eGPUTextureUsage usage,
return result;
}
static VkImageCreateFlagBits to_vk_image_create(const eGPUTextureType texture_type)
{
VkImageCreateFlagBits result = static_cast<VkImageCreateFlagBits>(0);
if (ELEM(texture_type, GPU_TEXTURE_CUBE, GPU_TEXTURE_CUBE_ARRAY)) {
result = static_cast<VkImageCreateFlagBits>(result | VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT);
}
return result;
}
bool VKTexture::allocate()
{
BLI_assert(vk_image_ == VK_NULL_HANDLE);
@ -379,17 +399,23 @@ bool VKTexture::allocate()
int extent[3] = {1, 1, 1};
mip_size_get(0, extent);
int layers = 1;
if (ELEM(type_, GPU_TEXTURE_CUBE, GPU_TEXTURE_CUBE_ARRAY)) {
layers = extent[2];
extent[2] = 1;
}
VKContext &context = *VKContext::get();
const VKDevice &device = VKBackend::get().device_get();
VkImageCreateInfo image_info = {};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.flags = to_vk_image_create(type_);
image_info.imageType = to_vk_image_type(type_);
image_info.extent.width = extent[0];
image_info.extent.height = extent[1];
image_info.extent.depth = extent[2];
image_info.mipLevels = max_ii(mipmaps_, 1);
image_info.arrayLayers = 1;
image_info.arrayLayers = layers;
image_info.format = to_vk_format(format_);
/* Some platforms (NVIDIA) requires that attached textures are always tiled optimal.
*
@ -536,14 +562,15 @@ void VKTexture::image_view_update()
VkImageViewCreateInfo image_view_info = {};
image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_info.image = vk_image_;
image_view_info.viewType = to_vk_image_view_type(type_);
image_view_info.viewType = to_vk_image_view_type(type_, eImageViewUsage::ShaderBinding);
image_view_info.format = to_vk_format(format_);
image_view_info.components = to_vk_component_mapping(format_);
image_view_info.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_);
IndexRange mip_range = mip_map_range();
image_view_info.subresourceRange.baseMipLevel = mip_range.first();
image_view_info.subresourceRange.levelCount = mip_range.size();
image_view_info.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
image_view_info.subresourceRange.layerCount =
ELEM(type_, GPU_TEXTURE_CUBE, GPU_TEXTURE_CUBE_ARRAY) ? d_ : VK_REMAINING_ARRAY_LAYERS;
const VKDevice &device = VKBackend::get().device_get();
VkImageView image_view = VK_NULL_HANDLE;

@ -86,6 +86,8 @@ class VKTexture : public Texture {
*/
bool allocate();
int layer_count();
VkImageViewType vk_image_view_type() const;
/* -------------------------------------------------------------------- */