# OpenGL 4.5
[toc]
## OpenGL 4.5 跟 3.3 差在哪裡?
現在網路上的主流教學都是 3.3,所以多半應該都比較熟悉這個版本,但如果你還在學 2.x 甚至 1.x,那我建議你至少跳到 3.3,最好的話就直接學 4.5,因為你現在可能心想:幹嘛一定都要用最新版的 API?老實跟你說 OpenGL 4.5 也不新了,這個版本的 API 規範是在 2014/08/11 釋出的,最新的 4.6 則是 2017/07/31;而且 Khronos Group 有 Vulkan 了,硬要說比較的話,大概就是 OpenGL 3.x 對應的是 DirectX 3D 10、GL 4.x 對應的是 DX 11、Vulkan 對應 DX12。
你現在應該沒有聽到還有新遊戲是用 DX10 做了吧,艾爾登法環用 DX12,多數遊戲都是在 DX11;所以對我來說 OpenGL 4.5 是能唯一抗衡的 3D API 了。
## DSA 方法
## 程式碼
### 建立 VAO 與 VBO
#### OpenGL 3.3
```cpp=
unsigned int vao, vbo, ebo;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), reinterpret_cast<void*>(0));
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
```
#### OpenGL 4.5
```cpp=
unsigned int vao, vbo, ebo;
int binding_index = 2;
int attribute_index = 0;
int size = 3;
int offset = 0;
int stride = size * sizeof(float);
glCreateVertexArrays(1, &vao);
glCreateBuffers(1, &vbo);
glNamedBufferStorage(vbo, m_Vertices.size() * sizeof(float), m_Vertices.data(), GL_MAP_WRITE_BIT);
glVertexArrayVertexBuffer(vao, binding_index, vbo, offset, stride);
glVertexArrayAttribFormat(vao, attribute_index, size, GL_FLOAT, GL_FALSE, 0);
glVertexArrayAttribBinding(vao, attribute_index, binding_index);
glEnableVertexArrayAttrib(vao, attribute_index);
glCreateBuffers(1, &ebo);
glNamedBufferStorage(ebo, m_Indices.size() * sizeof(unsigned int), m_Indices.data(), GL_MAP_WRITE_BIT);
glVertexArrayElementBuffer(vao, ebo);
```
3.3 版本的 OpenGL 寫法會有點類似狀態機,你要綁定 vao、vbo 或者 ebo 到當前的狀態下,你才可以對該 Buffer 進行操作。但現在 OpenGL 提出一種新的做法,就是你可以不用綁了,你需要傳參數進去才知道對哪一個 Buffer 修改。
例如,`glVertexArray` 開頭的就是代表需要 `vao` 傳入, `glBindVertexBuffer()` 和 `glVertexArrayVertexBuffer()` 兩個版本的函數但基本上是做同一件事情,前者是依據當前綁定的 `vao` 做操作,後者需要參數傳入。至於 `vbo` 跟 `ebo` 以前都是用 `glBufferData()`,現在也可以用 `glNamedBufferData()`。
另外 4.4 版本才有的 `glBufferStorage()`,在 4.5 也有 `glNamedBufferStorage()` 可以使用。有 `glNamed` 前綴就是代表需要傳入 buffer id,型態是 `GLuint`。
然後 Storage 跟 Data 的差別,其實兩個基本上都是做一樣的事情,把資料送入 buffer,差別在於 Storage 產生出來的 buffer 是屬於 **immutable**,其產生出來的記憶體大小是不可變的,但你可以透過 `glBufferSubData()` (2.x 就有了函式) 來修改與更新 buffer 內部的資料。
其實 `glBufferSubData()` 它都有一個 sub 了,顧名思義就是可以局部修改、更新 buffer 內的資料,也可以全部修改與更新。
以前我們圖學不是有作業嗎,可能要繪製 billboard 或者 view frustum,每一個 frame 都要更新頂點資料,如果都是用 `glBufferData()` 其實是最耗效能的,即使你有下 `GL_DYNAMIC_DRAW` 或許會好一點,但之所以效能會比 `glBufferStorage()` 差就是因為 data 每次跑,它就會重新 alloc 一個 buffer 出來,原本的 buffer 就會被孤立(應該是會回收吧不然就 memory leak了 哈哈)
如果你是用 `glBufferStorage()`,那產生出來的 buffer 就不能被改變,據說這樣會有性能優勢 (persistent mapped memory),然後你不能再次對相同的 buffer call `glBufferStorage()`,否則就會報錯了。
> 如果用 `glBufferStorage()` 建立 buffer,後續需要透過 `glBufferSubData()` 來修改或者更新 buffer 內部資料的話,要記得下 GL_DYNAMIC_STORAGE_BIT
然後有一個坑就是,如果想要使用 `glNamed` 函數,那些 buffer,用 `glCreate` 是最省事的。如果堅持要用 `glGen` 的話,就必須還是要 `glBindBuffer()`,那就失去用 `glNamed` 的含義了 XD
會有這樣的差別是因為,`glGenBuffer()` 還不會真正建立 buffer,此時不具備任何意義,必須要 `glBindBuffer()`,並且看你 bind 的 target,OpenGL 才會賦予這個 buffer 意義,也就是變成 vertex buffer object。
glVertexAttribPointer 被拆解成 glVertexArrayVertexBuffer、glVertexArrayAttribFormat 和 glVertexArrayAttribBinding

### 建立 Texture
#### OpenGL 3.3
```cpp=
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
```
#### OpenGL 4.5
```cpp=
unsigned int textureID;
unsigned int width = desc.width;
unsigned int height = desc.height;
// upload image
glCreateTextures(GL_TEXTURE_2D, 1, &textureID);
glTextureStorage2D(textureID, 1, GL_RGB8, width, height);
glTextureSubImage2D(textureID, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
// 設定 MAG MIN Filter
glTextureParameteri(textureID, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(textureID, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTextureParameteri(textureID, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTextureParameteri(textureID, GL_TEXTURE_MAG_FILTER, GL_REPEAT);
// Mipmap
glGenerateMipmap(textureID);
// 建立 Sampler
glCreateSamplers(1, &m_Sampler);
glSamplerParameteri(m_Sampler, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glSamplerParameteri(m_Sampler, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glSamplerParameteri(m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glSamplerParameteri(m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindSampler(0, m_Sampler);
```
據說 glBindTextureUnit 就等價於 glActiveTexture 和 glBindTexture
只是我想不通 Sampler 如果可以獨立創建,它有什麼好處 XD
因為在 DirectX 中,Texture 和 Sampler 基本上是兩個獨立傳入 shader 的東西
所以在 hlsl 你可以用 A Sampler 去讀取 B、C Texture,然後用 B Sampler 讀取 A Texture
但 OpenGL,似乎只能一個 Sampler 就對應到一個 Texture
正確的講法來說是 Texture Slot,你必須要把 Sampler 和 Texture 都綁定在同一個 binding index 上,並且從 shader 的 sampler2D,指定對應的 slot,才可以使用你選擇的 Sampler 去讀取你想要的 Texture。
## Reference
https://stackoverflow.com/questions/37972229/glvertexattribpointer-and-glvertexattribformat-whats-the-difference
https://stackoverflow.com/questions/21153729/glbindvertexbuffer-vs-glbindbuffer