【GLSL】複数のシェーダソースから構成されるシェーダプログラム

試した環境

  • glsl 4.3

本題

汎用的な処理を使い回し易くするためにその処理だけ別のファイルにしたい等、1つのシェーダプログラムに対して複数のシェーダソースを利用したいときがあります。 複数のシェーダソースの利用について、OpenGL Wiki には以下のように書いてあります。

Note: It is possible to attach multiple shader objects for the same shader stage to a program. This is perfectly legal. When linking them, all of the code will be combined. However, only one of those shader objects can have a main function. Linking like this will work very much like linking multiple object files in a C/C++ program: one .cpp file can call code in another .cpp file if it has forward declarations of functions. So you can have "libraries" of functions that individual shaders can choose to use.

https://www.khronos.org/opengl/wiki/Shader_Compilation#Program_setup

これを読むに、以下の条件がそろっていれば複数のシェーダソースの分割を行えるようです。

  • main() 関数が全体で1つだけであること。
  • 別ソースの関数を使用する際には、使用するソース内に前方宣言が書いてあること。

以下のようなシェーダソースであればよさそうです。

#version 430

mat4 hoge(float f) {
    return mat4(f);
}
#version 430

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

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

// 前方宣言
mat4 hoge(float f);

void main() {
    dst[0] = hoge(1.0);
}

複数シェーダソースから1つのシェーダプログラムを作成する処理は以下の通りです。

GLuint createComputeShaderProgram(const std::vector<const char*>& shader_srcs) {
    std::vector<GLuint> shaders;
    GLuint program = glCreateProgram();

    for (size_t i = 0; i < shader_srcs.size(); ++i) {
        const char* shader_src = shader_srcs[i];

        GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
        shaders.push_back(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());

            glDeleteProgram(program);
            for (auto sh : shaders) { glDeleteShader(sh); }

            std::string error_msg = "error occurred in compiling shader: ";
            throw std::runtime_error(error_msg + info_log.data());
        }
        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);
        for (auto sh : shaders) { glDeleteShader(sh); }

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

    for (auto sh : shaders) {
        glDetachShader(program, sh);
        glDeleteShader(sh);
    }

    return program;
}

各シェーダソースの単位でシェーダオブジェクトを用意し、コンパイルします。 そしてコンパイルしたシェーダオブジェクトを共通のシェーダプログラムにアタッチします。 最後にglLinkProgram()関数でリンクします。

参考

Shader Compilation - OpenGL Wiki