我的OpenGL学习进阶之旅介绍一下 映射缓冲区对象和复制缓冲区对象

Posted 欧阳鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅介绍一下 映射缓冲区对象和复制缓冲区对象相关的知识,希望对你有一定的参考价值。

一、介绍映射缓冲区对象

通过前面的博客

我们已经介绍了如何使用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命令返回请求的缓冲区数据存储返回的指针。如果出现错误或者发出无效的请求,该函数将返回NULLglUnmapBuffer命令取消之前的缓冲区映射。

GLboolean glUnmapBuffer (GLenum target);

参数说明:

  • target
    必须设置为GL_ARRAY_BUFFER

如果取消映射成功,则 glUnmapBuffer返回GL_TRUEglMapBufferRange返回的指针在成功执行取消映射之后不再可以使用。
如果顶点缓冲区对象数据存储中的数据在缓冲区映射之后已经破坏, 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 刷新修改后的区域,它的内容将是未定义的。

二、实战一下

下面代码演示了使用 glMapBufferRangeglUnmapBuffer写入顶点缓冲区对象的内容。

  • 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或者glBufferSubDataglMapBufferRange加载缓冲区对象。
所有这些技术都涉及从应用程序到设备的数据传输。

OpenGL ES 3.0还可以从一个缓冲区对象将数据完全复制到设备,这可用glCopyBufferSubData函数完成。

void glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);

参数说明:

  • readTarget
    读取的缓冲区对象目标
  • writeTarget
    写入的缓冲区对象目标。 readTargetwriteTarget都可用设置为如下目标中的任何一个(尽管它们不必设置为同一目标):
#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_BUFFERGL_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并实战一下

我的渲染技术进阶之旅glfw库简单介绍

我的渲染技术进阶之旅glfw库简单介绍

我的渲染技术进阶之旅glfw库简单介绍

我的OpenGL学习进阶之旅介绍一下OpenGL ES的 光栅化 : 剔除多边形偏移

我的OpenGL学习进阶之旅介绍一下OpenGL ES的 光栅化 : 剔除多边形偏移