2022年5月4日

DirectX 与 Vulkan 互操作

在过去几周里,我一直致力于为 Evergine Studio 添加 Vulkan 和 DirectX12 可用后端。Evergine 自其首个正式版本 2021.11.17.2-preview 起就支持 Vulkan 和 DirectX12,但将它们添加到 Evergine Studio 的问题在于,它是一个 WPF 应用。WPF 是一种微软用户界面技术,使用 DirectX9 渲染其控件,因此如果您想在 WPF 中创建 Evergine 控件,它必须使用 DirectX 进行渲染。此外,Evergine Studio 的架构由两个应用组成:一个仅用于 UI 的 WPF 应用,以及另一个带有后端的渲染进程。这使我们能够仅重新加载或重启渲染进程,而无需关闭 WPF 应用。

要实现这一目标,您需要在 WPF 应用进程中使用 DirectX 图形上下文,并创建一个纹理共享资源,用于在您的 WPF 自定义控件中绘制。DirectX 共享资源是一种特殊的资源类型,允许您与其他 DirectX 图形上下文(甚至是其他进程)共享资源。

 

var renderTargetDescription = new Texture2DDescription

{

CpuAccessFlags = CpuAccessFlags.None,

Width = (int)width,

Height = (int)height,

Usage = Vortice.Direct3D11.ResourceUsage.Default,

Format = Vortice.DXGI.Format.B8G8R8A8_UNorm,

ArraySize = 1,

BindFlags = BindFlags.RenderTarget,

OptionFlags = ResourceOptionFlags.Shared,

MipLevels = 1,

SampleDescription = new SampleDescription(1, 0),

};

 

renderTarget = ((DX11GraphicsContext)dx11GraphicsContext).DXDevice.CreateTexture2D(renderTargetDescription);

 

获得 DirectX 共享纹理后,您需要获取其共享句柄,以便从另一个图形上下文引用。

 

var resource = renderTarget.QueryInterface<IDXGIResource>();

var sharedHandle = resource.SharedHandle;

 

现在,可以轻松地从其他 DirectX 11 或 12 图形上下文(第二个应用)使用此纹理,并按以下方式打开纹理。

 

var dxGraphicsContext = this.graphicsContext as DX11GraphicsContext;

var sharedNativeTexture = dxGraphicsContext.DXDevice.OpenSharedResource<ID3D11Texture2D>(surfaceHandle);

this.SharedTexture = DX11Texture.FromDirectXTexture(dxGraphicsContext, sharedNativeTexture);

这样,您就从第一个应用获取了共享纹理,并可以像使用普通纹理一样在您的应用中使用它。

另一方面,如果您想与 Vulkan 上下文共享纹理,则稍微复杂一些。我发现了 Nvidia 的 James Jones 和 Mathias Schott 撰写的一篇有趣文章,关于 如何使用 VK_NV_external_memory Vulkan 扩展 来打开 DirectX11 共享纹理。并在 Evergine 中编写了一个方法来使用它。

 

uint width = 1280;

uint height = 720;

 

var vkGraphicsContext = graphicsContext as VKGraphicsContext;

 

bool dedicateMemoryExtension = true; //extProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_NV

 

// 创建 Vulkan 导入图像

VkExternalMemoryImageCreateInfoNV extMemoryImageInfo = new VkExternalMemoryImageCreateInfoNV();

extMemoryImageInfo.sType = VkStructureType.VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_NV;

extMemoryImageInfo.handleTypes = VkExternalMemoryHandleTypeFlagsNV.VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_KMT_BIT_NV;

 

VkDedicatedAllocationImageCreateInfoNV dedicatedImageCreateInfo = new VkDedicatedAllocationImageCreateInfoNV();

dedicatedImageCreateInfo.sType = VkStructureType.VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_IMAGE_CREATE_INFO_NV;

dedicatedImageCreateInfo.dedicatedAllocation = false;

 

if (dedicateMemoryExtension)

{

extMemoryImageInfo.pNext = &dedicatedImageCreateInfo;

dedicatedImageCreateInfo.dedicatedAllocation = true;

}

 

VkImageCreateInfo imageCreateInfo = new VkImageCreateInfo();

imageCreateInfo.sType = VkStructureType.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;

imageCreateInfo.pNext = &extMemoryImageInfo;

 

imageCreateInfo.imageType = VkImageType.VK_IMAGE_TYPE_2D;

imageCreateInfo.format = VkFormat.VK_FORMAT_B8G8R8A8_UNORM;

imageCreateInfo.extent.width = width;

imageCreateInfo.extent.height = height;

imageCreateInfo.extent.depth = 1;

imageCreateInfo.mipLevels = 1;

imageCreateInfo.arrayLayers = 1;

