Vulkan: Clearing Storage Buffers #105487
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -14,6 +14,8 @@ struct GPUStorageBuf;
|
|||
namespace blender {
|
||||
namespace gpu {
|
||||
|
||||
class VertBuf;
|
||||
|
||||
#ifdef DEBUG
|
||||
# define DEBUG_NAME_LEN 64
|
||||
#else
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "GPU_storage_buffer.h"
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
Jeroen-Bakker marked this conversation as resolved
Outdated
|
||||
|
||||
#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
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
void main()
|
||||
{
|
||||
int store_index = int(gl_GlobalInvocationID.x);
|
||||
data_out[store_index] = store_index * 4;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
|
@ -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"
|
||||
|
||||
Jeroen-Bakker marked this conversation as resolved
Outdated
Jeroen Bakker
commented
This include isn't needed This include isn't needed
|
||||
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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
{
|
||||
return vk_pipeline_;
|
||||
}
|
||||
|
||||
bool VKPipeline::is_valid() const
|
||||
{
|
||||
return vk_pipeline_ != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
} // namespace blender::gpu
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
This include isn't needed.