mayo 04, 2022

DirectX y Vulkan Interop

En las últimas semanas, he estado trabajando en la adición de Vulkan y DirectX12 backends disponibles para Evergine Studio. Evergine soporta Vulkan y DirectX12 desde su primera versión oficial 2021.11.17.2-preview, pero el problema de añadirlos a Evergine Studio es que es una aplicación WPF.

WPF es una tecnología de interfaz de usuario de Microsoft que utiliza DirectX9 para renderizar sus controles, así que si quieres crear un control de Evergine en WPF debe renderizarse con DirectX.  Además, la arquitectura de Evergine Studio está compuesta por dos aplicaciones, una aplicación WPF solo para propósitos de UI y otro proceso de renderizado con el backend. Esto nos permite recargar o reiniciar solo el proceso de renderizado sin cerrar la aplicación WPF.

Para lograr este objetivo, es necesario utilizar el contexto de gráficos DirectX en el proceso de la aplicación WPF y crear un recurso compartido de textura que utilice para dibujar en su control personalizado WPF. Los recursos compartidos DirectX son un tipo de recurso especial que permite compartir un recurso con otro contexto gráfico DirectX, incluso con otros procesos.

 

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);

 

Una vez que tienes la textura compartida de DirectX, necesitas obtener su handle compartido para referenciarlo desde otro contexto gráfico.

 

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

var sharedHandle = resource.SharedHandle;

 

Ahora es fácil utilizar esta textura desde otros contextos gráficos DirectX 11 o 12 (segunda app) y abrir la textura de la siguiente manera.

 

var dxGraphicsContext = this.graphicsContext as DX11GraphicsContext;

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

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

So, you obtain the shared texture from the first app, and you can use it as a common texture in your app.

Así, obtienes la textura compartida de la primera app, y puedes usarla como textura común en tu app.

Por otro lado, si quieres compartir la textura con un contexto Vulkan es un poco más difícil. He encontrado un interesante post de James Jones y Mathias Schott de Nvidia sobre cómo usar la extensión VK_NV_external_memory Vulkan para abrir una textura compartida de DirectX11. Y he escrito un método en Evergine para hacer uso de ella.

 

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 principal diferencia con la API de DirectX es que es necesario especificar la anchura y la altura de la textura compartida para crear una textura de Vulkan en la que los datos están vinculados a la textura compartida de DirectX. Por lo tanto, el mango de la textura compartida y el tamaño son necesarios para apoyar a las aplicaciones de las diferentes tecnologías de renderizado.

He creado un ejemplo usando Evergine para comprobar esta extensión de Vulkan. En el ejemplo, hay dos aplicaciones, una aplicación DX11 que crea una textura compartida y escribe su manejador de textura en la consola (Nota. el tamaño se fija en 1280×720 en ambas aplicaciones). La app Vulkan es el render que abre la textura compartida de DirectX usando la extensión VK_NV_external_memory y dibuja una tetera roja girando. La aplicación Vulkan recibe como primer argumento el handle de la textura compartida en hexadecimal.

 

Código fuente: https://github.com/Jorgemagic/DirectX-Vulkan-interop

Testea el ejemplo

> ./DX11app.exe

Dx11 Shared Texture handle: 0x00000000c0001942

> ./Vulkan.exe 0x00000000c0001942

Y el resultado:

Comentarios adicionales

En este punto, puedes hacer que la aplicación DX11 lance la aplicación Vulkan después de crear la textura compartida para abrir automáticamente el render, y eso será un proceso silencioso para los usuarios.

Referencias

Jorge Canton
Author
Jorge Cantón
Plain Concepts Research
Categories