Minimize o atraso do Android GLSurfaceView

Após algumas outras perguntas sobre o estouro de pilha, li o guia para as partes internas das superfícies Android, SurfaceViews, etc. aqui:

https://source.android.com/devices/graphics/architecture.html

Esse guia me deu uma compreensão muito melhorada de como todas as diferentes peças se encaixam no Android. Ele aborda como o eglSwapBuffers apenas empurra o quadro renderizado para uma fila que mais tarde será consumida pelo SurfaceFlinger quando preparar o próximo quadro para exibição. Se a fila estiver cheia, aguardará até que um buffer fique disponível para o próximo quadro antes de retornar. O documento acima descreve isso como "enchendo a fila" e confiando na "contrapressão" dos buffers de troca para limitar a renderização ao vsync da exibição. É o que acontece usando o modo de renderização contínua padrão do GLSurfaceView.

Se a sua renderização for simples e for concluída em muito menos que o período do quadro, o efeito negativo disso será um atraso adicional causado pelo BufferQueue, pois a espera no SwapBuffers não ocorrerá até que a fila esteja cheia e, portanto, o quadro ' a renderização re está sempre destinada a estar na parte de trás da fila e, portanto, não será exibida imediatamente no próximo vsync, pois provavelmente haverá buffers antes da fila.

Por outro lado, a renderização sob demanda geralmente ocorre com muito menos frequência do que a taxa de atualização de exibição, portanto, normalmente, o BufferQueues para essas visualizações está vazio e, portanto, quaisquer atualizações enviadas para essas filas serão capturadas pelo SurfaceFlinger no próximo vsync.

Então, aqui está a pergunta: como posso configurar um renderizador contínuo, mas com um atraso mínimo? O objetivo é que a fila de buffers esteja vazia no início de cada vsync, renderizo meu conteúdo em menos de 16 ms, empurro-o para a fila (contagem de buffer = 1) e, em seguida, é consumido pelo SurfaceFlinger no próximo vsync (contagem de buffer = 0), repita. O número de buffers na fila pode ser visto na systrace, portanto, o objetivo é ter essa alternativa entre 0 e 1.

O documento mencionado acima apresenta o Choreographer como uma maneira de obter retornos de chamada em cada vsync. No entanto, não estou convencido de que seja suficiente para conseguir o comportamento de atraso mínimo que estou buscando. Eu testei fazendo um requestRender () em um retorno de chamada vsync com um mínimo de onDrawFrame () e ele realmente exibe o comportamento de contagem de buffer 0/1. No entanto, e se o SurfaceFlinger não for capaz de fazer todo o seu trabalho dentro de um único período de quadro (talvez uma notificação seja exibida ou o que for)? Nesse caso, espero que meu renderizador produza 1 quadro por vsync, mas o consumidor final desse BufferQueue perdeu um quadro. Resultado: agora estamos alternando entre 1 e 2 buffers em nossa fila e ganhamos um quadro de atraso entre fazer a renderização e ver o quadro.

O documento parece sugerir uma análise do tempo entre o tempo vsync relatado e quando o retorno de chamada é executado. Eu posso ver como isso pode ajudar se seu retorno de chamada for entregue com atraso devido ao seu thread principal devido a um passe de layout ou algo assim. No entanto, acho que isso não permitiria detectar o SurfaceFlinger pulando uma batida e deixando de consumir um quadro. Existe alguma maneira de o aplicativo descobrir que o SurfaceFlinger caiu um quadro? Também parece que a incapacidade de dizer se a duração da fila interrompe a ideia de usar o tempo vsync para atualizações do estado do jogo, pois há um número desconhecido de quadros na fila antes que o que você está renderizando seja realmente exibido.

Reduzir o comprimento máximo da fila e confiar na contrapressão seria uma maneira de conseguir isso, mas não acho que exista uma API para definir o número máximo de buffers no GLSurfaceView BufferQueue?