Vulkan: Push constants #104880

Merged
Jeroen Bakker merged 73 commits from Jeroen-Bakker/blender:vulkan-push-constants into main 2023-03-06 12:29:06 +01:00
38 changed files with 1475 additions and 111 deletions
Showing only changes of commit 366404d274 - Show all commits

View File

@ -1201,7 +1201,9 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle context,
void *r_instance,
void *r_physical_device,
void *r_device,
uint32_t *r_graphic_queue_family);
uint32_t *r_graphic_queue_family,
void *r_queue);
void GHOST_GetVulkanCommandBuffer(GHOST_ContextHandle context, void *r_command_buffer);
/**
* Return VULKAN back-buffer resources handles for the given window.
@ -1209,7 +1211,6 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle context,
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id);

View File

@ -40,19 +40,16 @@ class GHOST_IContext {
virtual unsigned int getDefaultFramebuffer() = 0;
virtual GHOST_TSuccess getVulkanHandles(void *, void *, void *, uint32_t *) = 0;
virtual GHOST_TSuccess getVulkanHandles(void *, void *, void *, uint32_t *, void *) = 0;
virtual GHOST_TSuccess getVulkanCommandBuffer(void * /*r_command_buffer*/) = 0;
/**
* Gets the Vulkan framebuffer related resource handles associated with the Vulkan context.
* Needs to be called after each swap events as the framebuffer will change.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id) = 0;
virtual GHOST_TSuccess getVulkanBackbuffer(
void *image, void *framebuffer, void *render_pass, void *extent, uint32_t *fb_id) = 0;
virtual GHOST_TSuccess swapBuffers() = 0;

View File

@ -217,7 +217,6 @@ class GHOST_IWindow {
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id) = 0;

View File

@ -1203,22 +1203,29 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle contexthandle,
void *r_instance,
void *r_physical_device,
void *r_device,
uint32_t *r_graphic_queue_family)
uint32_t *r_graphic_queue_family,
void *r_queue)
{
GHOST_IContext *context = (GHOST_IContext *)contexthandle;
context->getVulkanHandles(r_instance, r_physical_device, r_device, r_graphic_queue_family);
context->getVulkanHandles(
r_instance, r_physical_device, r_device, r_graphic_queue_family, r_queue);
}
void GHOST_GetVulkanCommandBuffer(GHOST_ContextHandle contexthandle, void *r_command_buffer)
{
GHOST_IContext *context = (GHOST_IContext *)contexthandle;
context->getVulkanCommandBuffer(r_command_buffer);
}
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id)
{
GHOST_IWindow *window = (GHOST_IWindow *)windowhandle;
window->getVulkanBackbuffer(image, framebuffer, command_buffer, render_pass, extent, fb_id);
window->getVulkanBackbuffer(image, framebuffer, render_pass, extent, fb_id);
}
#endif /* WITH_VULKAN */

View File

@ -142,7 +142,13 @@ class GHOST_Context : public GHOST_IContext {
virtual GHOST_TSuccess getVulkanHandles(void * /*r_instance*/,
void * /*r_physical_device*/,
void * /*r_device*/,
uint32_t * /*r_graphic_queue_family*/) override
uint32_t * /*r_graphic_queue_family*/,
void * /*r_queue*/) override
{
return GHOST_kFailure;
};
virtual GHOST_TSuccess getVulkanCommandBuffer(void * /*r_command_buffer*/) override
{
return GHOST_kFailure;
};
@ -154,7 +160,6 @@ class GHOST_Context : public GHOST_IContext {
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void * /*image*/,
void * /*framebuffer*/,
void * /*command_buffer*/,
void * /*render_pass*/,
void * /*extent*/,
uint32_t * /*fb_id*/) override

View File

