---
# System prepended metadata

title: OpenGL 記憶體控管
tags: [OpenGL, C/C++]

---


## 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. 