
Interopérabilité DirectX et Vulkan
Ces dernières semaines, j’ai travaillé à l’ajout des backends Vulkan et DirectX12 dans Evergine Studio. Evergine prend en charge Vulkan et DirectX12 depuis sa première version officielle, 2021.11.17.2-preview, mais leur ajout à Evergine Studio pose un problème : il s’agit d’une application WPF. WPF est une technologie d’interface utilisateur de Microsoft qui utilise DirectX9 pour le rendu de ses contrôles ; si vous voulez créer un contrôle Evergine dans WPF, son rendu doit donc se faire avec DirectX. De plus, l’architecture d’Evergine Studio se compose de deux applications : une application WPF dédiée uniquement à l’interface, et un autre processus de rendu avec le backend. Cela nous permet de recharger ou de redémarrer uniquement le processus de rendu sans fermer l’application WPF.

Pour y parvenir, vous devez utiliser le contexte graphique DirectX dans le processus de l’application WPF et créer une ressource partagée de type texture, utilisée pour dessiner dans votre contrôle WPF personnalisé. Les ressources partagées DirectX sont un type de ressource particulier qui permet de partager une ressource avec un autre contexte graphique DirectX, y compris dans d’autres processus.
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);
Une fois la texture partagée DirectX créée, vous devez récupérer son handle partagé pour la référencer depuis un autre contexte graphique.
var resource = renderTarget.QueryInterface<IDXGIResource>();
var sharedHandle = resource.SharedHandle;
Il est alors facile d’utiliser cette texture depuis d’autres contextes graphiques DirectX 11 ou 12 (seconde application) et de l’ouvrir de la manière suivante.
var dxGraphicsContext = this.graphicsContext as DX11GraphicsContext;
var sharedNativeTexture = dxGraphicsContext.DXDevice.OpenSharedResource<ID3D11Texture2D>(surfaceHandle);
this.SharedTexture = DX11Texture.FromDirectXTexture(dxGraphicsContext, sharedNativeTexture);
Vous obtenez ainsi la texture partagée par la première application et pouvez l’utiliser comme une texture ordinaire dans votre application.
En revanche, partager la texture avec un contexte Vulkan est un peu plus compliqué. J’ai trouvé un article intéressant de James Jones et Mathias Schott, de Nvidia, sur l’utilisation de l’extension Vulkan VK_NV_external_memory pour ouvrir une texture partagée DirectX11. J’ai donc écrit une méthode dans Evergine pour l’utiliser.
uint width = 1280;
uint height = 720;
var vkGraphicsContext = graphicsContext as VKGraphicsContext;
bool dedicateMemoryExtension = true; //extProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_NV
// Creating the Vulkan Import Image
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);
// Binding Memory to the Vulkan Image
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);
// Create Framebuffer
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);
La principale différence avec l’API DirectX est que vous devez indiquer la largeur et la hauteur de la texture partagée pour créer une texture Vulkan dont les données sont liées à la texture partagée DirectX. Le handle et la taille de la texture partagée sont donc nécessaires pour faire cohabiter des applications utilisant différentes technologies de rendu.
J’ai créé un exemple avec Evergine pour tester cette extension Vulkan. Il comprend deux applications : une application DX11 qui crée une texture partagée et écrit son handle dans la console (remarque : la taille est fixée à 1280×720 dans les deux applications), et une application Vulkan chargée du rendu, qui ouvre la texture partagée DirectX à l’aide de l’extension VK_NV_external_memory et dessine une théière rouge en rotation. L’application Vulkan reçoit comme premier argument le handle de la texture partagée en hexadécimal.
Code source : https://github.com/Jorgemagic/DirectX-Vulkan-interop
Tester l’exemple
> ./DX11app.exe
Dx11 Shared Texture handle: 0x00000000c0001942
> ./Vulkan.exe 0x00000000c0001942
Et voici le résultat :

Remarques complémentaires
At this point, you can do the DX11 app launch the Vulkan App after creating the shared texture for automatically opening the render, and that will be a silent process for the users.