@ -288,19 +288,14 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id)
GHOST_TSuccess GHOST_ContextVK::getVulkanBackbuffer(
void *image, void *framebuffer, void *render_pass, void *extent, uint32_t *fb_id)
{
if (m_swapchain == VK_NULL_HANDLE) {
return GHOST_kFailure;
}
*((VkImage *)image) = m_swapchain_images[m_currentImage];
*((VkFramebuffer *)framebuffer) = m_swapchain_framebuffers[m_currentImage];
*((VkCommandBuffer *)command_buffer) = m_command_buffers[m_currentImage];
*((VkRenderPass *)render_pass) = m_render_pass;
*((VkExtent2D *)extent) = m_render_extent;
*fb_id = m_swapchain_id * 10 + m_currentFrame;
@ -311,12 +306,30 @@ GHOST_TSuccess GHOST_ContextVK::getVulkanBackbuffer(void *image,
GHOST_TSuccess GHOST_ContextVK::getVulkanHandles(void *r_instance,
void *r_physical_device,
void *r_device,
uint32_t *r_graphic_queue_family)
uint32_t *r_graphic_queue_family,
void *r_queue)
{
*((VkInstance *)r_instance) = m_instance;
*((VkPhysicalDevice *)r_physical_device) = m_physical_device;
*((VkDevice *)r_device) = m_device;
*r_graphic_queue_family = m_queue_family_graphic;
*((VkQueue *)r_queue) = m_graphic_queue;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::getVulkanCommandBuffer(void *r_command_buffer)
{
if (m_command_buffers.empty()) {
return GHOST_kFailure;
}
if (m_swapchain == VK_NULL_HANDLE) {
*((VkCommandBuffer *)r_command_buffer) = m_command_buffers[0];
}
else {
*((VkCommandBuffer *)r_command_buffer) = m_command_buffers[m_currentImage];
}
return GHOST_kSuccess;
}
@ -520,6 +533,9 @@ static GHOST_TSuccess getGraphicQueueFamily(VkPhysicalDevice device, uint32_t *r
*r_queue_index = 0;
for (const auto &queue_family : queue_families) {
/* Every vulkan implementation by spec must have one queue family that support both graphics
* and compute pipelines. We select this one; compute only queue family hints at async compute
* implementations.*/
if ((queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) &&
(queue_family.queueFlags & VK_QUEUE_COMPUTE_BIT)) {
return GHOST_kSuccess;
@ -619,16 +635,36 @@ static GHOST_TSuccess selectPresentMode(VkPhysicalDevice device,
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextVK::createCommandBuffers()
GHOST_TSuccess GHOST_ContextVK::createCommandPools()
{
m_command_buffers.resize(m_swapchain_image_views.size());
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = m_queue_family_graphic;
VK_CHECK(vkCreateCommandPool(m_device, &poolInfo, NULL, &m_command_pool));
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::createGraphicsCommandBuffer()
{
assert(m_command_pool != VK_NULL_HANDLE);
assert(m_command_buffers.size() == 0);
m_command_buffers.resize(1);
VkCommandBufferAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.commandPool = m_command_pool;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandBufferCount = static_cast<uint32_t>(m_command_buffers.size());
VK_CHECK(vkAllocateCommandBuffers(m_device, &alloc_info, m_command_buffers.data()));
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::createGraphicsCommandBuffers()
{
assert(m_command_pool != VK_NULL_HANDLE);
m_command_buffers.resize(m_swapchain_image_views.size());
VkCommandBufferAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
@ -637,7 +673,6 @@ GHOST_TSuccess GHOST_ContextVK::createCommandBuffers()
alloc_info.commandBufferCount = static_cast<uint32_t>(m_command_buffers.size());
VK_CHECK(vkAllocateCommandBuffers(m_device, &alloc_info, m_command_buffers.data()));
return GHOST_kSuccess;
}
@ -776,7 +811,7 @@ GHOST_TSuccess GHOST_ContextVK::createSwapchain()
VK_CHECK(vkCreateFence(m_device, &fence_info, NULL, &m_in_flight_fences[i]));
}
createCommandBuffers();
createGraphicsCommandBuffers();
return GHOST_kSuccess;
}
@ -841,6 +876,13 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
extensions_device.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
extensions_device.push_back("VK_KHR_dedicated_allocation");
extensions_device.push_back("VK_KHR_get_memory_requirements2");
/* Enable MoltenVK required instance extensions.*/
#ifdef VK_MVK_MOLTENVK_EXTENSION_NAME
requireExtension(
extensions_available, extensions_enabled, "VK_KHR_get_physical_device_properties2");
#endif
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
@ -903,6 +945,15 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
return GHOST_kFailure;
}
#ifdef VK_MVK_MOLTENVK_EXTENSION_NAME
/* According to the Vulkan specs, when `VK_KHR_portability_subset` is available it should be
* enabled. See
* https://vulkan.lunarg.com/doc/view/1.2.198.1/mac/1.2-extensions/vkspec.html#VUID-VkDeviceCreateInfo-pProperties-04451*/
if (device_extensions_support(m_physical_device, {VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME})) {
extensions_device.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
}
#endif
vector<VkDeviceQueueCreateInfo> queue_create_infos;
{
@ -962,11 +1013,14 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
vkGetDeviceQueue(m_device, m_queue_family_graphic, 0, &m_graphic_queue);
createCommandPools();
if (use_window_surface) {
vkGetDeviceQueue(m_device, m_queue_family_present, 0, &m_present_queue);
createSwapchain();
}
else {
createGraphicsCommandBuffer();
}
return GHOST_kSuccess;
}

View File

@ -113,18 +113,17 @@ class GHOST_ContextVK : public GHOST_Context {
GHOST_TSuccess getVulkanHandles(void *r_instance,
void *r_physical_device,
void *r_device,
uint32_t *r_graphic_queue_family);
uint32_t *r_graphic_queue_family,
void *r_queue);
GHOST_TSuccess getVulkanCommandBuffer(void *r_command_buffer);
/**
* Gets the Vulkan framebuffer related resource handles associated with the Vulkan context.
* Needs to be called after each swap events as the framebuffer will change.
* \return A boolean success indicator.
*/
GHOST_TSuccess getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id);
GHOST_TSuccess getVulkanBackbuffer(
void *image, void *framebuffer, void *render_pass, void *extent, uint32_t *fb_id);
/**
* Sets the swap interval for swapBuffers.
@ -200,6 +199,8 @@ class GHOST_ContextVK : public GHOST_Context {
GHOST_TSuccess pickPhysicalDevice(std::vector<const char *> required_exts);
GHOST_TSuccess createSwapchain();
GHOST_TSuccess destroySwapchain();
GHOST_TSuccess createCommandBuffers();
GHOST_TSuccess createCommandPools();
GHOST_TSuccess createGraphicsCommandBuffers();
GHOST_TSuccess createGraphicsCommandBuffer();
GHOST_TSuccess recordCommandBuffers();
};

View File

@ -109,13 +109,12 @@ uint GHOST_Window::getDefaultFramebuffer()
GHOST_TSuccess GHOST_Window::getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id)
{
return m_context->getVulkanBackbuffer(
image, framebuffer, command_buffer, render_pass, extent, fb_id);
image, framebuffer, render_pass, extent, fb_id);
}
GHOST_TSuccess GHOST_Window::activateDrawingContext()

View File

@ -274,12 +274,8 @@ class GHOST_Window : public GHOST_IWindow {
* Needs to be called after each swap events as the framebuffer will change.
* \return A boolean success indicator.
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id) override;
virtual GHOST_TSuccess getVulkanBackbuffer(
void *image, void *framebuffer, void *render_pass, void *extent, uint32_t *fb_id) override;
/**
* Returns the window user data.

View File

@ -190,16 +190,23 @@ set(OPENGL_SRC
set(VULKAN_SRC
vulkan/vk_backend.cc
vulkan/vk_batch.cc
vulkan/vk_buffer.cc
vulkan/vk_context.cc
vulkan/vk_command_buffer.cc
vulkan/vk_descriptor_pools.cc
vulkan/vk_descriptor_set.cc
vulkan/vk_drawlist.cc
vulkan/vk_fence.cc
vulkan/vk_framebuffer.cc
vulkan/vk_index_buffer.cc
vulkan/vk_pipeline.cc
vulkan/vk_memory.cc
vulkan/vk_pixel_buffer.cc
vulkan/vk_query.cc
vulkan/vk_shader.cc
vulkan/vk_shader_interface.cc
vulkan/vk_shader_log.cc
vulkan/vk_state_manager.cc
vulkan/vk_storage_buffer.cc
vulkan/vk_texture.cc
vulkan/vk_uniform_buffer.cc
@ -207,16 +214,23 @@ set(VULKAN_SRC
vulkan/vk_backend.hh
vulkan/vk_batch.hh
vulkan/vk_buffer.hh
vulkan/vk_context.hh
vulkan/vk_command_buffer.hh
vulkan/vk_descriptor_pools.hh
vulkan/vk_descriptor_set.hh
vulkan/vk_drawlist.hh
vulkan/vk_fence.hh
vulkan/vk_framebuffer.hh
vulkan/vk_index_buffer.hh
vulkan/vk_pipeline.hh
vulkan/vk_memory.hh
vulkan/vk_pixel_buffer.hh
vulkan/vk_query.hh
vulkan/vk_shader.hh
vulkan/vk_shader_interface.hh
vulkan/vk_shader_log.hh
vulkan/vk_state_manager.hh
vulkan/vk_storage_buffer.hh
vulkan/vk_texture.hh
vulkan/vk_uniform_buffer.hh
@ -510,6 +524,7 @@ set(GLSL_SRC_TEST
tests/shaders/gpu_compute_1d_test.glsl
tests/shaders/gpu_compute_2d_test.glsl
tests/shaders/gpu_compute_ibo_test.glsl
tests/shaders/gpu_compute_ssbo_test.glsl
tests/shaders/gpu_compute_vbo_test.glsl
tests/shaders/gpu_compute_dummy_test.glsl
)
@ -787,6 +802,7 @@ if(WITH_GTESTS)
tests/gpu_index_buffer_test.cc
tests/gpu_shader_builtin_test.cc
tests/gpu_shader_test.cc
tests/gpu_storage_buffer_test.cc
tests/gpu_testing.hh
)

View File

@ -14,6 +14,8 @@ struct GPUStorageBuf;
namespace blender {
namespace gpu {
class VertBuf;
#ifdef DEBUG
# define DEBUG_NAME_LEN 64
#else

View File

@ -212,7 +212,7 @@ GPU_TEST(gpu_shader_compute_ibo)
static void test_gpu_shader_compute_ssbo()
{
if (!GPU_compute_shader_support()) {
if (!GPU_compute_shader_support() && !GPU_shader_storage_buffer_objects_support()) {
/* We can't test as a the platform does not support compute shaders. */
std::cout << "Skipping compute shader test: platform not supported";
return;
@ -221,14 +221,14 @@ static void test_gpu_shader_compute_ssbo()
static constexpr uint SIZE = 128;
/* Build compute shader. */
GPUShader *shader = GPU_shader_create_from_info_name("gpu_compute_ibo_test");
GPUShader *shader = GPU_shader_create_from_info_name("gpu_compute_ssbo_test");
EXPECT_NE(shader, nullptr);
GPU_shader_bind(shader);
/* Construct IBO. */
GPUStorageBuf *ssbo = GPU_storagebuf_create_ex(
SIZE * sizeof(uint32_t), nullptr, GPU_USAGE_DEVICE_ONLY, __func__);
GPU_storagebuf_bind(ssbo, GPU_shader_get_ssbo(shader, "out_indices"));
GPU_storagebuf_bind(ssbo, GPU_shader_get_ssbo(shader, "data_out"));
/* Dispatch compute task. */
GPU_compute_dispatch(shader, SIZE, 1, 1);
@ -240,7 +240,7 @@ static void test_gpu_shader_compute_ssbo()
uint32_t data[SIZE];
GPU_storagebuf_read(ssbo, data);
for (int index = 0; index < SIZE; index++) {
uint32_t expected = index;
uint32_t expected = index * 4;
EXPECT_EQ(data[index], expected);
}

View File

@ -0,0 +1,50 @@
/* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "GPU_storage_buffer.h"
#include "BLI_vector.hh"
#include "gpu_testing.hh"
namespace blender::gpu::tests {
constexpr size_t SIZE = 128;
constexpr size_t SIZE_IN_BYTES = SIZE * sizeof(int);
static Vector<int32_t> test_data()
{
Vector<int32_t> data;
for (int i : IndexRange(SIZE)) {
data.append(i);
}
return data;
}
static void test_gpu_storage_buffer_create_update_read()
{
GPUStorageBuf *ssbo = GPU_storagebuf_create_ex(
SIZE_IN_BYTES, nullptr, GPU_USAGE_STATIC, __func__);
EXPECT_NE(ssbo, nullptr);
/* Upload some dummy data. */
const Vector<int32_t> data = test_data();
GPU_storagebuf_update(ssbo, data.data());
/* Read back data from SSBO. */
Vector<int32_t> read_data;
read_data.resize(SIZE, 0);
GPU_storagebuf_read(ssbo, read_data.data());
/* Check if data is the same.*/
for (int i : IndexRange(SIZE)) {
EXPECT_EQ(data[i], read_data[i]);
}
GPU_storagebuf_free(ssbo);
}
GPU_TEST(gpu_storage_buffer_create_update_read);
} // namespace blender::gpu::tests

View File

@ -15,18 +15,21 @@ namespace blender::gpu {
void GPUTest::SetUp()
{
GPU_backend_type_selection_set(gpu_backend_type);
GHOST_GLSettings glSettings = {0};
GHOST_GLSettings glSettings = {};
glSettings.context_type = draw_context_type;
glSettings.flags = GHOST_glDebugContext;
CLG_init();
ghost_system = GHOST_CreateSystem();
ghost_context = GHOST_CreateOpenGLContext(ghost_system, glSettings);
GHOST_ActivateOpenGLContext(ghost_context);
context = GPU_context_create(nullptr, ghost_context);
GPU_init();
GPU_context_begin_frame(context);
}
void GPUTest::TearDown()
{
GPU_context_end_frame(context);
GPU_exit();
GPU_context_discard(context);
GHOST_DisposeOpenGLContext(ghost_system, ghost_context);

View File

@ -0,0 +1,5 @@
void main()
{
int store_index = int(gl_GlobalInvocationID.x);
data_out[store_index] = store_index * 4;
}

View File

@ -60,8 +60,17 @@ void VKBackend::samplers_update()
{
}
void VKBackend::compute_dispatch(int /*groups_x_len*/, int /*groups_y_len*/, int /*groups_z_len*/)
void VKBackend::compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len)
{
VKContext &context = *VKContext::get();
VKShader *shader = static_cast<VKShader *>(context.shader);
VKCommandBuffer &command_buffer = context.command_buffer_get();
VKPipeline &pipeline = shader->pipeline_get();
VKDescriptorSet &descriptor_set = pipeline.descriptor_set_get();
descriptor_set.update(context.device_get());
command_buffer.bind(
descriptor_set, shader->vk_pipeline_layout_get(), VK_PIPELINE_BIND_POINT_COMPUTE);
command_buffer.dispatch(groups_x_len, groups_y_len, groups_z_len);
Jeroen-Bakker marked this conversation as resolved
Review

I may be missing something, but this whole switch block feels like it does not belong here. It looks way too specific to me.

Would rather see that logic as part of the VKPushConstants class itself, but no idea if this is doable in practice... At the very least would have it in a dedicated util function of VKBackend otherwise?

I may be missing something, but this whole `switch` block feels like it does not belong here. It looks way too specific to me. Would rather see that logic as part of the `VKPushConstants` class itself, but no idea if this is doable in practice... At the very least would have it in a dedicated util function of `VKBackend` otherwise?
Review

Yes you're right, will move this part into a method of VKPushConstants.

Yes you're right, will move this part into a method of VKPushConstants.
}
void VKBackend::compute_dispatch_indirect(StorageBuf * /*indirect_buf*/)
@ -123,9 +132,9 @@ UniformBuf *VKBackend::uniformbuf_alloc(int size, const char *name)
return new VKUniformBuffer(size, name);
}
StorageBuf *VKBackend::storagebuf_alloc(int size, GPUUsageType /*usage*/, const char *name)
StorageBuf *VKBackend::storagebuf_alloc(int size, GPUUsageType usage, const char *name)
{
return new VKStorageBuffer(size, name);
return new VKStorageBuffer(size, usage, name);
}
VertBuf *VKBackend::vertbuf_alloc()

View File

@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_buffer.hh"
namespace blender::gpu {
VKBuffer::~VKBuffer()
{
VKContext &context = *VKContext::get();
free(context);
}
bool VKBuffer::is_allocated() const
{
return allocation_ != VK_NULL_HANDLE;
}
static VmaAllocationCreateFlagBits vma_allocation_flags(GPUUsageType usage)
{
switch (usage) {
case GPU_USAGE_STATIC:
return static_cast<VmaAllocationCreateFlagBits>(
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT);
case GPU_USAGE_DYNAMIC:
return static_cast<VmaAllocationCreateFlagBits>(
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT);
case GPU_USAGE_DEVICE_ONLY:
return static_cast<VmaAllocationCreateFlagBits>(
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT |
VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT);
case GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY:
case GPU_USAGE_STREAM:
break;
}
BLI_assert_msg(false, "Unimplemented GPUUsageType");
return static_cast<VmaAllocationCreateFlagBits>(VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT);
}
bool VKBuffer::create(VKContext &context,
int64_t size_in_bytes,
GPUUsageType usage,
VkBufferUsageFlagBits buffer_usage)
{
BLI_assert(!is_allocated());
size_in_bytes_ = size_in_bytes;
VmaAllocator allocator = context.mem_allocator_get();
VkBufferCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
create_info.flags = 0;
create_info.size = size_in_bytes;
create_info.usage = buffer_usage;
/* For now the compute and graphics command queues are the same, so we can safely assume
* exclusive mode.*/
create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
create_info.queueFamilyIndexCount = 1;
create_info.pQueueFamilyIndices = context.queue_family_ptr_get();
VmaAllocationCreateInfo vma_create_info = {};
vma_create_info.flags = vma_allocation_flags(usage);
vma_create_info.priority = 1.0f;
vma_create_info.usage = VMA_MEMORY_USAGE_AUTO;
VkResult result = vmaCreateBuffer(
allocator, &create_info, &vma_create_info, &vk_buffer_, &allocation_, nullptr);
return result == VK_SUCCESS;
}
bool VKBuffer::update(VKContext &context, const void *data)
{
void *mapped_memory;
bool result = map(context, &mapped_memory);
if (result) {
memcpy(mapped_memory, data, size_in_bytes_);
unmap(context);
}
return result;
}
bool VKBuffer::map(VKContext &context, void **r_mapped_memory) const
{
VmaAllocator allocator = context.mem_allocator_get();
VkResult result = vmaMapMemory(allocator, allocation_, r_mapped_memory);
return result == VK_SUCCESS;
}
void VKBuffer::unmap(VKContext &context) const
{
VmaAllocator allocator = context.mem_allocator_get();
vmaUnmapMemory(allocator, allocation_);
}
bool VKBuffer::free(VKContext &context)
{
VmaAllocator allocator = context.mem_allocator_get();
vmaDestroyBuffer(allocator, vk_buffer_, allocation_);
return true;
}
} // namespace blender::gpu

View File

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_context_private.hh"
#include "vk_context.hh"
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
namespace blender::gpu {
/**
* Class for handing vulkan buffers (allocation/updating/binding).
*/
class VKBuffer {
int64_t size_in_bytes_;
VkBuffer vk_buffer_ = VK_NULL_HANDLE;
VmaAllocation allocation_ = VK_NULL_HANDLE;
public:
VKBuffer() = default;
virtual ~VKBuffer();
/** Has this buffer been allocated? */
bool is_allocated() const;
bool create(VKContext &context,
int64_t size,
GPUUsageType usage,
VkBufferUsageFlagBits buffer_usage);
bool update(VKContext &context, const void *data);
// TODO: add partial_update (update_sub)
bool free(VKContext &context);
bool map(VKContext &context, void **r_mapped_memory)const;
void unmap(VKContext &context)const ;
int64_t size_in_bytes() const
{
return size_in_bytes_;
}
VkBuffer vk_handle() const
{
return vk_buffer_;
}
};
} // namespace blender::gpu

View File

@ -0,0 +1,98 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_command_buffer.hh"
#include "vk_context.hh"
#include "BLI_assert.h"
namespace blender::gpu {
VKCommandBuffer::~VKCommandBuffer()
{
if (vk_device_ != VK_NULL_HANDLE) {
vkDestroyFence(vk_device_, vk_fence_, nullptr);
vk_fence_ = VK_NULL_HANDLE;
}
}
void VKCommandBuffer::init(const VkDevice vk_device,
const VkQueue vk_queue,
VkCommandBuffer vk_command_buffer)
{
vk_device_ = vk_device;
vk_queue_ = vk_queue;
vk_command_buffer_ = vk_command_buffer;
if (vk_fence_ == VK_NULL_HANDLE) {
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
vkCreateFence(vk_device_, &fenceInfo, nullptr, &vk_fence_);
}
}
void VKCommandBuffer::begin_recording()
{
vkWaitForFences(vk_device_, 1, &vk_fence_, VK_TRUE, UINT64_MAX);
vkResetFences(vk_device_, 1, &vk_fence_);
vkResetCommandBuffer(vk_command_buffer_, 0);
VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
vkBeginCommandBuffer(vk_command_buffer_, &begin_info);
}
void VKCommandBuffer::end_recording()
{
vkEndCommandBuffer(vk_command_buffer_);
}
void VKCommandBuffer::bind(const VKPipeline &pipeline, VkPipelineBindPoint bind_point)
{
vkCmdBindPipeline(vk_command_buffer_, bind_point, pipeline.vk_handle());
}
void VKCommandBuffer::bind(const VKDescriptorSet &descriptor_set,
const VkPipelineLayout vk_pipeline_layout,
VkPipelineBindPoint bind_point)
{
VkDescriptorSet vk_descriptor_set = descriptor_set.vk_handle();
vkCmdBindDescriptorSets(
vk_command_buffer_, bind_point, vk_pipeline_layout, 0, 1, &vk_descriptor_set, 0, 0);
}
void VKCommandBuffer::dispatch(int groups_x_len, int groups_y_len, int groups_z_len)
{
vkCmdDispatch(vk_command_buffer_, groups_x_len, groups_y_len, groups_z_len);
}
void VKCommandBuffer::submit()
{
end_recording();
encode_recorded_commands();
submit_encoded_commands();
begin_recording();
}
void VKCommandBuffer::encode_recorded_commands()
{
/* TODO: Intentionally not implemented. For the graphics pipeline we want to extract the
* resources and its usages so we can encode multiple commands in the same command buffer. This
* will also require to change the begin/end recording to be only inside this function. */
}
void VKCommandBuffer::submit_encoded_commands()
{
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &vk_command_buffer_;
vkQueueSubmit(vk_queue_, 1, &submit_info, vk_fence_);
}
} // namespace blender::gpu

View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#include "vk_pipeline.hh"
namespace blender::gpu {
/** Command buffer to keep track of the life-time of a command buffer.*/
class VKCommandBuffer : NonCopyable, NonMovable {
/** None owning handle to the command buffer and device. Handle is owned by `GHOST_ContextVK`.*/
VkDevice vk_device_ = VK_NULL_HANDLE;
VkCommandBuffer vk_command_buffer_ = VK_NULL_HANDLE;
VkQueue vk_queue_ = VK_NULL_HANDLE;
/** Owning handles */
VkFence vk_fence_ = VK_NULL_HANDLE;
public:
virtual ~VKCommandBuffer();
void init(const VkDevice vk_device, const VkQueue vk_queue, VkCommandBuffer vk_command_buffer);
void begin_recording();
void end_recording();
void bind(const VKPipeline &vk_pipeline, VkPipelineBindPoint bind_point);
void bind(const VKDescriptorSet &descriptor_set,
const VkPipelineLayout vk_pipeline_layout,
VkPipelineBindPoint bind_point);
void dispatch(int groups_x_len, int groups_y_len, int groups_z_len);
/**
* Stop recording commands, encode + send the recordings to Vulkan, wait for the until the
* commands have been executed and start the command buffer to accept recordings again.
*/
void submit();
private:
void encode_recorded_commands();
void submit_encoded_commands();
};
} // namespace blender::gpu

View File

@ -8,6 +8,7 @@
#include "vk_context.hh"
#include "vk_backend.hh"
#include "vk_state_manager.hh"
#include "GHOST_C-api.h"
@ -19,21 +20,28 @@ VKContext::VKContext(void *ghost_window, void *ghost_context)
if (ghost_window) {
ghost_context = GHOST_GetDrawingContext((GHOST_WindowHandle)ghost_window);
}
ghost_context_ = ghost_context;
GHOST_GetVulkanHandles((GHOST_ContextHandle)ghost_context,
&instance_,
&physical_device_,
&device_,
&graphic_queue_family_);
&vk_instance_,
&vk_physical_device_,
&vk_device_,
&vk_queue_family_,
&vk_queue_);
/* Initialize the memory allocator. */
VmaAllocatorCreateInfo info = {};
/* Should use same vulkan version as GHOST. */
info.vulkanApiVersion = VK_API_VERSION_1_2;
info.physicalDevice = physical_device_;
info.device = device_;
info.instance = instance_;
/* Should use same vulkan version as GHOST, but set to 1.0 for now. Raising it to 1.2 requires
* correct extensions and functions to be found, which doesn't out-of-the-box. We should fix
* this, but to continue the development at hand we lower the API to 1.0.*/
info.vulkanApiVersion = VK_API_VERSION_1_0;
info.physicalDevice = vk_physical_device_;
info.device = vk_device_;
info.instance = vk_instance_;
vmaCreateAllocator(&info, &mem_allocator_);
descriptor_pools_.init(vk_device_);
state_manager = new VKStateManager();
VKBackend::capabilities_init(*this);
}
@ -53,10 +61,17 @@ void VKContext::deactivate()
void VKContext::begin_frame()
{
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
GHOST_GetVulkanCommandBuffer(static_cast<GHOST_ContextHandle>(ghost_context_), &command_buffer);
command_buffer_.init(vk_device_, vk_queue_, command_buffer);
command_buffer_.begin_recording();
descriptor_pools_.reset();
}
void VKContext::end_frame()
{
command_buffer_.end_recording();
}
void VKContext::flush()