imageCreateInfo.samples = VkSampleCountFlags.VK_SAMPLE_COUNT_1_BIT;

imageCreateInfo.tiling = VkImageTiling.VK_IMAGE_TILING_OPTIMAL;

imageCreateInfo.usage = VkImageUsageFlags.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

imageCreateInfo.flags = VkImageCreateFlags.None;

imageCreateInfo.sharingMode = VkSharingMode.VK_SHARING_MODE_EXCLUSIVE;

 

VkImage image;

VkResult result = VulkanNative.vkCreateImage(vkGraphicsContext.VkDevice, &imageCreateInfo, null, &image);

VKHelpers.CheckErrors(vkGraphicsContext, result);

 

// 将内存绑定到 Vulkan 图像

VkMemoryRequirements memoryRequirements;

VulkanNative.vkGetImageMemoryRequirements(vkGraphicsContext.VkDevice, image, &memoryRequirements);

 

var memoryType = VKHelpers.FindMemoryType(vkGraphicsContext, memoryRequirements.memoryTypeBits, VkMemoryPropertyFlags.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

 

VkImportMemoryWin32HandleInfoNV importMemInfoNV = new VkImportMemoryWin32HandleInfoNV();

importMemInfoNV.sType = VkStructureType.VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_NV;

importMemInfoNV.pNext = null;

importMemInfoNV.handleType = VkExternalMemoryHandleTypeFlagsNV.VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_KMT_BIT_NV;

importMemInfoNV.handle = surfaceHandle;

 

VkMemoryAllocateInfo allocInfo = new VkMemoryAllocateInfo();

allocInfo.sType = VkStructureType.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;

allocInfo.pNext = &importMemInfoNV;

allocInfo.allocationSize = memoryRequirements.size;

 

if (memoryType == -1)

{

vkGraphicsContext.ValidationLayer?.Notify(“Vulkan”, “No suitable memory type.”);

}

 

allocInfo.memoryTypeIndex = (uint)memoryType;

 

if (dedicateMemoryExtension)

{

 

VkDedicatedAllocationMemoryAllocateInfoNV dedicatedAllocationInfo = new VkDedicatedAllocationMemoryAllocateInfoNV();

dedicatedAllocationInfo.sType = VkStructureType.VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_MEMORY_ALLOCATE_INFO_NV;

dedicatedAllocationInfo.image = image;

 

importMemInfoNV.pNext = &dedicatedAllocationInfo;

}

 

VkDeviceMemory deviceMemory;

result = VulkanNative.vkAllocateMemory(vkGraphicsContext.VkDevice, &allocInfo, null, &deviceMemory);

VKHelpers.CheckErrors(vkGraphicsContext, result);

 

result = VulkanNative.vkBindImageMemory(vkGraphicsContext.VkDevice, image, deviceMemory, 0);

VKHelpers.CheckErrors(vkGraphicsContext, result);

 

// 创建帧缓冲

TextureDescription textureDescription = new TextureDescription()

{

CpuAccess = ResourceCpuAccess.None,

Width = width,

Height = height,

Depth = 1,

Usage = Evergine.Common.Graphics.ResourceUsage.Default,

Format = PixelFormat.B8G8R8A8_UNorm,

ArraySize = 1,

Faces = 1,

MipLevels = 1,

SampleCount = TextureSampleCount.None,

Flags = TextureFlags.RenderTarget,

};

var SharedTexture = VKTexture.FromVulkanImage(vkGraphicsContext, ref textureDescription, image);

 

与 DirectX API 的主要区别在于,你需要指定共享纹理的宽度和高度来创建 Vulkan 纹理,其中数据由 DirectX 共享纹理绑定。因此,共享纹理句柄和大小是支持不同渲染技术应用所必需的。

我使用 Evergine 创建了一个示例来验证此 Vulkan 扩展。示例中有两个应用:一个 DX11 应用,用于创建共享纹理并在控制台中写入其纹理句柄(注意:两个应用中的尺寸均固定为 1280×720);Vulkan 应用是渲染器,它使用 VK_NV_external_memory 扩展打开 DirectX 共享纹理,并绘制一个旋转的红色茶壶。Vulkan 应用接收的第一个参数是十六进制的共享纹理句柄。

 

源代码:https://github.com/Jorgemagic/DirectX-Vulkan-interop

测试示例

> ./DX11app.exe

Dx11 共享纹理句柄:0x00000000c0001942

> ./Vulkan.exe 0x00000000c0001942

结果如下:

补充说明

此时,你可以让 DX11 应用在创建共享纹理后启动 Vulkan 应用以自动打开渲染器,这对用户来说将是一个静默过程。

参考文献

Jorge Canton
作者
Jorge Cantón
Plain Concepts Research