Skip to main content
Este tópico descreve as APIs de ritmo de quadros do Microsoft Game Development Kit com XBOX Extensions (GDKX) para agendamento de quadros. São abordados os objetivos, os recursos e as armadilhas dessas APIs. Você aprenderá a usar essas APIs para equilibrar a estabilidade do jogo e a latência.
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
Elas estão disponíveis há vários anos, mas não foram muito abordadas na documentação do GDKX. Figura 1. APIs de ritmo de quadros com uma alteração interruptiva. APIs de ritmo de quadros Na figura 1, o exemplo de código tem texto em preto e texto em magenta. Houve uma alteração interruptiva histórica que se tornou obrigatória em abril de 2019. As APIs à esquerda em preto são extensões padrão do Direct3D ou do XDK. Se você estava usando a API 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. A maneira antiga - inicialização A figura 2 representa o uso anterior da API 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. A maneira antiga - loop de quadros Anteriormente, para cada quadro, você usava o processo a seguir.
  1. Solicitar um buffer à cadeia de troca.
  2. Renderizar a imagem do quadro no buffer da cadeia de troca.
  3. 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. A nova maneira - inicialização A figura 4 mostra a inicialização da apresentação. Ao iniciar o jogo, você deve chamar mais código porque seu título tem mais controle do que antes. O título cria seus próprios buffers da cadeia de troca, especifica a taxa de quadros com antecedência e, em seguida, cria um evento de origem do quadro. Conforme mostrado na figura 4, em vez de criar uma interface de cadeia de troca, você cria seus próprios buffers da cadeia de troca um de cada vez, assim como recursos típicos. O sinalizador 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. A nova maneira - loop de quadros Conforme mostrado na figura 5, em vez de apenas chamar 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 APIs Present 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. Extensibilidade para vários planos de exibição A figura 6 mostra alguns valores fixos no código em magenta, mas não para os dois planos de exibição. Ao chamar 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. Desacoplar a taxa de renderização dos planos de exibição Conforme mostrado na figura 7, o desacoplamento das taxas de renderização é implícito na nova API porque você passa os recursos que serão exibidos. Como resultado, você pode passar o mesmo recurso para o plano da interface do usuário duas vezes seguidas.

Problema 3/10: sem PresentationThreshold para dar suporte a tearing limitado

O próximo problema é o PresentationThreshold 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. Solução: PresentationThreshold para dar suporte a tearing limitado A figura 8 mostra o uso de ImmediateThresholdPercent da nova API. 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. Suporte para taxas de quadros superiores a 60 fps Conforme mostrado na figura 9, a solução é usar o parâmetro LengthInMicroseconds. Ele ainda é um inteiro sem sinal, mas agora está em microssegundos. Qualquer taxa de quadros pode ser expressa aqui. Passe a duração em microssegundos que você deseja e, se a duração tiver suporte, a API será bem-sucedida. Se, em algum momento no futuro, for necessário dar suporte a 240 FPS, a API não precisará mudar. Quando o suporte a 24 FPS foi adicionado recentemente, a API não mudou.
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 chamava Present, 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. Maneira de concluir o quadro na fila de computação A figura 10 mostra que, com a nova API, não há mais uma interface de cadeia de troca. 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. Controle refinado sobre a limitação A figura 11 mostra a forma como a nova API é controlada. Esse controle é feito usando os dois parâmetros que estão em texto magenta. (Eles serão abordados com mais detalhes mais adiante neste tópico.) O primeiro parâmetro é PeriodInIntervals. Ele indica quantos intervalos da taxa de quadros são necessários para concluir um quadro do início ao fim. Por exemplo, se a taxa de quadros for de 60 FPS, o intervalo da taxa de quadros será de 16,7 ms. Pode ser necessário mais de um desses intervalos do início ao fim do quadro. O segundo parâmetro é IntervalOffsetInMicroseconds. Use-o para uma fração de quadro de latência. Talvez o período em intervalos fosse dois, mas, na realidade, era necessário algo entre um e dois. Um valor apropriado de deslocamento de intervalo pode ser encontrado para conseguir isso.

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. Sincronização explícita com a entrada do controle A figura 12 mostra uma das coisas para as quais o evento ORIGIN é útil se você o agendar de forma previsível. Um controle sem fio pode enviar uma atualização bem a tempo, talvez um ms antes de o título precisar dela. Isso reduz a latência, e é a mesma latência em todos os quadros.

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. Notificação de eventos significativos Conforme mostrado na figura 13, a nova API dá suporte a dois eventos por padrão: a notificação de Vblank e a notificação de Flip. O evento Flip ocorre quando um novo buffer da cadeia de troca se torna visível e é extensível. A notificação de Flip é uma maneira conveniente de saber quando é seguro reutilizar um buffer de troca. Mais coisas poderiam ser adicionadas ao Flip sem alterar a API.

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ê chamava Present 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. Maneira de o PIX mostrar um quadro A figura 14 mostra um token de pipeline de quadros. Ao aguardar o evento ORIGIN, você recebe um token de pipeline de quadros como valor de retorno. Armazene-o, pois você precisará dele mais tarde. Ao chamar 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 Present chegou à GPU?
Um título pode resolver alguns desses problemas por conta própria usando consultas. No entanto, seria melhor se o driver fornecesse todas essas informações em um só lugar.