View File

@ -9,6 +9,9 @@
#include "gpu_context_private.hh"
#include "vk_command_buffer.hh"
#include "vk_descriptor_pools.hh"
#include "vk_mem_alloc.h"
#ifdef __APPLE__
@ -22,13 +25,18 @@ namespace blender::gpu {
class VKContext : public Context {
private:
/** Copies of the handles owned by the GHOST context. */
VkInstance instance_ = VK_NULL_HANDLE;
VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
VkDevice device_ = VK_NULL_HANDLE;
uint32_t graphic_queue_family_ = 0;
VkInstance vk_instance_ = VK_NULL_HANDLE;
VkPhysicalDevice vk_physical_device_ = VK_NULL_HANDLE;
VkDevice vk_device_ = VK_NULL_HANDLE;
VKCommandBuffer command_buffer_;
uint32_t vk_queue_family_ = 0;
VkQueue vk_queue_ = VK_NULL_HANDLE;
/** Allocator used for texture and buffers and other resources. */
VmaAllocator mem_allocator_ = VK_NULL_HANDLE;
VKDescriptorPools descriptor_pools_;
void *ghost_context_;
public:
VKContext(void *ghost_window, void *ghost_context);
@ -54,7 +62,27 @@ class VKContext : public Context {
VkDevice device_get() const
{
return device_;
return vk_device_;
}
VKCommandBuffer &command_buffer_get()
{
return command_buffer_;
}
VkQueue queue_get() const
{
return vk_queue_;
}
const uint32_t *queue_family_ptr_get() const
{
return &vk_queue_family_;
}
VKDescriptorPools &descriptor_pools_get()
{
return descriptor_pools_;
}
VmaAllocator mem_allocator_get() const

View File

@ -0,0 +1,110 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_descriptor_pools.hh"
namespace blender::gpu {
VKDescriptorPools::VKDescriptorPools()
{
}
VKDescriptorPools::~VKDescriptorPools()
{
for (const VkDescriptorPool vk_descriptor_pool : pools_) {
BLI_assert(vk_device_ != VK_NULL_HANDLE);
vkDestroyDescriptorPool(vk_device_, vk_descriptor_pool, nullptr);
}
vk_device_ = VK_NULL_HANDLE;
}
void VKDescriptorPools::init(const VkDevice vk_device)
{
BLI_assert(vk_device_ == VK_NULL_HANDLE);
vk_device_ = vk_device;
add_new_pool();
}
void VKDescriptorPools::reset()
{
active_pool_index_ = 0;
}
void VKDescriptorPools::add_new_pool()
{
Vector<VkDescriptorPoolSize> pool_sizes = {
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, POOL_SIZE_UNIFORM_BUFFER},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, POOL_SIZE_STORAGE_BUFFER},
};
VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = POOL_SIZE_DESCRIPTOR_SETS;
pool_info.poolSizeCount = pool_sizes.size();
pool_info.pPoolSizes = pool_sizes.data();
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
VkResult result = vkCreateDescriptorPool(vk_device_, &pool_info, nullptr, &descriptor_pool);
UNUSED_VARS(result);
pools_.append(descriptor_pool);
}
VkDescriptorPool VKDescriptorPools::active_pool_get()
{
BLI_assert(!pools_.is_empty());
return pools_[active_pool_index_];
}
void VKDescriptorPools::activate_next_pool()
{
BLI_assert(!is_last_pool_active());
active_pool_index_ += 1;
}
void VKDescriptorPools::activate_last_pool()
{
active_pool_index_ = pools_.size() - 1;
}
bool VKDescriptorPools::is_last_pool_active()
{
return active_pool_index_ == pools_.size() - 1;
}
VKDescriptorSet VKDescriptorPools::allocate(const VkDescriptorSetLayout &descriptor_set_layout)
{
VkDescriptorSetAllocateInfo allocate_info = {};
VkDescriptorPool pool = active_pool_get();
allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocate_info.descriptorPool = pool;
allocate_info.descriptorSetCount = 1;
allocate_info.pSetLayouts = &descriptor_set_layout;
VkDescriptorSet vk_descriptor_set = VK_NULL_HANDLE;
VkResult result = vkAllocateDescriptorSets(vk_device_, &allocate_info, &vk_descriptor_set);
if (result == VK_ERROR_OUT_OF_POOL_MEMORY) {
if (is_last_pool_active()) {
add_new_pool();
activate_last_pool();
}
else {
activate_next_pool();
}
return allocate(descriptor_set_layout);
}
return VKDescriptorSet(pool, vk_descriptor_set);
}
void VKDescriptorPools::free(VKDescriptorSet &descriptor_set)
{
VkDescriptorSet vk_descriptor_set = descriptor_set.vk_handle();
VkDescriptorPool vk_descriptor_pool = descriptor_set.vk_pool_handle();
BLI_assert(pools_.contains(vk_descriptor_pool));
vkFreeDescriptorSets(vk_device_, vk_descriptor_pool, 1, &vk_descriptor_set);
descriptor_set.mark_freed();
}
} // namespace blender::gpu

View File

@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_vector.hh"
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#include "vk_descriptor_set.hh"
namespace blender::gpu {
/**
* List of VkDescriptorPools.
*
* In Vulkan a pool is constructed with a fixed size per resource type. When more resources are
* needed it a next pool should be created. VKDescriptorPools will keep track of those pools and
* construct new pools when the previous one is exhausted.
*
* At the beginning of a new frame the descriptor pools are reset. This will start allocating
* again from the first descriptor pool in order to use freed space from previous pools.
*/
class VKDescriptorPools {
/**
* Pool sizes to use. When one descriptor pool is requested to allocate a descriptor but isn't
* able to do so, it will fail.
*
* Better defaults should be set later on, when we know more about our resource usage.
*/
static constexpr uint32_t POOL_SIZE_UNIFORM_BUFFER = 1000;
static constexpr uint32_t POOL_SIZE_STORAGE_BUFFER = 1000;
static constexpr uint32_t POOL_SIZE_DESCRIPTOR_SETS = 1000;
VkDevice vk_device_ = VK_NULL_HANDLE;
Vector<VkDescriptorPool> pools_;
int64_t active_pool_index_ = 0;
public:
VKDescriptorPools();
~VKDescriptorPools();
void init(const VkDevice vk_device);
VKDescriptorSet allocate(const VkDescriptorSetLayout &descriptor_set_layout);
void free(VKDescriptorSet &descriptor_set);
/**
* Reset the pools to start looking for free space from the first descriptor pool.
*/
void reset();
private:
VkDescriptorPool active_pool_get();
void activate_next_pool();
void activate_last_pool();
bool is_last_pool_active();
void add_new_pool();
};
} // namespace blender::gpu

View File

@ -0,0 +1,88 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_descriptor_set.hh"
#include "vk_storage_buffer.hh"
#include "vk_vertex_buffer.hh"
#include "BLI_assert.h"
namespace blender::gpu {
VKDescriptorSet::~VKDescriptorSet()
{
if (vk_descriptor_set_ != VK_NULL_HANDLE) {
/* Handle should be given back to the pool.*/
VKContext &context = *VKContext::get();
context.descriptor_pools_get().free(*this);
BLI_assert(vk_descriptor_set_ == VK_NULL_HANDLE);
}
}
void VKDescriptorSet::mark_freed()
{
vk_descriptor_set_ = VK_NULL_HANDLE;
vk_descriptor_pool_ = VK_NULL_HANDLE;
}
void VKDescriptorSet::bind(VKStorageBuffer &buffer, int location)
{
Binding &binding = ensure_location(location);
binding.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
binding.vk_buffer = buffer.vk_handle();
binding.buffer_size = buffer.size_in_bytes();
}
void VKDescriptorSet::bind_as_ssbo(VKVertexBuffer &buffer, int location)
{
Binding &binding = ensure_location(location);
binding.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
binding.vk_buffer = buffer.vk_handle();
binding.buffer_size = buffer.size_used_get();
}
VKDescriptorSet::Binding &VKDescriptorSet::ensure_location(int location)
{
for (Binding &binding : bindings_) {
if (binding.location == location) {
return binding;
}
}
Binding binding;
binding.vk_buffer = VK_NULL_HANDLE;
binding.buffer_size = 0;
binding.location = location;
bindings_.append(binding);
return bindings_.last();
}
void VKDescriptorSet::update(VkDevice vk_device)
{
Vector<VkDescriptorBufferInfo> buffer_infos;
Vector<VkWriteDescriptorSet> descriptor_writes;
for (const Binding &binding : bindings_) {
VkDescriptorBufferInfo buffer_info = {};
buffer_info.buffer = binding.vk_buffer;
buffer_info.range = binding.buffer_size;
buffer_infos.append(buffer_info);
VkWriteDescriptorSet write_descriptor = {};
write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor.dstSet = vk_descriptor_set_;
write_descriptor.dstBinding = binding.location;
write_descriptor.descriptorCount = 1;
write_descriptor.descriptorType = binding.type;
write_descriptor.pBufferInfo = &buffer_infos.last();
descriptor_writes.append(write_descriptor);
}
vkUpdateDescriptorSets(
vk_device, descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
}
} // namespace blender::gpu

View File

@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
namespace blender::gpu {
class VKStorageBuffer;
class VKVertexBuffer;
class VKDescriptorSet : NonCopyable {
struct Binding {
int location;
VkDescriptorType type;
VkBuffer vk_buffer = VK_NULL_HANDLE;
VkDeviceSize buffer_size;
};
VkDescriptorPool vk_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSet vk_descriptor_set_ = VK_NULL_HANDLE;
Vector<Binding> bindings_;
public:
VKDescriptorSet() = default;
VKDescriptorSet(VkDescriptorPool vk_descriptor_pool, VkDescriptorSet vk_descriptor_set)
: vk_descriptor_pool_(vk_descriptor_pool), vk_descriptor_set_(vk_descriptor_set)
{
}
virtual ~VKDescriptorSet();
VKDescriptorSet &operator=(VKDescriptorSet &&other)
{
vk_descriptor_set_ = other.vk_descriptor_set_;
vk_descriptor_pool_ = other.vk_descriptor_pool_;
other.mark_freed();
return *this;
}
VkDescriptorSet vk_handle() const
{
return vk_descriptor_set_;
}
VkDescriptorPool vk_pool_handle() const
{
return vk_descriptor_pool_;
}
void bind_as_ssbo(VKVertexBuffer &buffer, int location);
void bind(VKStorageBuffer &buffer, int location);
/**
* Update the descriptor set on the device.
*/
void update(VkDevice vk_device);
void mark_freed();
private:
Binding &ensure_location(int location);
};
} // namespace blender::gpu

View File

@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_pipeline.hh"
#include "vk_context.hh"
namespace blender::gpu {
VKPipeline::VKPipeline(VkPipeline vk_pipeline, VKDescriptorSet &&vk_descriptor_set)
: vk_pipeline_(vk_pipeline)
{
descriptor_set_ = std::move(vk_descriptor_set);
}
VKPipeline::~VKPipeline()
{
VkDevice vk_device = VKContext::get()->device_get();
if (vk_pipeline_ != VK_NULL_HANDLE) {
vkDestroyPipeline(vk_device, vk_pipeline_, nullptr);
}
}
VKPipeline VKPipeline::create_compute_pipeline(VKContext &context,
VkShaderModule compute_module,
VkDescriptorSetLayout &descriptor_set_layout,
VkPipelineLayout &pipeline_layout)
{
VkDevice vk_device = context.device_get();
VkComputePipelineCreateInfo pipeline_info = {};
pipeline_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
pipeline_info.flags = 0;
pipeline_info.stage = {};
pipeline_info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
pipeline_info.stage.flags = 0;
pipeline_info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
pipeline_info.stage.module = compute_module;
pipeline_info.layout = pipeline_layout;
pipeline_info.stage.pName = "main";
VkPipeline pipeline;
if (vkCreateComputePipelines(vk_device, nullptr, 1, &pipeline_info, nullptr, &pipeline) !=
VK_SUCCESS) {
return VKPipeline();
}
VKDescriptorSet descriptor_set = context.descriptor_pools_get().allocate(descriptor_set_layout);
return VKPipeline(pipeline, std::move(descriptor_set));
}
VkPipeline VKPipeline::vk_handle() const
{
Jeroen-Bakker marked this conversation as resolved
Review

This should be fixed.

This should be fixed.
return vk_pipeline_;
}
bool VKPipeline::is_valid() const
{
return vk_pipeline_ != VK_NULL_HANDLE;
}
} // namespace blender::gpu

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "vk_descriptor_set.hh"
namespace blender::gpu {
class VKContext;
class VKPipeline : NonCopyable {
VKDescriptorSet descriptor_set_;
VkPipeline vk_pipeline_ = VK_NULL_HANDLE;
public:
VKPipeline() = default;
virtual ~VKPipeline();
VKPipeline(VkPipeline vk_pipeline, VKDescriptorSet &&vk_descriptor_set);
VKPipeline &operator=(VKPipeline &&other)
{
vk_pipeline_ = other.vk_pipeline_;
other.vk_pipeline_ = VK_NULL_HANDLE;
descriptor_set_ = std::move(other.descriptor_set_);
return *this;
}
static VKPipeline create_compute_pipeline(VKContext &context,
VkShaderModule compute_module,
VkDescriptorSetLayout &descriptor_set_layout,
VkPipelineLayout &pipeline_layouts);
VKDescriptorSet &descriptor_set_get()
{
return descriptor_set_;
}
VkPipeline vk_handle() const;
bool is_valid() const;
};
} // namespace blender::gpu

View File

@ -9,6 +9,7 @@
#include "vk_backend.hh"
#include "vk_memory.hh"
#include "vk_shader_interface.hh"
#include "vk_shader_log.hh"
#include "BLI_string_utils.h"
@ -603,6 +604,14 @@ VKShader::~VKShader()
vkDestroyShaderModule(device, compute_module_, vk_allocation_callbacks);
compute_module_ = VK_NULL_HANDLE;
}
if (pipeline_layout_ != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device, pipeline_layout_, nullptr);
pipeline_layout_ = VK_NULL_HANDLE;
}
if (layout_ != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(device, layout_, nullptr);
layout_ = VK_NULL_HANDLE;
}
}
void VKShader::build_shader_module(MutableSpan<const char *> sources,
@ -646,52 +655,162 @@ bool VKShader::finalize(const shader::ShaderCreateInfo *info)
return false;
}
if (vertex_module_ != VK_NULL_HANDLE) {
VkDevice vk_device = context_->device_get();
if (!finalize_descriptor_set_layouts(vk_device, *info)) {
return false;
}
if (!finalize_pipeline_layout(vk_device, *info)) {
return false;
}
/* TODO we might need to move the actual pipeline construction to a later stage as the graphics
* pipeline requires more data before it can be constructed.*/
bool result;
if (is_graphics_shader()) {
BLI_assert((fragment_module_ != VK_NULL_HANDLE && info->tf_type_ == GPU_SHADER_TFB_NONE) ||
(fragment_module_ == VK_NULL_HANDLE && info->tf_type_ != GPU_SHADER_TFB_NONE));
BLI_assert(compute_module_ == VK_NULL_HANDLE);
VkPipelineShaderStageCreateInfo vertex_stage_info = {};
vertex_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertex_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertex_stage_info.module = vertex_module_;
vertex_stage_info.pName = "main";
pipeline_infos_.append(vertex_stage_info);
if (geometry_module_ != VK_NULL_HANDLE) {
VkPipelineShaderStageCreateInfo geo_stage_info = {};
geo_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
geo_stage_info.stage = VK_SHADER_STAGE_GEOMETRY_BIT;
geo_stage_info.module = geometry_module_;
geo_stage_info.pName = "main";
pipeline_infos_.append(geo_stage_info);
}
if (fragment_module_ != VK_NULL_HANDLE) {
VkPipelineShaderStageCreateInfo fragment_stage_info = {};
fragment_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragment_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragment_stage_info.module = fragment_module_;
fragment_stage_info.pName = "main";
pipeline_infos_.append(fragment_stage_info);
}
result = finalize_graphics_pipeline(vk_device);
}
else {
BLI_assert(vertex_module_ == VK_NULL_HANDLE);
BLI_assert(geometry_module_ == VK_NULL_HANDLE);
BLI_assert(fragment_module_ == VK_NULL_HANDLE);
BLI_assert(compute_module_ != VK_NULL_HANDLE);
VkPipelineShaderStageCreateInfo compute_stage_info = {};
compute_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
compute_stage_info.stage = VK_SHADER_STAGE_GEOMETRY_BIT;
compute_stage_info.module = compute_module_;
compute_stage_info.pName = "main";
pipeline_infos_.append(compute_stage_info);
compute_pipeline_ = std::move(VKPipeline::create_compute_pipeline(
*context_, compute_module_, layout_, pipeline_layout_));
result = compute_pipeline_.is_valid();
}
#ifdef NDEBUG
UNUSED_VARS(info);
#endif
if (result) {
VKShaderInterface *vk_interface = new VKShaderInterface();
vk_interface->init(*info);
interface = vk_interface;
}
return result;
}
bool VKShader::finalize_graphics_pipeline(VkDevice /*vk_device */)
{
Vector<VkPipelineShaderStageCreateInfo> pipeline_stages;
VkPipelineShaderStageCreateInfo vertex_stage_info = {};
vertex_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertex_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertex_stage_info.module = vertex_module_;
vertex_stage_info.pName = "main";
pipeline_stages.append(vertex_stage_info);
if (geometry_module_ != VK_NULL_HANDLE) {
VkPipelineShaderStageCreateInfo geo_stage_info = {};
geo_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
geo_stage_info.stage = VK_SHADER_STAGE_GEOMETRY_BIT;
geo_stage_info.module = geometry_module_;
geo_stage_info.pName = "main";
pipeline_stages.append(geo_stage_info);
}
if (fragment_module_ != VK_NULL_HANDLE) {
VkPipelineShaderStageCreateInfo fragment_stage_info = {};
fragment_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragment_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragment_stage_info.module = fragment_module_;
fragment_stage_info.pName = "main";
pipeline_stages.append(fragment_stage_info);
}
return true;
}
bool VKShader::finalize_pipeline_layout(VkDevice vk_device,
const shader::ShaderCreateInfo & /*info*/)
{
VkPipelineLayoutCreateInfo pipeline_info = {};
pipeline_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_info.flags = 0;
pipeline_info.setLayoutCount = 1;
pipeline_info.pSetLayouts = &layout_;
if (vkCreatePipelineLayout(vk_device, &pipeline_info, nullptr, &pipeline_layout_) !=
VK_SUCCESS) {
return false;
};
return true;
}
static VkDescriptorType descriptor_type(
const shader::ShaderCreateInfo::Resource::BindType bind_type)
{
switch (bind_type) {
case shader::ShaderCreateInfo::Resource::BindType::IMAGE:
return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
case shader::ShaderCreateInfo::Resource::BindType::SAMPLER:
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
case shader::ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case shader::ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
}
BLI_assert_unreachable();
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
}
static VkDescriptorSetLayoutBinding create_descriptor_set_layout_binding(
const shader::ShaderCreateInfo::Resource &resource)
{
VkDescriptorSetLayoutBinding binding = {};
binding.binding = resource.slot;
binding.descriptorType = descriptor_type(resource.bind_type);
binding.descriptorCount = 1;
binding.stageFlags = VK_SHADER_STAGE_ALL;
binding.pImmutableSamplers = nullptr;
return binding;
}
static void add_descriptor_set_layout_bindings(
const Vector<shader::ShaderCreateInfo::Resource> &resources,
Vector<VkDescriptorSetLayoutBinding> &r_bindings)
{
for (const shader::ShaderCreateInfo::Resource &resource : resources) {
r_bindings.append(create_descriptor_set_layout_binding(resource));
}
}
static VkDescriptorSetLayoutCreateInfo create_descriptor_set_layout(
const Vector<shader::ShaderCreateInfo::Resource> &resources,
Vector<VkDescriptorSetLayoutBinding> &r_bindings)
{
add_descriptor_set_layout_bindings(resources, r_bindings);
VkDescriptorSetLayoutCreateInfo set_info = {};
set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
set_info.flags = 0;
set_info.pNext = nullptr;
set_info.bindingCount = r_bindings.size();
set_info.pBindings = r_bindings.data();
return set_info;
}
bool VKShader::finalize_descriptor_set_layouts(VkDevice vk_device,
const shader::ShaderCreateInfo &info)
{
if (info.pass_resources_.is_empty() && info.batch_resources_.is_empty()) {
return true;
}
/* Currently we create a single descriptor set. The goal would be to create one descriptor set
* for Frequency::PASS/BATCH. This isn't possible as areas expect that the binding location is
* static and predictable (eevee-next) or the binding location can be mapped to a single number
* (python). */
Vector<ShaderCreateInfo::Resource> all_resources;
all_resources.extend(info.pass_resources_);
all_resources.extend(info.batch_resources_);
Vector<VkDescriptorSetLayoutBinding> bindings;
VkDescriptorSetLayoutCreateInfo layout_info = create_descriptor_set_layout(all_resources,
bindings);
if (vkCreateDescriptorSetLayout(vk_device, &layout_info, nullptr, &layout_) != VK_SUCCESS) {
return false;
};
return true;
}
@ -712,10 +831,23 @@ void VKShader::transform_feedback_disable()
void VKShader::bind()
{
VKContext *context = VKContext::get();
if (is_compute_shader()) {
context->command_buffer_get().bind(compute_pipeline_, VK_PIPELINE_BIND_POINT_COMPUTE);
}
else {
BLI_assert_unreachable();
}
}
void VKShader::unbind()
{
if (is_compute_shader()) {
}
else {
BLI_assert_unreachable();
}
}
void VKShader::uniform_float(int /*location*/,
@ -954,4 +1086,9 @@ int VKShader::program_handle_get() const
return -1;
}
VKPipeline &VKShader::pipeline_get()
{
return compute_pipeline_;
}
} // namespace blender::gpu

