Vulkan: Initial Compute Shaders support. #104518

Closed
Jeroen Bakker wants to merge 31 commits from Jeroen-Bakker:vulkan-compute-shaders into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
49 changed files with 2477 additions and 156 deletions

View File

@ -1195,24 +1195,89 @@ int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context,
#ifdef WITH_VULKAN_BACKEND
/**
* Return VULKAN handles for the given context.
* Get Vulkan handles for the given context.
*
* These handles are the same for a given context.
* Should should only be called when using a Vulkan context.
* Other contexts will not return any handles and leave the
* handles where the parameters are referring to unmodified.
*
* \param context: GHOST context handle of a vulkan context to
* get the Vulkan handles from.
Jeroen-Bakker marked this conversation as resolved
Review

Documentation? ;)

Documentation? ;)
* \param r_instance: After calling this function the VkInstance
* referenced by this parameter will contain the VKInstance handle
* of the context associated with the `context` parameter.
* \param r_physical_device: After calling this function the VkPhysicalDevice
* referenced by this parameter will contain the VKPhysicalDevice handle
* of the context associated with the `context` parameter.
* \param r_device: After calling this function the VkDevice
* referenced by this parameter will contain the VKDevice handle
* of the context associated with the `context` parameter.
* \param r_graphic_queue_family: After calling this function the uint32_t
* referenced by this parameter will contain the graphic queue family id
* of the context associated with the `context` parameter.
* \param r_queue: After calling this function the VkQueue
* referenced by this parameter will contain the VKQueue handle
* of the context associated with the `context` parameter.
*/
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);
/**
* Return VULKAN back-buffer resources handles for the given window.
* Return Vulkan command buffer.
*
* Command buffers are different for each image in the swap chain.
* At the start of each frame the correct command buffer should be
* retrieved with this function.
*
* Should should only be called when using a Vulkan context.
* Other contexts will not return any handles and leave the
* handles where the parameters are referring to unmodified.
*
* \param context: GHOST context handle to a vulkan context to get the
* command queue from.
* \param r_command_buffer: After calling this function the VkCommandBuffer
* referenced by this parameter will contain the VKCommandBuffer handle
* of the current back buffer (when swap chains are enabled) or
* it will contain a general VkCommandQueue.
*/
void GHOST_GetVulkanCommandBuffer(GHOST_ContextHandle context, void *r_command_buffer);
/**
* Gets the Vulkan backbuffer related resource handles associated with the Vulkan context.
* Needs to be called after each swap event as the backbuffer will change.
*
* Should should only be called when using a Vulkan context with an active swap chain.
* Other contexts will not return any handles and leave the
* handles where the parameters are referring to unmodified.
*
* \param windowhandle: GHOST window handle to a window to get the resource from.
* \param r_image: After calling this function the VkImage
* referenced by this parameter will contain the VKImage handle
* of the current back buffer.
* \param r_framebuffer: After calling this function the VkFramebuffer
* referenced by this parameter will contain the VKFramebuffer handle
* of the current back buffer.
* \param r_render_pass: After calling this function the VkRenderPass
* referenced by this parameter will contain the VKRenderPass handle
* of the current back buffer.
* \param r_extent: After calling this function the VkExtent2D
* referenced by this parameter will contain the size of the
* frame buffer and image in pixels.
* \param r_fb_id: After calling this function the uint32_t
* referenced by this parameter will contain the id of the
* framebuffer of the current back buffer.
*/
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id);
void *r_image,
void *r_framebuffer,
void *r_render_pass,
void *r_extent,
uint32_t *r_fb_id);
#endif

View File

