improve rendering performance by batching edge and cube draws

This commit is contained in:
2026-02-22 01:41:39 +01:00
parent 05172d0d8f
commit 35de23e865
6 changed files with 136 additions and 28 deletions

View File

@ -41,10 +41,10 @@ constexpr int REPULSION_GRID_REFRESH = 5; // Frames between grid rebuilds
constexpr float VERLET_DAMPENING = 0.05; // [0, 1]
// Graph Drawing
constexpr float VERTEX_SIZE = 0.1;
constexpr float VERTEX_SIZE = 0.5;
constexpr Color VERTEX_COLOR = GREEN;
constexpr Color EDGE_COLOR = DARKGREEN;
constexpr int DRAW_VERTICES_LIMIT = 10000;
constexpr int DRAW_VERTICES_LIMIT = 100000;
// Klotski Drawing
constexpr int BOARD_PADDING = 5;

View File

@ -118,6 +118,8 @@ public:
auto GetMass(const State &state) -> Mass &;
auto GetMass(const State &state) const -> const Mass &;
auto AddSpring(const State &massA, const State &massB, float spring_constant,
float dampening_constant, float rest_length) -> void;

View File

@ -49,6 +49,10 @@ private:
RenderTexture klotski_target;
RenderTexture menu_target;
float *cube;
Mesh graph;
Material mat;
public:
bool mark_solutions;
bool connect_solutions;
@ -62,6 +66,7 @@ public:
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
GetScreenHeight() - MENU_HEIGHT);
menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT);
AllocateGraphMesh();
}
Renderer(const Renderer &copy) = delete;
@ -73,23 +78,31 @@ public:
UnloadRenderTexture(render_target);
UnloadRenderTexture(klotski_target);
UnloadRenderTexture(menu_target);
UnloadMesh(graph);
UnloadMaterial(mat);
MemFree(cube);
}
public:
auto UpdateCamera(const MassSpringSystem &masssprings, const State &current)
-> void;
auto UpdateCamera(const MassSpringSystem &mass_springs,
const State &current_state) -> void;
auto UpdateTextureSizes() -> void;
auto DrawMassSprings(const MassSpringSystem &masssprings,
const State &current,
auto AllocateGraphMesh() -> void;
auto ReallocateGraphMeshIfNecessary(const MassSpringSystem &mass_springs)
-> void;
auto DrawMassSprings(const MassSpringSystem &mass_springs,
const State &current_state,
const std::unordered_set<State> &winning_states) -> void;
auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
int sel_y, int block_add_x, int block_add_y,
const WinCondition win_condition) -> void;
auto DrawMenu(const MassSpringSystem &masssprings, int current_preset,
auto DrawMenu(const MassSpringSystem &mass_springs, int current_preset,
const State &current_state,
const std::unordered_set<State> &winning_states) -> void;

View File

@ -105,8 +105,11 @@ 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);
// TODO: Don't render each frame
renderer.DrawKlotski(state.current_state, input.hov_x, input.hov_y,
input.sel_x, input.sel_y, input.block_add_x,
input.block_add_y, state.CurrentWinCondition());

View File

@ -89,6 +89,10 @@ auto MassSpringSystem::GetMass(const State &state) -> Mass & {
return masses.at(state);
}
auto MassSpringSystem::GetMass(const State &state) const -> const Mass & {
return masses.at(state);
}
auto MassSpringSystem::AddSpring(const State &massA, const State &massB,
float spring_constant,
float dampening_constant, float rest_length)

View File

