OpenGL Compute Shader を実行するだけのコード

試した環境

はじめに

OpenGL の Compute Shader を実行するために必要な処理をまとめたコードを載せます。 最初にソースコード全体を載せ、そのあと簡単な説明を記します。
しかしコード内容が OpenGL WikiShader CompilationCompute Shader のコードを合わせたようなものなので、 該当ページのリンクを載せて、詳しい説明は省略します。

ソースコード

#include <iostream>
#include <vector>

#include <gl/glew.h>
#include <glfw/glfw3.h>

namespace {

const char* compute_shader_source = R"(
#version 430

uniform uint element_size;

layout(std430, binding = 3) buffer layout_dst
{
    float dst[];
};

layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;

void main() {
    uint index = gl_GlobalInvocationID.x;
    if (index >= element_size) { return; }

    dst[index] = mix(0.0, 3.141592653589, float(index) / element_size);
}
)";

void initOpenGL() {
    auto inits_glfw = glfwInit();
    if (inits_glfw != GLFW_TRUE) {
        throw std::runtime_error("error occurred: glfwInit!");
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);

    GLFWwindow* window = glfwCreateWindow(1, 1, "invisible window", nullptr, nullptr);
    if (window == nullptr) {
        throw std::runtime_error("error occurred: glfwCreateWindow!");
    }
    glfwMakeContextCurrent(window);

    auto inits_glew = glewInit();
    if (inits_glew != GLEW_OK) {
        throw std::runtime_error("error occurred: glewInit!");
    }
}

void terminateOpenGL() {
    glfwTerminate();
}


GLuint createComputeShaderProgram(const char* shader_src) {
    GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
    glShaderSource(shader, 1, &shader_src, nullptr);
    glCompileShader(shader);

    GLint compiles = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiles);
    if (compiles == GL_FALSE) {
        GLint log_length = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
        std::vector<GLchar> info_log(log_length);
        glGetShaderInfoLog(shader, log_length, &log_length, info_log.data());

        glDeleteShader(shader);

        std::string error_msg = "error occurred in compiling shader: ";
        throw std::runtime_error(error_msg + info_log.data());
    }

    GLuint program = glCreateProgram();

    glAttachShader(program, shader);
    glLinkProgram(program);

    GLint links = 0;
    glGetProgramiv(program, GL_LINK_STATUS, &links);
    if (links == GL_FALSE) {
        GLint log_length = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
        std::vector<GLchar> info_log(log_length);
        glGetProgramInfoLog(program, log_length, &log_length, info_log.data());

        glDeleteProgram(program);
        glDeleteShader(shader);

        std::string error_msg = "error occurred in linking shader: ";
        throw std::runtime_error(error_msg + info_log.data());
    }

    glDetachShader(program, shader);
    glDeleteShader(shader);

    return program;
}

void deleteComputeShaderProgram(GLuint program) {
    glDeleteProgram(program);
}


void compute() {
    uint32_t num = 10000;

    GLuint shader_program = createComputeShaderProgram(compute_shader_source);

    // create buffer
    GLuint uniform_element_size = glGetUniformLocation(shader_program, "element_size");
    GLuint ssbo;
    glGenBuffers(1, &ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, num * sizeof(float), nullptr, GL_DYNAMIC_COPY);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    glUseProgram(shader_program);

    glUniform1ui(uniform_element_size, num);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, ssbo);

    glDispatchCompute(num / 256 + 1, 1, 1);

    glUseProgram(0);

    std::vector<float> data(num);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, num * sizeof(float), data.data());
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    for (auto v : data) { std::cout << v << '\n'; }

    glDeleteBuffers(1, &ssbo);

    deleteComputeShaderProgram(shader_program);
}

}

int main(int argc, char* argv[]) {
    try {
        initOpenGL();

        compute();

        terminateOpenGL();
    }
    catch (std::exception & e) {
        std::cerr << e.what() << std::endl;
    }
}

ソースコードの簡単な説明

initOpenGL

GLEW と GLFW の初期化を行います。 コンピュートシェーダを実行するだけなので描画用のウィンドウは不要ですが、 context がないとglewInit()が失敗するので、サイズ1x1で非表示のウィンドウを作成しています。

compute

createComputeShaderProgram()でシェーダを使うためのプログラムオブジェクトの準備をします。 基本はOpenGL WikiShader Compilation のページの Shader and program objects 節の説明の通りです。 そのページでは頂点シェーダとフラグメントシェーダで説明されてますが、それがコンピュートシェーダに変わっただけです。 頂点シェーダとフラグメントシェーダと違ってコンピュートシェーダは1つのシェーダで構成されていますが、glLinkProgram()は必要です。

次に計算結果を保持するためのバッファオブジェクトを用意しています。 uniform変数の設定を含めてこの辺りはコンピュートシェーダ特有の処理ではないので説明を省略します。

そしてglDispatchCompute()で Compute Shader を実行します。 ここが実際にシェーダを実行する処理なのですが、 OpenGL WikiCompute Shader のページに詳しい説明があるので、ここでは説明を省略します。

その後glGetBufferSubData()で計算結果を取得し、 バッファオブジェクトとプログラムオブジェクトの終了処理を行っています。

terminateOpenGL

ここでは GLFW の終了処理のglfwTerminate()だけ呼んでいます。

おわりに

使用しないウィンドウを作成しているので、ウィンドウを作らずに GLEW の初期化を行う方法があればそのようにしたいです。
そもそも描画処理せずに GPGPU を実行したいのであれば、OpenGL の Compute Shader ではなく CUDA など別の手法を検討した方がいいのかもしれません。

参考