試した環境
- GLEW 2.1.0
- GLFW 3.3.1
- Microsoft Visual Studio Community 2019 Version 16.4.5
- Windows 10 バージョン 1903
はじめに
OpenGL の Compute Shader を実行するために必要な処理をまとめたコードを載せます。
最初にソースコード全体を載せ、そのあと簡単な説明を記します。
しかしコード内容が OpenGL Wiki の Shader Compilation と Compute 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 Wiki の Shader Compilation のページの Shader and program objects 節の説明の通りです。
そのページでは頂点シェーダとフラグメントシェーダで説明されてますが、それがコンピュートシェーダに変わっただけです。
頂点シェーダとフラグメントシェーダと違ってコンピュートシェーダは1つのシェーダで構成されていますが、glLinkProgram()
は必要です。
次に計算結果を保持するためのバッファオブジェクトを用意しています。 uniform変数の設定を含めてこの辺りはコンピュートシェーダ特有の処理ではないので説明を省略します。
そしてglDispatchCompute()
で Compute Shader を実行します。
ここが実際にシェーダを実行する処理なのですが、
OpenGL Wiki の Compute Shader のページに詳しい説明があるので、ここでは説明を省略します。
その後glGetBufferSubData()
で計算結果を取得し、
バッファオブジェクトとプログラムオブジェクトの終了処理を行っています。
terminateOpenGL
ここでは GLFW の終了処理のglfwTerminate()
だけ呼んでいます。
おわりに
使用しないウィンドウを作成しているので、ウィンドウを作らずに GLEW の初期化を行う方法があればそのようにしたいです。
そもそも描画処理せずに GPGPU を実行したいのであれば、OpenGL の Compute Shader ではなく CUDA など別の手法を検討した方がいいのかもしれません。