diff --git a/include/config.hpp b/include/config.hpp index 6f81771..fdb76c3 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -3,8 +3,9 @@ #include -// TODO: Using the octree from the last frame completely breaks the physics :/ -// #define ASYNC_OCTREE +// Calculate the octree parallel to the layout calculation. +// Layout uses the octree from last frame. +#define ASYNC_OCTREE // Gets set by CMake // #define THREADPOOL // Enable physics threadpool diff --git a/include/octree.hpp b/include/octree.hpp index 39d3510..2ed1595 100644 --- a/include/octree.hpp +++ b/include/octree.hpp @@ -44,10 +44,10 @@ private: public: octree() = default; - octree(const octree& copy) = delete; - auto operator=(const octree& copy) -> octree& = delete; - octree(octree&& move) = delete; - auto operator=(octree&& move) -> octree& = delete; + // octree(const octree& copy) = delete; + // auto operator=(const octree& copy) -> octree& = delete; + // octree(octree&& move) = delete; + // auto operator=(octree&& move) -> octree& = delete; private: [[nodiscard]] INLINE static inline auto get_octant(const Vector3& box_min, diff --git a/src/cpu_layout_engine.cpp b/src/cpu_layout_engine.cpp index 60dda68..f4f53e5 100644 --- a/src/cpu_layout_engine.cpp +++ b/src/cpu_layout_engine.cpp @@ -90,16 +90,16 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const std::optional // Start building the octree for the next physics update. // Move the snapshot into the closure so it doesn't get captured by reference (don't use [&]) - octree_future = octree_thread.submit_task([&tree_buffer, positions = std::move(positions)]() + octree_future = octree_thread.submit_task([&tree_buffer, &thread_pool, positions = std::move(positions)]() { - octree::build_octree(tree_buffer, positions); + octree::build_octree_morton(tree_buffer, positions, thread_pool); }); // Rebuild the tree synchronously if we changed the number of masses to not use // an empty tree from the last frame in the frame where the graph was generated if (last_mass_count != mass_springs.positions.size()) { traceln("Rebuilding octree synchronously because graph size changed"); - octree::build_octree(mass_springs.tree, mass_springs.positions); + octree::build_octree_morton(mass_springs.tree, mass_springs.positions, thread_pool); last_mass_count = mass_springs.positions.size(); } #else diff --git a/src/gpu_example.cpp b/src/gpu_example.cpp index e304704..e69de29 100644 --- a/src/gpu_example.cpp +++ b/src/gpu_example.cpp @@ -1,900 +0,0 @@ -// gpu_force_layout_raylib_rlgl.cpp -// -// Single-file GPU force-directed layout example for raylib + rlgl + OpenGL 4.3 compute shaders. -// Includes: -// - grid-based repulsion (GPU) -// - Hooke springs from edge pairs (GPU) -// - integration (GPU) -// - point rendering directly from GPU buffer -// -// Requires: -// - raylib built with GRAPHICS_API_OPENGL_43 -// - desktop OpenGL 4.3+ -// - GL_ARB_shader_atomic_float (present on RTX 4070) -// -// Build example: -// g++ -std=c++2b -O2 gpu_force_layout_raylib_rlgl.cpp -lraylib -lGL -ldl -lpthread -lm -o -// gpu_layout -// -// Notes: -// - This is a practical starter architecture, not a finished engine. -// - For very large graphs, add multilevel coarsening and a coarse second grid. -// - -#include "raylib.h" -#include "raymath.h" -#include "rlgl.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// rlgl exposes OpenGL headers on desktop builds -#ifndef GRAPHICS_API_OPENGL_43 -// You can still compile this file, but compute shaders will not work unless raylib/rlgl -// was built with OpenGL 4.3 support. -#endif - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -static void Require(bool cond, const char* msg) -{ - if (!cond) throw std::runtime_error(msg); -} - -static bool HasExtension(const char* extName) -{ - GLint count = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &count); - for (GLint i = 0; i < count; ++i) { - const char* ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, (GLuint)i)); - if (ext && std::strcmp(ext, extName) == 0) return true; - } - return false; -} - -static unsigned int BuildComputeProgramRlgl(const char* source) -{ - unsigned int shaderId = rlCompileShader(source, RL_COMPUTE_SHADER); - Require(shaderId != 0, "Failed to compile compute shader"); - unsigned int programId = rlLoadComputeShaderProgram(shaderId); - Require(programId != 0, "Failed to link compute shader program"); - rlUnloadShader(shaderId); - return programId; -} - -static unsigned int BuildGraphicsProgramRlgl(const char* vs, const char* fs) -{ - unsigned int vsId = rlCompileShader(vs, RL_VERTEX_SHADER); - Require(vsId != 0, "Failed to compile vertex shader"); - - unsigned int fsId = rlCompileShader(fs, RL_FRAGMENT_SHADER); - Require(fsId != 0, "Failed to compile fragment shader"); - - unsigned int progId = rlLoadShaderProgram(vsId, fsId); - Require(progId != 0, "Failed to link graphics shader program"); - - rlUnloadShader(vsId); - rlUnloadShader(fsId); - return progId; -} - -static void Dispatch1D(unsigned int count, unsigned int localSizeX = 256) -{ - unsigned int groupsX = (count + localSizeX - 1) / localSizeX; - rlComputeShaderDispatch(groupsX, 1, 1); -} - -static Matrix MatrixFromCamera(const Camera3D& cam) -{ - return GetCameraMatrix(cam); -} - -//------------------------------------------------------------------------------ -// GPU data layouts (std430-friendly) -//------------------------------------------------------------------------------ - -struct ParticleGPU -{ - float pos_mass[4]; // xyz position, w mass - float vel_pad[4]; // xyz velocity - float force_pad[4]; // xyz accumulated force -}; -static_assert(sizeof(ParticleGPU) == 48); - -struct CellAccumGPU -{ - float sum_mass[4]; // xyz = sum(pos * mass), w = total mass - std::uint32_t count; - std::uint32_t pad[3]; -}; -static_assert(sizeof(CellAccumGPU) == 32); - -struct CellFinalGPU -{ - float com_mass[4]; // xyz = center of mass, w = total mass - std::uint32_t count; - std::uint32_t pad[3]; -}; -static_assert(sizeof(CellFinalGPU) == 32); - -struct EdgeGPU -{ - std::uint32_t u; - std::uint32_t v; - float weight; - float restLength; -}; -static_assert(sizeof(EdgeGPU) == 16); - -//------------------------------------------------------------------------------ -// Compute shaders -//------------------------------------------------------------------------------ - -static const char* CS_CLEAR_GRID = R"GLSL( -#version 430 -layout(local_size_x = 256) in; - -struct CellAccum { - vec4 sum_mass; - uvec4 count_pad; -}; - -layout(std430, binding=0) buffer GridAccumBuffer { - CellAccum acc[]; -}; - -uniform uint cellCount; - -void main() -{ - uint id = gl_GlobalInvocationID.x; - if (id >= cellCount) return; - - acc[id].sum_mass = vec4(0.0); - acc[id].count_pad = uvec4(0u); -} -)GLSL"; - -static const char* CS_ACCUM_GRID = R"GLSL( -#version 430 -#extension GL_ARB_shader_atomic_float : enable -layout(local_size_x = 256) in; - -struct Particle { - vec4 pos_mass; - vec4 vel_pad; - vec4 force_pad; -}; - -struct CellAccum { - vec4 sum_mass; - uvec4 count_pad; -}; - -layout(std430, binding=0) buffer ParticleBuffer { - Particle particles[]; -}; - -layout(std430, binding=1) buffer GridAccumBuffer { - CellAccum acc[]; -}; - -uniform uint particleCount; -uniform vec3 gridMin; -uniform float cellSize; -uniform ivec3 gridDim; - -int cellIndex(ivec3 c, ivec3 dim) -{ - return c.x + c.y*dim.x + c.z*dim.x*dim.y; -} - -void main() -{ - uint id = gl_GlobalInvocationID.x; - if (id >= particleCount) return; - - vec3 pos = particles[id].pos_mass.xyz; - float m = particles[id].pos_mass.w; - - ivec3 c = ivec3(floor((pos - gridMin) / cellSize)); - c = clamp(c, ivec3(0), gridDim - ivec3(1)); - - int idx = cellIndex(c, gridDim); - - atomicAdd(acc[idx].sum_mass.x, pos.x * m); - atomicAdd(acc[idx].sum_mass.y, pos.y * m); - atomicAdd(acc[idx].sum_mass.z, pos.z * m); - atomicAdd(acc[idx].sum_mass.w, m); - atomicAdd(acc[idx].count_pad.x, 1u); -} -)GLSL"; - -static const char* CS_FINALIZE_GRID = R"GLSL( -#version 430 -layout(local_size_x = 256) in; - -struct CellAccum { - vec4 sum_mass; - uvec4 count_pad; -}; - -struct CellFinal { - vec4 com_mass; - uvec4 count_pad; -}; - -layout(std430, binding=0) buffer GridAccumBuffer { - CellAccum acc[]; -}; - -layout(std430, binding=1) buffer GridFinalBuffer { - CellFinal cell[]; -}; - -uniform uint cellCount; - -void main() -{ - uint id = gl_GlobalInvocationID.x; - if (id >= cellCount) return; - - float mass = acc[id].sum_mass.w; - uint count = acc[id].count_pad.x; - - vec3 com = vec3(0.0); - if (mass > 0.0) { - com = acc[id].sum_mass.xyz / mass; - } - - cell[id].com_mass = vec4(com, mass); - cell[id].count_pad = uvec4(count, 0u, 0u, 0u); -} -)GLSL"; - -static const char* CS_REPULSION = R"GLSL( -#version 430 -layout(local_size_x = 256) in; - -struct Particle { - vec4 pos_mass; - vec4 vel_pad; - vec4 force_pad; -}; - -struct CellFinal { - vec4 com_mass; - uvec4 count_pad; -}; - -layout(std430, binding=0) buffer ParticleBuffer { - Particle particles[]; -}; - -layout(std430, binding=1) buffer GridFinalBuffer { - CellFinal cell[]; -}; - -uniform uint particleCount; -uniform vec3 gridMin; -uniform float cellSize; -uniform ivec3 gridDim; - -uniform int radiusCells; -uniform float softening; -uniform float repulsionK2; -uniform float maxRepulsionForce; -uniform float selfCellFactor; - -int cellIndex(ivec3 c, ivec3 dim) -{ - return c.x + c.y*dim.x + c.z*dim.x*dim.y; -} - -void main() -{ - uint id = gl_GlobalInvocationID.x; - if (id >= particleCount) return; - - vec3 pos = particles[id].pos_mass.xyz; - ivec3 base = ivec3(floor((pos - gridMin) / cellSize)); - base = clamp(base, ivec3(0), gridDim - ivec3(1)); - - vec3 force = vec3(0.0); - - for (int dz = -radiusCells; dz <= radiusCells; ++dz) - for (int dy = -radiusCells; dy <= radiusCells; ++dy) - for (int dx = -radiusCells; dx <= radiusCells; ++dx) - { - ivec3 c = base + ivec3(dx, dy, dz); - if (any(lessThan(c, ivec3(0))) || any(greaterThanEqual(c, gridDim))) continue; - - int idx = cellIndex(c, gridDim); - float mass = cell[idx].com_mass.w; - if (mass <= 0.0) continue; - - vec3 com = cell[idx].com_mass.xyz; - vec3 d = pos - com; - - float r2 = dot(d, d) + softening; - float factor = ((dx == 0) && (dy == 0) && (dz == 0)) ? selfCellFactor : 1.0; - - // k^2 / r magnitude with direction d/r = k^2 * d / r^2 - float invR = inversesqrt(r2); - force += factor * (repulsionK2 * mass) * d * (invR * invR); - } - - float len = length(force); - if (len > maxRepulsionForce) { - force *= (maxRepulsionForce / len); - } - - particles[id].force_pad.xyz = force; -} -)GLSL"; - -static const char* CS_SPRINGS = R"GLSL( -#version 430 -#extension GL_ARB_shader_atomic_float : enable -layout(local_size_x = 256) in; - -struct Particle { - vec4 pos_mass; - vec4 vel_pad; - vec4 force_pad; -}; - -struct Edge { - uint u; - uint v; - float weight; - float restLength; -}; - -layout(std430, binding=0) buffer ParticleBuffer { - Particle particles[]; -}; - -layout(std430, binding=1) buffer EdgeBuffer { - Edge edges[]; -}; - -uniform uint edgeCount; -uniform float springK; -uniform float maxSpringForce; - -void main() -{ - uint id = gl_GlobalInvocationID.x; - if (id >= edgeCount) return; - - Edge e = edges[id]; - vec3 pu = particles[e.u].pos_mass.xyz; - vec3 pv = particles[e.v].pos_mass.xyz; - - vec3 d = pv - pu; - float dist = length(d) + 1e-6; - vec3 dir = d / dist; - - // Hooke: F = k * (dist - rest) - float stretch = dist - e.restLength; - float mag = springK * e.weight * stretch; - mag = clamp(mag, -maxSpringForce, maxSpringForce); - - vec3 F = mag * dir; - - atomicAdd(particles[e.u].force_pad.x, F.x); - atomicAdd(particles[e.u].force_pad.y, F.y); - atomicAdd(particles[e.u].force_pad.z, F.z); - - atomicAdd(particles[e.v].force_pad.x, -F.x); - atomicAdd(particles[e.v].force_pad.y, -F.y); - atomicAdd(particles[e.v].force_pad.z, -F.z); -} -)GLSL"; - -static const char* CS_INTEGRATE = R"GLSL( -#version 430 -layout(local_size_x = 256) in; - -struct Particle { - vec4 pos_mass; - vec4 vel_pad; - vec4 force_pad; -}; - -layout(std430, binding=0) buffer ParticleBuffer { - Particle particles[]; -}; - -uniform uint particleCount; -uniform float dt; -uniform float damping; -uniform float stepScale; - -void main() -{ - uint id = gl_GlobalInvocationID.x; - if (id >= particleCount) return; - - vec3 vel = particles[id].vel_pad.xyz; - vec3 force = particles[id].force_pad.xyz; - - vel = (vel + dt * force) * damping; - vec3 pos = particles[id].pos_mass.xyz + (dt * stepScale) * vel; - - particles[id].vel_pad.xyz = vel; - particles[id].pos_mass.xyz = pos; - particles[id].force_pad.xyz = vec3(0.0); -} -)GLSL"; - -//------------------------------------------------------------------------------ -// Rendering shaders -//------------------------------------------------------------------------------ - -static const char* VS_POINTS = R"GLSL( -#version 430 -layout(location=0) in vec3 aPos; - -uniform mat4 uMVP; -uniform float uPointSize; - -void main() -{ - gl_Position = uMVP * vec4(aPos, 1.0); - gl_PointSize = uPointSize; -} -)GLSL"; - -static const char* FS_POINTS = R"GLSL( -#version 430 -out vec4 FragColor; - -void main() -{ - vec2 p = gl_PointCoord * 2.0 - 1.0; - if (dot(p, p) > 1.0) discard; - FragColor = vec4(0.90, 0.95, 1.00, 1.00); -} -)GLSL"; - -//------------------------------------------------------------------------------ -// Main -//------------------------------------------------------------------------------ - -int main() -{ - SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE); - InitWindow(1400, 900, "raylib rlgl GPU force-directed layout"); - - TraceLog(LOG_INFO, "GL_VERSION: %s", glGetString(GL_VERSION)); - TraceLog(LOG_INFO, "GLSL_VERSION: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); - - try { - Require(HasExtension("GL_ARB_compute_shader"), - "Missing GL_ARB_compute_shader. Build raylib with OpenGL 4.3."); - Require(HasExtension("GL_ARB_shader_storage_buffer_object"), - "Missing GL_ARB_shader_storage_buffer_object."); - Require(HasExtension("GL_ARB_shader_atomic_float"), "Missing GL_ARB_shader_atomic_float."); - - //-------------------------------------------------------------------------- - // Simulation parameters - //-------------------------------------------------------------------------- - - const std::uint32_t nodeCount = 30000; // change to 100000 on your machine - const std::uint32_t approxEdgeCount = nodeCount * 2; - - const int gridX = 64; - const int gridY = 64; - const int gridZ = 64; - const std::uint32_t gridCellCount = std::uint32_t(gridX * gridY * gridZ); - - Vector3 simMin{-40.0f, -40.0f, -40.0f}; - Vector3 simMax{40.0f, 40.0f, 40.0f}; - Vector3 simExtent = Vector3Subtract(simMax, simMin); - - float cellSize = std::max( - {simExtent.x / float(gridX), simExtent.y / float(gridY), simExtent.z / float(gridZ)}); - - float dt = 0.020f; - float damping = 0.90f; - float stepScale = 1.0f; - float repulsionK2 = 0.35f; - float springK = 0.18f; - float springRest = 1.2f; - float softening = 0.02f; - float maxRepulsionForce = 60.0f; - float maxSpringForce = 40.0f; - float selfCellFactor = 0.15f; - int radiusCells = 2; - - //-------------------------------------------------------------------------- - // CPU-side initial data - //-------------------------------------------------------------------------- - - std::vector particles(nodeCount); - for (std::uint32_t i = 0; i < nodeCount; ++i) { - float rx = float(GetRandomValue(-10000, 10000)) / 10000.0f; - float ry = float(GetRandomValue(-10000, 10000)) / 10000.0f; - float rz = float(GetRandomValue(-10000, 10000)) / 10000.0f; - - Vector3 p{simMin.x + (rx * 0.5f + 0.5f) * simExtent.x, - simMin.y + (ry * 0.5f + 0.5f) * simExtent.y, - simMin.z + (rz * 0.5f + 0.5f) * simExtent.z}; - - particles[i].pos_mass[0] = p.x; - particles[i].pos_mass[1] = p.y; - particles[i].pos_mass[2] = p.z; - particles[i].pos_mass[3] = 1.0f; - - particles[i].vel_pad[0] = 0.0f; - particles[i].vel_pad[1] = 0.0f; - particles[i].vel_pad[2] = 0.0f; - particles[i].vel_pad[3] = 0.0f; - - particles[i].force_pad[0] = 0.0f; - particles[i].force_pad[1] = 0.0f; - particles[i].force_pad[2] = 0.0f; - particles[i].force_pad[3] = 0.0f; - } - - std::vector edges; - edges.reserve(approxEdgeCount); - - // Example spring graph: - // - chain edge - // - one random edge - // This gives a graph-like structure instead of pure gas. - for (std::uint32_t i = 0; i + 1 < nodeCount; ++i) { - edges.push_back(EdgeGPU{i, i + 1, 1.0f, springRest}); - } - - for (std::uint32_t i = 0; i < nodeCount; ++i) { - std::uint32_t j = std::uint32_t(GetRandomValue(0, int(nodeCount - 1))); - if (j != i) { - edges.push_back(EdgeGPU{i, j, 1.0f, springRest}); - } - } - - const std::uint32_t edgeCount = (std::uint32_t)edges.size(); - - //-------------------------------------------------------------------------- - // GPU buffers via rlgl - //-------------------------------------------------------------------------- - - unsigned int particleSsbo = - rlLoadShaderBuffer((unsigned int)(particles.size() * sizeof(ParticleGPU)), - particles.data(), RL_DYNAMIC_COPY); - - unsigned int gridAccumSsbo = rlLoadShaderBuffer( - gridCellCount * (unsigned int)sizeof(CellAccumGPU), nullptr, RL_DYNAMIC_COPY); - - unsigned int gridFinalSsbo = rlLoadShaderBuffer( - gridCellCount * (unsigned int)sizeof(CellFinalGPU), nullptr, RL_DYNAMIC_COPY); - - unsigned int edgeSsbo = rlLoadShaderBuffer((unsigned int)(edges.size() * sizeof(EdgeGPU)), - edges.data(), RL_STATIC_DRAW); - - Require(particleSsbo != 0, "Failed to create particle SSBO"); - Require(gridAccumSsbo != 0, "Failed to create gridAccum SSBO"); - Require(gridFinalSsbo != 0, "Failed to create gridFinal SSBO"); - Require(edgeSsbo != 0, "Failed to create edge SSBO"); - - //-------------------------------------------------------------------------- - // Programs via rlgl - //-------------------------------------------------------------------------- - - unsigned int progClear = BuildComputeProgramRlgl(CS_CLEAR_GRID); - unsigned int progAccum = BuildComputeProgramRlgl(CS_ACCUM_GRID); - unsigned int progFinalize = BuildComputeProgramRlgl(CS_FINALIZE_GRID); - unsigned int progRepel = BuildComputeProgramRlgl(CS_REPULSION); - unsigned int progSprings = BuildComputeProgramRlgl(CS_SPRINGS); - unsigned int progIntegrate = BuildComputeProgramRlgl(CS_INTEGRATE); - unsigned int progPoints = BuildGraphicsProgramRlgl(VS_POINTS, FS_POINTS); - - //-------------------------------------------------------------------------- - // Rendering state - //-------------------------------------------------------------------------- - - GLuint vao = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - glBindBuffer(GL_ARRAY_BUFFER, particleSsbo); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (GLsizei)sizeof(ParticleGPU), - (const void*)0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glBindVertexArray(0); - - Camera3D camera{}; - camera.position = {0.0f, 0.0f, 110.0f}; - camera.target = {0.0f, 0.0f, 0.0f}; - camera.up = {0.0f, 1.0f, 0.0f}; - camera.fovy = 60.0f; - camera.projection = CAMERA_PERSPECTIVE; - - SetTargetFPS(60); - - float orbit = 0.0f; - - //-------------------------------------------------------------------------- - // Main loop - //-------------------------------------------------------------------------- - - while (!WindowShouldClose()) { - // Simple interactive tuning - if (IsKeyPressed(KEY_ONE)) radiusCells = 1; - if (IsKeyPressed(KEY_TWO)) radiusCells = 2; - if (IsKeyPressed(KEY_THREE)) radiusCells = 3; - - if (IsKeyDown(KEY_Q)) repulsionK2 *= 1.01f; - if (IsKeyDown(KEY_A)) repulsionK2 *= 0.99f; - - if (IsKeyDown(KEY_W)) springK *= 1.01f; - if (IsKeyDown(KEY_S)) springK *= 0.99f; - - if (IsKeyDown(KEY_E)) damping = std::min(0.999f, damping + 0.0005f); - if (IsKeyDown(KEY_D)) damping = std::max(0.70f, damping - 0.0005f); - - if (IsKeyPressed(KEY_SPACE)) stepScale = 1.0f; - stepScale = std::max(0.10f, stepScale * 0.9994f); - - orbit += 0.0020f; - camera.position = {110.0f * std::sin(orbit), 35.0f, 110.0f * std::cos(orbit)}; - - // Flush raylib draw batch before raw GL / compute work - rlDrawRenderBatchActive(); - - // ------------------------------------------------------------ - // Pass 1: clear grid accum - // ------------------------------------------------------------ - rlEnableShader(progClear); - rlBindShaderBuffer(gridAccumSsbo, 0); - - { - unsigned int u = (unsigned int)gridCellCount; - int loc = rlGetLocationUniform(progClear, "cellCount"); - rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1); - } - - Dispatch1D(gridCellCount); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - // ------------------------------------------------------------ - // Pass 2: accumulate particles into grid - // ------------------------------------------------------------ - rlEnableShader(progAccum); - rlBindShaderBuffer(particleSsbo, 0); - rlBindShaderBuffer(gridAccumSsbo, 1); - - { - unsigned int u = nodeCount; - int loc = rlGetLocationUniform(progAccum, "particleCount"); - rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1); - } - { - int loc = rlGetLocationUniform(progAccum, "gridMin"); - float v[3] = {simMin.x, simMin.y, simMin.z}; - rlSetUniform(loc, v, RL_SHADER_UNIFORM_VEC3, 1); - } - { - int loc = rlGetLocationUniform(progAccum, "cellSize"); - rlSetUniform(loc, &cellSize, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progAccum, "gridDim"); - int v[3] = {gridX, gridY, gridZ}; - rlSetUniform(loc, v, RL_SHADER_UNIFORM_IVEC3, 1); - } - - Dispatch1D(nodeCount); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - // ------------------------------------------------------------ - // Pass 3: finalize cell COM - // ------------------------------------------------------------ - rlEnableShader(progFinalize); - rlBindShaderBuffer(gridAccumSsbo, 0); - rlBindShaderBuffer(gridFinalSsbo, 1); - - { - unsigned int u = (unsigned int)gridCellCount; - int loc = rlGetLocationUniform(progFinalize, "cellCount"); - rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1); - } - - Dispatch1D(gridCellCount); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - // ------------------------------------------------------------ - // Pass 4: repulsion - // ------------------------------------------------------------ - rlEnableShader(progRepel); - rlBindShaderBuffer(particleSsbo, 0); - rlBindShaderBuffer(gridFinalSsbo, 1); - - { - unsigned int u = nodeCount; - int loc = rlGetLocationUniform(progRepel, "particleCount"); - rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "gridMin"); - float v[3] = {simMin.x, simMin.y, simMin.z}; - rlSetUniform(loc, v, RL_SHADER_UNIFORM_VEC3, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "cellSize"); - rlSetUniform(loc, &cellSize, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "gridDim"); - int v[3] = {gridX, gridY, gridZ}; - rlSetUniform(loc, v, RL_SHADER_UNIFORM_IVEC3, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "radiusCells"); - rlSetUniform(loc, &radiusCells, RL_SHADER_UNIFORM_INT, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "softening"); - rlSetUniform(loc, &softening, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "repulsionK2"); - rlSetUniform(loc, &repulsionK2, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "maxRepulsionForce"); - rlSetUniform(loc, &maxRepulsionForce, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progRepel, "selfCellFactor"); - rlSetUniform(loc, &selfCellFactor, RL_SHADER_UNIFORM_FLOAT, 1); - } - - Dispatch1D(nodeCount); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - // ------------------------------------------------------------ - // Pass 5: springs - // ------------------------------------------------------------ - rlEnableShader(progSprings); - rlBindShaderBuffer(particleSsbo, 0); - rlBindShaderBuffer(edgeSsbo, 1); - - { - unsigned int u = edgeCount; - int loc = rlGetLocationUniform(progSprings, "edgeCount"); - rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1); - } - { - int loc = rlGetLocationUniform(progSprings, "springK"); - rlSetUniform(loc, &springK, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progSprings, "maxSpringForce"); - rlSetUniform(loc, &maxSpringForce, RL_SHADER_UNIFORM_FLOAT, 1); - } - - Dispatch1D(edgeCount); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - - // ------------------------------------------------------------ - // Pass 6: integrate - // ------------------------------------------------------------ - rlEnableShader(progIntegrate); - rlBindShaderBuffer(particleSsbo, 0); - - { - unsigned int u = nodeCount; - int loc = rlGetLocationUniform(progIntegrate, "particleCount"); - rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1); - } - { - int loc = rlGetLocationUniform(progIntegrate, "dt"); - rlSetUniform(loc, &dt, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progIntegrate, "damping"); - rlSetUniform(loc, &damping, RL_SHADER_UNIFORM_FLOAT, 1); - } - { - int loc = rlGetLocationUniform(progIntegrate, "stepScale"); - rlSetUniform(loc, &stepScale, RL_SHADER_UNIFORM_FLOAT, 1); - } - - Dispatch1D(nodeCount); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT); - - rlDisableShader(); - - // ------------------------------------------------------------ - // Draw - // ------------------------------------------------------------ - BeginDrawing(); - ClearBackground(Color{9, 11, 16, 255}); - - BeginMode3D(camera); - DrawCubeWiresV(Vector3Scale(Vector3Add(simMin, simMax), 0.5f), simExtent, - Fade(GRAY, 0.20f)); - - rlDrawRenderBatchActive(); - - Matrix view = MatrixFromCamera(camera); - Matrix proj = MatrixPerspective(DEG2RAD * camera.fovy, - (float)GetScreenWidth() / (float)GetScreenHeight(), - 0.1f, 1000.0f); - Matrix mvp = MatrixMultiply(proj, view); - - glUseProgram(progPoints); - glBindVertexArray(vao); - - GLint locMvp = glGetUniformLocation(progPoints, "uMVP"); - GLint locPt = glGetUniformLocation(progPoints, "uPointSize"); - glUniformMatrix4fv(locMvp, 1, GL_FALSE, (const float*)&mvp); - glUniform1f(locPt, 2.0f); - - glEnable(GL_PROGRAM_POINT_SIZE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_DEPTH_TEST); - - glDrawArrays(GL_POINTS, 0, (GLsizei)nodeCount); - - glBindVertexArray(0); - glUseProgram(0); - - EndMode3D(); - - DrawText(TextFormat("nodes=%u edges=%u grid=%dx%dx%d radius=%d", nodeCount, edgeCount, - gridX, gridY, gridZ, radiusCells), - 10, 10, 20, RAYWHITE); - DrawText(TextFormat("repulsionK2=%.3f springK=%.3f damping=%.3f stepScale=%.3f", - repulsionK2, springK, damping, stepScale), - 10, 36, 20, RAYWHITE); - DrawText( - "Keys: 1/2/3 radius, Q/A repulsion, W/S spring, E/D damping, SPACE reset cooling", - 10, 62, 18, Fade(RAYWHITE, 0.8f)); - - EndDrawing(); - } - - //-------------------------------------------------------------------------- - // Cleanup - //-------------------------------------------------------------------------- - - glDeleteVertexArrays(1, &vao); - - rlUnloadShaderProgram(progPoints); - rlUnloadShaderProgram(progClear); - rlUnloadShaderProgram(progAccum); - rlUnloadShaderProgram(progFinalize); - rlUnloadShaderProgram(progRepel); - rlUnloadShaderProgram(progSprings); - rlUnloadShaderProgram(progIntegrate); - - rlUnloadShaderBuffer(edgeSsbo); - rlUnloadShaderBuffer(gridFinalSsbo); - rlUnloadShaderBuffer(gridAccumSsbo); - rlUnloadShaderBuffer(particleSsbo); - } - catch (const std::exception& e) { - TraceLog(LOG_ERROR, "%s", e.what()); - } - - CloseWindow(); - return 0; -} diff --git a/src/main.cpp b/src/main.cpp index 5ace2c3..4a69c90 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -332,7 +332,13 @@ auto main(const int argc, char* argv[]) -> int #endif infoln("Using background thread for physics."); - infoln("Using octree-barnes-hut for graph layout."); + infoln("Using linear octree + Barnes-Hut for graph layout."); + + #ifdef ASYNC_OCTREE + infoln("Using asynchronous octree build."); + #else + infoln("Using synchronous octree build."); + #endif #ifdef THREADPOOL infoln("Additional thread-pool enabled ({} threads).", threads.get_thread_count()); diff --git a/src/renderer.cpp b/src/renderer.cpp index 2f466c6..a66f32f 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -67,7 +67,10 @@ auto renderer::draw_mass_springs(const std::vector& masses) -> void // TODO: This would benefit greatly from a spatial data structure. // Would it be worth to copy the octree from the physics thread? input.collision_mass = -1; - if (input.mouse_in_graph_pane()) { + if (input.mouse_in_graph_pane() && IsKeyDown(KEY_Q)) { + #ifdef TRACY + ZoneNamedN(mass_collisions, "MassCollisions", true); + #endif const Ray ray = GetScreenToWorldRayEx( GetMousePosition() - Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), camera.camera, graph_target.texture.width, graph_target.texture.height);