@ -40,19 +40,84 @@ class GHOST_IContext {
virtual unsigned int getDefaultFramebuffer() = 0;
virtual GHOST_TSuccess getVulkanHandles(void *, void *, void *, uint32_t *) = 0;
/**
* Get Vulkan handles for the given context.
*
* These handles are the same for a given context.
* Should should only be called when using a Vulkan context.
* Other contexts will not return any handles and leave the
* handles where the parameters are referring to unmodified.
*
* \param r_instance: After calling this function the VkInstance
* referenced by this parameter will contain the VKInstance handle
* of the context associated with the `context` parameter.
* \param r_physical_device: After calling this function the VkPhysicalDevice
* referenced by this parameter will contain the VKPhysicalDevice handle
* of the context associated with the `context` parameter.
* \param r_device: After calling this function the VkDevice
* referenced by this parameter will contain the VKDevice handle
* of the context associated with the `context` parameter.
* \param r_graphic_queue_family: After calling this function the uint32_t
* referenced by this parameter will contain the graphic queue family id
* of the context associated with the `context` parameter.
* \param r_queue: After calling this function the VkQueue
* referenced by this parameter will contain the VKQueue handle
* of the context associated with the `context` parameter.
* \returns GHOST_kFailure when context isn't a Vulkan context.
* GHOST_kSuccess when the context is a Vulkan context and the
* handles have been set.
*/
virtual GHOST_TSuccess getVulkanHandles(void *r_instance,
void *r_physical_device,
void *r_device,
uint32_t *r_graphic_queue_family,
void *r_queue) = 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.
* Return Vulkan command buffer.
*
* Command buffers are different for each image in the swap chain.
* At the start of each frame the correct command buffer should be
* retrieved with this function.
*
* \param r_command_buffer: After calling this function the VkCommandBuffer
* referenced by this parameter will contain the VKCommandBuffer handle
* of the current back buffer (when swap chains are enabled) or
* it will contain a general VkCommandQueue.
* \returns GHOST_kFailure when context isn't a Vulkan context.
* GHOST_kSuccess when the context is a Vulkan context and the
* handles have been set.
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id) = 0;
virtual GHOST_TSuccess getVulkanCommandBuffer(void *r_command_buffer) = 0;
/**
* Gets the Vulkan backbuffer related resource handles associated with the Vulkan context.
* Needs to be called after each swap event as the backbuffer will change.
*
* \param r_image: After calling this function the VkImage
* referenced by this parameter will contain the VKImage handle
* of the current back buffer.
* \param r_framebuffer: After calling this function the VkFramebuffer
* referenced by this parameter will contain the VKFramebuffer handle
* of the current back buffer.
* \param r_render_pass: After calling this function the VkRenderPass
* referenced by this parameter will contain the VKRenderPass handle
* of the current back buffer.
* \param r_extent: After calling this function the VkExtent2D
* referenced by this parameter will contain the size of the
* frame buffer and image in pixels.
* \param r_fb_id: After calling this function the uint32_t
* referenced by this parameter will contain the id of the
* framebuffer of the current back buffer.
* \returns GHOST_kFailure when context isn't a Vulkan context.
* GHOST_kSuccess when the context is a Vulkan context and the
* handles have been set.
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void *r_image,
void *r_framebuffer,
void *r_render_pass,
void *r_extent,
uint32_t *r_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

@ -136,27 +136,88 @@ class GHOST_Context : public GHOST_IContext {
}
/**
* Gets the Vulkan context related resource handles.
* \return A boolean success indicator.
* Get Vulkan handles for the given context.
*
* These handles are the same for a given context.
* Should should only be called when using a Vulkan context.
* Other contexts will not return any handles and leave the
* handles where the parameters are referring to unmodified.
*
* \param r_instance: After calling this function the VkInstance
* referenced by this parameter will contain the VKInstance handle
* of the context associated with the `context` parameter.
* \param r_physical_device: After calling this function the VkPhysicalDevice
* referenced by this parameter will contain the VKPhysicalDevice handle
* of the context associated with the `context` parameter.
* \param r_device: After calling this function the VkDevice
* referenced by this parameter will contain the VKDevice handle
* of the context associated with the `context` parameter.
* \param r_graphic_queue_family: After calling this function the uint32_t
* referenced by this parameter will contain the graphic queue family id
* of the context associated with the `context` parameter.
* \param r_queue: After calling this function the VkQueue
* referenced by this parameter will contain the VKQueue handle
* of the context associated with the `context` parameter.
* \returns GHOST_kFailure when context isn't a Vulkan context.
* GHOST_kSuccess when the context is a Vulkan context and the
* handles have been set.
*/
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;
};
/**
* 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.
* Return Vulkan command buffer.
*
* Command buffers are different for each image in the swap chain.
* At the start of each frame the correct command buffer should be
* retrieved with this function.
*
* \param r_command_buffer: After calling this function the VkCommandBuffer
* referenced by this parameter will contain the VKCommandBuffer handle
* of the current back buffer (when swap chains are enabled) or
* it will contain a general VkCommandQueue.
* \returns GHOST_kFailure when context isn't a Vulkan context.
* GHOST_kSuccess when the context is a Vulkan context and the
* handles have been set.
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void * /*image*/,
void * /*framebuffer*/,
void * /*command_buffer*/,
void * /*render_pass*/,
void * /*extent*/,
virtual GHOST_TSuccess getVulkanCommandBuffer(void * /*r_command_buffer*/) override
{
return GHOST_kFailure;
};
/**
* Gets the Vulkan backbuffer related resource handles associated with the Vulkan context.
* Needs to be called after each swap event as the backbuffer will change.
*
* \param r_image: After calling this function the VkImage
* referenced by this parameter will contain the VKImage handle
* of the current back buffer.
* \param r_framebuffer: After calling this function the VkFramebuffer
* referenced by this parameter will contain the VKFramebuffer handle
* of the current back buffer.
* \param r_render_pass: After calling this function the VkRenderPass
* referenced by this parameter will contain the VKRenderPass handle
* of the current back buffer.
* \param r_extent: After calling this function the VkExtent2D
* referenced by this parameter will contain the size of the
* frame buffer and image in pixels.
* \param r_fb_id: After calling this function the uint32_t
* referenced by this parameter will contain the id of the
* framebuffer of the current back buffer.
* \returns GHOST_kFailure when context isn't a Vulkan context.
* GHOST_kSuccess when the context is a Vulkan context and the
* handles have been set.
*/
virtual GHOST_TSuccess getVulkanBackbuffer(void * /*r_image*/,
void * /*r_framebuffer*/,
void * /*r_render_pass*/,
void * /*r_extent*/,
uint32_t * /*fb_id*/) override
{
return GHOST_kFailure;

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

@ -191,16 +191,24 @@ 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_common.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
@ -208,16 +216,24 @@ 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_common.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 +526,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 +804,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

@ -26,6 +26,14 @@ namespace blender::gpu {
typedef struct ShaderInput {
uint32_t name_offset;
uint32_t name_hash;
/**
* Location is openGl legacy and its legacy usages should be phased out in Blender 3.7.
*
* Vulkan backend use location to encode the descriptor set binding. This binding is different
* than the binding stored in the binding attribute. In Vulkan the binding inside a descriptor
* set must be unique. In future the location will also be used to select the right descriptor
* set.
*/
int32_t location;
/** Defined at interface creation or in shader. Only for Samplers, UBOs and Vertex Attributes. */
int32_t binding;

View File

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

View File

@ -42,6 +42,12 @@ GPU_SHADER_CREATE_INFO(gpu_compute_vbo_test)
.compute_source("gpu_compute_vbo_test.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(gpu_compute_ssbo_test)
.local_group_size(1)
.storage_buf(0, Qualifier::WRITE, "int", "data_out[]")
.compute_source("gpu_compute_ssbo_test.glsl")
.do_static_compilation(true);
GPU_SHADER_CREATE_INFO(gpu_compute_ssbo_binding_test)
.local_group_size(1)
.storage_buf(0, Qualifier::WRITE, "int", "data0[]")

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_binding(shader, "out_indices"));
GPU_storagebuf_bind(ssbo, GPU_shader_get_ssbo_binding(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);
}
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

@ -9,11 +9,8 @@
#include "gpu_backend.hh"
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#include "vk_common.hh"
#include "shaderc/shaderc.hpp"
namespace blender::gpu {

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;
/* We use the same command queue for the compute and graphics pipeline, so it is safe to use
* exclusive resource handling. */
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,53 @@
/* 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_common.hh"
#include "vk_context.hh"
#include "vk_mem_alloc.h"
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);
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,144 @@
/* 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_buffer.hh"
#include "vk_context.hh"
#include "vk_memory.hh"
#include "vk_texture.hh"
#include "BLI_assert.h"
namespace blender::gpu {
VKCommandBuffer::~VKCommandBuffer()
{
if (vk_device_ != VK_NULL_HANDLE) {
VK_ALLOCATION_CALLBACKS;
vkDestroyFence(vk_device_, vk_fence_, vk_allocation_callbacks);
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) {
VK_ALLOCATION_CALLBACKS;
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
vkCreateFence(vk_device_, &fenceInfo, vk_allocation_callbacks, &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::copy(VKBuffer &dst_buffer,
VKTexture &src_texture,
Span<VkBufferImageCopy> regions)
{
vkCmdCopyImageToBuffer(vk_command_buffer_,
src_texture.vk_image_handle(),
VK_IMAGE_LAYOUT_GENERAL,
dst_buffer.vk_handle(),
regions.size(),
regions.data());
}
void VKCommandBuffer::pipeline_barrier(VkPipelineStageFlags source_stages,
VkPipelineStageFlags destination_stages)
{
vkCmdPipelineBarrier(vk_command_buffer_,
source_stages,
destination_stages,
0,
0,
nullptr,
0,
nullptr,
0,
nullptr);
}
void VKCommandBuffer::pipeline_barrier(Span<VkImageMemoryBarrier> image_memory_barriers)
{
vkCmdPipelineBarrier(vk_command_buffer_,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_DEPENDENCY_BY_REGION_BIT,
0,
nullptr,
0,
nullptr,
image_memory_barriers.size(),
image_memory_barriers.data());
}
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()
{
/* 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 with
* the correct synchorinzations. */
}
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,54 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_common.hh"
Jeroen-Bakker marked this conversation as resolved

Cant we make that part of a header to avoid the duplication?

Cant we make that part of a header to avoid the duplication?
#include "vk_pipeline.hh"
namespace blender::gpu {
class VKBuffer;
class VKTexture;
/** 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);
/* Copy the contents of a texture mip level to the dst buffer.*/
void copy(VKBuffer &dst_buffer, VKTexture &src_texture, Span<VkBufferImageCopy> regions);
void pipeline_barrier(VkPipelineStageFlags source_stages,
VkPipelineStageFlags destination_stages);
void pipeline_barrier(Span<VkImageMemoryBarrier> image_memory_barriers);
/**
* 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

@ -0,0 +1,197 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_common.hh"
namespace blender::gpu {
VkImageAspectFlagBits to_vk_image_aspect_flag_bits(const eGPUTextureFormat format)
{
switch (format) {
case GPU_RGBA32F:
case GPU_RGBA8UI:
case GPU_RGBA8I:
case GPU_RGBA8:
case GPU_RGBA32UI:
case GPU_RGBA32I:
case GPU_RGBA16UI:
case GPU_RGBA16I:
case GPU_RGBA16F:
case GPU_RGBA16:
case GPU_RG8UI:
case GPU_RG8I:
case GPU_RG8:
case GPU_RG32UI:
case GPU_RG32I:
case GPU_RG32F:
case GPU_RG16UI:
case GPU_RG16I:
case GPU_RG16F:
case GPU_RG16:
case GPU_R8UI:
case GPU_R8I:
case GPU_R8:
case GPU_R32UI:
case GPU_R32I:
case GPU_R32F:
case GPU_R16UI:
case GPU_R16I:
case GPU_R16F:
case GPU_R16:
case GPU_RGB10_A2:
case GPU_R11F_G11F_B10F:
case GPU_SRGB8_A8:
case GPU_RGB16F:
case GPU_SRGB8_A8_DXT1:
case GPU_SRGB8_A8_DXT3:
case GPU_SRGB8_A8_DXT5:
case GPU_RGBA8_DXT1:
case GPU_RGBA8_DXT3:
case GPU_RGBA8_DXT5:
return VK_IMAGE_ASPECT_COLOR_BIT;
case GPU_DEPTH32F_STENCIL8:
case GPU_DEPTH24_STENCIL8:
return static_cast<VkImageAspectFlagBits>(VK_IMAGE_ASPECT_DEPTH_BIT |
VK_IMAGE_ASPECT_STENCIL_BIT);
case GPU_DEPTH_COMPONENT24:
case GPU_DEPTH_COMPONENT16:
return VK_IMAGE_ASPECT_DEPTH_BIT;
case GPU_DEPTH_COMPONENT32F:
/* Not supported by Vulkan*/
BLI_assert_unreachable();
}
return static_cast<VkImageAspectFlagBits>(0);
}
VkFormat to_vk_format(const eGPUTextureFormat format)
{
switch (format) {
case GPU_RGBA32F:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case GPU_RGBA8UI:
case GPU_RGBA8I:
case GPU_RGBA8:
case GPU_RGBA32UI:
case GPU_RGBA32I:
case GPU_RGBA16UI:
case GPU_RGBA16I:
case GPU_RGBA16F:
case GPU_RGBA16:
case GPU_RG8UI:
case GPU_RG8I:
case GPU_RG8:
case GPU_RG32UI:
case GPU_RG32I:
case GPU_RG32F:
case GPU_RG16UI:
case GPU_RG16I:
case GPU_RG16F:
case GPU_RG16:
case GPU_R8UI:
case GPU_R8I:
case GPU_R8:
case GPU_R32UI:
case GPU_R32I:
case GPU_R32F:
case GPU_R16UI:
case GPU_R16I:
case GPU_R16F:
case GPU_R16:
case GPU_RGB10_A2:
case GPU_R11F_G11F_B10F:
case GPU_DEPTH32F_STENCIL8:
case GPU_DEPTH24_STENCIL8:
case GPU_SRGB8_A8:
/* Texture only format */
case GPU_RGB16F:
/* Special formats texture only */
case GPU_SRGB8_A8_DXT1:
case GPU_SRGB8_A8_DXT3:
case GPU_SRGB8_A8_DXT5:
case GPU_RGBA8_DXT1:
case GPU_RGBA8_DXT3:
case GPU_RGBA8_DXT5:
/* Depth Formats */
case GPU_DEPTH_COMPONENT32F:
case GPU_DEPTH_COMPONENT24:
case GPU_DEPTH_COMPONENT16:
BLI_assert_unreachable();
}
return VK_FORMAT_UNDEFINED;
}
VkImageType to_vk_image_type(const eGPUTextureType type)
{
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_BUFFER:
case GPU_TEXTURE_1D_ARRAY:
return VK_IMAGE_TYPE_1D;
case GPU_TEXTURE_2D:
case GPU_TEXTURE_2D_ARRAY:
return VK_IMAGE_TYPE_2D;
case GPU_TEXTURE_3D:
case GPU_TEXTURE_CUBE:
case GPU_TEXTURE_CUBE_ARRAY:
return VK_IMAGE_TYPE_3D;
case GPU_TEXTURE_ARRAY:
/* GPU_TEXTURE_ARRAY should always be used together with 1D, 2D, or CUBE*/
BLI_assert_unreachable();
break;
}
return VK_IMAGE_TYPE_1D;
}
VkImageViewType to_vk_image_view_type(const eGPUTextureType type)
{
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_BUFFER:
return VK_IMAGE_VIEW_TYPE_1D;
case GPU_TEXTURE_2D:
return VK_IMAGE_VIEW_TYPE_2D;
case GPU_TEXTURE_3D:
return VK_IMAGE_VIEW_TYPE_3D;
case GPU_TEXTURE_CUBE:
return VK_IMAGE_VIEW_TYPE_CUBE;
case GPU_TEXTURE_1D_ARRAY:
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case GPU_TEXTURE_2D_ARRAY:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case GPU_TEXTURE_CUBE_ARRAY:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case GPU_TEXTURE_ARRAY:
/* GPU_TEXTURE_ARRAY should always be used together with 1D, 2D, or CUBE*/
BLI_assert_unreachable();
break;
}
return VK_IMAGE_VIEW_TYPE_1D;
}
VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat /*format*/)
{
/* TODO: this should map to OpenGL defaults based on the eGPUTextureFormat. The implementation of
* this function will be implemented when implementing other parts of VKTexture.*/
VkComponentMapping component_mapping;
component_mapping.r = VK_COMPONENT_SWIZZLE_R;
component_mapping.g = VK_COMPONENT_SWIZZLE_G;
component_mapping.b = VK_COMPONENT_SWIZZLE_B;
component_mapping.a = VK_COMPONENT_SWIZZLE_A;
return component_mapping;
}
} // namespace blender::gpu

