From 9446e1b86c0f2d6ae281f766b517c13697de872b Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Sun, 22 Feb 2026 02:49:30 +0100 Subject: [PATCH] improve rendering performance even more by using instanced rendering --- include/config.hpp | 2 ++ include/renderer.hpp | 51 +++++++++++++++++++++++++++----- instancing_fragment.glsl | 8 +++++ instancing_vertex.glsl | 13 +++++++++ src/main.cpp | 5 ++-- src/renderer.cpp | 63 ++++++++++++++++++++++++++++++++++++---- 6 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 instancing_fragment.glsl create mode 100644 instancing_vertex.glsl diff --git a/include/config.hpp b/include/config.hpp index d3ea536..fc9cea8 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -4,6 +4,8 @@ #include #define PRINT_TIMINGS +// #define BATCHING +#define INSTANCING // Fastest (so far) // #define WEB // Window diff --git a/include/renderer.hpp b/include/renderer.hpp index fe7c630..e13db91 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -49,9 +49,17 @@ private: RenderTexture klotski_target; RenderTexture menu_target; + Material vertex_mat; + + // Batching float *cube; Mesh graph; - Material mat; + + // Instancing + int transforms_size; + Matrix *transforms; + Mesh cube_instance; + Shader instancing_shader; public: bool mark_solutions; @@ -60,13 +68,16 @@ public: public: Renderer() : camera(OrbitCamera3D(Vector3(0, 0, 0), CAMERA_DISTANCE)), - mark_solutions(false), connect_solutions(false) { + mark_solutions(false), connect_solutions(false), cube(nullptr), + transforms_size(0), transforms(nullptr) { render_target = LoadRenderTexture(GetScreenWidth() / 2.0, GetScreenHeight() - MENU_HEIGHT); klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0, GetScreenHeight() - MENU_HEIGHT); menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT); - AllocateGraphMesh(); + + vertex_mat = LoadMaterialDefault(); + vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR; } Renderer(const Renderer ©) = delete; @@ -78,9 +89,23 @@ public: UnloadRenderTexture(render_target); UnloadRenderTexture(klotski_target); UnloadRenderTexture(menu_target); - UnloadMesh(graph); - UnloadMaterial(mat); - MemFree(cube); + + UnloadMaterial(vertex_mat); + + // Batching + if (cube != nullptr) { + MemFree(cube); + UnloadMesh(graph); + } + + // Instancing + if (transforms != nullptr) { + MemFree(transforms); + UnloadMesh(cube_instance); + + // I think the shader already gets unloaded with the material? + // UnloadShader(instancing_shader); + } } public: @@ -89,10 +114,20 @@ public: auto UpdateTextureSizes() -> void; - auto AllocateGraphMesh() -> void; +#ifdef BATCHING + auto AllocateGraphBatching() -> void; - auto ReallocateGraphMeshIfNecessary(const MassSpringSystem &mass_springs) + auto ReallocateGraphBatchingIfNecessary(const MassSpringSystem &mass_springs) -> void; +#endif + +#ifdef INSTANCING + auto AllocateGraphInstancing(const MassSpringSystem &mass_springs) -> void; + + auto + ReallocateGraphInstancingIfNecessary(const MassSpringSystem &mass_springs) + -> void; +#endif auto DrawMassSprings(const MassSpringSystem &mass_springs, const State ¤t_state, diff --git a/instancing_fragment.glsl b/instancing_fragment.glsl new file mode 100644 index 0000000..493b423 --- /dev/null +++ b/instancing_fragment.glsl @@ -0,0 +1,8 @@ +#version 330 + +uniform vec4 colDiffuse; +out vec4 finalColor; + +void main() { + finalColor = colDiffuse; +} diff --git a/instancing_vertex.glsl b/instancing_vertex.glsl new file mode 100644 index 0000000..d5c2faa --- /dev/null +++ b/instancing_vertex.glsl @@ -0,0 +1,13 @@ +#version 330 + +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; +in mat4 instanceTransform; + +uniform mat4 mvp; + +void main() { + gl_Position = mvp * instanceTransform * vec4(vertexPosition, 1.0); +} diff --git a/src/main.cpp b/src/main.cpp index 993a5ec..bed3c72 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,8 +43,8 @@ auto main(int argc, char *argv[]) -> int { #endif // RayLib window setup - SetTraceLogLevel(LOG_ERROR); - SetConfigFlags(FLAG_VSYNC_HINT); + // SetTraceLogLevel(LOG_ERROR); + // SetConfigFlags(FLAG_VSYNC_HINT); SetConfigFlags(FLAG_MSAA_4X_HINT); SetConfigFlags(FLAG_WINDOW_RESIZABLE); SetConfigFlags(FLAG_WINDOW_ALWAYS_RUN); @@ -105,7 +105,6 @@ auto main(int argc, char *argv[]) -> int { renderer.UpdateCamera(mass_springs, state.current_state); renderer.UpdateTextureSizes(); - renderer.ReallocateGraphMeshIfNecessary(mass_springs); renderer.DrawMassSprings(mass_springs, state.current_state, state.winning_states); diff --git a/src/renderer.cpp b/src/renderer.cpp index 8035675..361c482 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -114,7 +114,8 @@ auto Renderer::UpdateTextureSizes() -> void { menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT); } -auto Renderer::AllocateGraphMesh() -> void { +#ifdef BATCHING +auto Renderer::AllocateGraphBatching() -> void { int vertices = 36; int max_masses = 100000; @@ -137,13 +138,10 @@ auto Renderer::AllocateGraphMesh() -> void { } UnloadMesh(indexed_cube); - mat = LoadMaterialDefault(); - mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR; - std::cout << "Allocated graph mesh." << std::endl; } -auto Renderer::ReallocateGraphMeshIfNecessary( +auto Renderer::ReallocateGraphBatchingIfNecessary( const MassSpringSystem &mass_springs) -> void { if (graph.vertexCount / 3 > mass_springs.masses.size()) { return; @@ -165,6 +163,35 @@ auto Renderer::ReallocateGraphMeshIfNecessary( std::cout << "Reallocated graph mesh." << std::endl; } +#endif + +#ifdef INSTANCING +auto Renderer::AllocateGraphInstancing(const MassSpringSystem &mass_springs) + -> void { + cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE); + + instancing_shader = + LoadShader("instancing_vertex.glsl", "instancing_fragment.glsl"); + instancing_shader.locs[SHADER_LOC_MATRIX_MVP] = + GetShaderLocation(instancing_shader, "mvp"); + instancing_shader.locs[SHADER_LOC_VECTOR_VIEW] = + GetShaderLocation(instancing_shader, "viewPos"); + + vertex_mat.shader = instancing_shader; + + transforms = (Matrix *)MemAlloc(mass_springs.masses.size() * sizeof(Matrix)); + transforms_size = mass_springs.masses.size(); +} + +auto Renderer::ReallocateGraphInstancingIfNecessary( + const MassSpringSystem &mass_springs) -> void { + if (transforms_size != mass_springs.masses.size()) { + transforms = (Matrix *)MemRealloc(transforms, mass_springs.masses.size() * + sizeof(Matrix)); + transforms_size = mass_springs.masses.size(); + } +} +#endif auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, const State ¤t_state, @@ -186,7 +213,30 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, } rlEnd(); + // Draw masses (instanced) +#ifdef INSTANCING + if (transforms == nullptr) { + AllocateGraphInstancing(mass_springs); + } + ReallocateGraphInstancingIfNecessary(mass_springs); + + int i = 0; + for (const auto &[state, mass] : mass_springs.masses) { + transforms[i] = + MatrixTranslate(mass.position.x, mass.position.y, mass.position.z); + ++i; + } + DrawMeshInstanced(cube_instance, vertex_mat, transforms, + mass_springs.masses.size()); +#endif + // Draw masses (batched) +#ifdef BATCHING + if (cube == nullptr) { + AllocateGraphBatching(); + } + ReallocateGraphBatchingIfNecessary(mass_springs); + int vertices = 36; int current_size = mass_springs.masses.size(); int i = 0; @@ -208,11 +258,12 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, graph.vertexCount = current_size * vertices; graph.triangleCount = current_size * 12; - DrawMesh(graph, mat, MatrixIdentity()); + DrawMesh(graph, vertex_mat, MatrixIdentity()); // Restore the vertex count graph.vertexCount = full_size; graph.triangleCount = full_size / 3; +#endif // Mark current state const Mass ¤t_mass = mass_springs.GetMass(current_state);