En el desarrollo con cocos2d-x, el manejo eficiente del renderizado OpenGL es crucial. Este artículo se centra en el comando QUAD_COMMAND, explicando cómo se utilizan los objetos de arreglo de vértices (VAO) y los objetos de búfer de vértices (VBO) para optimizar la transmisión de datos entre CPU y GPU.
Conceptos de VAO y VBO
Los VBO (Vertex Buffer Objects) son búferes en la memoria de la tarjeta gráfica que almacenan datos de vértices, como posiciones, colores y coordenadas de textura. Esto reduce la necesidad de enviar datos repetidamente desde la CPU. Los VAO (Vertex Array Objects) encapsulan múltiples VBO y su estado de configuración, simplificando el código de renderizado y mejorando el rendimiento al minimizar las llamadas a OpenGL durante el dibujo.
En cocos2d-x, se verifica la compatibilidad con VAO; si está disponible, se prefiere su uso para mayor eficiencia.
Inicialización de búferes
El código de inicialización varía según el soporte de VAO. A continuación se muestra una versión reestructurada con nombres de variables y funciones modificados:
void Renderer::initializeBuffers() {
if (Configuration::getInstance()->supportsShareableVAO()) {
setupVAOWithVBOs();
} else {
setupVBOsOnly();
}
}
void Renderer::setupVAOWithVBOs() {
glGenVertexArrays(1, &mVertexArrayObj);
GL::bindVAO(mVertexArrayObj);
glGenBuffers(2, mBufferHandles);
glBindBuffer(GL_ARRAY_BUFFER, mBufferHandles[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(mQuadData[0]) * MAX_QUADS, mQuadData, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_T2F, vertices));
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_T2F, colors));
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORDS);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*)offsetof(V3F_C4B_T2F, texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferHandles[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(mIndices[0]) * MAX_QUADS * 6, mIndices, GL_STATIC_DRAW);
GL::bindVAO(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
CHECK_GL_ERROR_DEBUG();
}
void Renderer::setupVBOsOnly() {
glGenBuffers(2, mBufferHandles);
mapBufferData();
}
void Renderer::mapBufferData() {
GL::bindVAO(0);
glBindBuffer(GL_ARRAY_BUFFER, mBufferHandles[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(mQuadData[0]) * MAX_QUADS, mQuadData, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferHandles[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(mIndices[0]) * MAX_QUADS * 6, mIndices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
CHECK_GL_ERROR_DEBUG();
}
Funciones clave de OpenGL
glBindBuffer: Selecciona el búfer activo para operaciones subsecuentes. El primer parámetro define el tipo (GL_ARRAY_BUFFER para vértices, GL_ELEMENT_ARRAY_BUFFER para índices). El segundo parámetro es el identificador del búfer; al usar uno nuevo se crea, con 0 se deselecciona.
glBufferData: Asigna memoria en la GPU para el búfer enlazado. Los parámetros incluyen el tamaño, los datos iniciales y el patrón de uso (GL_DYNAMIC_DRAW para datos que cambian frecuentemente, GL_STATIC_DRAW para datos estáticos).
Flujo de renderizado con VAO
Durante el dibujo, si se soporta VAO, se actualizan los datos en el VBO y se enlaza el VAO para reducir llamadas. El código adaptado del renderizado es el siguiente:
void Renderer::drawQuads() {
if (Configuration::getInstance()->supportsShareableVAO()) {
glBindBuffer(GL_ARRAY_BUFFER, mBufferHandles[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(mQuadData[0]) * mQuadCount, nullptr, GL_DYNAMIC_DRAW);
void* mappedData = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(mappedData, mQuadData, sizeof(mQuadData[0]) * mQuadCount);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GL::bindVAO(mVertexArrayObj);
} else {
glBindBuffer(GL_ARRAY_BUFFER, mBufferHandles[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(mQuadData[0]) * mQuadCount, mQuadData, GL_DYNAMIC_DRAW);
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F_Quad), (GLvoid*)offsetof(V3F_C4B_T2F_Quad, vertices));
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F_Quad), (GLvoid*)offsetof(V3F_C4B_T2F_Quad, colors));
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F_Quad), (GLvoid*)offsetof(V3F_C4B_T2F_Quad, texCoords));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferHandles[1]);
}
glDrawElements(GL_TRIANGLES, mQuadCount * 6, GL_UNSIGNED_SHORT, (GLvoid*)(mStartIndex * 6 * sizeof(mIndices[0])));
}
glMapBuffer y glUnmapBuffer: Permiten mapear la memoria del búfer para actualizaciones eficientes. enableVertexAttribs activa los atributos de vértices (posición, color, coordenadas de textura). glVertexAttribPointer configura el formato de los datos de vértices.
Manejo de QUAD_COMMAND
Los comandos QUAD_COMMAND se acumulan en un búfer. Si el número total de cuadrados excede el tamaño del VBO, se ejecuta el dibujo inmediato. El código de procesameinto se reestructura así:
void Renderer::processCommand(RenderCommand* command) {
if (command->getType() == RenderCommand::Type::QUAD_COMMAND) {
auto quadCmd = static_cast<quadcommand>(command);
if (mQuadCount + quadCmd->getQuadCount() > MAX_QUADS) {
flushBatchedQuads();
}
mBatchedCommands.push_back(quadCmd);
memcpy(mQuadData + mQuadCount, quadCmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * quadCmd->getQuadCount());
transformToWorld(mQuadData + mQuadCount, quadCmd->getQuadCount(), quadCmd->getModelViewMatrix());
mQuadCount += quadCmd->getQuadCount();
}
}
void Renderer::flushBatchedQuads() {
drawQuads();
mLastMaterialID = 0;
mQuadCount = 0;
mBatchedCommands.clear();
}
</quadcommand>
La función flushBatchedQuads se invoca para dibujar todos los comandos acumulados. Los recursos de VBO se liberan en el destructor usando glDeleteBuffers.