This repo demonstrates interop between Vulkan and DirectX via embedding the former into WinUI 3 and WPF using VK_KHR_external_memory_win32 extension. It's basically follows great Alexander Overvoorde's tutorial for creating resources, but excludes swapchain infrastructure and creates a framebuffer using a shared Direct3D 11 texture.
Whether you are using your own or third party abstractions - it should be easy to adapt the code for use.
The example is written naively, step by step - see VulkanInterop.Initialize and Window code behind.
Silk.NET - bindings used for DirectX and Vulkan calls.
Damaged Helmet - model used as an example. SharpGLTF - loader used to read the model.
vulkan-interop-directx.mp4
We need to
In case of WinUI - create a DXGI swapchain, get the texture and set the swapchain to the WinUI SwapChainPanel.
var swapchainDescription = new SwapChainDesc1
{
...
Width = width,
Height = height,
Format = Format.FormatR8G8B8A8Unorm,
SwapEffect = SwapEffect.FlipSequential,
SampleDesc = new SampleDesc(1u, 0u),
BufferUsage = DXGI.UsageBackBuffer
};
ThrowHResult(dxgiFactory.CreateSwapChainForComposition
(
dxgiDevice,
swapchainDescription,
default(ComPtr<IDXGIOutput>),
ref swapchain
));
backbufferTexture = swapchain.GetBuffer<ID3D11Texture2D>(0u);
target.As<ISwapChainPanelNative>().SetSwapChain(swapchain);In case of WPF - create a D3D9 texture and get the surface that will be used with WPF D3DImage.
void* d3d9shared = null;
ThrowHResult(d3d9device.CreateTexture
(
width,
height,
1u,
D3D9.UsageRendertarget,
Silk.NET.Direct3D9.Format.X8R8G8B8,
Pool.Default,
ref backbufferTexture,
ref d3d9shared
));
ThrowHResult(backbufferTexture.GetSurfaceLevel(0u, ref surface));In case of WinUI - this is regular D3D11 texture.
var renderTargetDescription = new Texture2DDesc
{
...
Width = width,
Height = height,
BindFlags = (uint)BindFlag.RenderTarget,
MiscFlags = (uint)ResourceMiscFlag.Shared
};
ThrowHResult(d3d11device.CreateTexture2D(renderTargetDescription, null, ref renderTargetTexture));With WinUI we also need to query both back buffer and render target textures to D3D11 resources for future copy operations.
backbufferResource = backbufferTexture.QueryInterface<ID3D11Resource>();
renderTargetResource = renderTargetTexture.QueryInterface<ID3D11Resource>();In case of WPF, we get the texture using shared handle of the D3D9 texture we created earlier.
renderTargetTexture = d3d11device.OpenSharedResource<ID3D11Texture2D>(d3d9shared);void* handle;
var resource = renderTargetTexture.QueryInterface<IDXGIResource>();
ThrowHResult(resource.GetSharedHandle(&handle));
resource.Dispose();
renderTargetSharedHandle = (nint)handle;Then create the view and framebuffer - see VulkanInterop.CreateImageViews.
var externalMemoryImageInfo = new ExternalMemoryImageCreateInfo
(
handleTypes: ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit
);
var imageInfo = new ImageCreateInfo
(
...
format: targetFormat,
usage: ImageUsageFlags.ColorAttachmentBit,
pNext: &externalMemoryImageInfo
);
var importMemoryInfo = new ImportMemoryWin32HandleInfoKHR
(
handleType: ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
handle: renderTargetSharedHandle
);
vk.CreateImage(device, imageInfo, null, out directImage).Check();Note that D3D9
X8R8G8B8texture format map to VulkanB8G8R8A8Unorm
With WinUI we need to call Direct3D 11 to copy data from the render target to the back buffer and present it.
// *rendering*
d3d11context.CopyResource(backbufferResource, renderTargetResource);
ThrowHResult(swapchain.Present(0u, (uint)SwapChainFlag.None));With WPF we only need to set the D3D9 surface to the D3DImage, because the back buffer already contains the rendered image.
d3dImage.Lock();
// *rendering*
d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (nint)d3d9surface.Handle);
d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
d3dImage.Unlock();- .NET 8
- Windows SDK 10.0.22621
The example is dotnet build ready.