View File

@ -0,0 +1,26 @@
/* 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 "gpu_texture_private.hh"
namespace blender::gpu {
VkImageAspectFlagBits to_vk_image_aspect_flag_bits(const eGPUTextureFormat format);
VkFormat to_vk_format(const eGPUTextureFormat format);
VkComponentMapping to_vk_component_mapping(const eGPUTextureFormat format);
VkImageViewType to_vk_image_view_type(const eGPUTextureType type);
VkImageType to_vk_image_type(const eGPUTextureType type);
} // namespace blender::gpu

View File

@ -8,6 +8,8 @@
#include "vk_context.hh"
#include "vk_backend.hh"
#include "vk_memory.hh"
#include "vk_state_manager.hh"
#include "GHOST_C-api.h"
@ -15,25 +17,34 @@ namespace blender::gpu {
VKContext::VKContext(void *ghost_window, void *ghost_context)
{
VK_ALLOCATION_CALLBACKS;
ghost_window_ = ghost_window;
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 (1.2), but set to 1.0 as 1.2 requires
* correct extensions and functions to be found by VMA, which isn't working as expected and
* requires more research. To continue development we lower the API to version 1.0.*/
info.vulkanApiVersion = VK_API_VERSION_1_0;
info.physicalDevice = vk_physical_device_;
info.device = vk_device_;
info.instance = vk_instance_;
info.pAllocationCallbacks = vk_allocation_callbacks;
vmaCreateAllocator(&info, &mem_allocator_);
descriptor_pools_.init(vk_device_);
state_manager = new VKStateManager();
VKBackend::capabilities_init(*this);
}
@ -53,18 +64,27 @@ 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()
{
command_buffer_.submit();
}
void VKContext::finish()
{
command_buffer_.submit();
}
void VKContext::memory_statistics_get(int * /*total_mem*/, int * /*free_mem*/)

