## OpenGL 記憶體控管
OpenGL擁有 VAO,VBO,EBO 等等的VRAM物件,並與Shader(for GPU rendering program)搭配進行渲染。
### Vertex Buffer Object (VBO)
VBO(Vertex Buffer Object) 是 OpenGL 中的一種緩衝區物件,用於在顯示卡記憶體(VRAM)中儲存頂點資料(如位置、顏色、法線、紋理座標等)。它使得數據從系統記憶體傳輸到VRAM中,並允許 GPU 有效率地存取這些數據,從而提高渲染效能。
```c++=
//Step1
GLuint vbo;
glGenBuffers(1, &vbo); // create a VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo); // binding VBO into GL_ARRAY_BUFFER
//Step2 : uploading cpu data into VRAM of gpu, 一次性上傳
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//Step3 : unbind to avoid any modification
glBindBuffer(GL_ARRAY_BUFFER, 0);
//Step4 : drawing
while (draw) {
//Once use vbo, we need to bind.
glBindBuffer(GL_ARRAY_BUFFER, vbo);
//And then call glDrawElements() to draw
glDrawElements(GL_TRIANGLES, indexSize, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
```
#### 解綁 VBO 與釋放 VRAM 資料的差異:
- 解綁(```glBindBuffer(GL_ARRAY_BUFFER, 0);```):
解綁操作只是取消目前緩衝區物件與目標綁定的關係。它不會刪除或釋放顯存中的資料。
解綁定後,目前綁定的 VBO 物件會被解除綁定,之後的 OpenGL 呼叫就不再操作該對象,直到它被重新綁定到某個目標。
- 釋放資料(```glDeleteBuffers(1, &vbo);```):
若要釋放VRAM中的數據,可以使用 ```glDeleteBuffers``` 來刪除緩衝區對象,這將釋放與該物件相關的記憶體。
### Element Buffer Object (EBO)
EBO 是 OpenGL 中用於儲存索引資料的一種緩衝區物件。它的作用是透過索引來組織和存取頂點數據,而**不需要重複儲存vertex資訊**。 EBO 在某些渲染場景中是非常必要的,尤其是在需要處理大量頂點資料時,能顯著提高效能並減少記憶體使用。
### Vertex Array Object (VAO)
VAO 是一個狀態容器,用於記錄與 vertex 資料相關的資料格式。VAO 不能單獨使用,需要和 VBO 配合。再進階的使用會和 EBO 一起使用。本身存在是為了高效性:透過儲存指向 VBO 的方式,VAO 可以在不同的渲染呼叫時重複使用相同的 VBO 配置(i.g. 3D 位置),而不需要每次都重新綁定 VBO 和設定屬性。
#### VAO 儲存的內容
- vertex 屬性配置資料**格式**
VAO 記錄每個vertex屬性(如位置、顏色、法線等)在緩衝區中的**格式**(如資料類型、步長、偏移量等)。這些資訊由 ```glVertexAttribPointer``` 配置。
- 指向 VBO 的指標(綁定狀態)
VAO 記錄的是 綁定的 VBO(通常是 ```GL_ARRAY_BUFFER``` 類型的緩衝區)的位置和狀態。例如,```glVertexAttribPointer``` 指定的 VBO 被綁定為 ```GL_ARRAY_BUFFER``` 時,VAO 會記錄該 VBO 的綁定資訊。簡而言之,VAO 會儲存“目前綁定的 VBO 的標識符”,而不是直接儲存緩衝區的資料。
- EBO綁定狀態
如果使用了索引緩衝(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)),VAO 也會記錄目前綁定的 EBO。
<center>
<img width=60% src=https://hackmd.io/_uploads/SyyWxYVfyg.png>
</center>
#### VAO 和 VBO 的關係
- VBO 儲存資料:VBO 本身用於儲存實際的頂點資料(如位置、顏色、法線等)。
- VAO 儲存狀態:VAO 不會儲存 VBO 的數據,而是儲存 VBO 的綁定狀態以及如何從 VBO 中讀取資料的方式(例如,頂點屬性的格式和偏移量)。
#### 不靠VAO僅VBO的繪圖方式
這種方式需要在每次繪製之前重新配置頂點屬性指標 (```glVertexAttribPointer```),因為沒有 VAO 來儲存這些設定。
:::spoiler 不靠VAO僅VBO的繪圖方式
```c++=
// 1. 建立並綁定 VBO
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 2. 配置頂點屬性指標
// 頂點位置屬性 (vec3)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 紋理座標屬性 (vec2)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 頂點顏色屬性 (vec3)
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float)));
glEnableVertexAttribArray(2);
// 3. 解除 VBO 綁定(資料配置已儲存在 GPU 中)
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 繪製時每次都重新設定
while (!glfwWindowShouldClose(window)) {
glClear(GL_COLOR_BUFFER_BIT);
// 重新綁定 VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 再次設定頂點屬性指標
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float)));
glEnableVertexAttribArray(2);
// 繪製呼叫
glDrawArrays(GL_TRIANGLES, 0, 6);
glfwSwapBuffers(window);
glfwPollEvents();
}
```
:::
#### VAO+VBO+EBO 程式碼
請與上圖搭配使用:
```c++=
int main() {
//...
initVertex();
while(isDraw) {
draw(frame, projection, view, model);
}
//...
}
void initVertex() {
glGenVertexArrays(1, &mVAO);
glGenBuffers(1, &mVBO);
glGenBuffers(1, &mEBO);
// Bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
// [1] bind Vertex Array Object [VAO]
glBindVertexArray(mVAO);
// [2] copy our vertices array in a vertex buffer object [VBO] for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, mVBO);
glBufferData(GL_ARRAY_BUFFER, positionSize * 6 * sizeof(float), p_vertexes, GL_STATIC_DRAW);
// [3] copy our index array in a element buffer object [EBO] for OpenGL to use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize * sizeof(unsigned int), p_indexes, GL_STATIC_DRAW);
// [4] set the vertex attributes pointers for shader, [VAO] will store glVertexAttribPointer and [VBO].
// ours shader program of vertex:
// layout (location = 0) in vec3 position;
// layout (location = 1) in vec2 texCoord;
// layout (location = 2) in float alpha;
// format :
// glVertexAttribPointer(location, dimention, dataType, IsNormalized, stride of a vertex, startPositionOfPointer)
// glEnableVertexAttribArray(location)
// layout (location = 0) in vec3 position;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)0); //according to vertex shader
glEnableVertexAttribArray(0); //according to vertex shader
// layout (location = 1) in vec2 texCoord;
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)(sizeof(float) * 3));
glEnableVertexAttribArray(1);
// layout (location = 2) in float alpha;
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(float) * 6, (void*)(sizeof(float) * 5));
glEnableVertexAttribArray(2);
// [5] finish configuration, then unbind buffer to avoid any modification and occuping GPU resource.
// Note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// [Remember]: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound.
// i.g. Do not write : glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void draw(const cv::Mat& frame, const glm::mat4& projection, const glm::mat4& view, const glm::mat4& model) {
//[Texture]
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame.data);
//[Vertex]
shader.use();
shader.setMat4("projection", projection);
shader.setMat4("view", view);
shader.setMat4("model", model);
//[Start Drawing]
//Once use VAO, we need to bind.
glBindVertexArray(mVAO);
//And then call glDrawElements() to draw
glDrawElements(GL_TRIANGLES, indexSize, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
```
## Reference
1. [OpengGL 記憶體控管, csdn](https://blog.csdn.net/csxiaoshui/article/details/53197527)
2.