C++でOpenGLを使ってゲーム用ライブラリを作った話 #2

# はじめに
[農工大アドベントカレンダー21日目の記事](https://qiita.com/advent-calendar/2024/tuat)です。

前回記事 [C++でOpenGLを使ってゲーム用ライブラリを作った話 #1](https://qiita.com/upiscium/items/120828a9d69115209845)

# この連載のリポジトリ
https://github.com/uPiscium/QiitaGameLib

今回は`Article2`フォルダの内容です。

# 宣伝
https://github.com/uPiscium/TerreateGraphics

今回の連載の元ネタにあたる、筆者が実際に作成したライブラリです。
気が向いたら覗いてみてください。(スターしてくれると筆者がとっても喜びます✨)

# TL;DR
VAO、VBO、IBOを自作クラスでラッピングしていい感じにしたよ。以上
(今回の[コード全文](#コード全文))

# 本記事の内容
- [今回の目標](#今回の目標)
- [クラスの設計](#クラスの設計)
- [バッファの説明](#バッファの説明)
- [変更の反映](#変更の反映)
- [おわりに](#おわりに)
- [コード全文](#コード全文)

# 今回の目標
なんだかんだOpenGLの記事、第3回ですね。
今回は前回、前々回と放置してきたバッファに手をつけようと思います。今回の記事では説明が多くなってしまい少々苦しいですが、頑張っていきましょう〜

# クラスの設計
前回と同じ感じで適当に書いていきましょう〜
`Buffer`は頂点データの格納と描画ができればいいので今回の記事では下みたいな設計にしました。
```mermaid
classDiagram
class Buffer {
  - GLuint vao
  - GLuint vbo
  - GLuint ibo
  - GLuint indexCount
  - unsigned size
  - int bufferType
  + Buffer(int bufferType)
  + ~Buffer()
  + unsigned GetSize()
  + void AddAttribute(int loc, int comps, int offset)
  + void SetBuffer(std::vector~float~ data)
  + void SetIndices(std::vector~unsigned~ indices)
  + void ReloadBuffer(std::vector~float~ data)
  + void Draw(int drawmode)
}
```
## コンストラクタ
バッファのタイプを設定して描画に必要なバッファを生成する。バッファのタイプは`GL_XXX_YYY`で表される。(詳しい説明は[Khronos-glBufferData-](https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBufferData.xhtml)を参照)
- データタイプ(`XXX`の部分)

| 名前 | 説明 |
|:---:|:---:|
|STREAM|内容の変更が**1回**で使われる,描画される回数が**少ない** |
|STATIC|内容の変更が**1回**で使われる,描画される回数が**多い**|
|DYNAMIC|内容の変更が**高頻度**で使われる,描画される回数が**多い**|

- アクセスタイプ(`YYY`の部分)

| 名前 | 説明 |
|:---:|:---:|
|DRAW|アプリケーション側からデータが変更され、OpenGL側から読み出される(描画される・コピーされる)|
|READ|OpenGL側からデータが変更され、アプリケーション側から読み出される|
|COPY|OpenGL内だけでデータのコピーが行われる(つまりアプリケーション側からはノータッチ)|

## デストラクタ
生成したバッファを破棄してリソースを解放する。

## GetSize()関数
現在のバッファサイズを取得する。(特に利用用途はない)

## AddAttribute()関数
頂点属性を追加する。[前々回の記事]()に色々書きましたがもう一回載せときます。

- VAO
複数の`VBO`をまとめて管理するためのオブジェクトで複数の`VBO`を利用する場合に使うと便利。(今回の場合は`VBO`を1つしか利用しない実装なのであんまり意味がないです。) 

- VBO
図形の頂点情報(位置や色など)を格納して置くためのバッファを管理するオブジェクトでこれが無いと図形を描画することができません。

- IBO(これは今回が初出)
頂点情報をどんなふうに組み合わせて図形を組み立てるかを指定するためのもの。これを使うと頂点情報の重複を避けてバッファ効率を上げることができます。(今は特に気にしなくて大丈夫です)

## SetBuffer()関数
`VBO`を初期化してデータを送信する。送信時に`VBO`が保持するバッファのサイズを送信するデータのサイズに**リサイズ**してから送信する。
構成済みの頂点データが渡されることを想定。(構成済みとは関数内で特に順番を変える等の処理を行わなくても良い状態を指します)

## SetIndices()関数
`IBO`にインデックスデータを送信する。送信時に`IBO`が保持するインデックスバッファのサイズを**リサイズ**してから送信する。インデックスの要素数が描画時に必要なのでこの関数の呼び出し時に保存しておく。

## ReloadBuffer()関数
`VBO`のデータを更新する。更新時には`VBO`のバッファのサイズは変更されない。送信するデータのサイズが`VBO`のバッファのサイズと異なる場合はエラーを返す。

## Draw()関数
指定された描画モードでバッファを描画する。
この関数が呼び出される前に`SetBuffer()`関数と`SetIndices()`関数が少なくとも1回は呼び出されている必要がある。(呼び出していない場合は何も描画されない)

---

ここからは実際にコードを書いていきましょう〜
まずはヘッダファイル(`includes/Article2/buffer.hpp`)の中身
```cpp
#ifndef __BUFFER_HPP__
#define __BUFFER_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>

#include <vector>

class Buffer {
private:
  GLuint mVAO;
  GLuint mVBO;
  GLuint mIBO;
  GLuint mIndexCount; // インデックスの要素数
  unsigned mSize;
  int mBufferType;

public:
  Buffer(int bufferType);
  ~Buffer();

  unsigned const &GetSize() const { return mSize; }

  void SetBuffer(std::vector<float> data);
  void SetIndices(std::vector<unsigned> indices);

  void AddAttribute(int loc, int comps, int offset);

  void ReloadBuffer(std::vector<float> data);
  void Draw(int drawmode);
};
#endif // __BUFFER_HPP__
```
次に、ソースファイル(`impls/Article2/buffer.cpp`)の中身
```cpp
#include <stdexcept>

#include "../../includes/Article2/buffer.hpp"

Buffer::Buffer(int bufferType) {
  // VAO, VBO, IBOの生成
  glGenVertexArrays(1, &mVAO);
  glGenBuffers(1, &mVBO);
  glGenBuffers(1, &mIBO);

  glBindVertexArray(mVAO); // VAOをバインド
  // VBOをバインド(バインドされているVAOにVBOを紐付け)
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);
  // IBOをバインド(バインドされているVAOにIBOを紐付け)
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);

  mIndexCount = 0;
  mSize = 0;
  mBufferType = bufferType;

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

Buffer::~Buffer() {
  // バッファの削除
  glDeleteVertexArrays(1, &mVAO);
  glDeleteBuffers(1, &mVBO);
  glDeleteBuffers(1, &mIBO);
}

void Buffer::SetBuffer(std::vector<float> data) {
  mSize = data.size();

  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);

  // データをVBOに転送
  glBufferData(GL_ARRAY_BUFFER, mSize * sizeof(float), data.data(),
               mBufferType);

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Buffer::SetIndices(std::vector<unsigned> indices) {
  mIndexCount = indices.size();

  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);

  // データをIBOに転送
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndexCount * sizeof(unsigned),
               indices.data(), mBufferType);

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void Buffer::AddAttribute(int loc, int comps, int offset) {
  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);

  // 頂点属性の設定
  glEnableVertexAttribArray(loc);
  glVertexAttribPointer(loc, comps, GL_FLOAT, GL_FALSE, 0,
                        reinterpret_cast<void *>(offset));

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Buffer::ReloadBuffer(std::vector<float> data) {
  if (data.size() !=
      mSize) { // データサイズがバッファサイズと一致しない場合はエラー
    throw std::runtime_error("Data size does not match buffer size");
  }

  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);

  // データをVBOに転送
  glBufferSubData(GL_ARRAY_BUFFER, 0, mSize * sizeof(float), data.data());

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Buffer::Draw(int drawmode) {
  if (mIndexCount == 0) {
    throw std::runtime_error("No indices to draw");
  }

  glBindVertexArray(mVAO); // VAOをバインド
  // バッファを描画(VAOにIBOが紐付いているのでIBOの情報を使って描画)
  glDrawElements(drawmode, mIndexCount, GL_UNSIGNED_INT, nullptr);
  glBindVertexArray(0); // VAOのバインドを解除
}
```
[前回の記事](https://qiita.com/upiscium/items/120828a9d69115209845)と同じ感じで、`main()`関数にベタ書きされていた内容を`Buffer`クラスの各メソッドに切り出しています。今回も各メソッドの処理内容はコメントに書いてありますが、少し複雑な部分があるので次の章で説明します。
(特に今後の処理には関係無いので読み飛ばしても大丈夫です〜)
また、今回のプログラムはクラスのデストラクタを利用する関係上、`glfwTerminate()`関数の呼び出しを工夫しないと実行後に`Segmentation fault`を吐きます。これを解決するために`init.hpp`と`init.cpp`を書き換えています。
<details><summary>init.hppの変更</summary>
まず、`init.hpp`の中身

```cpp
#ifndef __INIT_HPP__
#define __INIT_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>

class OpenGLContext {
private:
  static bool sGLFWInitialized;
  static bool sGLADInitialized;

public:
  OpenGLContext(unsigned major, unsigned minor);
  ~OpenGLContext();

public:
  static void InitGLAD();
};

#endif // __INIT_HPP__
```
次に、`init.cpp`の中身
```cpp
#include "../../includes/Article2/init.hpp"

#include <stdexcept>

bool OpenGLContext::sGLFWInitialized = false;
bool OpenGLContext::sGLADInitialized = false;

OpenGLContext::OpenGLContext(unsigned major, unsigned minor) {
  if (!glfwInit()) {
    throw std::runtime_error("Failed to initialize GLFW");
  }

  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}

OpenGLContext::~OpenGLContext() {
  if (OpenGLContext::sGLADInitialized) {
    glfwTerminate();
  }
}

void OpenGLContext::InitGLAD() {
  if (!OpenGLContext::sGLADInitialized) {
    if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
      throw std::runtime_error("Failed to initialize GLAD");
    }
    OpenGLContext::sGLADInitialized = true;
  }
}
```

`OpenGLContext`クラスのインスタンスを`main()`関数の最初の部分で作成すると自動的に`glfw`の初期化と終了を適切なタイミングで呼び出されるようにしました。ついでに`InitGLAD()`関数も静的関数として取り込んで、`window.hpp`内の`InitGLAD()`関数の呼び出しも置き換えました。
</details>

# バッファの説明
まず`#0`で少しだけ説明した頂点属性について説明します。
OpenGLの仕様では1つの頂点属性には次の表にある**6つ**の情報を指定する必要があります。
| 引数名 | 型 | 説明 |
|:-:|:-:|:-:|
|index|int|頂点属性のインデックス|
|size|int|頂点属性に含まれる要素数|
|type|int|頂点属性に含まれる要素の型|
|normalized|int|頂点属性に含まれる要素が正規化されているか|
|stride|int|頂点1つ分のデータ長|
|pointer|void*|頂点属性のオフセット|

- `index`はその頂点属性が何番目の頂点属性であるかを表しています。この値を利用して頂点属性を制御します(`glEnableVertexAttribArray()`関数が代表例)。下の図でそれぞれの頂点属性に注目すると`位置`は`0`、`法線`は`1`、`テクスチャ座標`は`2`になります
- `size`はその頂点属性を構成する要素の数を表しています。下の図でそれぞれの頂点属性に注目すると`位置`と`法線`は`x`,`y`,`z`の3つの要素で構成されているので`3`、`テクスチャ座標`は`u`,`v`の2つの要素で構成されているので`2`になります
- `type`はその頂点属性を構成する要素の型を表しています。下の図では全て`float`型で格納されるべき値なので`GL_FLOAT`を指定します。この他にも`GL_UNSIGNED_BYTE`等が使用できます。詳細は[Khronos Group -OpenGL Type-](https://www.khronos.org/opengl/wiki/OpenGL_Type)を参照してください
- `normalized`はその頂点属性を構成する要素が正規化されているかを指定します。今回は特に正規化が行われていないことを想定するので`GL_FALSE`を指定します。(基本的には`GL_FALSE`で問題ないと思います)
- `stride`は頂点1つ分のデータ長を表しています。下の図では`位置`が`3*sizeof(float)`、`法線`が`3*sizeof(float)`、`テクスチャ座標`が`2*sizeof(float)`で合計`32バイト`となり、1つの頂点は`32バイト`のデータで構成されていることがわかるので`32`になります
- `pointer`はその頂点属性がバッファの先頭からどれだけ離れているかを表しています。下の図でそれぞれの頂点属性に注目すると`位置`は先頭にあるので`0`、`法線`は`位置`の分だけ離れているので`3*sizeof(float) = 12`、`テクスチャ座標`は`位置`と`法線`の分だけ離れているので`3*sizeof(float)+3*sizeof(float)=24`になります
(型は`void*`が指定されていますが、実際にはバッファ先頭からの距離を整数値で表したものをポインタにキャストしたものを渡します)

### 頂点属性のイメージ
![OpenGL.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816960/ec345ce9-7b46-f9f6-a4c1-768bb1789b9f.png)

---

もうすでにお腹一杯になるくらい説明した気もしますがもう少々お付き合いください。
(`Buffer`関連でやることが多いのも`OpenGL`の勉強が大変な要因だと思う)

次に`glBindVertex()`関数と`glBindBuffer()`関数について説明します。これらの関数は`OpenGL`のバッファスロットに特定のバッファをセットしたり、外したりするために呼び出します。

下の図のように`VBO`や`IBO`といったバッファは`glBindBuffer()`関数によってバインドされたときにバインドされている`VAO`に帰属します。ここで注意しなければいけないのが関数を呼び出す順番です。`VAO`バインドされている間に`VBO`をアンバインドしてしまうと紐付けが解除されてしまうので、アンバインドするときは **`VAO`→`VBO`&`IBO`の順**で行う必要があります。これを適切に制御しないとデータの変更が反映されなかったり、要らん変更が反映されたりして面倒なことになります。

じゃあ、「バッファをアンバインドしないでバインドしっぱなしすれば良いじゃん！」と思うかもしれませんが、残念なことにこれができないんですね。`OpenGL`の仕様で同時にバインドできるバッファは各種類につき**1つまで**になっています。
(このあたりの仕様が意外と厄介で筆者はこれに散々苦しめられました...)

また、頂点属性も **バインドされている`VBO`** に帰属するので`VBO`がバインドされている状態で頂点属性の設定関数を呼び出さないと、せっかくの設定が**全く反映されない**ことになります。
(筆者はこの仕様にも散々はまり、4回位挫折しました...)

加えて、`glBufferData()`関数などのバッファに対して制御を行う関数も**バインドされている`VBO`** に対して変更を加えてくるので変更したいバッファを呼び出し前にバインドしておく必要があります。

### バインドのイメージ
![OpenGL2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816960/933c18f3-d958-9761-2e2f-9975c92a0ba0.png)

今回はなかなか説明が長くなってしまいました...
では、最後に`main()`関数を書き換えて変更を反映して締めくくりましょう！

# 変更の反映
まず今回作成したヘッダファイルをインクルードしましょう。
```diff_cpp
...

+ #include "../../includes/Article2/buffer.hpp"
#include "../../includes/Article2/init.hpp"
#include "../../includes/Article2/window.hpp"

...
```
続けて、初期化部分やらバッファの生成部分やらを書き換えましょう〜
```diff_cpp
...

int main() {
  // GLFWの初期化
- if (!glfwInit()) {
-   std::cerr << "Failed to initialize GLFW" << std::endl;
-   return -1;
- }

- // OpenGLのバージョン設定 (3.3 Core Profile)
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

+ OpenGLContext context(3, 3);

  // 三角形の頂点データ
- float vertices[] = {
-     0.0f,  0.5f,  0.0f, // 上
-     -0.5f, -0.5f, 0.0f, // 左下
-     0.5f,  -0.5f, 0.0f  // 右下
- };

+ std::vector<float> vertices = {
+     0.0f,  0.5f,  0.0f, // 上
+     -0.5f, -0.5f, 0.0f, // 左下
+     0.5f,  -0.5f, 0.0f  // 右下
+ };

- // VAOとVBOの作成
- unsigned int VAO, VBO;
- glGenVertexArrays(1, &VAO);
- glGenBuffers(1, &VBO);

- // VAOの設定
- glBindVertexArray(VAO);

- // VBOの設定
- glBindBuffer(GL_ARRAY_BUFFER, VBO);
- glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

- // 頂点属性の設定
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
- glEnableVertexAttribArray(0);

+ Buffer buffer(GL_STATIC_DRAW);
+ buffer.SetBuffer(vertices);   // データをバッファにセット
+ buffer.AddAttribute(0, 3, 0); // 頂点属性を追加
+ buffer.SetIndices({0, 1, 2}); // インデックスをセット

...
```
最後に描画ループと終了処理を書き換えておしまいです。
```diff_cpp
  ...
  // メインループ
  while (!window.IsClosed()) {
    ...

    // 三角形の描画
-   glBindVertexArray(VAO);
-   glDrawArrays(GL_TRIANGLES, 0, 3);
+   buffer.Draw(GL_TRIANGLES);

    // バッファのスワップとイベントのポーリング
    window.Update();
  }

- // リソースの解放
- /* glDeleteVertexArrays(1, &VAO); */
- /* glDeleteBuffers(1, &VBO); */
- /* glDeleteProgram(shaderProgram); */

- // GLFWの終了処理
- /* glfwTerminate(); */
  return 0;
}

...
```
バッファの生成と初期化の部分がだいぶスッキリとして、頂点属性の設定部分も見やすくなりましたね！描画部分はあまり変化がありませんが、何をやっているかが少しわかりやすくなりましたね。これで今回の記事でやることはおわりです！お疲れ様でした！

# おわりに
今回は`OpenGL`のバッファについて色々とやって来ましたが、今までの記事とは違いつまらん説明が大量に生成されてしまいました... 
次回はもう少し軽い感じの記事にしたいなぁとは思っているのですが、いい加減シェーダーに手をつけないと行けない気がしているので今回と同じくらい説明だらけになる予感...(つらい)
それではまた次の記事でお会いしましょう。(^^)ﾉｼ

# コード全文
<details><summary>buffer.hpp</summary>

```cpp
#ifndef __BUFFER_HPP__
#define __BUFFER_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>

#include <vector>

class Buffer {
private:
  GLuint mVAO;
  GLuint mVBO;
  GLuint mIBO;
  GLuint mIndexCount; // インデックスの要素数
  unsigned mSize;
  int mBufferType;

public:
  Buffer(int bufferType);
  ~Buffer();

  unsigned const &GetSize() const { return mSize; }

  void SetBuffer(std::vector<float> data);
  void SetIndices(std::vector<unsigned> indices);

  void AddAttribute(int loc, int comps, int offset);

  void ReloadBuffer(std::vector<float> data);
  void Draw(int drawmode);
};
#endif // __BUFFER_HPP__
```

</details>
<details><summary>buffer.cpp</summary>

```cpp
#include <stdexcept>

#include "../../includes/Article2/buffer.hpp"

Buffer::Buffer(int bufferType) {
  // VAO, VBO, IBOの生成
  glGenVertexArrays(1, &mVAO);
  glGenBuffers(1, &mVBO);
  glGenBuffers(1, &mIBO);

  glBindVertexArray(mVAO); // VAOをバインド
  // VBOをバインド(バインドされているVAOにVBOを紐付け)
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);
  // IBOをバインド(バインドされているVAOにIBOを紐付け)
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);

  mIndexCount = 0;
  mSize = 0;
  mBufferType = bufferType;

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

Buffer::~Buffer() {
  // バッファの削除
  glDeleteVertexArrays(1, &mVAO);
  glDeleteBuffers(1, &mVBO);
  glDeleteBuffers(1, &mIBO);
}

void Buffer::SetBuffer(std::vector<float> data) {
  mSize = data.size();

  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);

  // データをVBOに転送
  glBufferData(GL_ARRAY_BUFFER, mSize * sizeof(float), data.data(),
               mBufferType);

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Buffer::SetIndices(std::vector<unsigned> indices) {
  mIndexCount = indices.size();

  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBO);

  // データをIBOに転送
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndexCount * sizeof(unsigned),
               indices.data(), mBufferType);

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void Buffer::AddAttribute(int loc, int comps, int offset) {
  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);

  // 頂点属性の設定
  glEnableVertexAttribArray(loc);
  glVertexAttribPointer(loc, comps, GL_FLOAT, GL_FALSE, 0,
                        reinterpret_cast<void *>(offset));

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Buffer::ReloadBuffer(std::vector<float> data) {
  if (data.size() !=
      mSize) { // データサイズがバッファサイズと一致しない場合はエラー
    throw std::runtime_error("Data size does not match buffer size");
  }

  // バッファをバインド
  glBindVertexArray(mVAO);
  glBindBuffer(GL_ARRAY_BUFFER, mVBO);

  // データをVBOに転送
  glBufferSubData(GL_ARRAY_BUFFER, 0, mSize * sizeof(float), data.data());

  // バインドを解除
  glBindVertexArray(0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Buffer::Draw(int drawmode) {
  if (mIndexCount == 0) {
    throw std::runtime_error("No indices to draw");
  }

  glBindVertexArray(mVAO); // VAOをバインド
  // バッファを描画(VAOにIBOが紐付いているのでIBOの情報を使って描画)
  glDrawElements(drawmode, mIndexCount, GL_UNSIGNED_INT, nullptr);
  glBindVertexArray(0); // VAOのバインドを解除
}
```

</details>
<details><summary>init.hpp</summary>

```cpp
#ifndef __INIT_HPP__
#define __INIT_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>

class OpenGLContext {
private:
  static bool sGLFWInitialized;
  static bool sGLADInitialized;

public:
  OpenGLContext(unsigned major, unsigned minor);
  ~OpenGLContext();

public:
  static void InitGLAD();
};

#endif // __INIT_HPP__
```

</details>
<details><summary>init.cpp</summary>

```cpp
#include "../../includes/Article2/init.hpp"

#include <stdexcept>

bool OpenGLContext::sGLFWInitialized = false;
bool OpenGLContext::sGLADInitialized = false;

OpenGLContext::OpenGLContext(unsigned major, unsigned minor) {
  if (!glfwInit()) {
    throw std::runtime_error("Failed to initialize GLFW");
  }

  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
}

OpenGLContext::~OpenGLContext() {
  if (OpenGLContext::sGLADInitialized) {
    glfwTerminate();
  }
}

void OpenGLContext::InitGLAD() {
  if (!OpenGLContext::sGLADInitialized) {
    if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) {
      throw std::runtime_error("Failed to initialize GLAD");
    }
    OpenGLContext::sGLADInitialized = true;
  }
}
```

</details>
<details><summary>window.hpp</summary>

```cpp
#ifndef __WINDOW_HPP__
#define __WINDOW_HPP__

#include <glad/gl.h>

#include <GLFW/glfw3.h>

#include <functional>
#include <string>

class Window; // クラスの前方宣言

// コールバック関数の型
typedef std::function<void(Window *, int, int)> FramebufferSizeCallback;
typedef std::function<void(Window *, int, int)> WindowPosCallback;
typedef std::function<void(Window *, int, int)> WindowSizeCallback;
typedef std::function<void(Window *, int)> CursorEnterCallback;
typedef std::function<void(Window *, double, double)> CursorPosCallback;
typedef std::function<void(Window *, double, double)> ScrollCallback;

namespace callback {
// コールバック関数のラッパー
void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height);
void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y);
void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height);
void CursorEnterCallbackWrapper(GLFWwindow *window, int entered);
void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y);
void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset);
} // namespace callback

// ウィンドウクラス
class Window {
private:
  GLFWwindow *mWindow;
  std::pair<double, double> mCursorPos;
  std::pair<double, double> mScroll;
  FramebufferSizeCallback mFramebufferSizeCallback;
  WindowPosCallback mWindowPosCallback;
  WindowSizeCallback mWindowSizeCallback;
  CursorEnterCallback mCursorEnterCallback;
  CursorPosCallback mCursorPosCallback;
  ScrollCallback mScrollCallback;

  // コールバック関数のフレンド宣言
  friend void(callback::FramebufferSizeCallbackWrapper)(GLFWwindow *, int width,
                                                        int height);
  friend void(callback::WindowPosCallbackWrapper)(GLFWwindow *window, int x,
                                                  int y);
  friend void(callback::WindowSizeCallbackWrapper)(GLFWwindow *window,
                                                   int width, int height);
  friend void(callback::CursorEnterCallbackWrapper)(GLFWwindow *window,
                                                    int entered);
  friend void(callback::CursorPosCallbackWrapper)(GLFWwindow *window, double x,
                                                  double y);
  friend void(callback::ScrollCallbackWrapper)(GLFWwindow *window,
                                               double xoffset, double yoffset);

public:
  Window(int width, int height, std::string title);

  std::pair<double, double> GetCursorPos() const { return mCursorPos; }
  std::pair<double, double> GetScroll() const { return mScroll; }

  void SetFramebufferSizeCallback(FramebufferSizeCallback callback) {
    mFramebufferSizeCallback = callback;
  }
  void SetWindowPosCallback(WindowPosCallback callback) {
    mWindowPosCallback = callback;
  }
  void SetWindowSizeCallback(WindowSizeCallback callback) {
    mWindowSizeCallback = callback;
  }
  void SetCursorEnterCallback(CursorEnterCallback callback) {
    mCursorEnterCallback = callback;
  }
  void SetCursorPosCallback(CursorPosCallback callback) {
    mCursorPosCallback = callback;
  }
  void SetScrollCallback(ScrollCallback callback) {
    mScrollCallback = callback;
  }

  bool IsPressed(int key) const {
    return glfwGetKey(mWindow, key) == GLFW_PRESS;
  }
  bool IsClicked(int mousebutton) const {
    return glfwGetMouseButton(mWindow, mousebutton) == GLFW_PRESS;
  }
  bool IsClosed() const { return glfwWindowShouldClose(mWindow); }

  void Close() { glfwSetWindowShouldClose(mWindow, true); }
  void Update();
  void Fill(float r, float g, float b, float a);
};

#endif // __WINDOW_HPP__
```

</details>
<details><summary>window.cpp</summary>

```cpp
#include "../../includes/Article2/window.hpp"
#include "../../includes/Article2/init.hpp"

namespace callback {
void FramebufferSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window); // 紐付けたポインタ
  if (ptr->mFramebufferSizeCallback) { // コールバック関数が登録されている
    ptr->mFramebufferSizeCallback(ptr, width, height);
  }
}

void WindowPosCallbackWrapper(GLFWwindow *window, int x, int y) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  if (ptr->mWindowPosCallback) {
    ptr->mWindowPosCallback(ptr, x, y);
  }
}

void WindowSizeCallbackWrapper(GLFWwindow *window, int width, int height) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  if (ptr->mWindowSizeCallback) {
    ptr->mWindowSizeCallback(ptr, width, height);
  }
}

void CursorEnterCallbackWrapper(GLFWwindow *window, int entered) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  if (ptr->mCursorEnterCallback) {
    ptr->mCursorEnterCallback(ptr, entered);
  }
}

void CursorPosCallbackWrapper(GLFWwindow *window, double x, double y) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  ptr->mCursorPos = {x, y};
  if (ptr->mCursorPosCallback) {
    ptr->mCursorPosCallback(ptr, x, y);
  }
}

void ScrollCallbackWrapper(GLFWwindow *window, double xoffset, double yoffset) {
  Window *ptr = (Window *)glfwGetWindowUserPointer(window);
  ptr->mScroll = {xoffset, yoffset};
  if (ptr->mScrollCallback) {
    ptr->mScrollCallback(ptr, xoffset, yoffset);
  }
}
} // namespace callback

Window::Window(int width, int height, std::string title) {
  // ウィンドウの作成
  mWindow = glfwCreateWindow(width, height, title.c_str(), NULL, NULL);
  glfwMakeContextCurrent(mWindow);
  OpenGLContext::InitGLAD();

  glfwSetWindowUserPointer(mWindow, this); // 自分のポインタをGLFWwindowと紐付け
  // コールバックを登録
  glfwSetFramebufferSizeCallback(mWindow,
                                 callback::FramebufferSizeCallbackWrapper);
  glfwSetWindowPosCallback(mWindow, callback::WindowPosCallbackWrapper);
  glfwSetWindowSizeCallback(mWindow, callback::WindowSizeCallbackWrapper);
  glfwSetCursorEnterCallback(mWindow, callback::CursorEnterCallbackWrapper);
  glfwSetCursorPosCallback(mWindow, callback::CursorPosCallbackWrapper);
  glfwSetScrollCallback(mWindow, callback::ScrollCallbackWrapper);
}

void Window::Update() {
  // バッファのスワップとイベントのポーリング
  glfwSwapBuffers(mWindow);
  glfwPollEvents();
}

void Window::Fill(float r, float g, float b, float a) {
  // 背景色の設定とクリア
  glClearColor(r, g, b, a);
  glClear(GL_COLOR_BUFFER_BIT);
}
```

</details>
<details><summary>main.cpp</summary>

```cpp
#include <glad/gl.h>

#include <GLFW/glfw3.h>
#include <iostream>

#include "../../includes/Article2/buffer.hpp"
#include "../../includes/Article2/init.hpp"
#include "../../includes/Article2/window.hpp"

// 関数宣言
void framebuffer_size_callback(Window *window, int width, int height);
unsigned int createShaderProgram(const char *vertexSource,
                                 const char *fragmentSource);

// 頂点シェーダーのソースコード
const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
    gl_Position = vec4(aPos, 1.0);
}
)";

// フラグメントシェーダーのソースコード
const char *fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;
void main() {
    FragColor = vec4(1.0, 0.5, 0.2, 1.0);
}
)";

int main() {
  // GLFWの初期化
  OpenGLContext context(3, 3);

  // ウィンドウの作成
  Window window(800, 600, "OpenGL");

  // ビューポートの設定
  glViewport(0, 0, 800, 600);
  window.SetFramebufferSizeCallback(framebuffer_size_callback);

  // シェーダープログラムの作成
  unsigned int shaderProgram =
      createShaderProgram(vertexShaderSource, fragmentShaderSource);

  // 三角形の頂点データ
  std::vector<float> vertices = {
      0.0f,  0.5f,  0.0f, // 上
      -0.5f, -0.5f, 0.0f, // 左下
      0.5f,  -0.5f, 0.0f  // 右下
  };

  Buffer buffer(GL_STATIC_DRAW);
  buffer.SetBuffer(vertices);   // データをバッファにセット
  buffer.AddAttribute(0, 3, 0); // 頂点属性を追加
  buffer.SetIndices({0, 1, 2}); // インデックスをセット

  // メインループ
  while (!window.IsClosed()) {
    // 入力処理
    if (window.IsPressed(GLFW_KEY_ESCAPE)) {
      window.Close();
    }

    // 背景色の設定とクリア
    window.Fill(0.2f, 0.2f, 0.2f, 1.0f);

    // シェーダープログラムの使用
    glUseProgram(shaderProgram);

    // 三角形の描画
    buffer.Draw(GL_TRIANGLES);

    // バッファのスワップとイベントのポーリング
    window.Update();
  }

  return 0;
}

// ウィンドウサイズが変更されたときのコールバック関数
void framebuffer_size_callback(Window *window, int width, int height) {
  glViewport(0, 0, width, height);
}

// シェーダープログラムの作成関数
unsigned int createShaderProgram(const char *vertexSource,
                                 const char *fragmentSource) {
  // 頂点シェーダーのコンパイル
  unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
  glShaderSource(vertexShader, 1, &vertexSource, nullptr);
  glCompileShader(vertexShader);

  // コンパイルエラーチェック
  int success;
  char infoLog[512];
  glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
  if (!success) {
    glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
    std::cerr << "ERROR::VERTEX_SHADER::COMPILATION_FAILED\n"
              << infoLog << std::endl;
  }

  // フラグメントシェーダーのコンパイル
  unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
  glCompileShader(fragmentShader);

  // コンパイルエラーチェック
  glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
  if (!success) {
    glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
    std::cerr << "ERROR::FRAGMENT_SHADER::COMPILATION_FAILED\n"
              << infoLog << std::endl;
  }

  // シェーダープログラムのリンク
  unsigned int shaderProgram = glCreateProgram();
  glAttachShader(shaderProgram, vertexShader);
  glAttachShader(shaderProgram, fragmentShader);
  glLinkProgram(shaderProgram);

  // リンクエラーチェック
  glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
  if (!success) {
    glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
    std::cerr << "ERROR::SHADER_PROGRAM::LINKING_FAILED\n"
              << infoLog << std::endl;
  }

  // シェーダーオブジェクトの削除
  glDeleteShader(vertexShader);
  glDeleteShader(fragmentShader);

  return shaderProgram;
}
```

</details>