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:
parent
5400fe941e
commit
2ee2ae93fb
@ -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>(®ion, 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;
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
Loading…
Reference in New Issue
Block a user