Aplicativos Direct2D com multithread

Se você desenvolver Direct2D aplicativos, talvez seja necessário acessar Direct2D recursos de mais de um thread. Em outros casos, talvez você queira usar vários threadings para obter melhor desempenho ou melhor capacidade de resposta (como usar um thread para exibição de tela e um thread separado para renderização offline).

Este tópico descreve as práticas recomendadas para desenvolver aplicativos Direct2D multithreaded com pouca ou nenhuma renderização direct3D. Os defeitos de software causados por problemas de simultaneidade podem ser difíceis de rastrear e é útil planejar sua política multithreading e seguir as práticas recomendadas descritas aqui.

Observação

Se você acessar duas Direct2D recursos criados a partir de duas fábricas de Direct2D threaded individuais diferentes, isso não causará conflitos de acesso, desde que os dispositivos e contextos de dispositivo Direct3D subjacentes também sejam distintos. Ao falar sobre "acessar Direct2D recursos" neste artigo, isso realmente significa "acessar Direct2D recursos criados do mesmo dispositivo Direct2D" a menos que indicado o contrário.

Desenvolvendo aplicativos Thread-Safe que chamam somente APIs de Direct2D

Você pode criar uma instância de fábrica de Direct2D multithread. Você pode usar e compartilhar uma fábrica multithread e todos os seus recursos de mais de um thread, mas os acessos a esses recursos (por meio de chamadas Direct2D) são serializados por Direct2D, portanto, não ocorrem conflitos de acesso. Se o aplicativo chamar apenas Direct2D APIs, essa proteção será feita automaticamente Direct2D em um nível granular com sobrecarga mínima. O código para criar uma fábrica multithread aqui.

ID2D1Factory* m_D2DFactory;

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

A imagem aqui mostra como Direct2D serializa dois threads que fazem chamadas usando apenas a API Direct2D.

diagrama de dois threads serializados.

Desenvolvendo aplicativos Thread-Safe Direct2D com chamadas direct3D ou DXGI mínimas

É mais do que comum que um aplicativo Direct2D também faça algumas chamadas Direct3D ou DXGI. Por exemplo, um thread de exibição será desenhado em Direct2D, em seguida, apresentará usando uma cadeia de troca DXGI.

Nesse caso, garantir a segurança de threads é mais complicado: algumas chamadas Direct2D acessam indiretamente os recursos subjacentes do Direct3D, que podem ser acessados simultaneamente por outro thread que chama Direct3D ou DXGI. Como essas chamadas direct3D ou DXGI estão fora de Direct2D reconhecimento e controle, você precisa criar uma fábrica de Direct2D multithreaded, mas você deve fazer mor para evitar conflitos de acesso.

O diagrama aqui mostra um conflito de acesso a recursos do Direct3D devido ao thread T0 acessar um recurso indiretamente por meio de uma chamada Direct2D e T2 acessando o mesmo recurso diretamente por meio de uma chamada Direct3D ou DXGI.

Observação

A proteção de thread que Direct2D fornece (o bloqueio azul nesta imagem) não ajuda nesse caso.

 

diagrama de proteção de thread.

Para evitar conflitos de acesso a recursos aqui, recomendamos que você adquira explicitamente o bloqueio que Direct2D usa para sincronização de acesso interno e aplique esse bloqueio quando um thread precisar fazer chamadas Direct3D ou DXGI que possam causar conflito de acesso, conforme mostrado aqui. Em particular, você deve ter um cuidado especial com o código que usa exceções ou um sistema inicial com base em códigos de retorno HRESULT. Por esse motivo, recomendamos que você use um padrão RAII (Aquisição de Recursos é Inicialização) para chamar os métodos Enter e Leave .

Observação

É importante que você emparelhe chamadas para os métodos Enter e Leave , caso contrário, seu aplicativo pode fazer deadlock.

 

O código aqui mostra um exemplo de quando bloquear e desbloquear em chamadas Direct3D ou 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();
}

Observação