View File

@ -24,7 +24,9 @@ class VKShader : public Shader {
VkShaderModule fragment_module_ = VK_NULL_HANDLE;
VkShaderModule compute_module_ = VK_NULL_HANDLE;
bool compilation_failed_ = false;
Vector<VkPipelineShaderStageCreateInfo> pipeline_infos_;
VkDescriptorSetLayout layout_;
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
VKPipeline compute_pipeline_;
public:
VKShader(const char *name);
@ -57,12 +59,32 @@ class VKShader : public Shader {
/* DEPRECATED: Kept only because of BGL API. */
int program_handle_get() const override;
VKPipeline &pipeline_get();
/* TODO: should be part of VKPipeline.*/
VkPipelineLayout vk_pipeline_layout_get() const
{
return pipeline_layout_;
}
private:
Vector<uint32_t> compile_glsl_to_spirv(Span<const char *> sources, shaderc_shader_kind kind);
void build_shader_module(Span<uint32_t> spirv_module, VkShaderModule *r_shader_module);
void build_shader_module(MutableSpan<const char *> sources,
shaderc_shader_kind stage,
VkShaderModule *r_shader_module);
bool finalize_descriptor_set_layouts(VkDevice vk_device, const shader::ShaderCreateInfo &info);
bool finalize_pipeline_layout(VkDevice vk_device, const shader::ShaderCreateInfo &info);
bool finalize_graphics_pipeline(VkDevice vk_device);
bool is_graphics_shader() const
{
return !is_compute_shader();
}
bool is_compute_shader() const
{
return compute_module_ != VK_NULL_HANDLE;
}
};
} // namespace blender::gpu

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_shader_interface.hh"
namespace blender::gpu {
void VKShaderInterface::init(const shader::ShaderCreateInfo &info)
{
using namespace blender::gpu::shader;
ssbo_len_ = 0;
Vector<ShaderCreateInfo::Resource> all_resources;
all_resources.extend(info.pass_resources_);
all_resources.extend(info.batch_resources_);
for (ShaderCreateInfo::Resource &res : all_resources) {
switch (res.bind_type) {
case ShaderCreateInfo::Resource::BindType::IMAGE:
case ShaderCreateInfo::Resource::BindType::SAMPLER:
case ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
// BLI_assert_msg(false, "not implemented yet");
break;
case ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
ssbo_len_++;
break;
}
}
int32_t input_tot_len = ssbo_len_;
inputs_ = static_cast<ShaderInput *>(
MEM_calloc_arrayN(input_tot_len, sizeof(ShaderInput), __func__));
ShaderInput *input = inputs_;
name_buffer_ = (char *)MEM_mallocN(info.interface_names_size_, "name_buffer");
uint32_t name_buffer_offset = 0;
for (const ShaderCreateInfo::Resource &res : all_resources) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER) {
copy_input_name(input, res.storagebuf.name, name_buffer_, name_buffer_offset);
input->location = input->binding = res.slot;
enabled_ssbo_mask_ |= (1 << input->binding);
input++;
}
}
sort_inputs();
}
} // namespace blender::gpu

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_shader_create_info.hh"
#include "gpu_shader_interface.hh"
namespace blender::gpu {
class VKShaderInterface : public ShaderInterface {
public:
VKShaderInterface() = default;
void init(const shader::ShaderCreateInfo &info);
};
} // namespace blender::gpu

