Aplicativos Direct2D multithreaded
Se você desenvolver aplicativos Direct2D, talvez seja necessário acessar recursos Direct2D de mais de um thread. Em outros casos, você pode querer usar multi-threading 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 o desenvolvimento de 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 de multithreading e seguir as práticas recomendadas descritas aqui.
Observação
Se você acessar dois recursos Direct2D criados a partir de duas fábricas Direct2D de thread único diferentes, isso não causará conflitos de acesso, desde que os dispositivos Direct3D subjacentes e os contextos de dispositivos também sejam distintos. Ao falar sobre "acessar recursos Direct2D" neste artigo, isso realmente significa "acessar recursos Direct2D criados a partir do mesmo dispositivo Direct2D", salvo indicação em contrário.
Desenvolvendo aplicativos Thread-Safe que chamam somente APIs Direct2D
Você pode criar um multithreaded Direct2D instância de fábrica. Você pode usar e compartilhar uma fábrica multithreaded e todos os seus recursos de mais de um thread, mas os acessos a esses recursos (por meio de chamadas Direct2D) são serializados pelo Direct2D, portanto, não ocorrem conflitos de acesso. Se seu aplicativo chamar apenas APIs Direct2D, essa proteção será feita automaticamente pelo Direct2D em um nível granular com sobrecarga mínima. O código para criar uma fábrica multithreaded 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.
Desenvolvendo aplicativos Direct2D Thread-Safe com o mínimo de chamadas Direct3D ou DXGI
É mais do que frequente que um aplicativo Direct2D também faça algumas chamadas Direct3D ou DXGI. Por exemplo, um thread de exibição desenhará em Direct2D e apresentará usando uma cadeia de permuta DXGI .
Nesse caso, garantir a segurança do thread é mais complicado: algumas chamadas de Direct2D acessam indiretamente os recursos Direct3D subjacentes, que podem ser acessados simultaneamente por outro thread que chama Direct3D ou DXGI. Como essas chamadas Direct3D ou DXGI estão fora do conhecimento e controle do Direct2D, você precisa criar uma fábrica Direct2D multithreaded, mas deve fazer mais para evitar conflitos de acesso.
O diagrama aqui mostra um conflito de acesso a recursos do Direct3D devido ao thread T0 acessando 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 cadeado azul nesta imagem) não ajuda neste caso.
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 de saída antecipada baseado em códigos de retorno HRESULT. Por esse motivo, recomendamos que você use um padrão RAII (Resource Acquisition Is Initialization) para chamar os métodos Enter e Leave.
Observação
É importante emparelhar chamadas para os métodos Entere Leave, caso contrário, seu aplicativo pode travar.
O código aqui mostra um exemplo de quando bloquear e, em seguida, desbloquear em torno de 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 (notavelmente 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 certificar-se de que esse comportamento não cause impasses. Para obter mais informações, consulte o tópico Visão geral do DXGI.
Quando você usa os métodos Enter e Leave, as chamadas são protegidas pelo automático Direct2D e pelo bloqueio explícito, para que o aplicativo não entre em conflito de acesso.
Existem outras abordagens para contornar esta questão. No entanto, recomendamos que você proteja explicitamente chamadas Direct3D ou DXGI com o bloqueio Direct2D porque geralmente oferece melhor desempenho, pois protege a simultaneidade em um nível muito mais fino e com menor sobrecarga 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 não sejam feitas duas chamadas de API individuais simultaneamente, você também deve garantir que os threads que fazem chamadas de API com monitoração de estado não interfiram entre si. Aqui está um exemplo.
- Há duas linhas de texto que você deseja renderizar na tela (pelo Thread 0) e fora da tela (pelo Thread 1): a Linha #1 é "A é maior" e a Linha #2 é "do que B", ambas serão desenhadas usando um pincel preto sólido.
- O segmento 1 desenha a primeira linha do texto.
- O segmento 0 reage a uma entrada do usuário, atualiza ambas as linhas de texto para "B é menor" e "que A", respectivamente, e altera a cor do pincel para vermelho sólido para seu próprio desenho;
- O fio 1 continua desenhando a segunda linha do texto, que agora é "que A", com o pincel vermelho;
- Finalmente, temos duas linhas de texto no alvo de desenho fora da tela: "A é maior" em preto e "que A" em vermelho.
Na linha superior, o Thread 0 desenha com cadeias de texto atuais e o pincel preto atual. O segmento 1 só termina o desenho fora da tela na metade superior.
Na linha do meio, o Thread 0 responde à interação do usuário, atualiza as cadeias de texto e o pincel e, em seguida, 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 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 o conteúdo do 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 do pincel colorido 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 gráficos de efeitos complexos) que são inicializados uma vez e, em seguida, 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 Direct2D multithreaded, você deve criar uma fábrica Direct2D multithreaded 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 tendo uma cópia dos recursos mutáveis para cada thread.