O que o DirectX 12 mudará nos games?

Matéria original do site PC Gamer.com

Traduzida por Marcus Sarmanho Hermes Marques

Editor Chefe do PC Facts


O ecossistema de desenvolvimento de jogos modernos


Obviamente, tirar todas estas responsabilidades da API e do nível do driver e passar para o game em si, vai aumentar o tamanho do código e os esforços no desenvolvimento de games no futuro. De fato, um argumento comum a favor de interfaces de mais alto nível em todas as áreas da computação é proteger o programador de ter que lidar com todas as complexidades de cada sistema. Entretanto, pela forma com que a maioria dos games atuais é construído, APIs de baixo nível os favoreceriam.

Diferentemente dos anos 90 e início dos anos 2000, é muito raro hoje que algum jogo seja baseado em uma engine construída diretamente em cima de uma dada API gráfica. Muitas das maiores produtoras de games tem seus próprios times de desenvolvedores de engines dedicados puramente a manter suas tecnologias atualizadas e até os desenvolvedores independentes tem engines de alta qualidade e com manutenção profissional. Dentre elas, os desenvolvedores, quaisquer que sejam, podem escolher portanto a que melhor lhes atenda. Dito isso, esse aumento da complexidade da programação de APIs de baixo nível em muitos casos será absorvida por desenvolvedores de middleware - que tem os recursos e a expertise para lidar com esses desafios - em vez de atropelar os desenvolvedores de games diretamente.

Ainda assim, só porque uma API não é demandada a checar a correição de tudo a que é solicitada - o que pode ser pesado em termos de performance - não significa que ela não tenha permissão para isso. Todas as APIs de baixo nível oferecem camadas de validação opcional que deveriam ajudar a mitigar o aumento de responsabilidade do desenvolvedor. Não para tanto que o suporte de ferramentas sempre demora quando chega uma nova tecnologia e isso demanda um tempo para que as ferramentas situem seu lugar em APIs estabelecidas.


Como APIs de baixo nível funcionam?


Claramente, há grandes incentivos tanto da perspectiva do Hardware quanto do Software por uma renovação quando da chegada de API de design gráfico em baixo nível e os ecossistemas atuais das engines parecem permitir isso. Mas a que exatamente o "baixo nível" nos remete? O DirectX 12 traz uma gama de mudanças no cenário das API mas os principais ganhos de eficiência que veremos estão pautados em três tópicos centrais:

  • Criar e submeter atividades

  • Gerenciar o estado da Pipeline

  • Gerenciar recursos e memórias assíncronas

Todas essas mudanças estão conectadas em alguma extensão e trabalham bem juntas para minimizar o overhead mas, para esclarecer melhor, iremos olhar para cada uma delas separadamente.


Criar e submeter atividades

O objetivo definitivo de uma API gráfica é que o programa que a esteja utilizando monte um quadro de instruções que a GPU possa acompanhar, geralmente chamada de lista de comando ou command list. Em APIs de nível mais alto, este processo é mais abstrato enquanto que no directx 12 é executado de forma mais direta. A figura a seguir compara o uso em uma API clássica de alto nível (DirectX 9) com a uma API que tentava trazer a primeira implementação de paralelização no DirectX 11 e finalmente o processo mais direto do DirectX 12.

Nos bons velhos tempos de aumento de frequências de processadores ano a ano, o que o Direct3D 9 oferecia era ótimo: a aplicação usava um contexto único para submeter draw calls dos quais a API verifica e manda ao Driver, que por sua vez gera uma lista de comandos para a GPU consumir. Tudo isso é um processo sequencial, logo, na era dos mercados com chips multicore, foi necessário uma tentativa para permitir a paralelização.

Isso se manifestou no Direct3D 11 por um conceito de contextos difusos que permitem aos games submeter draw comando independentes em threads múltiplos. Entretanto, todos definitivamente precisam passar por um único e imediato contexto e o driver só poderá completar a lista de comando uma vez que tudo tenha lhe sido repassado. Por mais que o DirectX 11 permita um certo grau de paralelização, ele infelizmente causa um load pesado nas threads que tem que finalizar esse processo.

Já o Direct3D 12 faz um corte na relação com o intermediador metafórico nesse sistema. O código do game pode diretamente gerar um número arbitrário da lista de comandos, em paralelo. Ele também controla quando eles são submetidos a GPU, o que pode acontecer com menos overhead que em conceitos anteriores ao passo que são feitos em formatos que o hardware pode consumir diretamente. Isso acarreta responsabilidades adicionais para o código do game como garantir a disponibilidade de todos os recursos usados em uma dada lista de comandos durante a integridade de sua execução, mas em compensação deve, por sua vez, permitir um equilíbrio em cargas paralelas.


