Vulkan/OpenXR: Support VK_KHR_external_memory_win32

This PR add support to use a win32 handle to perform share render
result with the OpenXR vulkan instance. This is only possible when
the GPU matches. Otherwise a CPU roundtrip will be performed.

Pull Request: https://projects.blender.org/blender/blender/pulls/137093
This commit is contained in:
Jeroen Bakker
2025-04-08 15:21:55 +02:00
parent 6db2c6b864
commit 7ecacbc3e6
7 changed files with 136 additions and 28 deletions

View File

@@ -780,6 +780,14 @@ typedef enum {
* will import the memory and copy the image to the swapchain.
*/
GHOST_kVulkanXRModeFD,
/**
* Use Win32 handle to transfer the render result to the XR swapchain.
*
* Application renders a view, export the memory in an win32 handle.
* GHOST_XrGraphicsBindingVulkan will import the memory and copy the image to the swapchain.
*/
GHOST_kVulkanXRModeWin32,
} GHOST_TVulkanXRModes;
typedef struct {

View File

@@ -1070,7 +1070,7 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
/* External memory extensions. */
required_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
#ifdef _WIN32
/* Placeholder to add VK_KHR_external_memory_win32 */
optional_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
#elif not defined(__APPLE__)
optional_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
#endif

View File

@@ -15,6 +15,10 @@
#include "GHOST_XrGraphicsBindingVulkan.hh"
#include "GHOST_Xr_intern.hh"
#ifdef _WIN32
# include <vulkan/vulkan_win32.h>
#endif
/** OpenXR/Vulkan specific function pointers. */
PFN_xrGetVulkanGraphicsRequirements2KHR
GHOST_XrGraphicsBindingVulkan::s_xrGetVulkanGraphicsRequirements2KHR_fn = nullptr;
@@ -311,6 +315,11 @@ GHOST_TVulkanXRModes GHOST_XrGraphicsBindingVulkan::choseDataTransferMode()
};
#ifdef _WIN32
bool has_vk_khr_external_memory_win32_extension = has_extension(
VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
if (has_vk_khr_external_memory_win32_extension) {
return GHOST_kVulkanXRModeWin32;
}
#elif defined(__APPLE__)
#else /* UNIX/Linux */
bool has_vk_khr_external_memory_fd_extension = has_extension(
@@ -408,16 +417,13 @@ void GHOST_XrGraphicsBindingVulkan::submitToSwapchainImage(
switch (m_data_transfer_mode) {
case GHOST_kVulkanXRModeFD:
case GHOST_kVulkanXRModeWin32:
submitToSwapchainImageGpu(vulkan_image, draw_info);
break;
case GHOST_kVulkanXRModeCPU:
submitToSwapchainImageCpu(vulkan_image, draw_info);
break;
default:
// assert(false);
break;
}
}
@@ -553,9 +559,18 @@ void GHOST_XrGraphicsBindingVulkan::submitToSwapchainImageGpu(
/* Create an image handle */
VkExternalMemoryImageCreateInfo vk_external_memory_image_info = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
nullptr,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT};
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, nullptr, 0};
switch (m_data_transfer_mode) {
case GHOST_kVulkanXRModeFD:
vk_external_memory_image_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
break;
case GHOST_kVulkanXRModeWin32:
vk_external_memory_image_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
break;
case GHOST_kVulkanXRModeCPU:
break;
}
VkImageCreateInfo vk_image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
&vk_external_memory_image_info,
@@ -581,16 +596,40 @@ void GHOST_XrGraphicsBindingVulkan::submitToSwapchainImageGpu(
vkGetImageMemoryRequirements(m_vk_device, vk_image, &vk_memory_requirements);
/* Import the memory */
VkDeviceMemory device_memory = VK_NULL_HANDLE;
VkMemoryDedicatedAllocateInfo vk_memory_dedicated_allocation_info = {
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, nullptr, vk_image, VK_NULL_HANDLE};
VkImportMemoryFdInfoKHR import_memory_info = {VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
&vk_memory_dedicated_allocation_info,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
int(openxr_data.gpu.image_handle)};
VkMemoryAllocateInfo allocate_info = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &import_memory_info, vk_memory_requirements.size};
VkDeviceMemory device_memory;
vkAllocateMemory(m_vk_device, &allocate_info, nullptr, &device_memory);
switch (m_data_transfer_mode) {
case GHOST_kVulkanXRModeFD: {
VkImportMemoryFdInfoKHR import_memory_info = {VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
&vk_memory_dedicated_allocation_info,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
int(openxr_data.gpu.image_handle)};
VkMemoryAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
&import_memory_info,
vk_memory_requirements.size};
vkAllocateMemory(m_vk_device, &allocate_info, nullptr, &device_memory);
break;
}
case GHOST_kVulkanXRModeWin32: {
#ifdef _WIN32
VkImportMemoryWin32HandleInfoKHR import_memory_info = {
VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
&vk_memory_dedicated_allocation_info,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT,
HANDLE(openxr_data.gpu.image_handle)};
VkMemoryAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
&import_memory_info,
vk_memory_requirements.size};
vkAllocateMemory(m_vk_device, &allocate_info, nullptr, &device_memory);
#endif
break;
}
case GHOST_kVulkanXRModeCPU:
break;
}
/* Bind the imported memory to the image. */
vkBindImageMemory(m_vk_device, vk_image, device_memory, openxr_data.gpu.memory_offset);

View File

