我的OpenGL学习进阶之旅介绍一下 映射缓冲区对象和复制缓冲区对象
Posted 欧阳鹏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅介绍一下 映射缓冲区对象和复制缓冲区对象相关的知识,希望对你有一定的参考价值。
一、介绍映射缓冲区对象
通过前面的博客
- 【我的OpenGL学习进阶之旅】顶点属性、顶点数组
https://ouyangpeng.blog.csdn.net/article/details/121388737 - 【我的OpenGL学习进阶之旅】介绍顶点缓冲区对象VBO和元素数组缓冲区对象EBO,并对比使用VBO和不使用VBO绘制三角形的效果
https://ouyangpeng.blog.csdn.net/article/details/121726513 - 【我的OpenGL学习进阶之旅】介绍 顶点数组对象VAO并实战一下 https://ouyangpeng.blog.csdn.net/article/details/121729276
我们已经介绍了如何使用glBufferData
或者glBufferSubData
将数据加载到缓冲区对象。
应用程序也可以将缓冲区对象数据存储映射到应用程序的地址空间(也可以解除映射)。
1.1 应用程序映射缓冲区而不使用glBufferData
或者glBufferSubData
加载数据的几个理由
应用程序映射缓冲区而不使用glBufferData
或者glBufferSubData
加载数据的几个理由:
- 映射缓冲区可以减少应用程序的内存占用,因为可能只需要存储数据的一个副本。
- 在使用共享内存的架构上,映射缓冲区返回GPU存储缓冲区的地址空间的直接指针。通过映射缓冲区,应用程序可以避免复制步骤,从而实现更好的更新性能。
1.2 glMapBufferRange
命令
glMapBufferRange
命令返回指向所有或者一部分(范围)缓冲区对象数据存储的指针。
这个指针可以供应用程序使用,以读取或者更新缓冲区对象的内容。
glUnmapBuffer
命令用于指示更新已完成和释放映射的指针。
void *glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);
参数说明:
- target
可以设置如下目标中的任何一个
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_COPY_READ_BUFFER 0x8F36
#define GL_COPY_WRITE_BUFFER 0x8F37
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E
#define GL_UNIFORM_BUFFER 0x8A11
- offset
缓冲区数据存储中的偏移,以字节数计算 - length
需要映射的缓冲区数据的字节数 - access
访问标志的位域组合。应用程序必须指定如下标志中的至少一个
标志 | 说明 |
---|---|
GL_MAP_READ_BIT | 应用程序将从返回的指针读取 |
GL_MAP_WRITE_BIT | 应用程序将写入返回的指针 |
此外,应用程序还可以包含如下可选访问标志:
标志 | 说明 |
---|---|
GL_MAP_INVALIDATE_BUFFER_BIT | 表示整个缓冲区的内容可以在返回指针之前由驱动程序放弃。这个标志不能与GL_MAP_READ_BIT组合使用 |
GL_MAP_INVALIDATE_RANGE_BIT | 表示指定范围内的缓冲区的内容可以在返回指针之前由驱动程序放弃。这个标志不能与GL_MAP_READ_BIT组合使用 |
GL_MAP_FLUSH_EXPLICIT_BIT | 表示应用程序将明确地用glFlushMappedBufferRange刷新对映射范围子范围的操作。这个标志不能与GL_MAP_WRITE_BIT组合使用 |
GL_MAP_UNSYNCHRONIZED_BIT | 表示应用程序在返回缓冲区范围的指针之前不需要的等待缓冲区对象上的未决操作。如果有未决操作,则未决操作的结果和缓冲区对象上的任何操作都变为未定义 |
1.3 glUnmapBuffer
命令
glMapBufferRange
命令返回请求的缓冲区数据存储返回的指针。如果出现错误或者发出无效的请求,该函数将返回NULL
。glUnmapBuffer
命令取消之前的缓冲区映射。
GLboolean glUnmapBuffer (GLenum target);
参数说明:
- target
必须设置为GL_ARRAY_BUFFER
如果取消映射成功,则 glUnmapBuffer
返回GL_TRUE
。 glMapBufferRange
返回的指针在成功执行取消映射之后不再可以使用。
如果顶点缓冲区对象数据存储中的数据在缓冲区映射之后已经破坏, glUnmapBuffer
将返回GL_FALSE
,这可能是因为屏幕分辨率的变化、OpenGL ES上下文使用多个屏幕或者导致映射内存被抛弃的内存不足事件所导致。
1.4 刷新映射的缓冲区 glFlushMappedBufferRange
应用程序可能希望用glMapBufferRange
来映射缓冲区对象的一个范围(或者全部),但是只关心映射范围的不同子区域。
为了避免调用glUnmapBuffer
时刷新整个映射范围的潜在性能损失,应用程序可以用GL_MAP_FLUSH_EXPLICIT_BIT
访问标志(和GL_MAP_WRITE_BIT
组合)映射。当应用程序完成映射范围的一部分的更新时,可以用glFlushMappedBufferRange
指出这个事实。
void glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length);
参数说明:
- target
可以设置如下目标中的任何一个
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_COPY_READ_BUFFER 0x8F36
#define GL_COPY_WRITE_BUFFER 0x8F37
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E
#define GL_UNIFORM_BUFFER 0x8A11
- offset
从映射缓冲区起始点的偏移量,以字节数计算 - length
从偏移点开始属性的缓冲区数据的字节数
如果应用程序用GL_MAP_FLUSH_EXPLICIT_BIT
映射,但是没有明确地用glFlushMappedBufferRange
刷新修改后的区域,它的内容将是未定义的。
二、实战一下
下面代码演示了使用 glMapBufferRange
和 glUnmapBuffer
写入顶点缓冲区对象的内容。
- NativeTriangleMapBuffers.h
#pragma once
#include <BaseGLSample.h>
#define VERTEX_POS_SIZE 3 // x, y and z
#define VERTEX_COLOR_SIZE 4 // r, g, b, and a
#define VERTEX_POS_INDX 0
#define VERTEX_COLOR_INDX 1
class NativeTriangleMapBuffers : public BaseGLSample
public:
NativeTriangleMapBuffers() = default;
virtual ~NativeTriangleMapBuffers() = default;
virtual void create();
virtual void draw();
virtual void shutdown();
private:
GLuint vboIds[2];
void DrawPrimitiveWithVBOsMapBuffers(GLint numVertices, GLfloat *vtxBuf,
GLint vtxStride, GLint numIndices,
GLushort *indices);
;
- NativeTriangleMapBuffers.cpp
#include <cstring>
#include "NativeTriangleMapBuffers.h"
// 可以参考这篇讲解: https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/
void NativeTriangleMapBuffers::create()
GLUtils::printGLInfo();
// 顶点着色器
VERTEX_SHADER = GLUtils::openTextFile(
"vertex/vertex_shader_hello_triangle2.glsl");
// 片段着色器
FRAGMENT_SHADER = GLUtils::openTextFile(
"fragment/fragment_shader_hello_triangle2.glsl");
mProgram = GLUtils::createProgram(&VERTEX_SHADER, &FRAGMENT_SHADER);
if (!mProgram)
LOGD("Could not create program")
return;
vboIds[0] = 0;
vboIds[1] = 0;
// 设置清除颜色
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
void NativeTriangleMapBuffers::draw()
// 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
GLfloat vertices[3 * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE)] =
// 逆时针 三个顶点
0.0f, 0.5f, 0.0f, // v0 上角
1.0f, 0.0f, 0.0f, 1.0f, // c0
-0.5f, -0.5f, 0.0f, // v1 左下角
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.5f, -0.5f, 0.0f, // v2 右下角
0.0f, 0.0f, 1.0f, 1.0f // c2
;
// Index buffer data
GLushort indices[3] = 0, 1, 2;
// Clear the color buffer
// 清除屏幕
// 在OpenGL ES中,绘图中涉及多种缓冲区类型:颜色、深度、模板。
// 这个例子,绘制三角形,只向颜色缓冲区中绘制图形。在每个帧的开始,我们用glClear函数清除颜色缓冲区
// 缓冲区将用glClearColor指定的颜色清除。
// 这个例子,我们调用了GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f); 因此屏幕清为白色。
// 清除颜色应该由应用程序在调用颜色缓冲区的glClear之前设置。
glClear(GL_COLOR_BUFFER_BIT);
// Use the program object
// 在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用这个程序对象(也就是之前写的着色器)了。
// 当我们渲染一个物体时要使用着色器程序 , 将其设置为活动程序。这样就可以开始渲染了
glUseProgram(mProgram);
DrawPrimitiveWithVBOsMapBuffers(3, vertices,
sizeof(GLfloat) * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE),
3, indices);
void NativeTriangleMapBuffers::DrawPrimitiveWithVBOsMapBuffers(
GLint numVertices, GLfloat *vtxBuf,
GLint vtxStride, GLint numIndices, GLushort *indices)
GLuint offset = 0;
// vboIds[0] - used to store vertex attribute data
// vboIds[l] - used to store element indices
if (vboIds[0] == 0 && vboIds[1] == 0)
// 应用程序也可以将缓冲区对象数据存储映射到应用程序的地址空间(也可以解除映射)
// 应用程序映射缓冲区而不使用 glBufferData 或者 glBufferSubData加载的理由:
// 1. 映射缓冲区可以减少应用程序的内存占用,因为可能只需要存储数据的一个副本
// 2. 在使用共享内存的架构上,映射缓冲区返回GPU存储缓冲区的地址空间的直接地址。
// 通过映射缓冲区,应用程序可以避免复制步骤,从而实现更好的更新性能
GLfloat *vtxMappedBuf;
GLushort *idxMappedBuf;
// Only allocate on the first draw
glGenBuffers(2, vboIds);
glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, vtxStride * numVertices, nullptr, GL_STATIC_DRAW);
// glMapBufferRange 命令返回指向所有或者一部分(范围)缓冲区对象数据存储的指针。
// 这个指针可以供应用程序使用,以读取或者更新缓冲区对象的内容
// target 我们设置为 GL_ARRAY_BUFFER
// offset 表示 缓冲区数据存储中的偏移量,以字节数计算。 我们这里设置为0
// length 表示 需要映射的缓冲区数据的字节数
// access 表示 访问标志的位域组合。应用程序必须指定如下标志中的至少一个:
// GL_MAP_READ_BIT 应用程序将从返回的指针读取
// GL_MAP_WRITE_BIT 应用程序将写入返回的指针
// 此外,应用程序可以包含如下可选访问标志:
// GL_MAP_INVALIDATE_BUFFER_BIT
// 表示整个缓冲区的内容可以在返回指针之前由驱动程序放弃。这个标志不能与GL_MAP_READ_BIT组合使用
// GL_MAP_INVALIDATE_RANGE_BIT
// 表示指定范围内的缓冲区的内容可以在返回指针之前由驱动程序放弃。这个标志不能与GL_MAP_READ_BIT组合使用
// GL_MAP_FLUSH_EXPLICIT_BIT
// 表示应用程序将明确地用glFlushMappedBufferRange刷新对映射范围子范围的操作。这个标志不能与GL_MAP_WRITE_BIT组合使用
// GL_MAP_UNSYNCHRONIZED_BIT
// 表示应用程序在返回缓冲区范围的指针之前不需要的等待缓冲区对象上的未决操作。
// 如果有未决操作,则未决操作的结果和缓冲区对象上的任何操作都变为未定义
vtxMappedBuf = (GLfloat *) glMapBufferRange(GL_ARRAY_BUFFER, 0,
vtxStride * numVertices,
GL_MAP_WRITE_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT);
if (vtxMappedBuf == nullptr)
LOGE("Error mapping vertex buffer object.")
return;
// Copy the data into the mapped buffer
memcpy(vtxMappedBuf, vtxBuf, vtxStride * numVertices);
// Unmap the buffer
// glUnmapBuffer命令取消之前的缓冲区映射
if (glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE)
LOGE("Error unmapping array buffer object.")
return;
//============================================================================================//
// Map the index buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * numIndices, nullptr,
GL_STATIC_DRAW);
idxMappedBuf = (GLushort *) glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0,
sizeof(GLushort) * numIndices,
GL_MAP_WRITE_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT);
if (idxMappedBuf == nullptr)
LOGE("Error mapping element array buffer object.")
return;
// Copy the data into the mapped buffer
memcpy(idxMappedBuf, indices, sizeof(GLushort) * numIndices);
// Unmap the buffer
if (glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER) == GL_FALSE)
LOGE("Error unmapping element array buffer object.")
return;
glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
glEnableVertexAttribArray(VERTEX_POS_INDX);
glVertexAttribPointer(VERTEX_POS_INDX,
VERTEX_POS_SIZE,
GL_FLOAT, GL_FALSE, vtxStride,
(const void *) offset);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
glEnableVertexAttribArray(VERTEX_COLOR_INDX);
offset += VERTEX_POS_SIZE * sizeof(GLfloat);
glVertexAttribPointer(VERTEX_COLOR_INDX,
VERTEX_COLOR_SIZE,
GL_FLOAT, GL_FALSE, vtxStride,
(const void *) offset);
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, nullptr);
glDisableVertexAttribArray(VERTEX_POS_INDX);
glDisableVertexAttribArray(VERTEX_COLOR_INDX);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
void NativeTriangleMapBuffers::shutdown()
// Delete program object
GLUtils::DeleteProgram(mProgram);
glDeleteBuffers(2, &vboIds[0]);
- vertex/vertex_shader_hello_triangle2.glsl
#version 300 es
// 位置变量的属性位置值为 0
layout(location = 0) in vec4 a_position;
// 颜色变量的属性位置值为 1
layout(location = 1) in vec4 a_color;
// 向片段着色器输出一个颜色
out vec4 v_color;
void main()
v_color = a_color;
gl_Position = a_position;
- fragment/fragment_shader_hello_triangle2.glsl
#version 300 es
// 表示OpenGL ES着色器语言V3.00
// 声明着色器中浮点变量的默认精度
precision mediump float;
// 声明由上一步顶点着色器传入进来的颜色值
in vec4 v_color;
// 声明一个输出变量fragColor,这是一个4分量的向量,
// 写入这个变量的值将被输出到颜色缓冲器
out vec4 o_fragColor;
void main()
o_fragColor = v_color;
- 效果展示
三、复制缓冲区对象:glCopyBufferSubData
函数
迄今为止,我们已经说明了如何使用glBufferData
或者glBufferSubData
和glMapBufferRange
加载缓冲区对象。
所有这些技术都涉及从应用程序到设备的数据传输。
OpenGL ES 3.0还可以从一个缓冲区对象将数据完全复制到设备,这可用glCopyBufferSubData
函数完成。
void glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
参数说明:
- readTarget
读取的缓冲区对象目标 - writeTarget
写入的缓冲区对象目标。readTarget
和writeTarget
都可用设置为如下目标中的任何一个(尽管它们不必设置为同一目标):
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_COPY_READ_BUFFER 0x8F36
#define GL_COPY_WRITE_BUFFER 0x8F37
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E
#define GL_UNIFORM_BUFFER 0x8A11
- readOffset
需要复制的读缓冲数据中的偏移量,以字节表示 - writeOffset
需要复制的写缓冲数据中的偏移量,以字节表示 - size
从读缓冲区数据复制到写缓冲区数据的字节数
调用glCopyBufferSubData
函数将从绑定的readTarget
的缓冲区复制指定的字节到writeTarget
。缓冲区绑定根据每个目标的最后一次glBindBuffer
调用确定。任何类型的缓冲区对象(数组、元素数组、变化反馈等)都可用绑定到GL_COPY_READ_BUFFER
或 GL_COPY_WRITE_BUFFER
目标。这两个目标是一种方便的措施,使得应用程序在执行缓冲区的复制时不必改变任何真正的缓冲区绑定。
四、程序源码
本例子可以在Github找到源代码 https://github.com/ouyangpeng/OpenGLESDemo
https://github.com/ouyangpeng/OpenGLESDemo/blob/master/app/src/main/cpp/sample/triangle/NativeTriangleMapBuffers.h
https://github.com/ouyangpeng/OpenGLESDemo/blob/master/app/src/main/cpp/sample/triangle/NativeTriangleMapBuffers.cpp
以上是关于我的OpenGL学习进阶之旅介绍一下 映射缓冲区对象和复制缓冲区对象的主要内容,如果未能解决你的问题,请参考以下文章
我的OpenGL学习进阶之旅介绍 顶点数组对象VAO并实战一下