Algumas chamadas Direct3D ou DXGI (notadamente IDXGISwapChain::P resent) podem adquirir bloqueios e/ou disparar retornos de chamada no código da função ou método de chamada. Você deve estar ciente disso e garantir que esse comportamento não cause deadlocks. Para obter mais informações, consulte o tópico Visão geral do DXGI .

 

diagrama de bloqueio de thread direct2d e direct3d.

Quando você usa os métodos Enter e Leave, as chamadas são protegidas pelo Direct2D automático e pelo bloqueio explícito, para que o aplicativo não atinja o conflito de acesso.

Há outras abordagens para contornar esse problema. No entanto, recomendamos que você proteja explicitamente as chamadas Direct3D ou DXGI com o bloqueio de Direct2D, pois geralmente fornece melhor desempenho, pois protege a simultaneidade em um nível muito mais fino e com uma sobrecarga mais baixa sob a cobertura do Direct2D.

Garantindo a atomicidade das operações com estado

Embora os recursos de segurança de thread do DirectX possam ajudar a garantir que nenhuma chamada de API individual seja feita simultaneamente, você também deve garantir que os threads que fazem chamadas à API com estado não interfiram entre si. Veja um exemplo.

  1. Há duas linhas de texto que você deseja renderizar na tela (por Thread 0) e fora da tela (pelo Thread 1): a linha nº 1 é "A é maior" e a Linha nº 2 é "do que B", ambas desenhadas usando um pincel preto sólido.
  2. O Thread 1 desenha a primeira linha de texto.
  3. O Thread 0 reage a uma entrada do usuário, atualiza ambas as linhas de texto para "B é menor" e "do que A", respectivamente, e alterou a cor do pincel para vermelho sólido para seu próprio desenho;
  4. O Thread 1 continua desenhando a segunda linha de texto, que agora é "do que A", com o pincel de cor vermelho;
  5. Por fim, obtemos duas linhas de texto no destino de desenho fora da tela: "A é maior" em preto e "do que A" em vermelho.

um diagrama de threads dentro e fora da tela.

Na linha superior, o Thread 0 desenha com cadeias de caracteres de texto atuais e o pincel preto atual. O Thread 1 só termina o desenho fora da tela na metade superior.

Na linha intermediária, o Thread 0 responde à interação do usuário, atualiza as cadeias de caracteres de texto e o pincel e atualiza a tela. Neste ponto, o Thread 1 está bloqueado. Na linha inferior, a renderização final fora da tela após o Thread 1 retoma o desenho da metade inferior com um pincel alterado e uma cadeia de caracteres de texto alterada.

Para resolver esse problema, recomendamos que você tenha um contexto separado para cada thread, para que:

  • Você deve criar uma cópia do contexto do dispositivo para que os recursos mutáveis (ou seja, recursos que podem variar durante a exibição ou impressão, como conteúdo de texto ou o pincel de cor sólida no exemplo) não sejam alterados quando você renderizar. Neste exemplo, você deve manter uma cópia dessas duas linhas de texto e o pincel de cor antes de desenhar. Ao fazer isso, você garante que cada thread tenha conteúdo completo e consistente para desenhar e apresentar.
  • Você deve compartilhar recursos pesados (como bitmaps e grafos de efeito complexos) que são inicializados uma vez e nunca modificados entre threads para aumentar o desempenho.
  • Você pode compartilhar recursos leves (como pincéis de cores sólidas e formatos de texto) que são inicializados uma vez e, em seguida, nunca modificados entre threads ou não

Resumo

Ao desenvolver aplicativos de Direct2D multithreaded, você deve criar uma fábrica de Direct2D multithread e, em seguida, derivar todos os recursos Direct2D dessa fábrica. Se um thread fizer chamadas Direct3D ou DXGI, você também deverá adquirir explicitamente e aplicar o bloqueio Direct2D para proteger essas chamadas Direct3D ou DXGI. Além disso, você deve garantir a integridade do contexto com uma cópia de recursos mutáveis para cada thread.