Compartir a través de


Interoperabilidad de Direct3D 12

D3D12 se puede usar para escribir aplicaciones componentes.

Introducción a la interoperabilidad

D3D12 puede ser muy eficaz y permitir que las aplicaciones escriban código gráfico con eficiencia similar a la consola, pero no todas las aplicaciones necesitan reinventar la rueda y escribir toda la totalidad de su motor de representación desde cero. En algunos casos, otro componente o biblioteca ya lo ha hecho mejor, o en otros casos, el rendimiento de una parte del código no es tan crítico como su corrección y legibilidad.

En esta sección se tratan las siguientes técnicas de interoperabilidad:

  • D3D12 y D3D12, en el mismo dispositivo
  • D3D12 y D3D12, en diferentes dispositivos
  • D3D12 y cualquier combinación de D3D11, D3D10 o D2D, en el mismo dispositivo
  • D3D12 y cualquier combinación de D3D11, D3D10 o D2D, en diferentes dispositivos
  • D3D12 y GDI, o D3D12 y D3D11 y GDI

Motivos para usar la interoperabilidad

Hay varias razones por las que una aplicación querría la interoperabilidad D3D12 con otras API. He aquí algunos ejemplos:

  • Portabilidad incremental: quiere portar una aplicación completa de D3D10 o D3D11 a D3D12, al mismo tiempo que la tiene funcional en fases intermedias del proceso de portabilidad (para habilitar las pruebas y la depuración).
  • Código de caja negra: desea dejar una parte determinada de una aplicación tal cual mientras se migra el resto del código. Por ejemplo, puede que no sea necesario portar elementos de interfaz de usuario de un juego.
  • Componentes inmutables: es necesario usar componentes que no son propiedad de la aplicación, que no están escritos en el destino D3D12.
  • Un nuevo componente: no desea portar toda la aplicación, pero quiere usar un nuevo componente que se escribe mediante D3D12.

Hay cuatro técnicas principales para la interoperabilidad en D3D12:

  • Una aplicación puede optar por proporcionar una lista de comandos abierta a un componente, que registra algunos comandos de representación adicionales en un destino de representación ya enlazado. Esto equivale a proporcionar un contexto de dispositivo preparado a otro componente en D3D11 y es excelente para cosas como agregar texto o interfaz de usuario a un búfer de reserva ya enlazado.
  • Una aplicación puede optar por proporcionar una cola de comandos a un componente, junto con un recurso de destino deseado. Esto equivale a usar las API ClearState o DeviceContextState en D3D11 para proporcionar un contexto de dispositivo limpio a otro componente. Así funcionan los componentes como D2D.
  • Un componente puede optar por un modelo en el que genera una lista de comandos, potencialmente en paralelo, que la aplicación es responsable del envío más adelante. Se debe proporcionar al menos un recurso a través de los límites de los componentes. Esta misma técnica está disponible en D3D11 mediante contextos diferidos, aunque el rendimiento de D3D12 es más deseable.
  • Cada componente tiene sus propias colas o dispositivos, y la aplicación y los componentes deben compartir recursos e información de sincronización entre los límites del componente. Esto es similar al heredado ISurfaceQueuey el idXGIKeyedMutex más moderno.

Las diferencias entre estos escenarios es lo que se comparte exactamente entre los límites del componente. Se supone que el dispositivo se comparte, pero como básicamente es sin estado, no es realmente relevante. Los objetos clave son la lista de comandos, la cola de comandos, los objetos de sincronización y los recursos. Cada uno de ellos tiene sus propias complicaciones al compartirlas.

Uso compartido de una lista de comandos

El método más sencillo de interoperabilidad requiere compartir solo una lista de comandos con una parte del motor. Una vez completadas las operaciones de representación, la propiedad de la lista de comandos vuelve al autor de la llamada. La propiedad de la lista de comandos se puede rastrear a través de la pila. Puesto que las listas de comandos son de un solo subproceso, no hay forma de que una aplicación haga algo único o innovador mediante esta técnica.

Uso compartido de una cola de comandos

Probablemente la técnica más común para varios componentes que comparten un dispositivo en el mismo proceso.

Cuando la cola de comandos es la unidad de uso compartido, debe haber una llamada al componente para informarle de que todas las listas de comandos pendientes deben enviarse inmediatamente a la cola de comandos (y las colas de comandos internas deben sincronizarse). Esto equivale a la API de vaciado D3D11 y es la única manera en que la aplicación puede enviar sus propias listas de comandos o sincronizar primitivos.

Uso compartido de primitivos de sincronización

El patrón esperado para un componente que funciona en sus propios dispositivos o colas de comandos será aceptar un identificador ID3D12Fence o identificador compartido, y el par UINT64 al comenzar su trabajo, que esperará y, a continuación, un segundo identificador ID3D12Fence o identificador compartido, y el par UINT64 que señalizará cuando se complete todo el trabajo. Este patrón coincide con la implementación actual de IDXGIKeyedMutex y el diseño de sincronización del modelo de volteo DWM/DXGI.

Uso compartido de recursos

En gran medida, la parte más complicada de escribir una aplicación D3D12 que aprovecha varios componentes es cómo tratar con los recursos que se comparten entre límites de componentes. Esto se debe principalmente al concepto de estados de recursos. Aunque algunos aspectos del diseño del estado de recursos están diseñados para tratar con la sincronización dentro de la lista de comandos, otros tienen impacto entre listas de comandos, lo que afecta al diseño de recursos y a conjuntos válidos de operaciones o características de rendimiento para acceder a los datos de recursos.

Hay dos patrones de tratamiento con esta complicacion, ambos que implican esencialmente un contrato entre componentes.

  • El contrato se puede definir mediante el desarrollador de componentes y documentado. Esto podría ser tan sencillo como "el recurso debe estar en el estado predeterminado cuando se inicia el trabajo y se volverá a colocar en el estado predeterminado cuando se realice el trabajo" o podría tener reglas más complicadas para permitir cosas como compartir un búfer de profundidad sin forzar la resolución de profundidad intermedia.
  • La aplicación puede definir el contrato en tiempo de ejecución cuando el recurso se comparte entre los límites del componente. Consta de los mismos dos fragmentos de información: el estado en el que el recurso estará cuando el componente empiece a usarlo y el estado en el que el componente debe dejarlo cuando termine.

Elección de un modelo de interoperabilidad

Para la mayoría de las aplicaciones D3D12, compartir una cola de comandos es probablemente el modelo ideal. Permite la propiedad completa de la creación y el envío del trabajo, sin la sobrecarga de memoria adicional de tener colas redundantes y sin el impacto de rendimiento de tratar con los primitivos de sincronización de GPU.

Los primitivos de sincronización de uso compartido son necesarios una vez que los componentes necesitan tratar con diferentes propiedades de cola, como el tipo o la prioridad, o una vez que el uso compartido necesita abarcar los límites del proceso.

El uso compartido o la producción de listas de comandos no son ampliamente utilizados externamente por componentes de terceros, pero pueden usarse ampliamente en componentes que son internos de un motor de juego.

API de interoperabilidad

El tema Direct3D 11 en 12 le guía por el uso de gran parte de la superficie de API relacionada con los tipos de interoperación descritos en este tema.

Consulta también el método ID3D12Device::CreateSharedHandle , que puedes usar para compartir superficies entre las API de gráficos de Windows.