Aplicaciones Direct2D multiproceso

Si desarrollas aplicaciones de Direct2D , es posible que tengas que acceder a los recursos de Direct2D desde más de un subproceso. En otros casos, es posible que desee usar varios subprocesos para obtener un mejor rendimiento o una mejor capacidad de respuesta (como usar un subproceso para la visualización de pantalla y un subproceso independiente para la representación sin conexión).

En este tema se describen los procedimientos recomendados para desarrollar aplicaciones Direct2D multiproceso con poca o ninguna representación de Direct3D . Los defectos de software causados por problemas de simultaneidad pueden ser difíciles de rastrear y resulta útil planear la directiva de multithreading y seguir los procedimientos recomendados que se describen aquí.

Nota

Si accedes a dos recursos de Direct2D creados a partir de dos generadores de Direct2D uniprocesos diferentes, no causa conflictos de acceso, siempre y cuando los contextos de dispositivo y dispositivos de Direct3D subyacentes también sean distintos. Cuando se habla de "acceder a los recursos de Direct2D" en este artículo, realmente significa "acceder a los recursos de Direct2D creados desde el mismo dispositivo Direct2D", a menos que se indique lo contrario.

Desarrollo de aplicaciones de Thread-Safe que llaman solo a las API de Direct2D

Puedes crear una instancia de fábrica de Direct2D multiproceso. Puedes usar y compartir un generador multiproceso y todos sus recursos desde más de un subproceso, pero los accesos a esos recursos (a través de llamadas de Direct2D) se serializan mediante Direct2D, por lo que no se producen conflictos de acceso. Si la aplicación llama solo a las API de Direct2D, direct2D realiza automáticamente esta protección en un nivel granular con una sobrecarga mínima. El código para crear un generador multiproceso aquí.

ID2D1Factory* m_D2DFactory;

// Create a Direct2D factory.
HRESULT hr = D2D1CreateFactory(
    D2D1_FACTORY_TYPE_MULTI_THREADED,
    &m_D2DFactory
);

La imagen aquí muestra cómo Direct2D serializa dos subprocesos que realizan llamadas con solo la API de Direct2D.

diagrama de dos subprocesos serializados.

Desarrollar Thread-Safe aplicaciones direct2D con llamadas de Direct3D o DXGI mínimas

Es más que a menudo que una aplicación direct2D también realiza algunas llamadas direct3D o DXGI. Por ejemplo, un subproceso de presentación dibujará en Direct2D y, a continuación, se presentará mediante una cadena de intercambio DXGI.

En este caso, garantizar la seguridad de los subprocesos es más complicado: algunas llamadas direct2D acceden indirectamente a los recursos de Direct3D subyacentes, a los que otro subproceso puede acceder simultáneamente que llama a Direct3D o DXGI. Puesto que esas llamadas de Direct3D o DXGI no tienen conocimiento y control de Direct2D, debes crear una fábrica de Direct2D multiproceso, pero debes hacerlo de forma mor para evitar conflictos de acceso.

En el diagrama siguiente se muestra un conflicto de acceso a recursos de Direct3D debido a que el subproceso T0 accede indirectamente a un recurso a través de una llamada de Direct2D y T2 que accede al mismo recurso directamente a través de una llamada a Direct3D o DXGI.

Nota

La protección de subprocesos que proporciona Direct2D (el bloqueo azul en esta imagen) no ayuda en este caso.

 

diagrama de protección de subprocesos.

Para evitar conflictos de acceso a recursos aquí, te recomendamos que adquieras explícitamente el bloqueo que Usa Direct2D para la sincronización de acceso interno y aplica ese bloqueo cuando un subproceso necesite realizar llamadas direct3D o DXGI que puedan provocar conflictos de acceso, como se muestra aquí. En concreto, debe tener especial cuidado con el código que usa excepciones o un sistema de salida anticipado en función de los códigos de retorno HRESULT. Por este motivo, se recomienda usar un patrón RAII (inicialización de adquisición de recursos) para llamar a los métodos Enter y Leave .

Nota

Es importante emparejar las llamadas a los métodos Enter y Leave ; de lo contrario, la aplicación puede interbloquear.

 

El código aquí muestra un ejemplo de cuándo bloquear y, a continuación, y desbloquear en torno a las llamadas de Direct3D o DXGI.

