
DirectXとVulkanの相互運用
ここ数週間、Evergine StudioにVulkanとDirectX12の利用可能なバックエンドを追加する作業に取り組んできました。Evergineは最初の公式リリース2021.11.17.2-previewからVulkanとDirectX12をサポートしていますが、Evergine Studioにこれらを追加する際の問題は、Evergine StudioがWPFアプリケーションであることです。WPFは、そのコントロールのレンダリングにDirectX9を使用するMicrosoftのユーザーインターフェース技術であるため、WPFでEvergineコントロールを作成する場合は、DirectXでレンダリングする必要があります。さらに、Evergine Studioのアーキテクチャは2つのアプリケーションで構成されており、1つはUI専用のWPFアプリ、もう1つはバックエンドを持つレンダリングプロセスです。これにより、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グラフィックスコンテキスト(2番目のアプリ)から使用し、次の方法でテクスチャを開くことが簡単になります。
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氏による、DirectX11共有テクスチャを開くためのVK_NV_external_memory Vulkan拡張の使用方法に関する興味深い投稿を見つけました。そして、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との主な違いは、DirectX共有テクスチャによってデータがバインドされるVulkanテクスチャを作成するために、共有テクスチャの幅と高さを指定する必要がある点です。そのため、異なるレンダリング技術のアプリをサポートするには、共有テクスチャハンドルとサイズが必要になります。
このVulkan拡張を確認するために、Evergineを使用したサンプルを作成しました。サンプルには2つのアプリがあり、DX11アプリは共有テクスチャを作成し、そのテクスチャハンドルをコンソールに書き込みます(注:サイズは両方のアプリで1280×720に固定されています)。Vulkanアプリは、VK_NV_external_memory拡張を使用してDirectX共有テクスチャを開き、回転する赤いティーポットを描画するレンダラーです。Vulkanアプリは、最初の引数として16進数の共有テクスチャハンドルを受け取ります。
ソースコード: https://github.com/Jorgemagic/DirectX-Vulkan-interop
サンプルをテストする
> ./DX11app.exe
Dx11 共有テクスチャハンドル: 0x00000000c0001942
> ./Vulkan.exe 0x00000000c0001942
そして結果:

補足事項
この時点で、DX11アプリが共有テクスチャを作成した後にVulkanアプリを起動して自動的にレンダリングを開くようにすれば、ユーザーにとってはサイレントなプロセスになります。