View File

@ -9,26 +9,28 @@
#include "gpu_context_private.hh"
#include "vk_mem_alloc.h"
#include "vk_command_buffer.hh"
#include "vk_descriptor_pools.hh"
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#include "vk_mem_alloc.h"
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);
@ -52,9 +54,34 @@ class VKContext : public Context {
return static_cast<VKContext *>(Context::get());
}
VkPhysicalDevice physical_device_get() const
{
return vk_physical_device_;
}
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,116 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_descriptor_pools.hh"
#include "vk_memory.hh"
namespace blender::gpu {
VKDescriptorPools::VKDescriptorPools()
{
}
VKDescriptorPools::~VKDescriptorPools()
{
VK_ALLOCATION_CALLBACKS
for (const VkDescriptorPool vk_descriptor_pool : pools_) {
BLI_assert(vk_device_ != VK_NULL_HANDLE);
vkDestroyDescriptorPool(vk_device_, vk_descriptor_pool, vk_allocation_callbacks);
}
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()
{
VK_ALLOCATION_CALLBACKS
Vector<VkDescriptorPoolSize> pool_sizes = {
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, POOL_SIZE_STORAGE_BUFFER},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, POOL_SIZE_STORAGE_IMAGE},
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, POOL_SIZE_COMBINED_IMAGE_SAMPLER},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, POOL_SIZE_UNIFORM_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, vk_allocation_callbacks, &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,64 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_vector.hh"
#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_STORAGE_BUFFER = 1000;
static constexpr uint32_t POOL_SIZE_DESCRIPTOR_SETS = 1000;
static constexpr uint32_t POOL_SIZE_STORAGE_IMAGE = 1000;
static constexpr uint32_t POOL_SIZE_COMBINED_IMAGE_SAMPLER = 1000;
static constexpr uint32_t POOL_SIZE_UNIFORM_BUFFER = 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,128 @@
/* 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_index_buffer.hh"
#include "vk_storage_buffer.hh"
#include "vk_texture.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, const Location 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, const Location 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();
}
void VKDescriptorSet::bind_as_ssbo(VKIndexBuffer &buffer, const Location location)
{
Binding &binding = ensure_location(location);
binding.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
binding.vk_buffer = buffer.vk_handle();
binding.buffer_size = buffer.size_get();
}
void VKDescriptorSet::image_bind(VKTexture &texture, const Location location)
{
Binding &binding = ensure_location(location);
binding.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
binding.vk_image_view = texture.vk_image_view_handle();
}
VKDescriptorSet::Binding &VKDescriptorSet::ensure_location(const Location location)
{
for (Binding &binding : bindings_) {
if (binding.location == location) {
return binding;
}
}
Binding binding = {};
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_) {
if (!binding.is_buffer()) {
continue;
}
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);
}
Vector<VkDescriptorImageInfo> image_infos;
for (const Binding &binding : bindings_) {
if (!binding.is_image()) {
continue;
}
VkDescriptorImageInfo image_info = {};
image_info.imageView = binding.vk_image_view;
image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
image_infos.append(image_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.pImageInfo = &image_infos.last();
descriptor_writes.append(write_descriptor);
}
vkUpdateDescriptorSets(
vk_device, descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
bindings_.clear();
}
} // namespace blender::gpu

View File

@ -0,0 +1,147 @@
/* 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"
#include "gpu_shader_private.hh"
#include "vk_common.hh"
namespace blender::gpu {
class VKStorageBuffer;
class VKVertexBuffer;
class VKIndexBuffer;
class VKTexture;
/**
* In vulkan shader resources (images and buffers) are grouped in descriptor sets.
*
* The resources inside a descriptor set can be updated and bound per set.
*
* Currently Blender only supports a single descriptor set per shader, but it is planned to be able
* to use 2 descriptor sets per shader. Only for each #blender::gpu::shader::Frequency.
*/
class VKDescriptorSet : NonCopyable {
struct Binding;
public:
/**
* Binding location of a resource in a descriptor set.
*
* Locations and bindings are used for different reasons. In the Vulkan backend we use
* ShaderInput.location to store the descriptor set + the resource binding inside the descriptor
* set. To ease the development the VKDescriptorSet::Location will be used to hide this
* confusion.
*
* NOTE: [future development] When supporting multiple descriptor sets the encoding/decoding can
* be centralized here. Location will then also contain the descriptor set index.
*/
struct Location {
private:
/**
* References to a binding in the descriptor set.
*/
uint32_t binding;
Location() = default;
public:
Location(const ShaderInput *shader_input) : binding(shader_input->location)
{
}
bool operator==(const Location &other) const
{
return binding == other.binding;
}
operator uint32_t() const
{
return binding;
}
friend struct Binding;
};
private:
struct Binding {
Location location;
VkDescriptorType type;
VkBuffer vk_buffer = VK_NULL_HANDLE;
VkDeviceSize buffer_size = 0;
VkImageView vk_image_view = VK_NULL_HANDLE;
Binding()
{
location.binding = 0;
}
bool is_buffer() const
{
return ELEM(type, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
}
bool is_image() const
{
return ELEM(type, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE);
}
};
VkDescriptorPool vk_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSet vk_descriptor_set_ = VK_NULL_HANDLE;
/** A list of bindings that needs to be updated.*/
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, Location location);
void bind_as_ssbo(VKIndexBuffer &buffer, Location location);
void bind(VKStorageBuffer &buffer, Location location);
void image_bind(VKTexture &texture, Location location);
/**
* Update the descriptor set on the device.
*/
void update(VkDevice vk_device);
void mark_freed();
private:
Binding &ensure_location(Location location);
};
} // namespace blender::gpu