@@ -451,6 +451,19 @@ void VKContext::openxr_acquire_framebuffer_image_handler(GHOST_VulkanOpenXRData
openxr_data.gpu.memory_offset = exported_memory.memory_offset;
break;
}
case GHOST_kVulkanXRModeWin32: {
flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::WAIT_FOR_COMPLETION |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
VKMemoryExport exported_memory = color_attachment->export_memory(
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT);
openxr_data.gpu.image_handle = exported_memory.handle;
openxr_data.gpu.image_format = to_vk_format(color_attachment->device_format_get());
openxr_data.gpu.memory_size = exported_memory.memory_size;
openxr_data.gpu.memory_offset = exported_memory.memory_offset;
break;
}
}
}
@@ -468,6 +481,14 @@ void VKContext::openxr_release_framebuffer_image_handler(GHOST_VulkanOpenXRData
* https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_external_memory_fd.html#_issues
*/
break;
case GHOST_kVulkanXRModeWin32:
#ifdef _WIN32
/* Exported handle isn't consumed during import and should be freed after use. */
CloseHandle(HANDLE(openxr_data.gpu.image_handle));
openxr_data.gpu.image_handle = 0;
#endif
break;
}
}

View File

@@ -138,6 +138,11 @@ void VKDevice::init_functions()
/* VK_KHR_external_memory_fd */
functions.vkGetMemoryFd = LOAD_FUNCTION(vkGetMemoryFdKHR);
#ifdef _WIN32
/* VK_KHR_external_memory_win32 */
functions.vkGetMemoryWin32Handle = LOAD_FUNCTION(vkGetMemoryWin32HandleKHR);
#endif
#undef LOAD_FUNCTION
}
@@ -217,10 +222,16 @@ void VKDevice::init_memory_allocator()
/* External memory pool */
/* Initialize a dummy image create info to find the memory type index that will be used for
* allocating. */
VkExternalMemoryHandleTypeFlags vk_external_memory_handle_type = 0;
#ifdef _WIN32
vk_external_memory_handle_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
#else
vk_external_memory_handle_type = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
#endif
VkExternalMemoryImageCreateInfo external_image_create_info = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
nullptr,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT};
vk_external_memory_handle_type};
VkImageCreateInfo image_create_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
&external_image_create_info,
0,
@@ -244,7 +255,7 @@ void VKDevice::init_memory_allocator()
vmaFindMemoryTypeIndexForImageInfo(
mem_allocator_, &image_create_info, &allocation_create_info, &memory_type_index);
vma_pools.external_memory_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
vma_pools.external_memory_info.handleTypes = vk_external_memory_handle_type;
VmaPoolCreateInfo pool_create_info = {};
pool_create_info.memoryTypeIndex = memory_type_index;
pool_create_info.pMemoryAllocateNext = &vma_pools.external_memory_info;

View File

@@ -235,6 +235,11 @@ class VKDevice : public NonCopyable {
/* Extension: VK_KHR_external_memory_fd */
PFN_vkGetMemoryFdKHR vkGetMemoryFd = nullptr;
#ifdef _WIN32
/* Extension: VK_KHR_external_memory_win32 */
PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32Handle = nullptr;
#endif
} functions;
struct {

View File

@@ -8,6 +8,10 @@
#include "GPU_capabilities.hh"
/* vk_common needs to be included first to ensure win32 vulkan API is fully initialized, before
* working with it. */
#include "vk_common.hh"
#include "vk_texture.hh"
#include "vk_buffer.hh"
@@ -401,14 +405,31 @@ VKMemoryExport VKTexture::export_memory(VkExternalMemoryHandleTypeFlagBits handl
BLI_assert_msg(allocation_ != nullptr,
"Cannot export memory when the texture is not backed by any device memory.");
const VKDevice &device = VKBackend::get().device;
VkMemoryGetFdInfoKHR vk_memory_get_fd_info = {VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
nullptr,
allocation_info_.deviceMemory,
handle_type};
if (handle_type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT) {
VkMemoryGetFdInfoKHR vk_memory_get_fd_info = {VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
nullptr,
allocation_info_.deviceMemory,
handle_type};
int fd_handle = 0;
device.functions.vkGetMemoryFd(device.vk_handle(), &vk_memory_get_fd_info, &fd_handle);
return {uint64_t(fd_handle), allocation_info_.size, allocation_info_.offset};
}
int fd_handle = 0;
device.functions.vkGetMemoryFd(device.vk_handle(), &vk_memory_get_fd_info, &fd_handle);
return {uint64_t(fd_handle), allocation_info_.size, allocation_info_.offset};
#ifdef _WIN32
if (handle_type == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT) {
VkMemoryGetWin32HandleInfoKHR vk_memory_get_win32_handle_info = {
VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR,
nullptr,
allocation_info_.deviceMemory,
handle_type};
HANDLE win32_handle = nullptr;
device.functions.vkGetMemoryWin32Handle(
device.vk_handle(), &vk_memory_get_win32_handle_info, &win32_handle);
return {uint64_t(win32_handle), allocation_info_.size, allocation_info_.offset};
}
#endif
BLI_assert_unreachable();
return {};
}
bool VKTexture::init_internal()
@@ -585,9 +606,7 @@ bool VKTexture::allocate()
}
VkExternalMemoryImageCreateInfo external_memory_create_info = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
nullptr,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT};
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, nullptr, 0};
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
@@ -595,6 +614,11 @@ bool VKTexture::allocate()
if (bool(texture_usage & GPU_TEXTURE_USAGE_MEMORY_EXPORT)) {
image_info.pNext = &external_memory_create_info;
#ifdef _WIN32
external_memory_create_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
#else
external_memory_create_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
#endif
allocCreateInfo.pool = device.vma_pools.external_memory;
}
result = vmaCreateImage(device.mem_allocator_get(),