diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index cf810caf778..899aaa7de22 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -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. + * \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 diff --git a/intern/ghost/GHOST_IContext.h b/intern/ghost/GHOST_IContext.h index 52863e8c061..8499e6b2905 100644 --- a/intern/ghost/GHOST_IContext.h +++ b/intern/ghost/GHOST_IContext.h @@ -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; diff --git a/intern/ghost/GHOST_IWindow.h b/intern/ghost/GHOST_IWindow.h index 403f9e388cc..4fc24b6ca86 100644 --- a/intern/ghost/GHOST_IWindow.h +++ b/intern/ghost/GHOST_IWindow.h @@ -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; diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index a3c1eedc9c0..986f88205c7 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -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 */ diff --git a/intern/ghost/intern/GHOST_Context.h b/intern/ghost/intern/GHOST_Context.h index d4cc0d18e55..6fd212f7eb3 100644 --- a/intern/ghost/intern/GHOST_Context.h +++ b/intern/ghost/intern/GHOST_Context.h @@ -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; diff --git a/intern/ghost/intern/GHOST_ContextVK.cpp b/intern/ghost/intern/GHOST_ContextVK.cpp index 4ee48243234..3f9b197939a 100644 --- a/intern/ghost/intern/GHOST_ContextVK.cpp +++ b/intern/ghost/intern/GHOST_ContextVK.cpp @@ -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(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(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 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; } diff --git a/intern/ghost/intern/GHOST_ContextVK.h b/intern/ghost/intern/GHOST_ContextVK.h index 1a2d38bc701..0daa9e40027 100644 --- a/intern/ghost/intern/GHOST_ContextVK.h +++ b/intern/ghost/intern/GHOST_ContextVK.h @@ -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 required_exts); GHOST_TSuccess createSwapchain(); GHOST_TSuccess destroySwapchain(); - GHOST_TSuccess createCommandBuffers(); + GHOST_TSuccess createCommandPools(); + GHOST_TSuccess createGraphicsCommandBuffers(); + GHOST_TSuccess createGraphicsCommandBuffer(); GHOST_TSuccess recordCommandBuffers(); }; diff --git a/intern/ghost/intern/GHOST_Window.cpp b/intern/ghost/intern/GHOST_Window.cpp index 202f803f710..a1444bd5c8a 100644 --- a/intern/ghost/intern/GHOST_Window.cpp +++ b/intern/ghost/intern/GHOST_Window.cpp @@ -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() diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h index 04ce9fed950..e008fcc72c1 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -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. diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 4b378384a9d..e4b57804929 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -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 ) diff --git a/source/blender/gpu/intern/gpu_shader_interface.hh b/source/blender/gpu/intern/gpu_shader_interface.hh index fc9eafca3a5..24c4bde0971 100644 --- a/source/blender/gpu/intern/gpu_shader_interface.hh +++ b/source/blender/gpu/intern/gpu_shader_interface.hh @@ -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; diff --git a/source/blender/gpu/intern/gpu_storage_buffer_private.hh b/source/blender/gpu/intern/gpu_storage_buffer_private.hh index cdc53dc0a26..c05d8dc5b98 100644 --- a/source/blender/gpu/intern/gpu_storage_buffer_private.hh +++ b/source/blender/gpu/intern/gpu_storage_buffer_private.hh @@ -14,6 +14,8 @@ struct GPUStorageBuf; namespace blender { namespace gpu { +class VertBuf; + #ifdef DEBUG # define DEBUG_NAME_LEN 64 #else diff --git a/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh index 1141e915c87..e612c7ebbb1 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh @@ -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[]") diff --git a/source/blender/gpu/tests/gpu_shader_test.cc b/source/blender/gpu/tests/gpu_shader_test.cc index ce1397f3327..cbbee0192a1 100644 --- a/source/blender/gpu/tests/gpu_shader_test.cc +++ b/source/blender/gpu/tests/gpu_shader_test.cc @@ -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); } diff --git a/source/blender/gpu/tests/gpu_storage_buffer_test.cc b/source/blender/gpu/tests/gpu_storage_buffer_test.cc new file mode 100644 index 00000000000..38004391dd8 --- /dev/null +++ b/source/blender/gpu/tests/gpu_storage_buffer_test.cc @@ -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 test_data() +{ + Vector 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 data = test_data(); + GPU_storagebuf_update(ssbo, data.data()); + + /* Read back data from SSBO. */ + Vector 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 \ No newline at end of file diff --git a/source/blender/gpu/tests/gpu_testing.cc b/source/blender/gpu/tests/gpu_testing.cc index 1f54837cca3..fd28a18a24b 100644 --- a/source/blender/gpu/tests/gpu_testing.cc +++ b/source/blender/gpu/tests/gpu_testing.cc @@ -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); diff --git a/source/blender/gpu/tests/shaders/gpu_compute_ssbo_test.glsl b/source/blender/gpu/tests/shaders/gpu_compute_ssbo_test.glsl new file mode 100644 index 00000000000..b7a7390f309 --- /dev/null +++ b/source/blender/gpu/tests/shaders/gpu_compute_ssbo_test.glsl @@ -0,0 +1,5 @@ +void main() +{ + int store_index = int(gl_GlobalInvocationID.x); + data_out[store_index] = store_index * 4; +} diff --git a/source/blender/gpu/vulkan/vk_backend.cc b/source/blender/gpu/vulkan/vk_backend.cc index 74d9d8bed8e..5989a899f6a 100644 --- a/source/blender/gpu/vulkan/vk_backend.cc +++ b/source/blender/gpu/vulkan/vk_backend.cc @@ -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(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() diff --git a/source/blender/gpu/vulkan/vk_backend.hh b/source/blender/gpu/vulkan/vk_backend.hh index f51798eb9c0..01c85792018 100644 --- a/source/blender/gpu/vulkan/vk_backend.hh +++ b/source/blender/gpu/vulkan/vk_backend.hh @@ -9,11 +9,8 @@ #include "gpu_backend.hh" -#ifdef __APPLE__ -# include -#else -# include -#endif +#include "vk_common.hh" + #include "shaderc/shaderc.hpp" namespace blender::gpu { diff --git a/source/blender/gpu/vulkan/vk_buffer.cc b/source/blender/gpu/vulkan/vk_buffer.cc new file mode 100644 index 00000000000..f882783041d --- /dev/null +++ b/source/blender/gpu/vulkan/vk_buffer.cc @@ -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( + VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT); + case GPU_USAGE_DYNAMIC: + return static_cast( + VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT); + case GPU_USAGE_DEVICE_ONLY: + return static_cast( + 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(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 diff --git a/source/blender/gpu/vulkan/vk_buffer.hh b/source/blender/gpu/vulkan/vk_buffer.hh new file mode 100644 index 00000000000..875876ccab1 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_buffer.hh @@ -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 diff --git a/source/blender/gpu/vulkan/vk_command_buffer.cc b/source/blender/gpu/vulkan/vk_command_buffer.cc new file mode 100644 index 00000000000..12821142b32 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_command_buffer.cc @@ -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 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 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 diff --git a/source/blender/gpu/vulkan/vk_command_buffer.hh b/source/blender/gpu/vulkan/vk_command_buffer.hh new file mode 100644 index 00000000000..61bb12497e5 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_command_buffer.hh @@ -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" +#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 regions); + void pipeline_barrier(VkPipelineStageFlags source_stages, + VkPipelineStageFlags destination_stages); + void pipeline_barrier(Span 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 diff --git a/source/blender/gpu/vulkan/vk_common.cc b/source/blender/gpu/vulkan/vk_common.cc new file mode 100644 index 00000000000..ecfccad8294 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_common.cc @@ -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(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(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 diff --git a/source/blender/gpu/vulkan/vk_common.hh b/source/blender/gpu/vulkan/vk_common.hh new file mode 100644 index 00000000000..b2645cd2e8c --- /dev/null +++ b/source/blender/gpu/vulkan/vk_common.hh @@ -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 +#else +# include +#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 diff --git a/source/blender/gpu/vulkan/vk_context.cc b/source/blender/gpu/vulkan/vk_context.cc index 853968daa22..f28d570fd0f 100644 --- a/source/blender/gpu/vulkan/vk_context.cc +++ b/source/blender/gpu/vulkan/vk_context.cc @@ -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_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*/) diff --git a/source/blender/gpu/vulkan/vk_context.hh b/source/blender/gpu/vulkan/vk_context.hh index 5ad1a5029ba..a2521ade77c 100644 --- a/source/blender/gpu/vulkan/vk_context.hh +++ b/source/blender/gpu/vulkan/vk_context.hh @@ -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 -#else -# include -#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(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 diff --git a/source/blender/gpu/vulkan/vk_descriptor_pools.cc b/source/blender/gpu/vulkan/vk_descriptor_pools.cc new file mode 100644 index 00000000000..a83e83f7aa3 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_descriptor_pools.cc @@ -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 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 \ No newline at end of file diff --git a/source/blender/gpu/vulkan/vk_descriptor_pools.hh b/source/blender/gpu/vulkan/vk_descriptor_pools.hh new file mode 100644 index 00000000000..71292e019a3 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_descriptor_pools.hh @@ -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 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 \ No newline at end of file diff --git a/source/blender/gpu/vulkan/vk_descriptor_set.cc b/source/blender/gpu/vulkan/vk_descriptor_set.cc new file mode 100644 index 00000000000..e41c21dda54 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_descriptor_set.cc @@ -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 buffer_infos; + Vector 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 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 diff --git a/source/blender/gpu/vulkan/vk_descriptor_set.hh b/source/blender/gpu/vulkan/vk_descriptor_set.hh new file mode 100644 index 00000000000..f16f939c98c --- /dev/null +++ b/source/blender/gpu/vulkan/vk_descriptor_set.hh @@ -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 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 diff --git a/source/blender/gpu/vulkan/vk_index_buffer.cc b/source/blender/gpu/vulkan/vk_index_buffer.cc index 70387bd0fce..1d6e620a395 100644 --- a/source/blender/gpu/vulkan/vk_index_buffer.cc +++ b/source/blender/gpu/vulkan/vk_index_buffer.cc @@ -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(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(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_INDEX_BUFFER_BIT)); +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_index_buffer.hh b/source/blender/gpu/vulkan/vk_index_buffer.hh index 7586b49ff21..26134f8d390 100644 --- a/source/blender/gpu/vulkan/vk_index_buffer.hh +++ b/source/blender/gpu/vulkan/vk_index_buffer.hh @@ -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 diff --git a/source/blender/gpu/vulkan/vk_memory.cc b/source/blender/gpu/vulkan/vk_memory.cc index e02ca0b014f..87a4d78d66b 100644 --- a/source/blender/gpu/vulkan/vk_memory.cc +++ b/source/blender/gpu/vulkan/vk_memory.cc @@ -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 diff --git a/source/blender/gpu/vulkan/vk_memory.hh b/source/blender/gpu/vulkan/vk_memory.hh index 991801de7b4..2f42eaa90c0 100644 --- a/source/blender/gpu/vulkan/vk_memory.hh +++ b/source/blender/gpu/vulkan/vk_memory.hh @@ -7,11 +7,7 @@ #pragma once -#ifdef __APPLE__ -# include -#else -# include -#endif +#include "vk_common.hh" namespace blender::gpu { diff --git a/source/blender/gpu/vulkan/vk_pipeline.cc b/source/blender/gpu/vulkan/vk_pipeline.cc new file mode 100644 index 00000000000..7df78597e50 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_pipeline.cc @@ -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 diff --git a/source/blender/gpu/vulkan/vk_pipeline.hh b/source/blender/gpu/vulkan/vk_pipeline.hh new file mode 100644 index 00000000000..b718924be7b --- /dev/null +++ b/source/blender/gpu/vulkan/vk_pipeline.hh @@ -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 diff --git a/source/blender/gpu/vulkan/vk_shader.cc b/source/blender/gpu/vulkan/vk_shader.cc index 6a69a66392e..7d5f2409bc6 100644 --- a/source/blender/gpu/vulkan/vk_shader.cc +++ b/source/blender/gpu/vulkan/vk_shader.cc @@ -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 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)) { + 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 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 &resources, + Vector &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 &resources, + Vector &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 all_resources; + all_resources.extend(info.pass_resources_); + all_resources.extend(info.batch_resources_); + + Vector 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(interface); +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_shader.hh b/source/blender/gpu/vulkan/vk_shader.hh index 60924d35f21..b4c138f00b4 100644 --- a/source/blender/gpu/vulkan/vk_shader.hh +++ b/source/blender/gpu/vulkan/vk_shader.hh @@ -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 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 compile_glsl_to_spirv(Span sources, shaderc_shader_kind kind); void build_shader_module(Span spirv_module, VkShaderModule *r_shader_module); void build_shader_module(MutableSpan 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 diff --git a/source/blender/gpu/vulkan/vk_shader_interface.cc b/source/blender/gpu/vulkan/vk_shader_interface.cc new file mode 100644 index 00000000000..cec6213bc5b --- /dev/null +++ b/source/blender/gpu/vulkan/vk_shader_interface.cc @@ -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 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( + 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 diff --git a/source/blender/gpu/vulkan/vk_shader_interface.hh b/source/blender/gpu/vulkan/vk_shader_interface.hh new file mode 100644 index 00000000000..4e099feb680 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_shader_interface.hh @@ -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 diff --git a/source/blender/gpu/vulkan/vk_state_manager.cc b/source/blender/gpu/vulkan/vk_state_manager.cc new file mode 100644 index 00000000000..4e6eb3298bd --- /dev/null +++ b/source/blender/gpu/vulkan/vk_state_manager.cc @@ -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 \ No newline at end of file diff --git a/source/blender/gpu/vulkan/vk_state_manager.hh b/source/blender/gpu/vulkan/vk_state_manager.hh new file mode 100644 index 00000000000..4d2643485c1 --- /dev/null +++ b/source/blender/gpu/vulkan/vk_state_manager.hh @@ -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 \ No newline at end of file diff --git a/source/blender/gpu/vulkan/vk_storage_buffer.cc b/source/blender/gpu/vulkan/vk_storage_buffer.cc index 40c770fa1d2..5c6d8a10244 100644 --- a/source/blender/gpu/vulkan/vk_storage_buffer.cc +++ b/source/blender/gpu/vulkan/vk_storage_buffer.cc @@ -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(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 diff --git a/source/blender/gpu/vulkan/vk_storage_buffer.hh b/source/blender/gpu/vulkan/vk_storage_buffer.hh index ab3cb584ea0..a9ee1a6256b 100644 --- a/source/blender/gpu/vulkan/vk_storage_buffer.hh +++ b/source/blender/gpu/vulkan/vk_storage_buffer.hh @@ -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 diff --git a/source/blender/gpu/vulkan/vk_texture.cc b/source/blender/gpu/vulkan/vk_texture.cc index 3215268b7bc..bb587d477aa 100644 --- a/source/blender/gpu/vulkan/vk_texture.cc +++ b/source/blender/gpu/vulkan/vk_texture.cc @@ -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(®ion, 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( + 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(&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(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 diff --git a/source/blender/gpu/vulkan/vk_texture.hh b/source/blender/gpu/vulkan/vk_texture.hh index 087c4ca8a9a..e0b1fe84ec9 100644 --- a/source/blender/gpu/vulkan/vk_texture.hh +++ b/source/blender/gpu/vulkan/vk_texture.hh @@ -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(tex); +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_vertex_buffer.cc b/source/blender/gpu/vulkan/vk_vertex_buffer.cc index f2326ddb134..5d44b2e2c4f 100644 --- a/source/blender/gpu/vulkan/vk_vertex_buffer.cc +++ b/source/blender/gpu/vulkan/vk_vertex_buffer.cc @@ -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(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(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)); +} + } // namespace blender::gpu diff --git a/source/blender/gpu/vulkan/vk_vertex_buffer.hh b/source/blender/gpu/vulkan/vk_vertex_buffer.hh index ebc3e105553..1bf8e782a1e 100644 --- a/source/blender/gpu/vulkan/vk_vertex_buffer.hh +++ b/source/blender/gpu/vulkan/vk_vertex_buffer.hh @@ -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