diff --git a/cpp/functions/gfx/mesh_gpu.cpp b/cpp/functions/gfx/mesh_gpu.cpp new file mode 100644 index 00000000..6b8ea084 --- /dev/null +++ b/cpp/functions/gfx/mesh_gpu.cpp @@ -0,0 +1,69 @@ +#include "gfx/mesh_gpu.h" + +#include + +namespace fn::gfx { + +MeshGpu mesh_gpu_upload(const Mesh& mesh) { + MeshGpu g{}; + if (mesh.positions.empty() || mesh.indices.empty()) return g; + if (mesh.normals.size() != mesh.positions.size()) return g; + + const size_t nverts = mesh.positions.size() / 3; + + // Interleave pos.xyz + normal.xyz, stride 6 floats. + std::vector interleaved; + interleaved.resize(nverts * 6); + for (size_t i = 0; i < nverts; ++i) { + interleaved[i*6 + 0] = mesh.positions[i*3 + 0]; + interleaved[i*6 + 1] = mesh.positions[i*3 + 1]; + interleaved[i*6 + 2] = mesh.positions[i*3 + 2]; + interleaved[i*6 + 3] = mesh.normals[i*3 + 0]; + interleaved[i*6 + 4] = mesh.normals[i*3 + 1]; + interleaved[i*6 + 5] = mesh.normals[i*3 + 2]; + } + + glGenVertexArrays(1, &g.vao); + glGenBuffers(1, &g.vbo); + glGenBuffers(1, &g.ebo); + + glBindVertexArray(g.vao); + + glBindBuffer(GL_ARRAY_BUFFER, g.vbo); + glBufferData(GL_ARRAY_BUFFER, + (GLsizeiptr)(interleaved.size() * sizeof(float)), + interleaved.data(), + GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g.ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + (GLsizeiptr)(mesh.indices.size() * sizeof(uint32_t)), + mesh.indices.data(), + GL_STATIC_DRAW); + + // location 0 = a_pos (vec3) + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, + (GLsizei)(6 * sizeof(float)), + (const void*)0); + // location 1 = a_normal (vec3) + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, + (GLsizei)(6 * sizeof(float)), + (const void*)(3 * sizeof(float))); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + g.index_count = (int)mesh.indices.size(); + return g; +} + +void mesh_gpu_destroy(MeshGpu& g) { + if (g.ebo) { glDeleteBuffers(1, &g.ebo); g.ebo = 0; } + if (g.vbo) { glDeleteBuffers(1, &g.vbo); g.vbo = 0; } + if (g.vao) { glDeleteVertexArrays(1, &g.vao); g.vao = 0; } + g.index_count = 0; +} + +} // namespace fn::gfx diff --git a/cpp/functions/gfx/mesh_gpu.h b/cpp/functions/gfx/mesh_gpu.h new file mode 100644 index 00000000..e1f37576 --- /dev/null +++ b/cpp/functions/gfx/mesh_gpu.h @@ -0,0 +1,25 @@ +#pragma once + +#include "gfx/gl_loader.h" +#include "gfx/mesh_obj_load.h" + +namespace fn::gfx { + +// VAO + VBO interleaved (pos.xyz, nrm.xyz) + EBO. ok() despues de upload. +struct MeshGpu { + GLuint vao = 0; + GLuint vbo = 0; + GLuint ebo = 0; + int index_count = 0; + bool ok() const { return vao != 0 && index_count > 0; } +}; + +// Sube un Mesh CPU al GPU. Requiere contexto GL activo. +// Si Mesh esta vacio o no es valido, devuelve MeshGpu{} (ok() == false). +// Layout: location 0 = vec3 a_pos, location 1 = vec3 a_normal. +MeshGpu mesh_gpu_upload(const Mesh& mesh); + +// Libera los recursos GL. Seguro con mesh_gpu vacio. +void mesh_gpu_destroy(MeshGpu& mesh_gpu); + +} // namespace fn::gfx diff --git a/cpp/functions/gfx/mesh_gpu.md b/cpp/functions/gfx/mesh_gpu.md new file mode 100644 index 00000000..c311ce50 --- /dev/null +++ b/cpp/functions/gfx/mesh_gpu.md @@ -0,0 +1,68 @@ +--- +name: mesh_gpu +kind: function +lang: cpp +domain: gfx +version: "1.0.0" +purity: impure +signature: "MeshGpu mesh_gpu_upload(const Mesh&); void mesh_gpu_destroy(MeshGpu&)" +description: "Sube un Mesh CPU a OpenGL como VAO + VBO interleaved (pos.xyz, normal.xyz) + EBO uint32. Layout: location 0 = a_pos vec3, location 1 = a_normal vec3, stride 6 floats." +tags: [opengl, mesh, vao, vbo, ebo, gpu, gfx] +uses_functions: [mesh_obj_load_cpp_gfx] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [GL/gl.h, GL/glext.h] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/gfx/mesh_gpu.cpp" +framework: opengl +params: + - name: mesh + desc: "Mesh CPU con positions/normals (mismo length, stride 3) e indices uint32. Si esta vacio o invalido, upload devuelve MeshGpu{} (ok()==false)." + - name: mesh_gpu + desc: "MeshGpu (vao/vbo/ebo, index_count). destroy libera todo y pone IDs a 0." +output: "mesh_gpu_upload: MeshGpu listo para draw con glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, 0). Si !ok(), no hubo upload." +--- + +# mesh_gpu + +CRUD GPU minimal para `Mesh`. Asume contexto OpenGL 3.3+ activo. + +## Layout de attribs + +```glsl +#version 330 core +layout(location = 0) in vec3 a_pos; +layout(location = 1) in vec3 a_normal; +``` + +Stride = `6 * sizeof(float)`, sin padding. + +## Uso tipico + +```cpp +auto cpu = fn::gfx::mesh_obj_load("model.obj"); +auto gpu = fn::gfx::mesh_gpu_upload(cpu); +if (!gpu.ok()) { /* falla */ return; } + +// Draw: +glUseProgram(prog); +glBindVertexArray(gpu.vao); +glDrawElements(GL_TRIANGLES, gpu.index_count, GL_UNSIGNED_INT, 0); +glBindVertexArray(0); + +// Cleanup: +fn::gfx::mesh_gpu_destroy(gpu); +``` + +## Validacion de input + +`mesh_gpu_upload` exige que `mesh.normals.size() == mesh.positions.size()`. `mesh_obj_parse` siempre genera normales (per-face si faltan) → invariante natural. + +## Notas + +- Indices son `GL_UNSIGNED_INT` (32-bit) para soportar meshes grandes sin tener que decidir formato dinamicamente. +- `GL_STATIC_DRAW`: el assumption es que la malla no cambia post-upload. Si necesitas streaming, crear otro helper con `GL_DYNAMIC_DRAW`.