Solução: maneira de um título recuperar dados de tempo detalhados

Figura 15. Maneira de um título recuperar dados de tempo detalhados. Maneira de um título recuperar dados de tempo detalhados A figura 15 mostra que a nova API recupera dados de tempo detalhados usando GetFrameStatisticsX. Ela fornece muitas estatísticas. Ela também é extensível da mesma forma que as outras APIs. Novos tipos de estatísticas podem ser adicionados sem alterar a assinatura da API.

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. Modulação da latência: três etapas simples A figura 16 mostra as três etapas recomendadas para modular a latência.
  1. Use as estatísticas de quadros para medir a latência ideal no seu título.
  2. Adicione uma margem para a variabilidade.
  3. Calcule seus parâmetros.
Na primeira etapa, há duas estatísticas importantes: o SignalTime do ORIGIN, que sinaliza o início do quadro, e o GPUProcessTime, que sinaliza o fim do quadro quando `Present’ é chamado e chega à GPU. O buffer de quadros que você deseja exibir é concluído, e a diferença entre as duas estatísticas é a latência ideal do seu título. Na segunda etapa, é melhor ter uma margem para quando a latência ideal não estiver exatamente correta. Se a estimativa de margem for muito baixa e a próxima latência for maior, você perderá quadros. Se a estimativa for muito alta, sua latência será maior do que precisa ser. A terceira etapa é usar todas essas informações para calcular os dois 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. Exemplo de modulação da latência A figura 17 mostra um exemplo de modulação da latência. Este exemplo pressupõe um título de 60 Hz com um intervalo de quadros de 16,7 ms. A latência medida é de 30 ms com base na diferença entre 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. Ritmo de quadros no PIX System Monitor A figura 19 destaca a seção D3D (Direct3D) do PIX System Monitor. A seção D3D acompanha eventos importantes para calcular a latência de quadros de ponta a ponta. A demonstração descreve os elementos a seguir.
  • 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 (PresentX chegou à 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. Exemplo de ritmo de quadros A figura 20 mostra a interface do usuário do exemplo de ritmo de quadros (Frame Pacing Sample). O exemplo não foi lançado ao público. O exemplo cria artificialmente tempos de quadro na CPU e na GPU e, em seguida, exibe os resultados na interface do usuário. A interface do usuário é dividida nas partes a seguir.
  • 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.
  • 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. Fração de quadro de latência 1 de 2 A figura 21 mostra o exemplo sem uma fração de quadro de latência. Observe que Frame Offset (ms) é zero. A atualização da CPU (evento ORIGIN) ocorre no Vblank. Isso significa que a latência é um número inteiro de quadros. Figura 22. Fração de quadro de latência 2/2. Fração de quadro de latência 2 de 2 A figura 22 mostra o exemplo em que Frame Offset (ms) está definido como três. Esse deslocamento é o maior que pode ser definido sem perder quadros. Isso deixa uma margem de latência de quase zero, o que é perigosamente baixo. Observe que os eventos ORIGIN, onde começam as atualizações da CPU, não estão mais sincronizados com os Vblanks onde o Flip termina.

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.
Latência desacoplada do tamanho da cadeia de troca 1 de 2 A figura 23 mostra o exemplo em que Frame Period está definido como dois. Isso geralmente requer três buffers de quadros. Observe que há três cores nas barras de tempo na parte inferior: roxo, verde e amarelo. Cada cor representa um buffer da cadeia de troca. Figura 24. Latência desacoplada do tamanho da cadeia de troca 2/2. Latência desacoplada do tamanho da cadeia de troca 2 de 2 A figura 24 mostra o exemplo em que o buffer de quadros foi alterado para dois. Observe que agora há duas cores nas barras de tempo na parte inferior: verde e amarelo. Observe também que ela é semelhante à figura 23. Na figura 24, a latência ideal no gráfico de linhas é 30,2. A margem de latência é 3,1. Se outro buffer de quadros for adicionado (figura 23), não haverá alteração. A latência do quadro permanece a mesma. O buffer de quadros extra não aumenta a latência, embora haja um custo adicional de memória. Esta demonstração também mostra que a taxa de quadros não mudou, embora o Flip verde do quadro N se sobreponha às barras verdes do quadro N+2. A taxa de quadros permaneceu estável em 59,0 FPS. Travamentos ainda podem ocorrer. Por exemplo, podem ocorrer travamentos em BlockOnFence_SwapThrottle na CPU se o buffer ainda estiver sendo exibido quando for passado para 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. Modulação da latência 1 de 4 A figura 25 mostra o exemplo com CPU Update aumentado para 14 ms. Observe que há uma margem de latência negativa. O título está perdendo os prazos dos quadros, e quadros estão sendo perdidos. De acordo com o Frame load do Menu, nenhuma das etapas de processamento custa menos de um intervalo de quadros (16,7 ms). Isso é o que acontece quando sua previsão de latência é agressiva demais. Figura 26. Modulação da latência 2/4. Modulação da latência 2 de 4 A figura 26 mostra o exemplo com o modo de ritmo automático de quadros iniciado. Ele corrigiu os quadros que estavam sendo perdidos aumentando o Frame Period de dois para três. Observe que o Frame Offset mudou de 0,00 para 7,45 ms, e a Legacy margin agora mostra 8,3 ms. Figura 27. Modulação da latência 3/4. Modulação da latência 3 de 4 A figura 27 mostra Auto Frame Load habilitado e um cenário em que um quadro excede o orçamento a cada segundo. Essa falha da GPU faz com que o próximo quadro seja perdido. Isso é mostrado no gráfico Frame time e nas lacunas na linha do tempo. Há uma falha a cada segundo porque a margem de latência é muito baixa. Figura 28. Modulação da latência 4/4. Modulação da latência 4 de 4 A figura 28 mostra o mesmo cenário com o modo Auto Frame Pacing ativado novamente. O algoritmo corrige esse problema sendo menos agressivo com as previsões de latência. A margem de latência aumenta para 8,3 ms e evita a perda de quadros. O gráfico da margem de latência não cairá abaixo de 0.

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

  1. Acesse Downloads do Game Development Kit (GDK).
  2. Em Select file type, selecione Conference Materials e, em seguida, selecione Confirm.
  3. 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
  4. Selecione Download Now e aguarde até que o download do arquivo .zip seja concluído.
  5. 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

  1. Acesse Downloads do Game Development Kit (GDK).
  2. Em Select file type, selecione GDK e, em seguida, selecione Confirm.
  3. Em Select build/version number, escolha a opção Microsoft GDKX (Sample only) mais recente.
  4. Selecione Download Now e aguarde até que o download do arquivo .zip seja concluído.
  5. Acesse a pasta Downloads local e descompacte (extraia) os arquivos da pasta compactada.
  6. Selecione GDKSamples > Graphics e, em seguida, procure o exemplo.

Confira também

Introdução ao streaming de jogos Visão geral da compensação de latência do streaming de jogos Análise detalhada da compensação de latência do streaming de jogos
Last modified on October 6, 2026