Estas informações eram atuais em abril de 2022. Vários recursos podem ter sido alterados ou atualizados desde então.
APIs de ritmo de quadros
As novas APIs de ritmo de quadros reformularam o processo de apresentação de gráficos. Elas visam fazer o seguinte.- Fornecer controle mais direto sobre a criação e o gerenciamento de recursos exibíveis
- Reduzir a latência de ponta a ponta, desde a amostragem do dispositivo de entrada até a varredura (scan-out) do recurso de quadro apresentado
- Ampliar a flexibilidade na configuração dinâmica de planos e parâmetros de exibição
- Aumentar a liberdade na seleção da fila de comandos para o envio da apresentação
- Estabelecer um mecanismo explícito de limitação da CPU para controle refinado sobre a execução do pipeline de simulação e renderização
- Aprimorar a manutenção e o relatório de estatísticas do pipeline de quadros

Present, chamaria as APIs em preto à esquerda. As outras duas à esquerda eram extensões que foram adicionadas na época da ERA. As APIs à direita em magenta são as novas APIs; por exemplo, PresentX.
Essa foi uma alteração interruptiva, portanto, foram necessárias alterações de código ao migrar para o GDKX. A migração tornou-se obrigatória há três anos. Você já as está usando se criou um jogo nos últimos três anos.
A maneira antiga - inicialização
Figura 2. A maneira antiga - inicialização.
Present.
Anteriormente, durante o período de inicialização no começo do jogo, você criava o que era chamado de cadeia de troca (swap chain). Elas não são mais usadas no mesmo sentido. Observe que o sistema alocava os buffers da cadeia de troca.
A maneira antiga - loop de quadros
Figura 3. A maneira antiga - loop de quadros.
- Solicitar um buffer à cadeia de troca.
- Renderizar a imagem do quadro no buffer da cadeia de troca.
- Chamar
Present.
Present era uma API simples que tinha apenas alguns parâmetros. A figura 3 mostra Present e o parâmetro frameInterval. Seu valor de um significava 60 Hertz (Hz).
Observe que não há controle de tempo. O sistema avança o máximo que puder, desde que esteja correto. No entanto, em alguns casos, Present pode travar.
A nova maneira - inicialização
Figura 4. A nova maneira - inicialização.
HEAP_ALLOW_DISPLAY indica que você pretende usar esses buffers para exibição. Em seguida, você chama várias novas APIs.
Uma nova API, SetFrameIntervalX, especifica com antecedência qual é a sua taxa de quadros de destino, em vez de esperar até que o quadro seja apresentado. Nesse caso, a taxa de quadros de destino é de 60 Hz. Para concluir a inicialização, agende um evento ORIGIN. Ele será usado para muitas coisas, incluindo quando você deseja que seu quadro comece.
Isso é o que você faz na inicialização. Para o loop de quadros, agora você faz o seguinte uma vez por quadro.
A nova maneira - loop de quadros
Figura 5. A nova maneira - loop de quadros.
Present, primeiro chame WaitFrameEventX. Ela aguarda o evento ORIGIN que você agendou, conforme mostrado na figura 4. Os quadros não podem mais ser iniciados quando você quiser. Você precisa esperar por eles.
Em seguida, renderize o quadro no buffer da cadeia de troca que você criou. Por fim, chame PresentX, a nova versão de Present. PresentX tem muitos parâmetros. Não é mais uma API simples.
Motivação para novas APIs
Esta seção lista uma série de problemas encontrados com as APIs mais antigas e como as novas APIs pretendem resolvê-los. São fornecidos exemplos de código, e as figuras usam texto em magenta para o código da solução.Tema principal: latência
O tema principal da nova API é o controle sobre a latência, que é um conceito importante. Se você acompanha o Digital Foundry, verá que as análises deles tentam medir a latência com exatidão. Usando o XBOX Game Streaming, os jogadores podem jogar na nuvem. A latência também é importante para jogos na nuvem. As APIsPresent anteriores não funcionavam tão bem no que diz respeito à latência. Por exemplo, quando você chamava Present, o driver travava pelo tempo que precisasse. Esse travamento podia ocorrer por muito tempo. Você não tinha controle sobre isso.
A latência nesse ponto tendia a ser um múltiplo inteiro do intervalo de quadros. Se você estivesse limitado pela GPU, tenderia a obter a maior latência possível, mesmo quando não precisasse dela.
As novas APIs têm latência explícita. O título pode solicitar uma latência exata em microssegundos em vez de um número inteiro de quadros. Você pode fazer essa latência aumentar e diminuir conforme necessário. Em períodos de estresse, você pode ter uma latência maior e evitar a perda de quadros. Em períodos sem estresse, você pode reduzir a latência e ter um jogo mais responsivo.
Problema 1/10: sem extensibilidade para vários planos de exibição
O XBOX One introduziu pela primeira vez o segundo plano de exibição. Ele foi exposto aos títulos, mas não era extensível.Solução: extensibilidade para vários planos de exibição
Figura 6. Extensibilidade para vários planos de exibição.
Present agora, você passa um número arbitrário de planos, e cada um deles tem um número arbitrário de exibições. Essa codificação atende a qualquer hardware em que possa haver mais de dois planos ou mais de uma exibição por plano.
Você pode ter um plano de exibição em que uma exibição é RGBE com um expoente compartilhado e uma segunda exibição contém o canal alfa. Outros cenários podem ser possíveis, como usar duas exibições para tela dividida ou duas exibições para o modo espectador. Esses cenários não têm suporte no momento, mas a API tem suporte implícito para eles.
Problema 2/10: não há como desacoplar a taxa de renderização dos planos de exibição
Esse problema está relacionado à extensibilidade dos planos de exibição. No passado, não havia como desacoplar a taxa de renderização dos planos de exibição. Por exemplo, um plano de exibição poderia renderizar a 60 quadros por segundo (FPS) e o outro a 30 FPS. O hardware dá suporte a isso, mas a API não dava. A extensibilidade dos planos de exibição é importante agora para jogos de 120 Hz, que estão se tornando mais comuns. Renderizar a interface do usuário a 120 Hz não é necessário. Se você estiver a 120 Hz, terá apenas pouco tempo para um quadro.Solução: maneira de desacoplar a taxa de renderização dos planos de exibição
Figura 7. Desacoplar a taxa de renderização dos planos de exibição.
Problema 3/10: sem PresentationThreshold para dar suporte a tearing limitado
O próximo problema é oPresentationThreshold da era do XBOX 360. É uma forma de equilibrar tearing (rasgo de tela) e perda de quadros.
PresentationThreshold era exposto por uma extensão no XBOX 360. É uma porcentagem de tearing de 0 a 100. Zero por cento é o padrão e significa nunca ter tearing. Cinquenta por cento significa que, se o orçamento do quadro for perdido e o quadro for concluído quando apenas os 50 por cento superiores do quadro tiverem sido varridos, você usaria Present imediatamente e, então, ocorreria tearing.
Se mais de 50 por cento for usado, o tearing não será necessário, e você desejaria aguardar o próximo Vblank. O outro extremo é 100 por cento, o que significa que, desde que a taxa de quadros seja atendida, não haverá tearing. No entanto, quando o destino do quadro é perdido, o tearing pode ocorrer.
Solução: usar PresentationThreshold para dar suporte a tearing limitado
Figura 8. PresentationThreshold para dar suporte a tearing limitado.
PresentationThreshold dá suporte a tearing limitado. Você pode usar isso agora mesmo. Muitos títulos usam o modo de 100 por cento, que provavelmente é o uso mais comum.
Problema 4/10: sem suporte para taxas de quadros superiores a 60 FPS
A API anterior não tinha suporte para taxas de quadros superiores a 60 FPS. Isso não podia ser expresso porque o intervalo de quadros era um UINT. Um significava 60 FPS. Não é possível ter metade de 1 - a metade não é um inteiro. Não havia como indicar que você queria 120 Hz com a API anterior. Uma nova API era necessária. A nova API dá suporte a 120 Hz. Ela dá suporte implicitamente a qualquer valor que seja múltiplo desse tempo de quadro. Com isso, você obtém 40 FPS ou, também recentemente, 24 FPS para cenas cinematográficas.Solução: suporte para taxas de quadros superiores a 60 FPS
Figura 9. Suporte para taxas de quadros superiores a 60 FPS.
A maneira correta de detectar o suporte a 120 FPS é usando GetDisplayModeListX. Ela não chama SetFrameIntervalX nem verifica o código de retorno.
Problema 5/10: não há como concluir o quadro na fila de computação
A API anterior chamavaPresent, que era um membro da interface da cadeia de troca. A cadeia de troca era implicitamente parte da fila de gráficos.
É comum que o último trabalho de um quadro seja totalmente baseado em computação. Seria útil fazer esse trabalho, chamar Present e já ter iniciado o trabalho de gráficos do próximo quadro. Os gráficos e a computação se sobrepõem um pouco e ficam sub-pipelined.
Havia um exemplo disponível que mostrava como concluir o quadro em uma fila de computação com a API anterior. No entanto, ele era complicado por causa dessa omissão.
Solução: maneira de concluir o quadro na fila de computação
Figura 10. Maneira de concluir o quadro na fila de computação.
Present foi movido para a fila de comandos para que a API possa expressar que o quadro seja concluído em qualquer fila de comandos. PresentX é um método de ID3D12CommandQueue (classe base) em vez de IDXGISwapChain, que não existe mais.
Até agora, isso tem suporte em gráficos e em computação assíncrona. Se houvesse algum motivo para dar suporte a isso em um novo tipo de fila ou em uma fila existente, como acesso direto à memória (DMA), isso poderia ser feito sem alterar a API.
Problema 6/10: a limitação não tem controle refinado
A API anterior não tinha controle refinado sobre a limitação (throttling). Trata-se de um travamento intencional para impedir que a latência cresça sem limites. Essa é a latência de renderização. Ela tendia a assumir como padrão um múltiplo inteiro do intervalo de quadros. Os títulos contornavam isso adicionando um terceiro buffer da cadeia de troca, sinalizando uma fence em algum ponto no meio do quadro e, em seguida, aguardando essa fence na CPU. Essencialmente, os títulos fazem a limitação manualmente antes que o driver possa fazê-la automaticamente. No entanto, isso é inexato. Você pode limitar por um quadro mais uma pré-passagem de profundidade, mas não por um quadro e 4 ms.Solução: controle refinado sobre a limitação
Figura 11. Controle refinado sobre a limitação.
Problema 7/10: a entrada do controle não tem sincronização explícita
A API anterior não permitia sincronizar com o controle. Isso contribui significativamente para a latência, especialmente para controles sem fio. Quando um botão é pressionado, o jogo não reage imediatamente. Para economizar bateria, os controles sem fio enviam atualizações apenas de tempos em tempos. Um jogador pode pressionar um botão logo antes de uma atualização e obter essa atualização imediatamente. No entanto, se um jogador pressionar um botão logo após a atualização, ele precisará aguardar a próxima atualização para que o pressionamento do botão seja reconhecido. Isso é latência. De certa forma, é o pior tipo de latência, porque não é a mesma em todos os quadros. Os jogadores podem compensar um pouco a latência se souberem qual será e ela for sempre a mesma. Caso contrário, fica difícil fazer coisas como pressionar o botão exatamente quando necessário em um jogo de reflexos rápidos.Solução: sincronização explícita com a entrada do controle
Figura 12. Sincronização explícita com a entrada do controle.
Problema 8/10: eventos significativos não têm notificações
A API anterior não permitia a notificação de eventos significativos, exceto Vblank. É importante que os títulos saibam quando a interrupção de Vblank ocorreu. Parte dessa funcionalidade foi removida. No entanto, ainda há maneiras de receber notificações de eventos significativos.Solução: notificação de eventos significativos
Figura 13. Notificação de eventos significativos.
Problema 9/10: o PIX não consegue mostrar um quadro
No passado, as Capturas de Tempo do PIX não conseguiam mostrar (acompanhar) um quadro inteiro. A única informação que o PIX tinha sobre um quadro era quando você chamavaPresent com a API anterior. Não havia informações sobre o que contribuía para essa chamada a Present nem sobre quando esse quadro começava.
Solução: maneira de o PIX mostrar um quadro
Figura 14. Maneira de o PIX mostrar um quadro.
Present, você passa o mesmo token. Agora o PIX reconhece:
- O início de um quadro e qual é o fim correspondente desse quadro.
- Que outros eventos fizeram parte do mesmo quadro quando você passa o mesmo token.
Problema 10/10: o título não consegue recuperar dados de tempo detalhados
A API anterior não consegue recuperar dados de tempo detalhados. Para o seu título, talvez você queira saber quanto tempo as coisas levaram ou quando elas aconteceram. Por exemplo:- Quanto tempo a GPU ficou ocupada em um quadro?
- Quando o quadro começou?
- Quando ocorreu o evento ORIGIN?
- Quando o quadro foi apresentado?
- Quando a chamada a
Presentchegou à GPU?
Solução: maneira de um título recuperar dados de tempo detalhados
Figura 15. Maneira de um título recuperar dados de tempo detalhados.
Uso recomendado das APIs de ritmo de quadros
Esta seção mostra como modular a latência: descobrir a latência que você tem, decidir qual deseja que ela seja e, em seguida, alterá-la usando as APIs de ritmo de quadros.Modulação da latência
Figura 16. Modulação da latência: três etapas simples.
- Use as estatísticas de quadros para medir a latência ideal no seu título.
- Adicione uma margem para a variabilidade.
- Calcule seus parâmetros.
PeriodInIntervals e IntervalOffsetInMicroseconds. Eles devem ser fornecidos ao driver.
Essas alterações são processadas em pipeline, portanto, entram em vigor em alguns quadros. Talvez você queira que o título reaja imediatamente aos aumentos de latência, mas reaja lentamente às quedas de latência.
Exemplo de modulação da latência
Figura 17. Exemplo de modulação da latência.
SignalTime e GPUProcessTime. Por exemplo, você decide que a margem de segurança é de meio quadro, ou 8,3 ms. Isso resulta em uma latência total solicitada de 38,3 ms.
Para calcular os dois parâmetros, PeriodInIntervals e IntervalOffsetInMicroseconds, observe o gráfico na figura 17. No lado direito do gráfico está o Vblank de destino. É nesse momento que o quadro deve estar pronto para Present. Contando para trás a margem de segurança de 8,3 ms, chega-se ao momento em que se prevê que o quadro termine. A partir daí, conte para trás a latência medida de 30 ms. Esse é o seu ponto ORIGIN.
A figura 17 calcula PeriodInIntervals, o período do quadro em intervalos, dividindo 38,3 ms (38300) pelo intervalo de quadros de 16,7 ms (16667). O resultado é arredondado para cima, para três intervalos.
IntervalOffsetInMicroseconds indica em que ponto do primeiro intervalo o quadro começará. O cálculo na figura 17 informa que é 11,7 ms.
No total, isso significa que o Vblank de destino é o terceiro Vblank após o ponto ORIGIN. Ele fica em algum ponto no meio de um intervalo de quadros (11,7 ms).
Esse processo não é tão complicado, mas poderia ser. Provavelmente será em títulos em que outras coisas são feitas ao mesmo tempo.
Se o seu título tiver resolução dinâmica, você precisará considerar isso no resultado. Se você tiver latência muito alta, poderá resolver isso reduzindo a resolução. Alguns títulos modulam dinamicamente a taxa de quadros, o que significa que, às vezes, são títulos de 60 hertz e, outras vezes, títulos de 120 hertz. Outros títulos usam ImmediateThreshold para permitir tearing se ultrapassarem o orçamento do quadro.
Você pode criar um algoritmo muito complicado para prever todas as suas contingências.
Uso não recomendado das APIs de ritmo de quadros
Esta seção aborda usos das APIs de ritmo de quadros que não recomendamos.O buffer de fundo não é apresentado dentro do tempo acordado
Os problemas abordados até agora neste tópico são semelhantes a um contrato. Você concorda que um quadro será concluído em determinado período. Se você não concluir um quadro no tempo acordado, poderão ocorrer problemas como perda de quadros. Antes, você só perdia quadros se a CPU ou a GPU excedesse o orçamento. Agora, você pode fazer uma previsão de latência, não cumpri-la e perder quadros mesmo que a CPU e a GPU estejam dentro do orçamento de tempo.O título usa uma cadeia de troca curta demais
Quando um título usa uma cadeia de troca curta demais, podem ocorrer problemas, como travamentos. O tamanho ideal da cadeia de troca éPeriodInIntervals mais um. Por exemplo, se você usar PeriodInIntervals igual a dois e IntervalOffsetInMicroseconds for zero, precisará de uma cadeia de troca de tamanho três. Isso ocorre porque você tem um quadro sendo exibido, outro quadro que está no segundo intervalo de seu cálculo e um terceiro quadro em seu primeiro intervalo de cálculo. São três quadros, todos ao mesmo tempo. Tecnicamente, eles precisam ter buffers da cadeia de troca diferentes; caso contrário, poderiam colidir.
Em alguns casos, não há penalidade para uma cadeia de troca curta. Nada acontece. No entanto, em outros casos, a CPU trava em PresentX ou a GPU trava em ResourceBarrier. Às vezes, você tem travamentos que não afetam a taxa de quadros. Outras vezes, os travamentos afetam a taxa de quadros.
Você pode criar exemplos de cada um desses cenários. O que você escolher depende de como o seu título funciona.
O título define PeriodInIntervals e IntervalOffsetInMicroseconds apenas uma vez na inicialização
Podem ocorrer problemas se o título definir os novos parâmetros,PeriodInIntervals e IntervalOffsetInMicroseconds, na inicialização e não fizer mais nada com eles. Isso é semelhante à maneira anterior de fazer as coisas, em que você definia uma latência de destino com antecedência e mantinha as mesmas escolhas, não importando o que acontecesse durante o jogo.
Podem ocorrer penalidades se você for agressivo demais ou conservador demais na escolha da latência. Agressivo demais, e você perde quadros quando não precisa, mesmo dentro do orçamento. Conservador demais, e você sempre tem uma latência maior do que precisa.
O título começa a calcular um quadro antes de o evento ORIGIN ser sinalizado
Esse problema ocorre quando você agenda o evento ORIGIN e aguarda por ele, mas o título começa os cálculos do quadro. Nesse caso, os cálculos não começaram quando você os definiu, então sua latência oscila para frente e para trás, tornando-se indeterminada. Isso resulta em nenhuma sincronização da entrada do controle e nenhuma possibilidade de acompanhamento de quadros no PIX.Dos problemas apresentados neste tópico, este é o mais importante. Não se sabe por que isso funciona apenas às vezes, quando você agenda o evento ORIGIN e depois aguarda por ele.
Demonstrações
Ritmo de quadros no PIX System Monitor
Figura 19. Ritmo de quadros no PIX System Monitor.
- Frames per Second: autoexplicativo.
- Total Frame Latency: tempo desde o evento ORIGIN até que o quadro tenha sido invertido (flip) e se tornado o buffer frontal.
- Frame Origin: quanto tempo após o Vblank o evento ORIGIN ocorreu (IntervalOffsetInMicroseconds)?
- Present to Flip: com quanta antecedência o quadro ficou pronto (
PresentXchegou à GPU) antes de ser invertido? - Wait for Signal: por quanto tempo
WaitFrameEventX(ORIGIN)ficou bloqueado?
Exemplo de ritmo de quadros
Figura 20. Interface do usuário do exemplo de ritmo de quadros.
- Menu:
- Localizado no canto superior direito.
- Dividido em duas seções:
- Parâmetros de carga do quadro.
- Parâmetros de ritmo de quadros.
- As cargas de quadro são impostas por temporizadores.
- Gráficos:
- Três gráficos de linhas localizados no canto superior esquerdo.
- Tempo/taxa de quadros.
- Latência ideal.
- Margem de latência (margem de segurança).
- Uma margem negativa indica um quadro atrasado.
- Três gráficos de linhas localizados no canto superior esquerdo.
- Linha do tempo:
- Semelhante a uma captura de tempo simplificada do PIX.
- Exibe as informações a seguir.
- Atualização da CPU.
- Renderização da CPU.
- Fila de gráficos da GPU.
- Fila de computação da GPU.
- Flip.
- Flip informa qual buffer da cadeia de troca está sendo exibido.
- O gráfico da linha do tempo é codificado por cores de acordo com o buffer da cadeia de troca.
- O exemplo na figura 20 tem três buffers da cadeia de troca representados por cores: roxo, verde e amarelo.
Fração de quadro de latência
A API anterior não permitia uma fração de quadro de latência. No entanto, a nova API permite, possibilitando que o título especifique deslocamentos fracionários em microssegundos. Figura 21. Fração de quadro de latência 1/2.