View File

@ -6,6 +6,8 @@
*/
#include "vk_index_buffer.hh"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
namespace blender::gpu {
@ -13,12 +15,31 @@ void VKIndexBuffer::upload_data()
{
}
void VKIndexBuffer::bind_as_ssbo(uint /*binding*/)
void VKIndexBuffer::bind_as_ssbo(uint binding)
{
VKContext &context = *VKContext::get();
if (!buffer_.is_allocated()) {
allocate(context);
}
VKShader *shader = static_cast<VKShader *>(context.shader);
const VKShaderInterface &shader_interface = shader->interface_get();
const ShaderInput *shader_input = shader_interface.shader_input_get(
shader::ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER, binding);
shader->pipeline_get().descriptor_set_get().bind_as_ssbo(*this, shader_input);
}
void VKIndexBuffer::read(uint32_t * /*data*/) const
void VKIndexBuffer::read(uint32_t *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_get());
buffer_.unmap(context);
}
}
void VKIndexBuffer::update_sub(uint /*start*/, uint /*len*/, const void * /*data*/)
@ -29,4 +50,14 @@ void VKIndexBuffer::strip_restart_indices()
{
}
void VKIndexBuffer::allocate(VKContext &context)
{
GPUUsageType usage = data_ == nullptr ? GPU_USAGE_DEVICE_ONLY : GPU_USAGE_STATIC;
buffer_.create(context,
size_get(),
usage,
static_cast<VkBufferUsageFlagBits>(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT));
}
} // namespace blender::gpu

View File