@ -1,8 +1,11 @@
#include "renderer.hpp"
#include <algorithm>
#include <cstring>
#include <format>
#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>
#include <unordered_set>
#include "config.hpp"
@ -111,8 +114,60 @@ auto Renderer::UpdateTextureSizes() -> void {
menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT);
}
auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings,
const State &current,
auto Renderer::AllocateGraphMesh() -> void {
int vertices = 36;
int max_masses = 100000;
graph = {0};
graph.vertexCount = max_masses * vertices;
graph.triangleCount = max_masses * 12;
graph.vertices = (float *)MemAlloc(graph.vertexCount * 3 * sizeof(float));
memset(graph.vertices, 0, graph.vertexCount * 3 * sizeof(float));
UploadMesh(&graph, true);
Mesh indexed_cube = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
cube = (float *)MemAlloc(indexed_cube.triangleCount * 3 * 3 * sizeof(float));
for (int i = 0; i < indexed_cube.triangleCount * 3; ++i) {
int idx = indexed_cube.indices[i];
cube[i * 3 + 0] = indexed_cube.vertices[idx * 3 + 0];
cube[i * 3 + 1] = indexed_cube.vertices[idx * 3 + 1];
cube[i * 3 + 2] = indexed_cube.vertices[idx * 3 + 2];
}
UnloadMesh(indexed_cube);
mat = LoadMaterialDefault();
mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
std::cout << "Allocated graph mesh." << std::endl;
}
auto Renderer::ReallocateGraphMeshIfNecessary(
const MassSpringSystem &mass_springs) -> void {
if (graph.vertexCount / 3 > mass_springs.masses.size()) {
return;
}
int vertices = 36;
int max_masses = mass_springs.masses.size() * 2;
UnloadMesh(graph);
graph = {0};
graph.vertexCount = max_masses * vertices;
graph.triangleCount = max_masses * 12;
graph.vertices = (float *)MemAlloc(graph.vertexCount * 3 * sizeof(float));
memset(graph.vertices, 0, graph.vertexCount * 3 * sizeof(float));
UploadMesh(&graph, true);
std::cout << "Reallocated graph mesh." << std::endl;
}
auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
const State &current_state,
const std::unordered_set<State> &winning_states)
-> void {
BeginTextureMode(render_target);
@ -120,32 +175,63 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings,
BeginMode3D(camera.camera);
// Draw springs
for (const auto &[states, spring] : masssprings.springs) {
const Mass a = spring.massA;
const Mass b = spring.massB;
DrawLine3D(a.position, b.position, EDGE_COLOR);
// Draw springs (batched)
rlBegin(RL_LINES);
for (const auto &[states, spring] : mass_springs.springs) {
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
rlVertex3f(spring.massA.position.x, spring.massA.position.y,
spring.massA.position.z);
rlVertex3f(spring.massB.position.x, spring.massB.position.y,
spring.massB.position.z);
}
rlEnd();
// Draw masses (high performance impact)
for (const auto &[state, mass] : masssprings.masses) {
if (state == current) {
DrawCube(mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE,
RED);
} else if (winning_states.contains(state)) {
// TODO: Would be better to store the winning flag in the state itself
// Draw masses (batched)
int vertices = 36;
int current_size = mass_springs.masses.size();
int i = 0;
for (const auto &[state, mass] : mass_springs.masses) {
for (int v = 0; v < vertices; ++v) {
int dst = (i * vertices + v) * 3;
int src = v * 3;
graph.vertices[dst + 0] = cube[src + 0] + mass.position.x;
graph.vertices[dst + 1] = cube[src + 1] + mass.position.y;
graph.vertices[dst + 2] = cube[src + 2] + mass.position.z;
}
++i;
}
UpdateMeshBuffer(graph, 0, graph.vertices,
current_size * vertices * 3 * sizeof(float), 0);
// Temporarily reduce the vertex count to the used part (we overallocate)
int full_size = graph.vertexCount;
graph.vertexCount = current_size * vertices;
graph.triangleCount = current_size * 12;
DrawMesh(graph, mat, MatrixIdentity());
// Restore the vertex count
graph.vertexCount = full_size;
graph.triangleCount = full_size / 3;
// Mark current state
const Mass &current_mass = mass_springs.GetMass(current_state);
DrawCube(current_mass.position, VERTEX_SIZE * 4, VERTEX_SIZE * 4,
VERTEX_SIZE * 4, RED);
// Mark winning states
if (mark_solutions || connect_solutions) {
for (const auto &state : winning_states) {
const Mass &winning_mass = mass_springs.GetMass(state);
if (mark_solutions) {
DrawCube(mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE,
DrawCube(winning_mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE,
4 * VERTEX_SIZE, BLUE);
}
if (connect_solutions) {
DrawLine3D(mass.position, masssprings.masses.at(current).position,
PURPLE);
DrawLine3D(winning_mass.position,
mass_springs.GetMass(current_state).position, PURPLE);
}
} else if (masssprings.masses.size() <= DRAW_VERTICES_LIMIT) {
DrawCube(mass.position, VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE,
VERTEX_COLOR);
}
}