很久以前研究过 用NV_DX_interop扩展让D3D和OpenGL共享资源 , OpenGL在当初设计的时候电脑和操作系统还是个相对比较简单的东西,因此OpenGL API设计没有考虑到现在计算机架构的一些特性,比如多核编程和多显卡并发。最近几年出来个Vulkan来接OpenGL的班,所以继续走起研究下D3D11和Vulkan的共享。
Vulkan主程序用了vulkan tutorial下面的一个教程Combined image sampler , 这段代码演示了把一个纹理vkImage贴到一个3D的四边形面上.
接下来是尝试打通一个D3D11 Texture2D和这个vkImage存放图像数据的buffer, 这样我可以通过修改Texture2D的内容来让显示的vkImage的内容也发生变化。D3D11 Texture2D和vkImage共享的代码流程主要参考自这个github VulkanSdkDemos/BindImageMemory2.cpp at d3d11-image-interop · roman380/VulkanSdkDemos · GitHub
要让vulkan和D3d11共享资源,大致需要这么五步:
#define GLFW_INCLUDE_VULKAN
#define VK_USE_PLATFORM_WIN32_KHR
#include
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME
const std::vector deviceExtensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,VK_KHR_BIND_MEMORY_2_EXTENSION_NAME
};...createInfo.enabledExtensionCount = static_cast(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {throw std::runtime_error("failed to create logical device!");
}
/* add for DX11/vulkan interop*/VkPhysicalDeviceExternalImageFormatInfo PhysicalDeviceExternalImageFormatInfo = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO };PhysicalDeviceExternalImageFormatInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT;VkPhysicalDeviceImageFormatInfo2 PhysicalDeviceImageFormatInfo2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2 };PhysicalDeviceImageFormatInfo2.pNext = &PhysicalDeviceExternalImageFormatInfo;PhysicalDeviceImageFormatInfo2.format = VK_FORMAT_R8G8B8A8_UNORM;PhysicalDeviceImageFormatInfo2.type = VK_IMAGE_TYPE_2D;PhysicalDeviceImageFormatInfo2.tiling = VK_IMAGE_TILING_OPTIMAL;PhysicalDeviceImageFormatInfo2.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;VkExternalImageFormatProperties ExternalImageFormatProperties = { VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES };VkImageFormatProperties2 ImageFormatProperties2 = { VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2 };ImageFormatProperties2.pNext = &ExternalImageFormatProperties;if (vkGetPhysicalDeviceImageFormatProperties2(physicalDevice, &PhysicalDeviceImageFormatInfo2, &ImageFormatProperties2) != VK_SUCCESS) {throw std::runtime_error("failed to vkGetPhysicalDeviceImageFormatProperties2!");}assert(ExternalImageFormatProperties.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT);assert(ExternalImageFormatProperties.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT);assert(ExternalImageFormatProperties.externalMemoryProperties.compatibleHandleTypes & VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT);/*******************************/VkExternalMemoryImageCreateInfo ExternalMemoryImageCreateInfo = { VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO };ExternalMemoryImageCreateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT;VkImageCreateInfo imageInfo{};imageInfo.pNext = &ExternalMemoryImageCreateInfo;imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;imageInfo.imageType = VK_IMAGE_TYPE_2D;imageInfo.extent.width = width;imageInfo.extent.height = height;imageInfo.extent.depth = 1;imageInfo.mipLevels = 1;imageInfo.arrayLayers = 1;imageInfo.format = format;imageInfo.tiling = tiling;imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;imageInfo.usage = usage;imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {throw std::runtime_error("failed to create image!");}
CD3D11_TEXTURE2D_DESC TextureDesc(DXGI_FORMAT_R8G8B8A8_UNORM, imageInfo.extent.width, imageInfo.extent.height, 1, 1);
#ifdef USE_KEYEDMUTEXTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
#elseTextureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
#endifVERIFY(SUCCEEDED(D3d11Device->CreateTexture2D(&TextureDesc, &Data, &Texture)));
VERIFY(SUCCEEDED(Texture->QueryInterface(&DxgiResource1)));VERIFY(SUCCEEDED(DxgiResource1->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &Handle)));VkMemoryDedicatedAllocateInfo MemoryDedicatedAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO };MemoryDedicatedAllocateInfo.image = image;VkImportMemoryWin32HandleInfoKHR ImportMemoryWin32HandleInfo = { VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR };ImportMemoryWin32HandleInfo.pNext = &MemoryDedicatedAllocateInfo;ImportMemoryWin32HandleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT;ImportMemoryWin32HandleInfo.handle = Handle;VkMemoryAllocateInfo MemoryAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };MemoryAllocateInfo.pNext = &ImportMemoryWin32HandleInfo;MemoryAllocateInfo.allocationSize = MemoryRequirements.size;// WARN: MemoryAllocateInfo.memoryTypeIndex remains zeroVkDeviceMemory ImageMemory;VERIFY(vkAllocateMemory(device, &MemoryAllocateInfo, nullptr, &ImageMemory) == VK_SUCCESS);VkBindImageMemoryInfo BindImageMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO };BindImageMemoryInfo.image = image;BindImageMemoryInfo.memory = ImageMemory;VERIFY(vkBindImageMemory2(device, 1, &BindImageMemoryInfo) == VK_SUCCESS);
这样这个vkImage里面存放像素的那块内存其实就是D3D11Texture2D里存放像素的那个内存区了。接下来只要用D3D11的API对这个Texture2D的像素做改动,vkImage的像素也会相应变化。
代码里有个编译参数
#define USE_KEYEDMUTEX
分别对应创建D3D11 texture2D时候的MiscFlags包含D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX或者D3D11_RESOURCE_MISC_SHARED的情况
主要区别是
总的来说,粗粗的学习了一下vkImage部分,没有发现什么官方建议的API用来防止D3D11和Vulkan之间同时访问共享资源导致数据完整性错误的机制。所以真正设计程序的时候,可能需要借用windows自己的mutex机制来保证这块。
PS: 这段原始参考代码是有坑的, 简单的说代码的原作者在注释里提到了一个问题,就是这段代码如果在vulkan初始化的时候打开了validation layer, 在后面获取vkImage所需存放像素的buffer大小 vkGetImageMemoryRequirements2()的时候就会出现memory access violation的错误。这段代码是他为了复现问题专门写的,他当时是在AMD的显卡上发现的,我这边用Intel显卡也有一样的问题。我也没搞懂导致这个问题的原因是什么,可能是vulkan库里面的某段代码或者显卡驱动里有错误吧 (当时没看懂代码里作者的注释,后来费了好大劲调试找问题无果,最后发现代码用release模式就能跑,最后定位在了debug模式编译时打开了validation layer) 这个还希望有明白的大佬能指点一下...
最后验证一下,在主循环里,每循环30帧就给Texture2D对象刷一个纯色(红,黄,蓝),这段代码因为没有D3D11的显示部分,而CopyResource()是一个异步操作,函数返回的时候相关的copy命令序列并不一定会进入GPU的command queue并且运行完毕,所以窗口里的贴图的颜色变化会有很明显的延迟 (有些D3D11相关的函数会自动做Flush GPU Command Queue的操作,比如Map()/UnMap(), Present()之类)。因此需要在每次做完CopyResource()以后调用Flush()一下,把拷贝命令刷进GPU的Command Queue里,这样延迟就不是很明显了。
void mainLoop() {unsigned long frame_counter = 0;while (!glfwWindowShouldClose(window)) {glfwPollEvents();drawFrame();
#if 1frame_counter++;if ((frame_counter % 90 )==0){
#ifdef USE_KEYEDMUTEXTexture_Mutex->AcquireSync(1, INFINITE);Texture_R_Mutex->AcquireSync(1, INFINITE);
#endif//texture刷红色D3d11DeviceContext->CopyResource(Texture, Texture_R);std::cout << "D3d11DeviceContext->CopyResource(Texture, Texture_R); : " << frame_counter << std::endl;D3d11DeviceContext->Flush();
#ifdef USE_KEYEDMUTEXTexture_Mutex->ReleaseSync(1);Texture_R_Mutex->ReleaseSync(1);
#endif}else if ((frame_counter % 60) == 0){
#ifdef USE_KEYEDMUTEXTexture_Mutex->AcquireSync(1, INFINITE);Texture_G_Mutex->AcquireSync(1, INFINITE);
#endif//texture刷绿色D3d11DeviceContext->CopyResource(Texture, Texture_G);std::cout << "D3d11DeviceContext->CopyResource(Texture, Texture_G); : " << frame_counter << std::endl;D3d11DeviceContext->Flush();
#ifdef USE_KEYEDMUTEXTexture_Mutex->ReleaseSync(1);Texture_G_Mutex->ReleaseSync(1);
#endif}else if ((frame_counter % 30) == 0){
#ifdef USE_KEYEDMUTEXTexture_Mutex->AcquireSync(1, INFINITE);Texture_B_Mutex->AcquireSync(1, INFINITE);
#endif//texture刷蓝色D3d11DeviceContext->CopyResource(Texture, Texture_B);std::cout << "D3d11DeviceContext->CopyResource(Texture, Texture_B); : " << frame_counter << std::endl;D3d11DeviceContext->Flush();
#ifdef USE_KEYEDMUTEXTexture_Mutex->ReleaseSync(1);Texture_G_Mutex->ReleaseSync(1);
#endif}
#endif}vkDeviceWaitIdle(device);}
运行一下, 一切正常,搞定收工
最后还是老规矩,源码奉上,仅供参考
https://gitee.com/tisandman/d3d11_vulkan_sharing