Android OpenGLES3绘图:使用映射缓冲区对象
OpenGL绘图一般是在主内存创建数据,计算后传给GPU内存,如果数据是频繁变化的,那么每一帧都要将数据用glBufferSubData方法复制到GPU。其实主内存数据可能是在一个固定的数组里,却要将这个数组反复复制到GPU。如果这个数组能放进GPU,在CPU计算完通知GPU刷新,就省去了复制的操作。
这就是映射缓冲区对象,它可以获取GPU中内存地址,或者可以认为是一段空的数组,操作这段数组就等于操作GPU内存中的数据。使用方法也很简单:用glMapBufferRange方法获取到数组后,在数组内操作,然后用glFlushMappedBufferRange方法通知GPU刷新。当然也有需要注意的地方。
之前写的Android OpenGLES3绘图 - 鱼群模拟,就是操作一段长度固定的数组,我们在这个项目上实验一下。
1 映射内存
原来分配GPU内存的操作是用ByteBuffer.allocate分配一段Native内存,通过vbo将它传给GPU。而glMapBufferRange方法返回了一个ByteBuffer对象,很容易认为这个ByteBuffer就是Native内存,可以直接使用。然而并不是,如果直接调用glMapBufferRange,返回的是null,必须要在分配完GPU内存后去调用。从“映射”这个名字也可以理解,它是将一段GPU内存映射出来,映射之前必须先给GPU分配内存。
还有Java的端序跟GPU分配的不一样,映射后要通过ByteBuffer.order(ByteOrder.LITTLE_ENDIAN)
转换才能使用。映射到ByteBuffer后就可以用glUnmapBuffer(GL_ARRAY_BUFFER)
方法取消映射了,不影响后面使用这个ByteBuffer。
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// initPos();
initAll();
program = ShaderUtils.loadProgramGroup();
// 分配内存空间,每个浮点型占4字节空间
posBuffer = ByteBuffer
.allocateDirect(2 * 4 * MAX_COUNT)
.order(ByteOrder.nativeOrder());
vao = new int[1];
glGenVertexArrays(1, vao, 0);
glBindVertexArray(vao[0]);
vbo = new int[1];
glGenBuffers(1, vbo, 0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
// glBufferData(GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, MAX_COUNT * 4 * 2, posBuffer, GL_STREAM_DRAW);
if (useMapBuffer) {
// 使用映射缓冲区对象,分配GPU内存直接操作
ByteBuffer mapBuffer = (ByteBuffer) glMapBufferRange(GL_ARRAY_BUFFER, 0, MAX_COUNT * 4 * 2, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
posBuffer = mapBuffer.order(ByteOrder.LITTLE_ENDIAN);
glUnmapBuffer(GL_ARRAY_BUFFER);
}
}
2 通知刷新
原来对ByteBuffer数据的操作都不用改,在计算完绘制时不用通过glBufferSubData
传输数据了,直接用glFlushMappedBufferRange
通知GPU刷新就行了。这里加了一个useMapBuffer布尔值,对比使用映射缓冲区对象前后的区别。
@Override
public void onDrawFrame(GL10 gl) {
super.onDrawFrame(gl);
// update();
updateAll();
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (useMapBuffer) {
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, MAX_COUNT * 4 * 2);
} else {
// 刷新vbo数据
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferSubData(GL_ARRAY_BUFFER, 0, MAX_COUNT * 4 * 2, posBuffer);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
从运行帧率上看,使用映射缓冲区对象后,性能有一点提升,但是并不明显。这跟具体项目有关,这个项目的性能瓶颈并不在此处。