====== OpenGLの修行 #4 - ビルド環境の見直し ======
[[https://www.youtube.com/watch?v=NCKH1Hma55E|YouTubeの動画ページ]]
  * 第3回からおよそ1年半の期間を経て再開する。
  * 環境の準備を実演するために、Windows(Vista、VirtualBox)でやってきた。
  * 次回からはLinuxでやっていく。
  * その前に、現在の雑なビルド環境に改善を加えておく。
===== メイン目標 =====
CMakeLists.txtに、GLFWのインクルードパスとライブラリパスがベタ書きされているのを直したい。
===== メニュー =====
  *  (おまけ) GLFWと動的にリンクする (DLLを利用する)
  *  GLFWをソースからビルドしてインストールする
  *  システムの任意の場所にインストールされたGLFWを利用する
  *  (おまけ) GLADをアプリケーションのソースから分離する
===== (おまけ) GLFWと動的にリンクする (DLLを利用する) =====
GLFWのWindows向けコンパイル済みバイナリには、DLLも含まれている。
===== GLFWをソースからビルドしてインストールする =====
インストール場所は[[https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html|CMAKE_INSTALL_PREFIX]]で指定する。
例:
  $ cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:/somewhere ..
===== システムの任意の場所にインストールされたGLFWを利用する =====
[[https://cmake.org/cmake/help/latest/command/find_package.html|find_package]]の検索パスは[[https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html|CMAKE_PREFIX_PATH]]で指定する。
例:
  $ cmake -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH=C:/somewhere ..
===== (おまけ) GLADをアプリケーションのソースから分離する =====
[[https://cmake.org/cmake/help/latest/command/add_subdirectory.html|add_subdirectory]]
====== ソースコード ======
{{ :youtube:hellotriangle_2.zip |ダウンロード}}
===== プロジェクトレイアウト =====
  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/* =====
[[https://glad.dav1d.de/#language=c&specification=gl&api=gl%3D4.1&api=gles1%3Dnone&api=gles2%3Dnone&api=glsc2%3Dnone&profile=core&loader=on|gladの入手]]
===== shaders/hello_triangle.frag =====
#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;
}
===== shaders/hello_triangle.vert =====
#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];
}
===== src/main.cpp =====
#include 
// glad.h must be included before glfw3.h
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
namespace fs = std::filesystem;
std::optional read_to_string(fs::path const &path) {
    if (std::ifstream ifs{path}; ifs) {
        return std::string(std::istreambuf_iterator{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 log(log_length);
        glGetShaderInfoLog(shader, log.size(), &log_length, log.data());
        std::cerr << path.string() << ':' << log.data() << '\n';
    }
}
std::optional 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 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 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";
}