单个VkInstance下,如何在多个VkDevice间共享VkBuffer或VkImage?
Great question! Since all your VkDevice instances map to the same underlying physical device (even with distinct VkPhysicalDevice objects), the cleanest and most Vulkan-spec-compliant approach to share resources between them is leveraging the external memory import/export system. This method is efficient, cross-platform (with minor OS-specific tweaks), and aligns perfectly with Vulkan's design.
Prerequisite Check
First, confirm your devices enable the required extensions:
- On Linux:
VK_KHR_external_memory_fd - On Windows:
VK_KHR_external_memory_win32
These are widely supported by modern GPU drivers, so they shouldn't be a problem.
Step-by-Step Implementation (Using VkBuffer as Example)
The flow is nearly identical for VkImage—just swap out buffer-specific structs with image equivalents.
1. Create & Export the Resource from the First Device (Device A)
- Create the Buffer: Define a
VkBufferCreateInfowith matching parameters you'll use across all devices (size, usage flags,sharingMode = VK_SHARING_MODE_EXCLUSIVE—concurrent sharing only works within a single device's queue families). - Allocate Exportable Memory: Attach a
VkExportMemoryAllocateInfoto yourVkMemoryAllocateInfovia thepNextchain. Specify the OS-appropriate handle type:- Linux:
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT - Windows:
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT
- Linux:
- Bind & Extract the External Handle: Bind the memory to your buffer, then call the OS-specific function to get the external handle (e.g.,
vkGetMemoryFdKHRon Linux,vkGetMemoryWin32HandleKHRon Windows).
2. Import & Recreate the Resource on the Second Device (Device B)
- Recreate an Identical Buffer: The
VkBufferCreateInfomust be exactly the same as the one used on Device A—same size, usage flags, alignment, etc. Mismatched parameters will cause binding failures. - Import the Memory: Use
VkImportMemoryAllocateInfo(attached toVkMemoryAllocateInfoviapNext) to import the external handle from Device A. Ensure the handle type matches what you exported. - Bind the Imported Memory: Call
vkBindBufferMemoryto attach the imported memory to your new buffer on Device B.
3. Critical: Synchronize Across Devices
Never skip synchronization—unsynchronized access to shared memory will lead to undefined behavior (data corruption, crashes, etc.):
- Before exporting from Device A: Ensure all pending operations on the resource are complete (use a
VkFenceorvkQueueWaitIdle). - Before using the resource on Device B: Insert appropriate memory barriers to make the memory contents visible to Device B's queues.
- After using on Device B: Synchronize again before Device A reuses the resource.
Simplified Code Snippet (Linux)
// --- Device A: Export Memory --- VkExportMemoryAllocateInfo exportInfo{}; exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO; exportInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; VkMemoryAllocateInfo allocInfoA{}; allocInfoA.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfoA.allocationSize = memRequirements.size; allocInfoA.memoryTypeIndex = selectedMemTypeA; allocInfoA.pNext = &exportInfo; VkDeviceMemory memA; vkAllocateMemory(deviceA, &allocInfoA, nullptr, &memA); vkBindBufferMemory(deviceA, bufferA, memA, 0); // Get the file descriptor int fd; VkMemoryGetFdInfoKHR fdInfo{}; fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR; fdInfo.memory = memA; fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; vkGetMemoryFdKHR(deviceA, &fdInfo, &fd); // --- Device B: Import Memory --- VkImportMemoryAllocateInfo importInfo{}; importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_ALLOCATE_INFO; importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; importInfo.handle = (VkExternalMemoryHandleTypeFlagBits)fd; VkMemoryAllocateInfo allocInfoB{}; allocInfoB.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfoB.allocationSize = memRequirements.size; // Must match Device A allocInfoB.memoryTypeIndex = selectedMemTypeB; // Same memory properties as Device A allocInfoB.pNext = &importInfo; VkDeviceMemory memB; vkAllocateMemory(deviceB, &allocInfoB, nullptr, &memB); vkBindBufferMemory(deviceB, bufferB, memB, 0); // bufferB matches bufferA exactly
Why This Is the Best Approach
- Spec-Compliant: Follows Vulkan's official guidelines for cross-device resource sharing, so it works reliably across drivers and platforms.
- Efficient: Uses native GPU memory handles instead of intermediate shared files, minimizing overhead.
- Low Complexity: Avoids hacky workarounds and leverages Vulkan's built-in memory management.
Just remember to manage the external handle's lifecycle properly—only close it after all devices have freed their associated VkDeviceMemory objects.
内容的提问来源于stack exchange,提问作者nikitablack

