我在处理 openGL 项目时遇到了一个不寻常的问题。本质上,我需要灰度单通道格式的帧数据来处理一些简历。我正在使用自定义着色器、FBO 和 PBO 来完成任务。
程序流程如下。
- 绑定生成的FBO
- draw() 到 FBO
- 绑定 PBO 和 glReadPixels()
- 从前一帧和 glMapBufferRange() 绑定 PBO
- 处理从 glMapBufferRange() 提供的像素数据
我想确认该过程运行正常。我想知道的是是否可以做任何事情来提高性能。我将发布一些我正在使用的代码,以便我们都可以遵循。
PBO 生成器代码
final int[] pbuffers = new int[2];
GLES30.glGenBuffers(2, pbuffers, 0);
for (int i = 0; i < pbuffers.length; i++) {
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbuffers[i]);
GLES30.glBufferData(GLES30.GL_PIXEL_PACK_BUFFER, width * height, null, GLES30.GL_DYNAMIC_READ);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
}
pbo_id[PBO_PRIMARY_ID] = pbuffers[0];
pbo_id[PBO_SECONDARY_ID] = pbuffers[1];
列表中的第 3 步 -> 绑定 PBO 和 glReadPixels()
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo_id[currentBuffer]);
GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0);
JNI.glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, 0);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
final int prevBuffer = previousBuffer;
previousBuffer = currentBuffer;
currentBuffer = prevBuffer;
列表中的第 4 步 -> 从前一帧和 glMapBufferRange() 绑定 PBO。这是从上一帧执行 glReadPixels 的 PBO。
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, pbo_id[currentBuffer]);
JNI.glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height, GL_MAP_READ_BIT);
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
这就是性能问题的来源。目前我正在读回 480 x 360 单通道灰度的像素(从着色器计算)。我已经运行了一些基准测试,结果如下。
40-50ms -> JNI.glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, 0);
0-1ms -> JNI.glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height, GL_MAP_READ_BIT);
据我了解,来自 PBO 的 glReadPixels 并不意味着是一个阻塞调用,但无论出于何种原因,它都会在这里阻塞它(并且性能比仅仅从 FBO 读取要差得多)。似乎 glMapBufferRange 的行为符合预期,并正确返回了所需的数据。
我唯一能想到的是我正在使用 GL_RED 并且只读回一个通道,但这仍然不能解释为什么 glReadPixels 会阻塞。
我用于基准测试的设备(一致的行为)。
- HTC One M8s (40-50ms)
- Nexus 5x (20-30ms)
- 谷歌像素(15-30 毫秒)
在这件事上的任何帮助将不胜感激!与此同时,我将尝试做更多的实验,看看是否有什么明显的我错过了。
编辑-> 2017 年 3 月 16 日(为清楚起见添加了更多代码)
FBO 设置代码
final int[] values = new int[1];
GLES30.glGenTextures(1, values, 0);
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, values[0]);
// we only want GRAYSCALE / Single channel texture
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_R8, texWidth, texHeight, 0, GLES30.GL_RED, GLES30.GL_UNSIGNED_BYTE, null);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_NEAREST);
this.tex_id[0] = values[0];
GLES30.glGenFramebuffers(1, values, 0);
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, values[0]);
this.fbo_id[0] = values[0];
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, this.tex_id[0], 0);
final int status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
if (status != GLES30.GL_FRAMEBUFFER_COMPLETE) {
Debug.LogError("Framebuffer incomplete. Status: " + status);
}
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
完整的渲染代码。为了清楚起见,我已经尽可能多地解构了逻辑和流程。
// bind the offscreen FBO and render the current camera frame
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, dualFBO.getID());
camera.draw(ShaderType.GRAYSCALE);
// ping-pong the FBO ID's
dualFBO.swap();
// dualFBO will now return the ID for last frame
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, dualFBO.getID());
// bind the current PB and submit (meant to be async) glReadPixels
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, dualPBO.getID());
GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0);
// this call locks for 30-50ms... why? (meant to be async???)
JNI.glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, 0);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);
// ping-pong the PBO ID's.
dualPBO.swap();
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, dualPBO.getID());
// this call is instant
JNI.glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, width * height, GL_MAP_READ_BIT);
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, 0);