glTexImage2D 中的 ByteBuffer.wrap() 导致过多的垃圾收集

Posted

技术标签:

【中文标题】glTexImage2D 中的 ByteBuffer.wrap() 导致过多的垃圾收集【英文标题】:ByteBuffer.wrap() in glTexImage2D causing excessive garbage collection 【发布时间】:2012-07-09 12:37:41 【问题描述】:

我目前在 android GLSurfaceView() 中进行以下 openGL 调用 onDrawFrame():

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, cameraTexture[0]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, camPreviewSize.width, camPreviewSize.height, 0, GLES20.GL_LUMINANCE,     GLES20.GL_UNSIGNED_BYTE, ByteBuffer.wrap(yArray));
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);

我需要在每一帧都执行此操作(或类似操作),因为我正在使用相机内的 android 的 setPreviewCallback() 功能使用自定义回调方法处理来自 android 相机预览的实时提要,但我的垃圾收集绝对会进行狂野(以下重复大约每秒 10 次!):

....

GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 18ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 22ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 25ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 20ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 20ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 16ms
GC_FOR_ALLOC freed 1530K, 42% free 3671K/6307K, paused 22ms

....

yArray 是一个字节数组,我将它包装到一个缓冲区中。我已经使用 DDMS 完成了分析,实际上大多数分配都是字节数组,从我对 wrap 函数所做的阅读来看,它似乎可能会在调用 wrap 时创建一个底层 byte[],这将然后作为纹理被GC收集。

如何减少分配次数?我应该通过 GL 调用以某种方式进行更改吗?看来我可以只是重用相同的字节数组,但我不确定如何。

任何帮助将不胜感激!这么多垃圾吓死我了!

【问题讨论】:

【参考方案1】:

有一个与setPreviewCallback() 方法相关的well documented android bug 会导致过多的垃圾回收。这个问题的解决方案是使用较新的方法setPreviewCallbackWithBuffer,您可以在其中预分配一个缓冲区,该缓冲区使用addCallbackBuffer 方法在每个帧上添加和重新添加:

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) 
...
  mCamera.addCallbackBuffer(callbackBuffer);
  mCamera.setPreviewCallbackWithBuffer(class that implements PreviewCallback);
...

并且在 onPreviewFrame 函数中必须再次添加缓冲区:

public void onPreviewFrame(byte[] yuvArray, Camera camera) 
...
  camera.addCallbackBuffer(callbackBuffer);
...

解决最初怀疑ByteBuffer.wrap() 在GL 调用中分配内存:它不是。它实际上会使用底层数组(在提供的代码中为yArray),并且不会分配新的内存来进行垃圾回收。

【讨论】:

很高兴您找到了答案。尽管如此,如果您只是在每帧刷新相同大小/类型的纹理,您可能想尝试使用 glTexSubImage2D 而不是 glTexImage2D,它可能会提供更好的性能。见这里:opengl.org/wiki/Common_Mistakes#Updating_A_Texture 太棒了!使用 glTexSubImage2D 让它工作。我没有意识到这是 glTexSubImage2D 的真正预期用途——感谢您启发我。我不确定它是否会更快,但我想尽可能多地避免常见错误:)【参考方案2】:

您将在每次 onDrawFrame 调用时创建一个新的 ByteBuffer 实例,该实例立即成为垃圾并被收集。只有当数据的大小发生变化时才需要这个,因此纹理大小尺寸本身也发生了变化。我认为这不太可能。

一次性预分配ByteBuffer 并通过批量方法更新其内容。

【讨论】:

我试过这个并且遇到缓冲区溢出错误。我正在使用 .put 方法,使用数组是 put 方法的输入。我会再试一次,试着弄清楚这些,然后回复你。谢谢! @DanielSmith 确保在重新填充数组之前设置了 buffer.position(0)(否则它将附加到末尾)。 这确实是缓冲区的问题,但正如您从我上面提供的答案中看到的那样,这实际上并不是令人担忧的原因。

以上是关于glTexImage2D 中的 ByteBuffer.wrap() 导致过多的垃圾收集的主要内容,如果未能解决你的问题,请参考以下文章

glTexImage2D() 函数访问冲突错误

glTexImage2D 返回无效值

glTexImage2D与glDeleteTextures 求助

使用 glTexImage2D 和 glGetTexImage 的一些问题

将像素传递到 glTexImage2D() 后会发生啥情况?

OpenGL API 之 glTexImage2D