Минимизировать отставание Android GLSurfaceView

Следуя другим вопросам о переполнении стека, я прочитал руководство по внутренним компонентам Android Surfaces, SurfaceViews и т. Д. Здесь:

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

Это руководство дало мне гораздо лучшее понимание того, как все разные части сочетаются друг с другом на Android. В нем рассказывается, как eglSwapBuffers просто помещает визуализированный кадр в очередь, которая впоследствии будет использоваться SurfaceFlinger при подготовке следующего кадра для отображения. Если очередь заполнена, она будет ждать, пока буфер не станет доступным для следующего кадра, прежде чем вернуться. В приведенном выше документе это описывается как «заполнение очереди» и использование «противодействия» буферов подкачки для ограничения рендеринга vsync дисплея. Это то, что происходит, используя стандартный режим непрерывной визуализации GLSurfaceView.

Если ваш рендеринг прост и завершается гораздо меньше, чем период кадра, отрицательным эффектом этого является дополнительное отставание, вызванное BufferQueue, поскольку ожидание SwapBuffers не происходит до тех пор, пока очередь не заполнится, и, следовательно, кадр, который мы ' рендеринг всегда предназначен для того, чтобы быть в конце очереди, и поэтому не будет отображаться сразу при следующей vsync, так как в очереди есть вероятные буферы перед ним.

В отличие от этого, рендеринг по требованию, как правило, происходит гораздо реже, чем частота обновления дисплея, поэтому обычно BufferQueues для этих представлений пусты, и поэтому любые обновления, помещенные в эти очереди, будут захвачены SurfaceFlinger во время следующей vsync.

Итак, вот вопрос: как я могу настроить непрерывный рендер, но с минимальным запаздыванием? Цель состоит в том, чтобы очередь буфера была пуста в начале каждого vsync, я рендерил свой контент менее чем за 16 мсек, помещал его в очередь (количество буферов = 1), а затем он использовался SurfaceFlinger при следующем vsync (количество буферов) = 0), повторите. Количество буферов в очереди можно увидеть в systrace, поэтому цель состоит в том, чтобы это чередовалось между 0 и 1.

Документ, о котором я упоминал выше, представляет Choreographer как способ получения обратных вызовов для каждого vsync. Однако я не уверен, что этого достаточно для достижения минимального лаг-поведения, которого я добиваюсь. Я протестировал выполнение requestRender () для обратного вызова vsync с очень минимальным onDrawFrame (), и он действительно демонстрирует поведение числа буферов 0/1. Однако, что если SurfaceFlinger не сможет выполнить всю свою работу в течение одного периода кадра (возможно, появится уведомление или что-то еще)? В этом случае я ожидаю, что мой рендерер с радостью будет производить 1 кадр на vsync, но потребительская часть этого BufferQueue пропустила кадр. Результат: теперь мы чередуем от 1 до 2 буферов в нашей очереди, и у нас есть задержка между рендерингом и просмотром кадра.

Похоже, что документ предлагает посмотреть на смещение времени между сообщенным временем vsync и временем выполнения обратного вызова. Я вижу, как это может помочь, если ваш обратный вызов доставлен поздно из-за вашего основного потока из-за прохода макета или чего-то еще. Однако я не думаю, что это позволило бы обнаружить, что SurfaceFlinger пропускает такт и не использует кадр. Есть ли способ, которым приложение может решить, что SurfaceFlinger уронил кадр? Также кажется, что неспособность сказать, что длина очереди нарушает идею использования времени vsync для обновлений состояния игры, так как в очереди есть неизвестное количество кадров, прежде чем тот, который вы рендерит, будет фактически отображаться.

Сокращение максимальной длины очереди и использование обратного давления было бы одним из способов достижения этого, но я не думаю, что есть API для установки максимального количества буферов в GLSurfaceView BufferQueue?