Следуя другим вопросам, связанным с переполнением стека, я прочитал руководство для внутренних поверхностей 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.
В документе, который я упоминал выше, представлен хореограф как способ получить обратные вызовы для каждого vsync. Однако я не убежден, что этого достаточно, чтобы добиться минимального отставания, которое я испытываю. Я тестировал выполнение requestRender() в обратном вызове vsync с минимальным значением onDrawFrame(), и он действительно демонстрирует поведение счетчика 0/1. Однако что, если SurfaceFlinger не сможет выполнить всю свою работу за один период (возможно, появляется уведомление или что-то еще)? В этом случае я ожидаю, что мой рендеринг с радостью будет генерировать 1 кадр за vsync, но потребительский конец этого BufferQueue сбросил фрейм. Результат: теперь мы чередуем между 1 и 2 буферами в нашей очереди, и мы получили кадр задержки между выполнением рендеринга и просмотром фрейма.
В документе, как представляется, предлагается посмотреть смещение по времени между сообщенным временем vsync и началом обратного вызова. Я вижу, как это может помочь, если ваш обратный вызов доставляется последним из-за вашего основного потока из-за передачи макета или чего-то еще. Однако я не думаю, что это позволило бы обнаружить SurfaceFlinger, пропуская бит и не используя раму. Есть ли способ, которым приложение может работать, что SurfaceFlinger сбросил фрейм? Также кажется неспособностью сказать, что длина очереди нарушает идею использования времени vsync для обновлений состояния игры, так как там будет отображаться неизвестное количество кадров в очереди до того, как будет отображаться тот, который вы показываете.
Уменьшение максимальной длины очереди и использование избыточного давления было бы одним из способов достижения этого, но я не думаю, что существует API для установки максимального количества буферов в GLSurfaceView BufferQueue?