Latência desacoplada do tamanho da cadeia de troca
Figura 23. Latência desacoplada do tamanho da cadeia de troca 1/2. Com a nova API, a latência não depende mais do comprimento da cadeia de troca. Esta demonstração mostra o que acontece quando:- O tamanho da cadeia de troca é o tamanho que recomendamos.
- O tamanho da cadeia de troca é menor do que o recomendado.


Present. Um travamento também pode ocorrer em WaitOnSwapChainSurface na GPU se o buffer ainda estiver sendo exibido quando passar do estado PRESENT para o estado gravável. Quanto mais você puder adiar essas operações, menos buffering será necessário.
Você pode fazer com que esses travamentos ocorram no exemplo de ritmo de quadros diminuindo os tempos de CPU Update e CPU Render.
Modulação da latência
Figura 25. Modulação da latência 1/4.



Próximas etapas
Se quiser revisar informações dos materiais da apresentação relacionados a esta visão geral, baixe os arquivos a seguir do XBOX Developer Download Secure Data Storage (SDS).- Apresentação de slides: GR03_Understanding Frame Pacing and Latency with the GDKX Presentation APIs.pptx em Xfest 2022 Graphics Track PowerPoints (Xfest2022GfxPPTs.ZIP)
- Apresentação em vídeo: GR03.mp4 em Xfest 2022 Graphics Track Videos (Xfest2022GfxVids.ZIP)
Baixar as apresentações e os vídeos do Microsoft PowerPoint
- Acesse Downloads do Game Development Kit (GDK).
- Em Select file type, selecione Conference Materials e, em seguida, selecione Confirm.
- Em Select build/version number, escolha uma das apresentações de gráficos a seguir.
- Xfest 2022 Graphics Track PowerPoints
- Xfest 2022 Graphics Track Videos
- Selecione Download Now e aguarde até que o download do arquivo .zip seja concluído.
- Acesse a pasta Downloads local e descompacte (extraia) os arquivos da pasta compactada.
Baixar os exemplos de console do Microsoft Game Development Kit mencionados neste artigo
- Acesse Downloads do Game Development Kit (GDK).
- Em Select file type, selecione GDK e, em seguida, selecione Confirm.
- Em Select build/version number, escolha a opção Microsoft GDKX (Sample only) mais recente.
- Selecione Download Now e aguarde até que o download do arquivo .zip seja concluído.
- Acesse a pasta Downloads local e descompacte (extraia) os arquivos da pasta compactada.
- Selecione GDKSamples > Graphics e, em seguida, procure o exemplo.
