Aplicativos Direct2D multithreaded
Se você desenvolver aplicativos Direct2D, talvez seja necessário acessar recursos direct2D 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 o desenvolvimento de aplicativos Direct2D multithreaded com pouca ou nenhuma renderização de 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.
Nota
Se você acessar dois recursos Direct2D criados a partir de duas fábricas direct2D de thread único diferentes, ele não causará conflitos de acesso, desde que os direct3D subjacentes dispositivos e contextos de dispositivo também sejam distintos. Ao falar em "acessar recursos do Direct2D" neste artigo, ele realmente significa "acessar recursos direct2D criados do mesmo dispositivo Direct2D", a menos que indicado o contrário.
Desenvolvendo aplicativos Thread-Safe que chamam somente APIs Direct2D
Você pode criar uma instância de fábrica de Direct2D multithreaded. 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 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 no Direct2D e, em seguida, apresentará usando uma cadeia de troca DXGI .
Nesse caso, a segurança de threads subjacente é mais complicada: algumas chamadas direct2D acessam indiretamente os recursos de 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 controle e do reconhecimento do Direct2D, você precisa criar uma fábrica 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.
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 tomar cuidado especial com o código que usa exceções ou um sistema de início antecipado com base em códigos de retorno HRESULT. Por esse motivo, recomendamos que você use um padrão RAII (A aquisição de recursos é inicialização) para chamar os métodos Enter e Leave.
Nota
É importante que você emparelhe chamadas para os métodos Enter e Leave, caso contrário, seu aplicativo poderá ficar em deadlock.
O código aqui mostra um exemplo de quando bloquear e desbloquear direct3D ou chamadas 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
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 garantir que esse comportamento não cause deadlocks. 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 direct2D do 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 direct3D ou chamadas DXGI com o bloqueio Direct2D, pois ele geralmente fornece melhor desempenho, pois protege a simultaneidade em um nível muito mais fino e com uma sobrecarga menor sob a cobertura do Direct2D.
Garantindo a atomicidade das operações com estado
Embora os recursos de segurança de thread de 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. Aqui está um exemplo.
- Há duas linhas de texto que você deseja renderizar na tela (por Thread 0) e fora da tela (por Thread 1): a linha 1 é "A é maior" e a Linha 2 é "do que B", ambas desenhadas usando um pincel preto sólido.
- O Thread 1 desenha a primeira linha de texto.
- O Thread 0 reage a uma entrada do usuário, atualiza as duas linhas de texto para "B é menor" e "que A", respectivamente, e alterou a cor do pincel para vermelho sólido para seu próprio desenho;
- O Thread 1 continua desenhando a segunda linha de texto, que agora é "do que A", com o pincel de cor vermelho;
- Por fim, obtemos duas linhas de texto no destino de desenho fora da tela: "A é maior" em preto e "do que A" em vermelho.
Na linha superior, o Thread 0 desenha com cadeias 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, de modo 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 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 cores 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 Direct2D de multithreaded, você deve criar uma fábrica direct2D multithreaded e derivar todos os recursos do Direct2D dessa fábrica. Se um thread fizer direct3D ou chamadas 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 de recursos mutáveis para cada thread.