View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_state_manager.hh"
namespace blender::gpu {
void VKStateManager::apply_state()
{
}
void VKStateManager::force_state()
{
}
void VKStateManager::issue_barrier(eGPUBarrier /*barrier_bits*/)
{
}
void VKStateManager::texture_bind(Texture * /*tex*/, eGPUSamplerState /*sampler*/, int /*unit*/)
{
}
void VKStateManager::texture_unbind(Texture * /*tex*/)
{
}
void VKStateManager::texture_unbind_all()
{
}
void VKStateManager::image_bind(Texture * /*tex*/, int /*unit*/)
{
}
void VKStateManager::image_unbind(Texture * /*tex*/)
{
}
void VKStateManager::image_unbind_all()
{
}
void VKStateManager::texture_unpack_row_length_set(uint /*len*/)
{
}
} // namespace blender::gpu

View File

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_state_private.hh"
namespace blender::gpu {
class VKStateManager : public StateManager {
public:
void apply_state() override;
void force_state() override;
void issue_barrier(eGPUBarrier barrier_bits) override;
void texture_bind(Texture *tex, eGPUSamplerState sampler, int unit) override;
void texture_unbind(Texture *tex) override;
void texture_unbind_all() override;
void image_bind(Texture *tex, int unit) override;
void image_unbind(Texture *tex) override;
void image_unbind_all() override;
void texture_unpack_row_length_set(uint len) override;
};
} // namespace blender::gpu

