Quanto tempo leva para o OpenGL realmente atualizar a tela?

9

Eu tenho um aplicativo de teste OpenGL simples em C, que desenha coisas diferentes em resposta às principais entradas. (Mesa 8.0.4, tentado com o Mesa-EGL e com o GLFW, Ubuntu 12.04LTS em um PC com NVIDIA GTX650). Os sorteios são bem simples / rápidos (tipo de triângulo rotativo). Meu código de teste não limita a taxa de quadros de forma alguma, apenas se parece com isso:

while (true)
{
    draw();
    swap_buffers();
}

Eu cronometrei isso com muito cuidado e acho que o tempo entre uma eglSwapBuffers()(ou a glfwSwapBuffersmesma coisa) chamada para a próxima é de ~ 16,6 milissegundos. O tempo de depois de uma chamada para eglSwapBuffers()antes da próxima chamada é apenas um pouco menor que isso, mesmo que o que é desenhado seja muito simples. O tempo que a chamada dos buffers de troca demora é bem inferior a 1 ms.

No entanto, o tempo entre o aplicativo alterar o desenho, em resposta ao pressionamento de tecla e a alteração que realmente aparece na tela é> 150ms (aproximadamente 8 a 9 quadros). Isso é medido com uma gravação de câmera da tela e teclado a 60fps.

Portanto, as perguntas:

  1. Onde os sorteios são armazenados em buffer entre uma chamada para trocar buffers e realmente aparecer na tela? Por que o atraso? Parece que o aplicativo está desenhando muitos quadros à frente da tela o tempo todo.

  2. O que um aplicativo OpenGL pode fazer para causar um empate imediato na tela? (ou seja: sem buffer, apenas bloqueie até que o sorteio seja concluído; não preciso de alta taxa de transferência, preciso de baixa latência)

  3. O que um aplicativo pode fazer para tornar o sorteio imediato acima o mais rápido possível?

  4. Como um aplicativo pode saber o que realmente está na tela agora? (Ou quanto tempo / quantos quadros o atraso atual do buffer?)

Alex I
fonte
Você pode separar o tempo que o teclado leva para notificar seu aplicativo quanto tempo leva para renderizar isso?
ThorinII
@ ThorinII: Ponto justo. Provavelmente, posso configurar algo hacky usando um LED em uma porta paralela etc. para obter uma indicação de quando o aplicativo realmente pressiona as teclas. Ainda assim, é apenas fgetc (stdin), quão lento isso pode ser? : /
Alex I
Fiquei com a impressão de que o glFlush foi usado para causar uma descarga imediata de todos os comandos.
Programmdude
11
@AlexI verifique se esse gamedev.stackexchange.com/questions/66543/… pode ajudá-lo
concept3d
11
@ concept3d: Sim, é basicamente respondido, apenas pensei que você gostaria de algum representante extra :) Aparentemente, eu tenho que esperar um dia para premiá-lo.
que você

Respostas:

6

Qualquer função da API de desenho chamada da CPU será enviada ao buffer do anel de comando da GPU para ser executada posteriormente pela GPU. Isso significa que as funções do OpenGL são principalmente funções sem bloqueio. Portanto, a CPU e a GPU estarão trabalhando em paralelo.

O mais importante a ser observado é que seu aplicativo pode ser vinculado à CPU ou GPU. Depois que você chama glFinish, a CPU espera que a GPU conclua seus comandos de desenho, se a GPU estiver demorando mais e puder / estiver causando a paralisação da CPU, seus aplicativos estarão vinculados à GPU. Se a GPU terminar de desenhar comandos e a CPU estiver demorando muito para glFinish, seu aplicativo estará vinculado à CPU.

E note que há uma diferença entre glFlushe glFinish.

glFlush: indica que todos os comandos que foram enviados anteriormente ao GL devem ser concluídos em tempo finito.

glFinish: força todos os comandos GL anteriores a serem concluídos. Concluir não retorna até que todos os efeitos dos comandos emitidos anteriormente no cliente GL e no estado do servidor e no framebuffer sejam totalmente realizados. "

glXSwapBuffers executa um glFlush implícito antes de retornar. Os comandos OpenGL subsequentes podem ser emitidos imediatamente após a chamada glXSwapBuffers, mas não são executados até que a troca de buffer seja concluída.

O tempo real do quadro provavelmente será determinado por qual das duas CPU / GPU está demorando mais tempo para concluir seu trabalho.