void MyApp::DrawFromThread2()
{
    // We are accessing Direct3D resources directly without Direct2D's knowledge, so we
    // must manually acquire and apply the Direct2D factory lock.
    ID2D1Multithread* m_D2DMultithread;
    m_D2DFactory->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread));
    m_D2DMultithread->Enter();
    
    // Now it is safe to make Direct3D/DXGI calls, such as IDXGISwapChain::Present
    MakeDirect3DCalls();

    // It is absolutely critical that the factory lock be released upon
    // exiting this function, or else any consequent Direct2D calls will be blocked.
    m_D2DMultithread->Leave();
}

Nota

Algunas llamadas de Direct3D o DXGI (en particular , IDXGISwapChain::P resent) pueden adquirir bloqueos o desencadenar devoluciones de llamada en el código de la función o el método de llamada. Debe tener en cuenta esto y asegurarse de que este comportamiento no cause interbloqueos. Para obtener más información, consulta el tema información general de DXGI .

 

Diagrama de bloqueo de subprocesos direct2d y direct3d.

Cuando usas los métodos Enter y Leave , las llamadas están protegidas por el Direct2D automático y el bloqueo explícito, por lo que la aplicación no alcanza el conflicto de acceso.

Hay otros enfoques para solucionar este problema. Sin embargo, te recomendamos que protejas explícitamente las llamadas de Direct3D o DXGI con el bloqueo Direct2D porque normalmente proporciona un mejor rendimiento, ya que protege la simultaneidad en un nivel mucho más fino y con una sobrecarga menor bajo la cubierta de Direct2D.

Garantizar la atomicidad de las operaciones con estado

Aunque las características de seguridad de subprocesos de DirectX pueden ayudar a garantizar que no se realicen simultáneamente dos llamadas API individuales, también debe asegurarse de que los subprocesos que realizan llamadas API con estado no interfieren entre sí. A continuación se muestra un ejemplo:

  1. Hay dos líneas de texto que desea representar en pantalla (por subproceso 0) y fuera de pantalla (por subproceso 1): la línea 1 es "A es mayor" y la línea 2 es "que B", ambas se dibujarán con un pincel negro sólido.
  2. El subproceso 1 dibuja la primera línea de texto.
  3. El subproceso 0 reacciona a una entrada del usuario, actualiza ambas líneas de texto a "B es menor" y "A", respectivamente, y cambia el color del pincel a rojo sólido para su propio dibujo;
  4. El subproceso 1 continúa dibujando la segunda línea de texto, que ahora es "que A", con el pincel de color rojo;
  5. Por último, obtenemos dos líneas de texto en el destino de dibujo fuera de pantalla: "A es mayor" en negro y "que A" en rojo.

un diagrama de subprocesos en pantalla activados y desactivados.

En la fila superior, el subproceso 0 dibuja con cadenas de texto actuales y el pincel negro actual. El subproceso 1 solo finaliza el dibujo fuera de pantalla en la mitad superior.

En la fila central, el subproceso 0 responde a la interacción del usuario, actualiza las cadenas de texto y el pincel y, a continuación, actualiza la pantalla. En este momento, el subproceso 1 está bloqueado. En la fila inferior, la representación final fuera de pantalla después de que subproceso 1 reanude el dibujo de la mitad inferior con un pincel modificado y una cadena de texto modificada.

Para solucionar este problema, se recomienda tener un contexto independiente para cada subproceso, de modo que:

  • Debe crear una copia del contexto del dispositivo para que los recursos mutables (es decir, los recursos que pueden variar durante la presentación o la impresión, como el contenido del texto o el pincel de color sólido en el ejemplo) no cambien al representar. En este ejemplo, debe mantener una copia de esas dos líneas de texto y el pincel de color antes de dibujar. Al hacerlo, se garantiza que cada subproceso tiene contenido completo y coherente para dibujar y presentar.
  • Debe compartir recursos de gran peso (como mapas de bits y gráficos de efectos complejos) que se inicializan una vez y, a continuación, nunca se modifican entre subprocesos para aumentar el rendimiento.
  • Puede compartir recursos ligeros (como pinceles de color sólido y formatos de texto) que se inicializan una vez y, a continuación, nunca se modifican entre subprocesos o no.

Resumen

Al desarrollar aplicaciones direct2D multiproceso, debes crear una fábrica de Direct2D multiproceso y, a continuación, derivar todos los recursos de Direct2D de esa fábrica. Si un subproceso realizará llamadas a Direct3D o DXGI, también debes adquirir explícitamente el bloqueo de Direct2D para proteger esas llamadas de Direct3D o DXGI. Además, debe garantizar la integridad del contexto al tener una copia de los recursos mutables para cada subproceso.