View File

@ -4,19 +4,35 @@
/** \file
* \ingroup gpu
*/
#include "vk_shader.hh"
#include "vk_vertex_buffer.hh"
#include "vk_storage_buffer.hh"
namespace blender::gpu {
void VKStorageBuffer::update(const void * /*data*/)
void VKStorageBuffer::update(const void *data)
{
VKContext &context = *VKContext::get();
if (!buffer_.is_allocated()) {
allocate(context);
}
buffer_.update(context, data);
}
void VKStorageBuffer::bind(int /*slot*/)
void VKStorageBuffer::allocate(VKContext &context)
{
buffer_.create(context, size_in_bytes_, usage_, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
}
void VKStorageBuffer::bind(int slot)
{
VKContext &context = *VKContext::get();
if (!buffer_.is_allocated()) {
allocate(context);
}
VKShader *shader = static_cast<VKShader *>(context.shader);
shader->pipeline_get().descriptor_set_get().bind(*this, slot);
}
void VKStorageBuffer::unbind()
@ -35,8 +51,21 @@ void VKStorageBuffer::copy_sub(VertBuf * /*src*/,
{
}
void VKStorageBuffer::read(void * /*data*/)
void VKStorageBuffer::read(void *data)
{
VKContext &context = *VKContext::get();
if (!buffer_.is_allocated()) {
allocate(context);
}
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.submit();
void *mapped_memory;
if (buffer_.map(context, &mapped_memory)) {
memcpy(data, mapped_memory, size_in_bytes_);
buffer_.unmap(context);
}
}
} // namespace blender::gpu

View File

@ -10,12 +10,20 @@
#include "GPU_texture.h"
#include "gpu_storage_buffer_private.hh"
#include "gpu_vertex_buffer_private.hh"
#include "vk_buffer.hh"
namespace blender::gpu {
class VertBuf;
class VKStorageBuffer : public StorageBuf {
GPUUsageType usage_;
VKBuffer buffer_;
public:
VKStorageBuffer(int size, const char *name) : StorageBuf(size, name)
VKStorageBuffer(int size, GPUUsageType usage, const char *name)
: StorageBuf(size, name), usage_(usage)
{
}
@ -25,6 +33,19 @@ class VKStorageBuffer : public StorageBuf {
void clear(eGPUTextureFormat internal_format, eGPUDataFormat data_format, void *data) override;
void copy_sub(VertBuf *src, uint dst_offset, uint src_offset, uint copy_size) override;
void read(void *data) override;
VkBuffer vk_handle() const
{
return buffer_.vk_handle();
}
int64_t size_in_bytes() const
{
return buffer_.size_in_bytes();
}
private:
void allocate(VKContext &context);
};
} // namespace blender::gpu

View File

@ -7,6 +7,7 @@
#include "MEM_guardedalloc.h"
#include "vk_shader.hh"
#include "vk_vertex_buffer.hh"
namespace blender::gpu {
@ -16,8 +17,15 @@ VKVertexBuffer::~VKVertexBuffer()
release_data();
}
void VKVertexBuffer::bind_as_ssbo(uint /*binding*/)
void VKVertexBuffer::bind_as_ssbo(uint binding)
{
VKContext &context = *VKContext::get();
if (!buffer_.is_allocated()) {
allocate(context);
}
VKShader *shader = static_cast<VKShader *>(context.shader);
shader->pipeline_get().descriptor_set_get().bind_as_ssbo(*this, binding);
}
void VKVertexBuffer::bind_as_texture(uint /*binding*/)
@ -32,8 +40,17 @@ void VKVertexBuffer::update_sub(uint /*start*/, uint /*len*/, const void * /*dat
{
}
void VKVertexBuffer::read(void * /*data*/) const
void VKVertexBuffer::read(void *data) const
{
VKContext &context = *VKContext::get();
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.submit();
void *mapped_memory;
if (buffer_.map(context, &mapped_memory)) {
memcpy(data, mapped_memory, size_used_get());
buffer_.unmap(context);
}
}
void VKVertexBuffer::acquire_data()
@ -64,4 +81,13 @@ void VKVertexBuffer::duplicate_data(VertBuf * /*dst*/)
{
}
void VKVertexBuffer::allocate(VKContext &context)
{
buffer_.create(context,
size_used_get(),
usage_,
static_cast<VkBufferUsageFlagBits>(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT));
}
} // namespace blender::gpu

View File

@ -9,9 +9,13 @@
#include "gpu_vertex_buffer_private.hh"
#include "vk_buffer.hh"
namespace blender::gpu {
class VKVertexBuffer : public VertBuf {
VKBuffer buffer_;
public:
~VKVertexBuffer();
@ -22,12 +26,20 @@ class VKVertexBuffer : public VertBuf {
void update_sub(uint start, uint len, const void *data) override;
void read(void *data) const override;
VkBuffer vk_handle() const
{
return buffer_.vk_handle();
}
protected:
void acquire_data() override;
void resize_data() override;
void release_data() override;
void upload_data() override;
void duplicate_data(VertBuf *dst) override;
private:
void allocate(VKContext &context);
};
} // namespace blender::gpu