Gerenciar o Pipeline

Gráficos ou renderizar pipeline consiste em uma vasta quantidade de passos interconectados, como shading de pixels ou rasterization que trabalham juntos para renderizar uma cena 3D. Gerenciar o estado de vários componentes nesta pipeline é uma tarefa importante em qualquer jogo, já que este estado vai influenciar diretamente como cada draw operation é executada e portanto a imagem final.

A maioria das API de alto nível entregam uma visão abstrata, lógica deste estado, dividida em diversas categorias que podem ser configuradas independentemente. Isso nos proporciona uma conveniente imagem metalizada, abstrata, do processo de renderização mas não necessariamente com a visualização final de muitos draw calls com baixo overhead.

A figura acima, mesmo que simplificada, ilustra o erro básico. Muita descrições independentes podem existir em APIs de alto nível e ao início de cada draw call toda fração deles pode estar ativa. Sendo assim, o driver tem que construir a atual representação do hardware de sua combinação de estados - e talvez ainda verificar sua validade - antes de poder efetivamente executar o draw call. Enquanto que o uso do cache pode aliviar este problema, isso significa levar mais complexidade ao driver, correndo o risco de comportamento errático e imprevisível com bugs de difícil reversão pela perspectiva da aplicação, como já discutido antes.

Em DX12, a informação do estado é reunida em "objetos de estado de pipeline". São imutáveis uma vez construídos, logo permitem ao driver verificar e construir sua representação de hardware uma única vez após criada. Usá - los então é idealmente uma questão de apenas copiar a descrição relevante do local correto da memória do hardware antes de começar a "desenhar".


Gerenciar recursos e assincronia de memórias

Direct3D 12 permitirá - e forçará - desenvolvedores de games a gerenciar manualmente todos os recursos usados por seus jogos mais diretamente do que nunca. Enquanto APIs de baixo nível oferecem uma visão conveniente a recursos como texturas e deve esconder outros recursos como a representação de listas de comandos nativas das GPUs ou armazenar alguns tipos de estados criados pelos desenvolvedores completamente, no D3D12, tudo isso pode e deve ser gerenciado pelo desenvolvedor.

Isso não significa apenas controle direto sobre que recursos residem em cada memória a todo momento mas também tornam os programadores dos jogos responsáveis por garantir que todos os dados estejam onde a GPU precisa que eles estejam para quando for acessar. A GPU e CPU sempre agiram independentemente de cada uma de maneira assíncrona, mas os problemas potenciais surgidos dessa assincronia eram lidados pelo driver das API de alto nível.


Para lhe dar uma ideia de como isso pode ser um desperdício de performance e recursos, a figura acima ilustra um exemplo simples de como o risco supracitado ocorre. Do lado da CPU, a situação é clara: um Draw Call X usando dois recursos A e B é executado e depois o recurso B é modificado. Entretanto, devido à natureza assíncrona do processamentos da CPU e da GPU, a GPU só pode realmente executar o draw Call em questão muito depois de o código do game ter sido executado no CPU e o recurso B ter sido alterado. Nestas situações, drivers de API de alto nível poderiam nos piores casos serem forçadas a criar uma cópia fantasma do recursos requisitado pelo primeiro Call ou chamada. Em D3D12, os desenvolvedores serão encarregados de garantir que tal situação não ocorra.


Em suma

Juntando tudo, as mudanças discutidas acima devem resultar em uma API leve com o potencial de remediar, já na implementação, jogos para que sejam executados melhor e - talvez não menos importante - com consistência aprimorada.

O Controle de nível de aplicação e montagem de lista de comando permitem desde distribuição equilibrada de trabalhos paralelos até melhor utilização de CPUs multicore. Gerenciar o estado da Pipeline - de forma que consiga espelhar mais apropriadamente as reais requisições do hardware - irá reduzir a carga sobre a CPU e aumentar a quantidade de draws demonstradas. E o gerenciamento manual dos recursos, por mais complexo que seja, irá permitir os desenvolvedores dos jogos a entender toda a movimentação de dados em seus jogos. Este insight pode tornar mais fácil a entrega de uma experiência consistente e talvez problemas de stutter relacionados a loading e streaming sejam eliminados completamente

#PC