Android(ARCore)阻塞上的PixelBuffer对象和glReadPixel
Posted
技术标签:
【中文标题】Android(ARCore)阻塞上的PixelBuffer对象和glReadPixel【英文标题】:PixelBuffer Object and glReadPixel on Android(ARCore) blocking 【发布时间】:2018-05-06 16:57:40 【问题描述】:我知道默认的 glReadPixels() 会等到所有绘图命令都在 GL 线程上执行。但是当你绑定一个 PixelBuffer 对象然后调用 glReadPixels() 它应该是异步的并且不会等待任何东西。 但是当我绑定 PBO 并执行 glReadPixels() 时,它会阻塞一段时间。
这是我初始化 PBO 的方式:
mPboIds = IntBuffer.allocate(2);
GLES30.glGenBuffers(2, mPboIds);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(0));
GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, mPboSize, null, GLES30.GL_STATIC_READ); //allocates only memory space given data size
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(1));
GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, mPboSize, null, GLES30.GL_STATIC_READ);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
然后我使用两个缓冲区来乒乓球:
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(mPboIndex)); //1st PBO
JNIWrapper.glReadPixels(0, 0, mRowStride / mPixelStride, (int)height, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE); //read pixel from the screen and write to 1st buffer(native C++ code)
//don't load anything in the first frame
if (mInitRecord)
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
//reverse the index
mPboIndex = (mPboIndex + 1) % 2;
mPboNewIndex = (mPboNewIndex + 1) % 2;
mInitRecord = false;
return;
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, mPboIds.get(mPboNewIndex)); //2nd PBO
//glMapBufferRange returns pointer to the buffer object
//this is the same thing as calling glReadPixel() without a bound PBO
//The key point is that we can pipeline this call
ByteBuffer byteBuffer = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_PIXEL_PACK_BUFFER, 0, mPboSize, GLES30.GL_MAP_READ_BIT); //downdload from the GPU to CPU
Bitmap bitmap = Bitmap.createBitmap((int)mScreenWidth,(int)mScreenHeight, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(byteBuffer);
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
//reverse the index
mPboIndex = (mPboIndex + 1) % 2;
mPboNewIndex = (mPboNewIndex + 1) % 2;
在我的 draw 方法中每帧都会调用它。 据我了解,glReadPixels 根本不需要任何时间,但它大约需要 25 毫秒(在 Google Pixel 2 上)并且创建位图需要另外 40 毫秒。这只能达到 13 FPS,比没有 PBO 的 glReadPixels 差。
我的代码中有什么遗漏或错误吗?
【问题讨论】:
在我的情况下,调用只是阻塞了小缓冲区(例如 360x640 像素)。对于更大的缓冲区(例如 720x1280),调用变为异步的。可能是 GPU 的某种“优化”。我还没有找到禁用此行为的方法。 【参考方案1】:已编辑,因为您指出我的原始假设不正确(初始 PboIndex == PboNextIndex)。希望对您有所帮助,这是我刚刚在本机端编写的 C++ 代码,它使用 GLES 3 从 android 通过 JNI 调用。它似乎可以工作并且不会阻塞 glReadPixels(...)。注意只有一个 glPboIndex 变量:
glBindBuffer(GL_PIXEL_PACK_BUFFER, glPboIds[glPboIndex]);
glReadPixels(0, 0, frameWidth_, frameHeight_, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glPboReady[glPboIndex] = true;
glPboIndex = (glPboIndex + 1) % 2;
if (glPboReady[glPboIndex])
glBindBuffer(GL_PIXEL_PACK_BUFFER, glPboIds[glPboIndex]);
GLubyte* rgbaBytes = (GLubyte*)glMapBufferRange(
GL_PIXEL_PACK_BUFFER, 0, frameByteCount_, GL_MAP_READ_BIT);
if (rgbaBytes)
size_t minYuvByteCount = frameWidth_ * frameHeight_ * 3 / 2; // 12 bits/pixel
if (videoFrameBufferSize_ < minYuvByteCount)
return; // !!! not logging error inside render loop
convertToVideoYuv420NV21FromRgbaInverted(
videoFrameBufferAddress_, rgbaBytes,
frameWidth_, frameHeight_);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glPboReady[glPboIndex] = false;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
...
之前没有根据的假设:
您的问题没有显示设置 mPboIndex 和 mPboNewIndex 初始值的代码,但是如果将它们设置为相同的初始值,例如 0,那么它们将在每个循环中具有匹配的值,这将导致映射刚刚读过的同一个 PBO。在那个假设/真实场景中,即使使用了 2 个 PBO,它们也不会在 glReadPixels 和 glMapBufferRange 之间交替,这将阻塞直到 GPU 完成数据传输。我建议进行此更改以确保 PBO 交替:
mPboNewIndex = mPboIndex;
mPboIndex = (mPboNewIndex + 1) % 2;
【讨论】:
我的初始索引值设置如下:mPboIndex = 0;
mPboNewIndex = 1;
很抱歉我最初的假设是不正确且无用的:-(以上是关于Android(ARCore)阻塞上的PixelBuffer对象和glReadPixel的主要内容,如果未能解决你的问题,请参考以下文章
我的Android进阶之旅如何在Android中使用ARCore来增强人脸Augmented Faces?
我的Android进阶之旅如何在Android中使用ARCore来增强人脸Augmented Faces?
无法在 Android Studio 模拟器上安装 ARCore