Programação assíncrona (DirectX e C++)
Este tópico aborda vários pontos a serem considerados quando você estiver usando programação assíncrona e threading com DirectX.
Programação assíncrona e DirectX
Se você está apenas aprendendo sobre o DirectX, ou mesmo se tiver experiência com ele, considere colocar todo o pipeline de processamento gráfico em um thread. Em qualquer cena de um jogo, há recursos comuns, como bitmaps, sombreadores e outros ativos que exigem acesso exclusivo. Esses mesmos recursos exigem que você sincronize qualquer acesso a esses recursos entre os threads paralelos. A renderização é um processo difícil de paralelizar em vários threads.
No entanto, se o jogo for suficientemente complexo ou se você estiver procurando obter um desempenho aprimorado, poderá usar a programação assíncrona para paralelizar alguns dos componentes que não são específicos do pipeline de renderização. O hardware moderno apresenta várias CPUs principais e hyperthreaded, e seu aplicativo deve aproveitar isso! Você pode garantir isso usando a programação assíncrona para alguns dos componentes do seu jogo que não precisam de acesso direto ao contexto do dispositivo Direct3D, como:
- E/S de arquivo
- física
- IA
- networking
- audio
- controls
- Componentes de interface do usuário baseados em XAML
Seu aplicativo pode lidar com esses componentes em vários threads simultâneos. A E/S de arquivo, especialmente o carregamento de ativos, se beneficia muito do carregamento assíncrono, pois seu jogo ou aplicativo pode estar em um estado interativo enquanto vários (ou várias centenas) megabytes de ativos estão sendo carregados ou transmitidos. A maneira mais fácil de criar e gerenciar esses threads é usando a Biblioteca de Padrões Paralelos e o padrão de tarefa, conforme contido no namespace de simultaneidade definido em PPLTasks.h. O uso da Biblioteca de Padrões Paralelos aproveita diretamente as CPUs de vários núcleos e hyperthreaded e pode melhorar tudo, desde os tempos de carregamento percebidos até os engates e atrasos que acompanham os cálculos intensivos da CPU ou o processamento de rede.
Observação Em um aplicativo UWP (Plataforma Universal do Windows), a interface do usuário é executada inteiramente em um STA (single-threaded apartment). Se você estiver criando uma interface do usuário para seu jogo DirectX usando a interoperabilidade XAML, só poderá acessar os controles usando o STA.
Multithreading com dispositivos Direct3D
O multithreading para contextos de dispositivo só está disponível em dispositivos gráficos que dão suporte a um nível de recurso Direct3D de 11_0 ou posterior. No entanto, você pode querer maximizar o uso da poderosa GPU em muitas plataformas, como plataformas de jogos dedicadas. No caso mais simples, talvez você queira separar a renderização de uma sobreposição de HUD (heads-up display) da renderização e projeção da cena 3D e fazer com que ambos os componentes usem pipelines paralelos separados. No entanto, ambos os threads devem usar o mesmo ID3D11DeviceContext para criar e gerenciar os objetos de recurso (as texturas, malhas, sombreadores e outros ativos), que é de thread único e que requer que você implemente algum tipo de mecanismo de sincronização (como seções críticas) para acessá-lo com segurança. E, embora você possa criar listas de comandos separadas para o contexto do dispositivo em threads diferentes (para renderização adiada), não é possível reproduzir essas listas de comandos simultaneamente na mesma instância ID3D11DeviceContext .
Agora, seu aplicativo também pode usar ID3D11Device, que é seguro para multithreading, para criar objetos de recurso. Então, por que nem sempre usar ID3D11Device em vez de ID3D11DeviceContext? Bem, atualmente, o suporte de driver para multithreading pode não estar disponível para algumas interfaces gráficas. Você pode consultar o dispositivo e descobrir se ele dá suporte a multithreading, mas se estiver procurando alcançar o público mais amplo, poderá ficar com ID3D11DeviceContext de thread único para gerenciamento de objetos de recurso. Dito isso, quando o driver de dispositivo gráfico não dá suporte a multithreading ou listas de comandos, o Direct3D 11 tenta lidar com o acesso sincronizado ao contexto do dispositivo internamente; e se as listas de comandos não forem suportadas, ele fornecerá uma implementação de software. Como resultado, você pode escrever código multithread que será executado em plataformas com interfaces gráficas que não têm suporte de driver para acesso ao contexto do dispositivo multithread.
Se o aplicativo der suporte a threads separados para processar listas de comandos e exibir quadros, você provavelmente desejará manter a GPU ativa, processando as listas de comandos enquanto exibe quadros em tempo hábil, sem gagueira ou atraso perceptível. Nesse caso, você pode usar um ID3D11DeviceContext separado para cada thread e compartilhar recursos (como texturas) criando-os com o sinalizador D3D11_RESOURCE_MISC_SHARED. Nesse cenário, ID3D11DeviceContext::Flush deve ser chamado no thread de processamento para concluir a execução da lista de comandos antes de exibir os resultados do processamento do objeto de recurso no thread de exibição.
Renderização adiada
A renderização adiada registra comandos gráficos em uma lista de comandos para que eles possam ser reproduzidos em outro momento e foi projetada para dar suporte à renderização em um thread durante a gravação de comandos para renderização em threads adicionais. Depois que esses comandos forem concluídos, eles poderão ser executados no thread que gera o objeto de exibição final (buffer de quadro, textura ou outra saída gráfica).
Crie um contexto adiado usando ID3D11Device::CreateDeferredContext (em vez de D3D11CreateDevice ou D3D11CreateDeviceAndSwapChain, que criam um contexto imediato). Para obter mais informações, consulte Renderização imediata e adiada.
Tópicos relacionados