Я создаю пользовательский предварительный просмотр камеры с помощью GLSurfaceView, используя OpenGl для визуализации кадров, предоставленных мне камерой. У меня полностью реализована камера, и она работает так, как я ожидал, чтобы камера работала без потери кадров в секунду, с правильным соотношением сторон и т. д. Но затем возникла проблема, когда мне нужно было захватить кадры, поступающие с камеры, и моей первой мыслью было использовать glReadPixles. ()
Используя GLES20.glReadPixels(), я обнаружил, что некоторые устройства испытывают потерю кадров в секунду, в основном это были устройства с более высоким разрешением экрана, это имеет смысл, потому что glReadPixels необходимо считывать больше пикселей с более высоким разрешением.
Я немного покопался и обнаружил, что у других были похожие проблемы с glReadPixels, и многие предлагали использовать PBO, а также использовать два из них, действующие как двойной буфер, который позволил бы мне считывать данные пикселей, не блокируя/останавливая текущий процесс рендеринга. Я полностью понимаю концепцию двойной буферизации, я новичок в OpenGL и нуждаюсь в некотором руководстве о том, как заставить работать PBO с двойной буферизацией.
Я нашел несколько решений для двойной буферизации PBO, но я никогда не могу найти полное решение, чтобы полностью понять, как оно взаимодействует с GLES.
Моя реализация GLSurfaceView.Renderer.onDrawFrame()
// mBuffer and mBitmap are declared and allocated outside of the onDrawFrame Method
// Buffer is used to store pixel data from glReadPixels
mBuffer.rewind();
GLES20.glUseProgram(hProgram);
if (tex_matrix != null)
{
GLES20.glUniformMatrix4fv(muTexMatrixLoc, 1, false, tex_matrix, 0);
}
GLES20.glUniformMatrix4fv(muMVPMatrixLoc, 1, false, mMvpMatrix, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex_id);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, GLConstants.VERTEX_NUM);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
// Read pixels from the current GLES context
GLES10.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mBuffer);
// Copy the Pixels from the buffer
mBitmap.copyPixelsFromBuffer(mBuffer);
GLES20.glUseProgram(0);
После изрядного количества исследований и раскопок я нашел решение для glReadPixels и как использовать PBO для буферизации изображений/кадров для последующей обработки.
Итак, первое, что нам нужно сделать, это предоставить дополнительную функцию в GLES2. В вашем модуле приложения добавьте новый каталог с именем cpp, затем создайте новый файл c с именем GlesHelper (или как вы хотите его назвать)
И вставьте следующий код:
#include <jni.h>
#include <GLES2/gl2.h>
JNIEXPORT void JNICALL
// Change
Java_com_your_full_package_name_helper_GlesHelper_glReadPixels(JNIEnv *env, jobject instance, jint x,
jint y, jint width, jint height,
jint format, jint type) {
// TODO
glReadPixels(x, y, width, height, format, type, 0);
}
Затем нам нужно добавить CMakeFile в корень вашего проекта. Щелкните правой кнопкой мыши, новый файл, введите CMakeLists.txt.
И вставьте следующий код
cmake_minimum_required(VERSION 3.4.1)
add_library( # Specifies the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main//cpp//GlesHelper.c )
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib}
GLESv2)
Теперь откройте файл вашего приложения/модуля build.gradle.
Вставьте это в раздел android.defaultConfig файла Gradle.
externalNativeBuild {
// Encapsulates your CMake build configurations.
cmake {
// Provides a relative path to your CMake build script.
cppFlags "-std=c++11 -fexceptions"
arguments "-DANDROID_STL=c++_shared"
}
}
Затем вставьте это в раздел android файла Gradle.
externalNativeBuild {
// Encapsulates your CMake build configurations.
cmake {
// Provides a relative path to your CMake build script.
path "CMakeLists.txt"
}
}
Итак, это все, что касается MakeFile и c, все настройки позволяют перейти к некоторому java
Создайте новый файл в своем проекте, который соответствует пакету в файле c, т.е. com_your_full_package_name_helper = com.your.full.package.name.helper
Убедитесь, что они совпадают правильно, то же самое с именем класса и именем функции.
Итак, ваш класс должен выглядеть так
package com.your.full.package.name.helper;
public class GlesHelper
{
public static native void glReadPixels(int x, int y, int width, int height, int format, int type);
}
Поскольку мы добавили собственный код в проект, нам нужно использовать System.loadLibrary("native-lib") для загрузки в нашем новом методе.
Прежде чем мы начнем следующий бит, добавьте эти переменные-члены в ваш Renderer.
/**
* The PBO Ids, increase the allocate amount for more PBO's
* The more PBO's the smoother the frame rate (to an extent)
* Side affect of having more PBO's the frames you get from the PBO's will lag behind by the amount of pbo's
*/
private IntBuffer mPboIds = IntBuffer.allocate(2);;
/**
* The current PBO Index
*/
private int mCurrentPboIndex = 0;
/**
* The next PBO Index
*/
private int mNextPboIndex = 1;
Итак, теперь нам нужно инициализировать наши PBO, это довольно просто.
// Generate the buffers for the pbo's
GLES30.glGenBuffers(mPboIds.capacity(), mPboIds);
// Loop for how many pbo's we have
for (int i = 0; i < mPboIds.capacity(); i++)
{
// Bind the Pixel_Pack_Buffer to the current pbo id
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(i));
// Buffer empty data, capacity is the width * height * 4
GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, capacity, null, GLES30.GL_STATIC_READ);
}
// Reset the current buffer so we can draw properly
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
Затем, прежде чем мы начнем рисовать, вызовите этот метод, он прочитает данные пикселей в pbo, поменяет буферы местами и предоставит вам доступ к данным пикселей.
/**
* Reads the pixels from the PBO and swaps the buffers
*/
private void readPixelsFromPBO()
{
// Bind the current buffer
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(mCurrentPboIndex));
// Read pixels into the bound buffer
GlesHelper.glReadPixels(0, 0, mViewWidth, mViewHeight, GLES20.GL_RGBA, GLES30.GL_UNSIGNED_BYTE);
// Bind the next buffer
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(mNextPboIndex));
// Map to buffer to a byte buffer, this is our pixel data
ByteBuffer pixelsBuffer = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, mViewWidth * mViewHeight * 4, GLES30.GL_MAP_READ_BIT);
if (mSkipFirstFrame)
{
// Skip the first frame as the PBO's have nothing in them until the second render cycle
}
// Set skip first frame to true so we can begin frame processing
mSkipFirstFrame = true;
// Swap the buffer index
mCurrentPboIndex = (mCurrentPboIndex + 1) % mPboIds.capacity();
mNextPboIndex = (mNextPboIndex + 1) % mPboIds.capacity();
// Unmap the buffers
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, GLES20.GL_NONE);
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES20.GL_NONE);
}
Итак, возвращаясь к моему первоначальному вопросу, наш Redner/onDrawMethod будет выглядеть примерно так.
// Use the OpenGL Program for rendering
GLES20.glUseProgram(mProgram);
// If the Texture Matrix is not null
if (textureMatrix != null)
{
// Apply the Matrix
GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, textureMatrix, 0);
}
// Apply the Matrix
GLES20.glUniformMatrix4fv(mMVPMatrixLoc, 1, false, mMvpMatrix, 0);
// Bind the Texture
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
// Draw the texture
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, GLConstants.VERTEX_NUM);
// Unbind the Texture
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
// Read from PBO
readPixelsFromPBO()
Я надеюсь, что это поможет кому-то, у кого есть аналогичная проблема с производительностью с glReadPixels или, по крайней мере, изо всех сил пытается внедрить PBO.
Возможно связано: Чтение данных пикселей из буфера кадра по умолчанию в OpenGL: производительность FBO против PBO