@ -9,9 +9,13 @@
#include "gpu_index_buffer_private.hh"
#include "vk_buffer.hh"
namespace blender::gpu {
class VKIndexBuffer : public IndexBuf {
VKBuffer buffer_;
public:
void upload_data() override;
@ -21,8 +25,14 @@ class VKIndexBuffer : public IndexBuf {
void update_sub(uint start, uint len, const void *data) override;
VkBuffer vk_handle()
{
return buffer_.vk_handle();
}
private:
void strip_restart_indices() override;
void allocate(VKContext &context);
};
} // namespace blender::gpu

View File

@ -37,7 +37,9 @@ void *vk_memory_reallocation(void *user_data,
void vk_memory_free(void * /*user_data*/, void *memory)
{
MEM_freeN(memory);
if (memory != nullptr) {
MEM_freeN(memory);
}
}
#endif

View File

@ -7,11 +7,7 @@
#pragma once
#ifdef __APPLE__
# include <MoltenVK/vk_mvk_moltenvk.h>
#else
# include <vulkan/vulkan.h>
#endif
#include "vk_common.hh"
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
*/
#include "vk_pipeline.hh"
#include "vk_context.hh"
#include "vk_memory.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()
{
VK_ALLOCATION_CALLBACKS
VkDevice vk_device = VKContext::get()->device_get();
if (vk_pipeline_ != VK_NULL_HANDLE) {
vkDestroyPipeline(vk_device, vk_pipeline_, vk_allocation_callbacks);
}
}
VKPipeline VKPipeline::create_compute_pipeline(VKContext &context,
VkShaderModule compute_module,
VkDescriptorSetLayout &descriptor_set_layout,
VkPipelineLayout &pipeline_layout)
{
VK_ALLOCATION_CALLBACKS
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, vk_allocation_callbacks, &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

View File

@ -0,0 +1,50 @@
/* 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"
#include "vk_common.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"
@ -324,9 +325,11 @@ static std::ostream &print_qualifier(std::ostream &os, const Qualifier &qualifie
return os;
}
static void print_resource(std::ostream &os, const ShaderCreateInfo::Resource &res)
static void print_resource(std::ostream &os,
const ShaderInput &shader_input,
const ShaderCreateInfo::Resource &res)
{
os << "layout(binding = " << res.slot;
os << "layout(binding = " << shader_input.location;
if (res.bind_type == ShaderCreateInfo::Resource::BindType::IMAGE) {
os << ", " << to_string(res.image.format);
}
@ -372,6 +375,18 @@ static void print_resource(std::ostream &os, const ShaderCreateInfo::Resource &r
}
}
static void print_resource(std::ostream &os,
const VKShaderInterface &shader_interface,
const ShaderCreateInfo::Resource &res)
{
const ShaderInput *shader_input = shader_interface.shader_input_get(res);
if (shader_input == nullptr) {
BLI_assert_msg(shader_input, "Cannot find shader input for resource");
return;
}
print_resource(os, *shader_input, res);
}
static void print_resource_alias(std::ostream &os, const ShaderCreateInfo::Resource &res)
{
int64_t array_offset;
@ -603,6 +618,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_, vk_allocation_callbacks);
pipeline_layout_ = VK_NULL_HANDLE;
}
if (layout_ != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(device, layout_, vk_allocation_callbacks);
layout_ = VK_NULL_HANDLE;
}
}
void VKShader::build_shader_module(MutableSpan<const char *> sources,
@ -650,52 +673,181 @@ bool VKShader::finalize(const shader::ShaderCreateInfo *info)
return false;
}
if (vertex_module_ != VK_NULL_HANDLE) {
VKShaderInterface *vk_interface = new VKShaderInterface();
vk_interface->init(*info);
VkDevice vk_device = context_->device_get();
if (!finalize_descriptor_set_layouts(vk_device, *vk_interface, *info)) {
Jeroen-Bakker marked this conversation as resolved
/Users/clement/Blender/blender/source/blender/gpu/vulkan/vk_shader.cc:680:25: warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
    compute_pipeline_ = std::move(VKPipeline::create_compute_pipeline(
                        ^
``` /Users/clement/Blender/blender/source/blender/gpu/vulkan/vk_shader.cc:680:25: warning: moving a temporary object prevents copy elision [-Wpessimizing-move] compute_pipeline_ = std::move(VKPipeline::create_compute_pipeline( ^ ```
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_ = VKPipeline::create_compute_pipeline(
*context_, compute_module_, layout_, pipeline_layout_);
result = compute_pipeline_.is_valid();
}
#ifdef NDEBUG
UNUSED_VARS(info);
#endif
if (result) {
interface = vk_interface;
}
else {
delete 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*/)
{
VK_ALLOCATION_CALLBACKS
const uint32_t layout_count = layout_ == VK_NULL_HANDLE ? 0 : 1;
VkPipelineLayoutCreateInfo pipeline_info = {};
pipeline_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_info.flags = 0;
pipeline_info.setLayoutCount = layout_count;
pipeline_info.pSetLayouts = &layout_;
if (vkCreatePipelineLayout(
vk_device, &pipeline_info, vk_allocation_callbacks, &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_STORAGE_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 ShaderInput &shader_input, const shader::ShaderCreateInfo::Resource &resource)
{
VkDescriptorSetLayoutBinding binding = {};
binding.binding = shader_input.location;
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 VKShaderInterface &interface,
const Vector<shader::ShaderCreateInfo::Resource> &resources,
Vector<VkDescriptorSetLayoutBinding> &r_bindings)
{
for (const shader::ShaderCreateInfo::Resource &resource : resources) {
const ShaderInput *shader_input = interface.shader_input_get(resource);
if (shader_input == nullptr) {
BLI_assert_msg(shader_input, "Cannot find shader input for resource.");
continue;
}
r_bindings.append(create_descriptor_set_layout_binding(*shader_input, resource));
}
}
static VkDescriptorSetLayoutCreateInfo create_descriptor_set_layout(
const VKShaderInterface &interface,
const Vector<shader::ShaderCreateInfo::Resource> &resources,
Vector<VkDescriptorSetLayoutBinding> &r_bindings)
{
add_descriptor_set_layout_bindings(interface, 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 VKShaderInterface &shader_interface,
const shader::ShaderCreateInfo &info)
{
if (info.pass_resources_.is_empty() && info.batch_resources_.is_empty()) {
return true;
}
VK_ALLOCATION_CALLBACKS
/* 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(
shader_interface, all_resources, bindings);
if (vkCreateDescriptorSetLayout(vk_device, &layout_info, vk_allocation_callbacks, &layout_) !=
VK_SUCCESS) {
return false;
};
return true;
}
@ -716,10 +868,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*/,
@ -737,11 +902,13 @@ void VKShader::uniform_int(int /*location*/,
std::string VKShader::resources_declare(const shader::ShaderCreateInfo &info) const
{
VKShaderInterface interface;
interface.init(info);
std::stringstream ss;
ss << "\n/* Pass Resources. */\n";
for (const ShaderCreateInfo::Resource &res : info.pass_resources_) {
print_resource(ss, res);
print_resource(ss, interface, res);
}
for (const ShaderCreateInfo::Resource &res : info.pass_resources_) {
print_resource_alias(ss, res);
@ -749,7 +916,7 @@ std::string VKShader::resources_declare(const shader::ShaderCreateInfo &info) co
ss << "\n/* Batch Resources. */\n";
for (const ShaderCreateInfo::Resource &res : info.batch_resources_) {
print_resource(ss, res);
print_resource(ss, interface, res);
}
for (const ShaderCreateInfo::Resource &res : info.batch_resources_) {
print_resource_alias(ss, res);
@ -958,4 +1125,14 @@ int VKShader::program_handle_get() const
return -1;
}
VKPipeline &VKShader::pipeline_get()
{
return compute_pipeline_;
}
const VKShaderInterface &VKShader::interface_get() const
{
return *static_cast<const VKShaderInterface *>(interface);
}
} // namespace blender::gpu

View File

@ -15,6 +15,7 @@
#include "BLI_string_ref.hh"
namespace blender::gpu {
class VKShaderInterface;
class VKShader : public Shader {
private:
@ -24,7 +25,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_ = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
VKPipeline compute_pipeline_;
public:
VKShader(const char *name);
@ -58,12 +61,35 @@ class VKShader : public Shader {
/* DEPRECATED: Kept only because of BGL API. */
int program_handle_get() const override;
VKPipeline &pipeline_get();
VkPipelineLayout vk_pipeline_layout_get() const
{
return pipeline_layout_;
}
const VKShaderInterface &interface_get() const;
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 VKShaderInterface &shader_interface,
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,117 @@
/* 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;
attr_len_ = 0;
uniform_len_ = 0;
ssbo_len_ = 0;
ubo_len_ = 0;
image_offset_ = -1;
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:
uniform_len_++;
break;
case ShaderCreateInfo::Resource::BindType::SAMPLER:
image_offset_ = max_ii(image_offset_, res.slot);
uniform_len_++;
break;
case ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
ubo_len_++;
break;
case ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
ssbo_len_++;
break;
}
}
/* Make sure that the image slots don't overlap with the sampler slots.*/
image_offset_ += 1;
int32_t input_tot_len = ubo_len_ + uniform_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;
int location = 0;
/* Uniform blocks */
for (const ShaderCreateInfo::Resource &res : all_resources) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER) {
copy_input_name(input, res.image.name, name_buffer_, name_buffer_offset);
input->location = location++;
input->binding = res.slot;
input++;
}
}
/* Images, Samplers and buffers. */
for (const ShaderCreateInfo::Resource &res : all_resources) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::SAMPLER) {
copy_input_name(input, res.sampler.name, name_buffer_, name_buffer_offset);
input->location = location++;
input->binding = res.slot;
input++;
}
else if (res.bind_type == ShaderCreateInfo::Resource::BindType::IMAGE) {
copy_input_name(input, res.image.name, name_buffer_, name_buffer_offset);
input->location = location++;
input->binding = res.slot + image_offset_;
input++;
}
}
/* Storage buffers */
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 = location++;
input->binding = res.slot;
input++;
}
}
sort_inputs();
}
const ShaderInput *VKShaderInterface::shader_input_get(
const shader::ShaderCreateInfo::Resource &resource) const
{
return shader_input_get(resource.bind_type, resource.slot);
}
const ShaderInput *VKShaderInterface::shader_input_get(
const shader::ShaderCreateInfo::Resource::BindType &bind_type, int binding) const
{
switch (bind_type) {
case shader::ShaderCreateInfo::Resource::BindType::IMAGE:
return texture_get(binding + image_offset_);
case shader::ShaderCreateInfo::Resource::BindType::SAMPLER:
return texture_get(binding);
case shader::ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
return ssbo_get(binding);
case shader::ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
return ubo_get(binding);
}
return nullptr;
}
} // namespace blender::gpu

View File

@ -0,0 +1,39 @@
/* 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 {
private:
/**
* Offset when searching for a shader input based on a binding number.
*
* When shaders combine images and samplers, the images have to be offset to find the correct
* shader input. Both textures and images are stored in the uniform list and their ID can be
* overlapping.
*/
uint32_t image_offset_ = 0;
public:
VKShaderInterface() = default;
void init(const shader::ShaderCreateInfo &info);
/**
* Retrieve the shader input for the given resource.
*
* nullptr is returned when resource could not be found.
* Should only happen when still developing the Vulkan shader.
*/
const ShaderInput *shader_input_get(const shader::ShaderCreateInfo::Resource &resource) const;
const ShaderInput *shader_input_get(
const shader::ShaderCreateInfo::Resource::BindType &bind_type, int binding) const;
};
} // namespace blender::gpu

View File

@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2023 Blender Foundation. All rights reserved. */
/** \file
* \ingroup gpu
*/
#include "vk_state_manager.hh"
#include "vk_texture.hh"
namespace blender::gpu {
void VKStateManager::apply_state()
{
}
void VKStateManager::force_state()
{
}
void VKStateManager::issue_barrier(eGPUBarrier /*barrier_bits*/)
{
VKContext &context = *VKContext::get();
VKCommandBuffer &command_buffer = context.command_buffer_get();
/* TODO: Pipeline barriers should be added. We might be able to extract it from
* the actual pipeline, later on, but for now we submit the work as barrier. */
command_buffer.submit();
}
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 binding)
{
VKTexture *texture = unwrap(tex);
texture->image_bind(binding);
}
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,39 @@
/** \file
* \ingroup gpu
*/
#include "vk_shader.hh"
#include "vk_shader_interface.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);
const VKShaderInterface &shader_interface = shader->interface_get();
const ShaderInput *shader_input = shader_interface.shader_input_get(
shader::ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER, slot);
shader->pipeline_get().descriptor_set_get().bind(*this, shader_input);
}
void VKStorageBuffer::unbind()
@ -35,8 +55,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,8 +7,25 @@
#include "vk_texture.hh"
#include "vk_buffer.hh"
#include "vk_context.hh"
#include "vk_memory.hh"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "BKE_global.h"
namespace blender::gpu {
VKTexture::~VKTexture()
{
VK_ALLOCATION_CALLBACKS
VKContext &context = *VKContext::get();
vmaDestroyImage(context.mem_allocator_get(), vk_image_, allocation_);
vkDestroyImageView(context.device_get(), vk_image_view_, vk_allocation_callbacks);
}
void VKTexture::generate_mipmap()
{
}
@ -33,9 +50,49 @@ void VKTexture::mip_range_set(int /*min*/, int /*max*/)
{
}
void *VKTexture::read(int /*mip*/, eGPUDataFormat /*format*/)
void *VKTexture::read(int mip, eGPUDataFormat format)
{
return nullptr;
/* Vulkan images cannot be directly mapped to host memory and requires a staging buffer.*/
VKContext &context = *VKContext::get();
VKBuffer staging_buffer;
/* NOTE: mip_size_get() won't override any dimension that is equal to 0. */
int extent[3] = {1, 1, 1};
mip_size_get(mip, extent);
size_t sample_len = extent[0] * extent[1] * extent[2];
/* NOTE: to_bytesize returns number of bits. */
size_t device_memory_size = sample_len * to_component_len(format_) * to_bytesize(format_) / 8;
/* NOTE: to_bytesize returns number of bytes here. */
size_t host_memory_size = sample_len * to_bytesize(format_, format);
staging_buffer.create(
context, device_memory_size, GPU_USAGE_DEVICE_ONLY, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
VkBufferImageCopy region = {};
region.imageExtent.width = extent[0];
region.imageExtent.height = extent[1];
region.imageExtent.depth = extent[2];
region.imageSubresource.aspectMask = to_vk_image_aspect_flag_bits(format_);
region.imageSubresource.mipLevel = mip;
region.imageSubresource.layerCount = 1;
VKCommandBuffer &command_buffer = context.command_buffer_get();
command_buffer.copy(staging_buffer, *this, Span<VkBufferImageCopy>(&region, 1));
command_buffer.submit();
void *mapped_data;
staging_buffer.map(context, &mapped_data);
void *data = MEM_mallocN(host_memory_size, __func__);
/* TODO: add conversion when data format is different.*/
BLI_assert_msg(device_memory_size == host_memory_size,
"Memory data conversions not implemented yet");
memcpy(data, mapped_data, host_memory_size);
staging_buffer.unmap(context);
return data;
}
void VKTexture::update_sub(int /*mip*/,
@ -61,7 +118,10 @@ uint VKTexture::gl_bindcode_get() const
bool VKTexture::init_internal()
{
return false;
/* Initialization can only happen after the usage is known. By the current API this isn't set
* at this moment, so we cannot initialize here. The initialization is postponed until the
* allocation of the texture on the device.*/
return true;
}
bool VKTexture::init_internal(GPUVertBuf * /*vbo*/)
@ -74,4 +134,102 @@ bool VKTexture::init_internal(const GPUTexture * /*src*/, int /*mip_offset*/, in
return false;
}
bool VKTexture::is_allocated()
{
return vk_image_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE;
}
bool VKTexture::allocate()
{
BLI_assert(!is_allocated());
int extent[3] = {1, 1, 1};
mip_size_get(0, extent);
VKContext &context = *VKContext::get();
VkImageCreateInfo image_info = {};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.imageType = to_vk_image_type(type_);
image_info.extent.width = extent[0];
image_info.extent.height = extent[1];
image_info.extent.depth = extent[2];
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = to_vk_format(format_);
image_info.tiling = VK_IMAGE_TILING_LINEAR;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_STORAGE_BIT;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
VkResult result;
if (G.debug &= G_DEBUG_GPU) {
VkImageFormatProperties image_format = {};
result = vkGetPhysicalDeviceImageFormatProperties(context.physical_device_get(),
image_info.format,
image_info.imageType,
image_info.tiling,
image_info.usage,
image_info.flags,
&image_format);
if (result != VK_SUCCESS) {
printf("Image type not supported on device.\n");
return false;
}
}
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
allocCreateInfo.flags = static_cast<VmaAllocationCreateFlagBits>(
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT);
allocCreateInfo.priority = 1.0f;
result = vmaCreateImage(context.mem_allocator_get(),
&image_info,
&allocCreateInfo,
&vk_image_,
&allocation_,
nullptr);
if (result != VK_SUCCESS) {
return false;
}
/* Promote image to the correct layout.*/
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier.image = vk_image_;
barrier.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_);
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
context.command_buffer_get().pipeline_barrier(Span<VkImageMemoryBarrier>(&barrier, 1));
VK_ALLOCATION_CALLBACKS
VkImageViewCreateInfo image_view_info = {};
image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_info.image = vk_image_;
image_view_info.viewType = to_vk_image_view_type(type_);
image_view_info.format = to_vk_format(format_);
image_view_info.components = to_vk_component_mapping(format_);
image_view_info.subresourceRange.aspectMask = to_vk_image_aspect_flag_bits(format_);
image_view_info.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_view_info.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
result = vkCreateImageView(
context.device_get(), &image_view_info, vk_allocation_callbacks, &vk_image_view_);
return result == VK_SUCCESS;
}
void VKTexture::image_bind(int binding)
{
if (!is_allocated()) {
allocate();
}
VKContext &context = *VKContext::get();
VKShader *shader = static_cast<VKShader *>(context.shader);
VKDescriptorSet::Location location(shader->interface_get().shader_input_get(
shader::ShaderCreateInfo::Resource::BindType::IMAGE, binding));
shader->pipeline_get().descriptor_set_get().image_bind(*this, location);
}
} // namespace blender::gpu

View File

@ -8,14 +8,22 @@
#pragma once
#include "gpu_texture_private.hh"
#include "vk_context.hh"
#include "vk_mem_alloc.h"
namespace blender::gpu {
class VKTexture : public Texture {
VkImage vk_image_ = VK_NULL_HANDLE;
VkImageView vk_image_view_ = VK_NULL_HANDLE;
VmaAllocation allocation_ = VK_NULL_HANDLE;
public:
VKTexture(const char *name) : Texture(name)
{
}
virtual ~VKTexture() override;
void generate_mipmap() override;
void copy_to(Texture *tex) override;
@ -34,10 +42,36 @@ class VKTexture : public Texture {
/* TODO(fclem): Legacy. Should be removed at some point. */
uint gl_bindcode_get() const override;
void image_bind(int location);
VkImage vk_image_handle() const
{
return vk_image_;
}
VkImageView vk_image_view_handle() const
{
return vk_image_view_;
}
protected:
bool init_internal() override;
bool init_internal(GPUVertBuf *vbo) override;
bool init_internal(const GPUTexture *src, int mip_offset, int layer_offset) override;
private:
/** Is this texture already allocated on device.*/
bool is_allocated();
/**
* Allocate the texture of the device. Result is `true` when texture is successfully allocated
* on the device.
*/
bool allocate();
VkImageViewType vk_image_view_type() const;
};
static inline VKTexture *unwrap(Texture *tex)
{
return static_cast<VKTexture *>(tex);
}
} // namespace blender::gpu

View File

@ -7,6 +7,8 @@
#include "MEM_guardedalloc.h"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "vk_vertex_buffer.hh"
namespace blender::gpu {
@ -16,8 +18,18 @@ 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);
const VKShaderInterface &shader_interface = shader->interface_get();
const ShaderInput *shader_input = shader_interface.shader_input_get(
shader::ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER, binding);
shader->pipeline_get().descriptor_set_get().bind_as_ssbo(*this, shader_input);
}
void VKVertexBuffer::bind_as_texture(uint /*binding*/)
@ -32,8 +44,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 +85,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