Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PixelBuffer Object and glReadPixel on Android(ARCore) blocking

I know that the default glReadPixels() waits until all the drawing commands are executed on the GL thread. But when you bind a PixelBuffer Object and then call the glReadPixels() it should be asynchronous and will not wait for anything. But when I bind PBO and do the glReadPixels() it is blocking for some time.

Here's how I initialize the 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);

and then I use the two buffers to ping-pong around:

    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;

This is called in my draw method every frame. From my understanding the glReadPixels should not take any time at all, but it's taking around 25ms (on Google Pixel 2) and creating the bitmap takes another 40ms. This only achieve like 13 FPS which is worse than glReadPixels without PBO.

Is there anything that I'm missing or wrong in my code?

like image 314
snowrain Avatar asked Nov 22 '17 20:11

snowrain


1 Answers

EDITED since you pointed out that my original hypothesis was incorrect (initial PboIndex == PboNextIndex). Hoping to be helpful, here is C++ code that I just wrote on the native side called through JNI from Android using GLES 3. It seems to work and not block on glReadPixels(...). Note there is only a single glPboIndex variable:

  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);

...

previous unfounded hypothesis:

Your question doesn't show the code that sets the initial values of mPboIndex and mPboNewIndex, but if they are set to identical initial values, such as 0, then they will have matching values within each loop which will result in mapping the same PBO that has just been read. In that hypothetical/real scenario, even if 2 PBOs are being used, they are not alternated between glReadPixels and glMapBufferRange which will then block until the GPU completes data transfer. I suggest this change to ensure that the PBOs alternate:

mPboNewIndex = mPboIndex;
mPboIndex = (mPboNewIndex + 1) % 2;
like image 163
c4augustus Avatar answered Sep 25 '22 08:09

c4augustus