Объект PixelBuffer и glReadPixel для блокировки Android (ARCore)

Я знаю, что по умолчанию 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;

В моем методе рисования каждый кадр называется. По моему мнению, glReadPixels не должен занимать какое-то время, но он занимает около 25 мс (в Google Pixel 2) и создает растровое изображение, занимая еще 40 мс. Это достигается только как 13 FPS, что хуже, чем glReadPixels без PBO.

Есть ли что-то, что мне не хватает или не так в моем коде?

Ответ 1

EDITED, так как вы указали, что моя первоначальная гипотеза была неправильной (исходный PboIndex == PboNextIndex). Надеясь быть полезным, вот код С++, который я только что написал на родной стороне, вызванный через JNI с Android, используя GLES 3. Кажется, что он работает, а не блокирует 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 PBOs, они не чередуются между glReadPixels и glMapBufferRange, которые затем блокируются до тех пор, пока GPU не завершит передачу данных. Я предлагаю это изменение, чтобы гарантировать, что ОПО чередуются:

mPboNewIndex = mPboIndex;
mPboIndex = (mPboNewIndex + 1) % 2;