Qt GUIにおけるOpenGL パフォーマンス向上:QOpenGLExtraFunctions::glMapBufferRange() によるデータ転送の最適化


QOpenGLExtraFunctions::glMapBufferRange() は、OpenGL バッファオブジェクトの一部または全体をクライアントメモリ空間にマッピングするための関数です。これは、バッファの内容を直接読み書きする必要がある場合に役立ちます。

機能

この関数は、以下の引数を取ります。

  • access: マッピングされたメモリへのアクセス権。有効な値は、GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE です。
  • length: マッピングするバッファのサイズ (バイト単位)。
  • offset: マッピングするバッファ内のオフセット (バイト単位)。
  • target: マッピングするバッファオブジェクトの種類。有効な値は、GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_BUFFER, GL_TEXTURE_BUFFER です。

この関数は、成功すると、マッピングされたメモリへのポインタを返します。失敗すると、NULL を返します。

#include <QOpenGLContext>
#include <QOpenGLExtraFunctions>

void mapBuffer(QOpenGLContext *context)
{
    QOpenGLExtraFunctions functions(context);

    // バッファオブジェクトを作成
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);

    // バッファにデータを割り当てる
    const GLsizeiptr size = 1024 * sizeof(GLfloat);
    GLfloat data[size];
    for (int i = 0; i < size; ++i) {
        data[i] = i / (GLfloat) size;
    }
    glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);

    // バッファの一部をマッピング
    void *ptr = functions.glMapBufferRange(GL_ARRAY_BUFFER, 0, 512, GL_READ_WRITE);
    if (ptr) {
        // マッピングされたメモリへのアクセス
        GLfloat *mappedData = reinterpret_cast<GLfloat *>(ptr);
        for (int i = 0; i < 512; ++i) {
            mappedData[i] *= 2.0f;
        }

        // マッピングを解除
        functions.glUnmapBuffer(GL_ARRAY_BUFFER);
    } else {
        // エラー処理
    }

    // バッファオブジェクトを解放
    glDeleteBuffers(1, &buffer);
}

この例では、glMapBufferRange() を使用して、バッファオブジェクトの一部をマッピングし、その内容を 2 倍にします。

  • マッピングを解除する前に、glFlush() または glFinish() を呼び出して、すべての変更がバッファに書き込まれていることを確認してください。
  • マッピングされたメモリへのアクセスは、OpenGL コマンドが完了するまで安全に行うことができます。
  • glMapBufferRange() を呼び出す前に、バッファオブジェクトがバインドされていることを確認してください。


#include <QCoreApplication>
#include <QOpenGLWidget>
#include <QPaintEvent>
#include <QOpenGLExtraFunctions>

class MyWidget : public QOpenGLWidget {
public:
    MyWidget() {
        setFixedSize(500, 500);
    }

protected:
    void initializeGL() override {
        QOpenGLExtraFunctions functions(context());

        // バッファオブジェクトを作成
        GLuint buffer;
        glGenBuffers(1, &buffer);
        glBindBuffer(GL_ARRAY_BUFFER, buffer);

        // バッファにデータを割り当てる
        const GLsizeiptr size = 1024 * sizeof(GLfloat);
        GLfloat data[size];
        for (int i = 0; i < size; ++i) {
            data[i] = i / (GLfloat) size;
        }
        glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);

        // Vertex シェーダーを作成
        GLuint vertexShader = compileShader(GL_VERTEX_SHADER,
                                          "#version 330\n"
                                          "layout (location = 0) in vec3 pos;\n"
                                          "void main() {\n"
                                          "    gl_Position = vec4(pos, 1.0f);\n"
                                          "}\n");

        // Fragment シェーダーを作成
        GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER,
                                          "#version 330\n"
                                          "out vec4 fragColor;\n"
                                          "void main() {\n"
                                          "    fragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                          "}\n");

        // シェーダープログラムを作成
        GLuint program = glCreateProgram();
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);

        // シェーダーを削除
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        // Vertex Buffer Object (VBO) を有効化
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);

        // プログラムを使用
        glUseProgram(program);
    }

    void paintGL() override {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // バッファの一部をマッピング
        QOpenGLExtraFunctions functions(context());
        void *ptr = functions.glMapBufferRange(GL_ARRAY_BUFFER, 0, 512, GL_READ_WRITE);
        if (ptr) {
            // マッピングされたメモリへのアクセス
            GLfloat *mappedData = reinterpret_cast<GLfloat *>(ptr);
            for (int i = 0; i < 512; ++i) {
                mappedData[i] *= 2.0f;
            }

            // マッピングを解除
            functions.glUnmapBuffer(GL_ARRAY_BUFFER);
        }

        // 三角形を描画
        glDrawArrays(GL_TRIANGLES, 0, 3);
    }

private:
    GLuint compileShader(GLenum type, const char *source) {
        GLuint shader = glCreateShader(type);
        glShaderSource(shader, 1, &source, nullptr);
        glCompileShader(shader);

        GLint success;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success) {
            char infoLog[512];
            glGetShaderInfoLog(shader, sizeof(infoLog), nullptr, infoLog);
            qDebug() << "Shader compilation failed:" << infoLog;
            glDeleteShader(shader);
            return 0;
        }

        return shader;
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    MyWidget widget;
    widget.show();
    return a.exec();
}

このコードは以下の動作をします。

  1. OpenGL コ


glBufferData() を使用してデータを直接書き込む

最も単純な方法は、glBufferData() 関数を使用してデータを直接バッファオブジェクトに書き込むことです。これは、バッファの内容を頻繁に変更する必要がない場合に適しています。

void updateBufferData(GLuint buffer, const GLfloat *data, GLsizeiptr size) {
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
}

glBindBufferRange() を使用してバッファオブジェクトをバインドする

glBindBufferRange() 関数を使用して、バッファオブジェクトを特定の範囲にバインドすることができます。これにより、glMapBuffer() または glMapBufferRange() を使用して、その範囲内のメモリを直接マッピングすることができます。

void mapBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr size) {
    glBindBufferRange(GL_ARRAY_BUFFER, 0, buffer, size, offset);
    void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
    if (ptr) {
        // マッピングされたメモリへのアクセス
        GLfloat *mappedData = reinterpret_cast<GLfloat *>(ptr);
        // ...

        // マッピングを解除
        glUnmapBuffer(GL_ARRAY_BUFFER);
    }
}

サードパーティ製のライブラリを使用する

OpenGL バッファオブジェクトをマッピングするためのサードパーティ製のライブラリもいくつかあります。これらのライブラリは、より高度な機能や、エラー処理の容易化などの利点を提供する場合があります。

最適な方法の選択

使用する方法は、特定のニーズによって異なります。パフォーマンスが重要な場合は、glBufferData() を使用してデータを直接書き込むのが最善の方法です。柔軟性と使いやすさが重要な場合は、glBindBufferRange() またはサードパーティ製のライブラリを使用する方がよい場合があります。

  • マッピングされたメモリへのアクセスは、スレッドセーフではないことに注意してください。マルチスレッド環境でバッファオブジェクトをマッピングする場合は、適切な同期メカニズムを使用する必要があります。
  • バッファオブジェクトをマッピングする前に、常に glFinish() を呼び出して、すべての OpenGL コマンドが完了していることを確認してください。