OpenGLの修行 #4 - ビルド環境の見直し

YouTubeの動画ページ

  • 第3回からおよそ1年半の期間を経て再開する。
  • 環境の準備を実演するために、Windows(Vista、VirtualBox)でやってきた。
  • 次回からはLinuxでやっていく。
  • その前に、現在の雑なビルド環境に改善を加えておく。

メイン目標

CMakeLists.txtに、GLFWのインクルードパスとライブラリパスがベタ書きされているのを直したい。

メニュー

  • (おまけ) GLFWと動的にリンクする (DLLを利用する)
  • GLFWをソースからビルドしてインストールする
  • システムの任意の場所にインストールされたGLFWを利用する
  • (おまけ) GLADをアプリケーションのソースから分離する

(おまけ) GLFWと動的にリンクする (DLLを利用する)

GLFWのWindows向けコンパイル済みバイナリには、DLLも含まれている。

GLFWをソースからビルドしてインストールする

インストール場所はCMAKE_INSTALL_PREFIXで指定する。

例:

$ cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:/somewhere ..

システムの任意の場所にインストールされたGLFWを利用する

find_packageの検索パスはCMAKE_PREFIX_PATHで指定する。

例:

$ cmake -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH=C:/somewhere ..

(おまけ) GLADをアプリケーションのソースから分離する

ソースコード

プロジェクトレイアウト

HelloTriangle_2
├── CMakeLists.txt
├── glad
│   ├── CMakeLists.txt
│   ├── include
│   │   ├── KHR
│   │   │   └── khrplatform.h
│   │   └── glad
│   │       └── glad.h
│   └── src
│       └── glad.c
├── shaders
│   ├── hello_triangle.frag
│   └── hello_triangle.vert
└── src
    └── main.cpp

CMakeLists.txt

cmake_minimum_required(VERSION 3.13 FATAL_ERROR)

project(HelloTriangle VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)

find_package(glfw3 REQUIRED)

add_subdirectory(glad)

add_executable(${PROJECT_NAME} src/main.cpp)

target_link_libraries(${PROJECT_NAME} glad glfw)

glad/CMakeLists.txt

enable_language(C)

add_library(glad OBJECT src/glad.c)

target_include_directories(
    glad
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
    )

glad/*

shaders/hello_triangle.frag

#version 410 core

out vec4 vert_color;

void main(void) {
    const vec4 positions[3] = vec4[3](vec4(-0.5, -0.5, 1.0, 1.0),
                                      vec4( 0.5, -0.5, 1.0, 1.0),
                                      vec4( 0.5,  0.5, 1.0, 1.0));
    gl_Position = positions[gl_VertexID];

    const vec4 colors[3] = vec4[3](vec4(1.0, 0.0, 0.0, 1.0),
                                   vec4(0.0, 1.0, 0.0, 1.0),
                                   vec4(0.0, 0.0, 1.0, 1.0));
    vert_color = colors[gl_VertexID];
}

shaders/hello_triangle.vert

#version 410 core

in vec4 vert_color;
out vec4 color;

void main(void) {
    // color = vec4(1.0, 0.0, 0.0, 1.0);
    color = vert_color;
}

src/main.cpp

#include <glad/glad.h>
// glad.h must be included before glfw3.h
#include <GLFW/glfw3.h>

#include <iostream>
#include <string>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <optional>
#include <vector>

namespace fs = std::filesystem;

std::optional<std::string> read_to_string(fs::path const &path) {
    if (std::ifstream ifs{path}; ifs) {
        return std::string(std::istreambuf_iterator<char>{ifs}, {});
    } else {
        return std::nullopt;
    }
}

void print_shader_compilation_errors(GLuint shader, fs::path const &path) {
    GLint log_length;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        std::vector<GLchar> log(log_length);
        glGetShaderInfoLog(shader, log.size(), &log_length, log.data());
        std::cerr << path.string() << ':' << log.data() << '\n';
    }
}

std::optional<GLuint> compile_shader(fs::path const &path, GLenum type) {
    if (auto shader_source = read_to_string(path); shader_source) {
        GLuint shader = glCreateShader(type);
        GLchar const *srcs[] = { shader_source->c_str() };
        glShaderSource(shader, 1, srcs, nullptr);
        glCompileShader(shader);
        
        GLint success;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (success) {
            return shader;
        } else {
            print_shader_compilation_errors(shader, path);
            return std::nullopt;
        }
    } else {
        std::cerr << "failed read file: " << path << '\n';
        return std::nullopt;
    }
}

std::optional<GLuint> link_shaders(GLuint vert, GLuint frag) {
    GLuint program = glCreateProgram();
    glAttachShader(program, vert);
    glAttachShader(program, frag);
    glLinkProgram(program);
    
    GLint success;
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (success) {
        return program;
    } else {
        return std::nullopt;
    }
}

std::optional<GLuint> shader_program_setup() {
    auto vert = compile_shader("shaders/hello_triangle.vert", GL_VERTEX_SHADER);
    auto frag = compile_shader("shaders/hello_triangle.frag", GL_FRAGMENT_SHADER);
    
    auto program = vert && frag ?
        link_shaders(*vert, *frag) : std::nullopt;
    
    if (vert) {
        glDeleteShader(*vert);
    }
    if (frag) {
        glDeleteShader(*frag);
    }
    
    return program;
}

int main() {
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    
    GLFWwindow *window = glfwCreateWindow(800, 600, "Hello Triangle!", nullptr, nullptr);
    if (!window) {
        std::cerr << "GLFW failed to create window\n" << std::endl;
        return 1;
    }
    
    glfwMakeContextCurrent(window);
    
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cerr << "glad failed to initialize OpenGL context\n";
        return 1;
    }
    
    auto *ver_gl = glGetString(GL_VERSION);
    std::cout << "OpenGL version: " << ver_gl << '\n';
    
    auto shader_program = shader_program_setup();
    if (!shader_program) {
        std::cerr << "failed to setup shader program\n";
        return 1;
    }
    
    GLuint VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    
    while (!glfwWindowShouldClose(window)) {
        glClearColor(0.0, 0.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        
        glUseProgram(*shader_program);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    std::cerr << "Program finished successfully\n";
}

文書の編集
文書の先頭へ