Конвейер обработки и рендеринга камер с нулевым копированием на Android

Мне нужно выполнить процесс, доступный только для чтения, для данных с камеры в режиме реального времени (только с плоскости Y) с последующей визуализацией на GPU. Кадры не должны отображаться до тех пор, пока обработка не будет завершена (поэтому я не всегда хочу отображать последний кадр с камеры, а только последний, который на стороне ЦП завершил обработку). Рендеринг отделен от обработки камеры и рассчитан на 60 кадров в секунду, даже если кадры камеры поступают с более низкой скоростью, чем эта.

Есть связанный, но высокоуровневый вопрос:Самая низкая потолочная камера к процессору с GPU на Android

Чтобы немного подробнее описать текущую настройку: у нас есть пул буферов на стороне приложения для данных камеры, где буферы либо «свободны», либо «отображаются», либо «ожидают отображения». Когда приходит новый кадр с камеры, мы берем свободный буфер, сохраняем кадр (или ссылку на него, если фактические данные находятся в некотором системном пуле буферов), выполняем обработку и сохраняем результаты в буфере, затем установите буфер «ожидающий показ». В потоке рендеринга, если в начале цикла рендеринга есть какой-либо буфер «ожидающий показ», мы вместо этого фиксируем его на «отображаемом», рендерим камеру и визуализируем другой контент, используя обработанную информацию, вычисленную из того же самого рамка камеры.

Благодаря ответу @ fadden на поставленный выше вопрос, я теперь понимаю, что функция «параллельного вывода» API android camera2 разделяет буферы между различными очередями вывода, поэтому не должна включать какие-либо копии данных, по крайней мере, на современном Android.

В комментарии было предложение, чтобы я мог фиксировать выходы SurfaceTexture и ImageReader одновременно и просто «сидеть в буфере», пока обработка не будет завершена. К сожалению, я не думаю, что это применимо в моем случае из-за разрозненного рендеринга, который мы все еще хотим использовать со скоростью 60 кадров в секунду, и который все еще будет нуждаться в доступе к предыдущему кадру, пока новый обрабатывается, чтобы гарантировать, что вещи не получаются не синхронизировано.

Одно решение, которое пришло на ум, - это использование нескольких SurfaceTextures - по одному в каждом из наших буферов на стороне приложения (в настоящее время мы используем 3). С этой схемой, когда мы получим новый кадр камеры, мы получим свободный буфер из нашего пула на стороне приложения. Тогда мы бы позвонилиacquireLatestImage() на ImageReader, чтобы получить данные для обработки, и вызватьupdateTexImage() на SurfaceTexture в свободном буфере. Во время рендеринга нам просто нужно убедиться, что SufaceTexture из буфера "in display" привязан к GL, и все должно быть синхронизировано большую часть времени (как заметил @fadden, существует гонка между вызовомupdateTexImage() а такжеacquireLatestImage() но это временное окно должно быть достаточно маленьким, чтобы сделать его редким, и, возможно, его можно обнаружить и исправить в любом случае, используя временные метки в буферах).

Я отмечаю в документах, чтоupdateTexImage() может вызываться только тогда, когда SurfaceTexture привязан к контексту GL, что предполагает, что мне понадобится контекст GL в потоке обработки камеры, чтобы поток камеры могupdateTexImage() в SurfaceTexture в «свободном» буфере, в то время как поток рендеринга все еще может выполнять рендеринг из SurfaceTexture из буфера «in display».

Итак, к вопросам:

Это похоже на разумный подход?Являются ли SurfaceTextures в основном легкой оболочкой для общего пула буферов или они потребляют некоторый ограниченный аппаратный ресурс и должны использоваться экономно?Все ли вызовы SurfaceTexture настолько дешевы, что использование нескольких звонков по-прежнему будет большим выигрышем, чем копирование данных?Планируется ли создание двух потоков с разными контекстами GL с разными привязками SurfaceTexture в каждом из них, или я запрашиваю мир боли и ошибок драйверов?

Это звучит достаточно многообещающе, и я собираюсь попробовать; но подумал, что стоит спросить здесь на тот случай, если кто-нибудь (в основном @fadden!) знает какие-либо внутренние детали, которые я упустил, что сделало бы эту идею плохой.

Ответы на вопрос(1)

Ваш ответ на вопрос