concept3d
fonte
Isso é muito útil. Algumas correções possíveis: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml "glXSwapBuffers executa um glFlush implícito antes de retornar", parece que na verdade não é um glFinish, portanto o buffer de comando pode ter bastante coisas nele quando os buffers de troca retornam.
que você
... Além disso, acho que o ponto mais interessante é que meu aplicativo de teste muito simples não tem CPU nem GPU limitado - não tem muito trabalho a fazer aqui - mas, de outra forma, ele acaba com a taxa de quadros baixa (exatamente o mesmo que o monitor taxa de atualização ??) e alta latência (por causa do buffer de comando, na verdade você explicou essa parte muito bem).
que você
@AlexI both low framerate (exactly same as monitor refresh rate??no, a menos que você esteja usando explicitamente o VSync.
precisa saber é o seguinte
@AlexI Também quero salientar que o tempo de quadro é diferente do FPS, use o tempo de quadro para criar um perfil do seu aplicativo porque é linear. O FPS, por outro lado, é uma medida ruim que não é linear.
precisa saber é o seguinte
Não estou explicitamente usando o vsync. Não estou fazendo nada para alterar as configurações padrão do vsync, nem sei onde alterá-las no OpenGL. Acabei de obter exatamente 60fps (dentro de um por cento).
que você
4

O OpenGL nunca atualiza a tela, tecnicamente.

Existe uma API do sistema de janelas separada da GL (por exemplo, GLX, WGL, CGL, EGL) que faz isso. Os swaps de buffer usando essas APIs geralmente invocam implicitamente, glFlush (...)mas em algumas implementações (por exemplo, o rasterizador GDI no Windows), ele faz um total glFinish (...):

 

    * No lado esquerdo, é o caminho do CDI (hardware) SwapBuffers (...). No lado direito, o caminho da GDI (software).

Se você tiver o VSYNC ativado e o buffer duplo, qualquer comando que modifique o buffer de fundo antes que ocorra uma troca pendente deverá parar até a troca. Há uma profundidade limitada na fila de comandos; portanto, esse comando interrompido acabará causando um congestionamento de tráfego no pipeline. Isso pode significar que, em vez de bloquear SwapBuffers (...)o aplicativo, ele bloqueia algum comando GL não relacionado até o VBLANK rolar. O que realmente se resume é quantos buffers de retorno você possui em sua cadeia de swap.

Desde que todos os buffers traseiros estejam cheios de quadros concluídos ainda a serem movidos para a frente, os buffers swap implicitamente causarão o bloqueio. Infelizmente, não há como controlar explicitamente o número de buffers de retaguarda usados ​​pela maioria das APIs do sistema de janelas GL (além de 0 com buffer único ou 1 com buffer duplo). O driver é livre para usar 2 buffers traseiros, se desejar (buffer triplo), mas você não pode solicitar isso no nível do aplicativo usando algo como GLX ou WGL.

Andon M. Coleman
fonte
Andon: Boa informação, obrigado. Eu acho que o que estou vendo é parcialmente um buffer duplo, mas: "a tentativa de modificar o buffer de fundo antes que a troca ocorra deve bloquear" - por quê? parece que tudo pode ser mandado para o buffer de comando, incluindo a troca :)
Alex I
"controla explicitamente o número de buffers retroativos usados ​​pela maioria das APIs do sistema de janelas GL (além de 0 com buffer único ou 1 com buffer duplo)" - como se controla o buffer único / duplo?
que você
@AlexI: esclareci o idioma com relação ao motivo pelo qual o comando pode levar ao bloqueio. Em outras APIs, existe um conceito conhecido como render-ahead, que é efetivamente a profundidade da fila de comandos. Quanto ao controle de buffer único / duplo, que é controlado pelo formato de pixel que você seleciona ao criar seu contexto de renderização. No WGL, você pode selecionar buffer único ou duplo, mas se você selecionar buffer duplo, o driver poderá realmente criar 2 buffers traseiros (portanto, o buffer duplo se tornará buffer triplo) se o usuário definir seu driver para fazer isso.
Andon M. Coleman
Na verdade, você não deseja renderizar muitos quadros à frente porque, embora isso reduza o bloqueio, também aumenta a latência. A melhor maneira de minimizar a latência é na verdade chamar glFinish (...)imediatamente após a troca de buffers. Isso limpará a fila de comandos, mas também significa que a GPU e a CPU serão sincronizadas (o que não é bom se você quiser manter a GPU funcionando sempre).
Andon M. Coleman
1

Presumo que você esteja familiarizado com este experimento ?

Essencialmente, John Carmack estava fazendo algo semelhante, gravando a tela e sincronizando os pixels enviados para a tela. Ele descobriu que boa parte da latência veio da tela. Outros fatores foram o atraso de entrada do teclado, drivers de vídeo e / ou a execução do próprio programa.

MichaelHouse
fonte