This framework is a CUDA/C++17 implementation of Smoothed Particle Hydrodynamics (SPH) using a uniform-grid neighbor search and hybrid SMS/TRA scheduling.
This project is the source code of "Novel Hierarchical Strategies for SPH-centric Algorithms on GPGPU" and "A General Novel Parallel Framework for SPH-centric Algorithms".
It provides a research implementation and benchmark for uniform-grid GPU SPH scheduling, neighbor exchange, and kernel optimization.
Source code contributors: Kemeng Huang, Jiming Ruan.
- CMake >= 3.18
- CUDA Toolkit 12.x or 13.x (tested with CUDA 12.4 and 13.2)
- A C++17 toolchain (tested with Visual Studio 2022/2026 on Windows)
- jsoncpp; GUI builds additionally require GLEW and FreeGLUT. For example, via
vcpkg (pick the triplet matching your platform):
vcpkg install glew freeglut jsoncpp --triplet x64-windows
For a headless-only machine, jsoncpp is the only vcpkg dependency; OpenGL, GLEW, GLUT,
render sources, shaders, and lodepng are not included in that build.
Platform note: out-of-the-box builds target Windows. The timer, screenshot helper, GL setup, and parts of the host code require platform adaptation before building on Linux or macOS.
Standard single-config generators (Ninja, Makefiles):
cmake -S . -B build -DCMAKE_BUILD_TYPE=ReleaseVisual Studio (multi-config):
cmake -S . -B build -G "Visual Studio 17 2022" -A x64Compute-only headless build:
cmake -S . -B build-headless -G "Visual Studio 17 2022" -A x64 \
-DGSPH_HEADLESS=ONIf the dependencies are not on the default search path, point CMake at vcpkg:
cmake -S . -B build ... -DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmake| Option | Default | Purpose |
|---|---|---|
GSPH_HEADLESS |
OFF |
Exclude OpenGL, GLUT/GLEW, lodepng, shaders, and render sources |
GSPH_USE_REGISTER_SMS |
ON |
Select the optimized register SMS path; OFF builds the shared-memory reference |
GSPH_ENABLE_HISTORICAL_ISSAME |
ON |
Preserve historical judgeTask pairing and GUI task colors |
GSPH_ENABLE_SMS_LOCAL_MERGE |
ON |
Cooperatively stage isSame pairs during density |
GSPH_ENABLE_SMS_LOCAL_MERGE_FORCE |
OFF |
Experimental paired force staging; slower on RTX 5090 |
GSPH_ENABLE_SAME_CELL_PAIR_FORCE |
OFF |
Research prototype for pair-once same-cell force; substantially slower |
GSPH_ENABLE_IPO |
ON |
Enable host IPO and CUDA device LTO in optimized configurations |
The default CUDA architecture is 89. Pass -DCMAKE_CUDA_ARCHITECTURES=120 for a native RTX
5090 build. Example optimized headless configuration:
cmake -S . -B build-headless -G "Visual Studio 17 2022" -A x64 \
-DGSPH_HEADLESS=ON \
-DCMAKE_CUDA_ARCHITECTURES=120 \
-DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmakecmake --build build --config Release # --config is only needed for multi-config generatorsOr open the generated solution/project in your IDE and build the Release target. The
executable is gsph (gsph.exe on Windows) under build/ or build/Release/, and the
runtime assets/ directory is copied next to it automatically. GUI builds also copy shaders/;
headless builds do not compile or copy graphics assets.
Launch the simulation from the build output directory (so the bundled assets are found):
build/Release/gsph.exe # Windows / Visual Studio
# or
./build/gsph # single-config generatorsThe default scene (assets/scene_default.json) generates ~3.94 million particles. For faster
iteration during development, create a smaller scene file and change kDefaultSceneFileName in
src/simulation/sph_hybrid_system.cpp.
GSPH_HEADLESS=ON produces an executable with no OpenGL imports or render code. It runs 200
frames by default; override the count with --benchmark N or --headless N:
build-headless/Release/gsph --benchmark 200The normal GUI build also accepts the same benchmark arguments and bypasses window creation at
runtime, but it remains linked to the graphics libraries. Use the CMake option when graphics
dependencies must be absent entirely. Headless kernels also omit the render-only color and
final_position updates; GUI builds retain both.
This prints wall-clock FPS, the TRA/SMS split, a state checksum, and per-stage
timings (grid arrange / density / force). --headless N is an alias.
- One 32-particle task is assigned to each warp; production blocks contain 64 threads.
BlockTaskcaches coarse-cell coordinates, cell begin/count, particle offset, and historical paired bounds, avoiding repeated integer division and cell-array loads in the physics kernels.position_d.wstores reciprocal density, eliminating a reciprocal from each force-neighbor interaction.- Density cooperatively stages eligible historical
isSamepairs. Force retains independent warp traversal because paired force staging reduced throughput. - CUB scans and radix sorts share one persistent device temporary allocation sized at startup. Persistent key/value alternates remove runtime sort allocations; no Thrust headers or calls remain in the source tree.
- The exact-grid launch path remains the default. The device-sized over-provisioned alternative
is available through
HYBRID_DEVICE_GRID_SIZINGinsrc/core/sph_utils.cuh, but measured slower.
The optimized register SMS path is enabled by default. Configure an equivalent legacy shared path build for A/B testing with:
cmake -S . -B build-shared -DGSPH_USE_REGISTER_SMS=OFFThe register build uses the project's historical judgeTask policy. Each globally aligned SMS
task pair (0,1), (2,3), ... gets isSame=1 exactly when both tasks have the same coarse
cellid; judgeTask stores the historical combined bounds in the first descriptor's packed
padding and handles an odd tail task. The original per-task bounds remain hot for independent
register traversal, and there is no additional search-volume heuristic.
Density uses cooperative staging for isSame pairs by default. Force keeps the original task
bounds and stays on the faster independent register iterator. The controls are:
-DGSPH_ENABLE_HISTORICAL_ISSAME=OFF # disable classification and task coloring
-DGSPH_ENABLE_SMS_LOCAL_MERGE=OFF # keep isSame/colors, disable density staging
-DGSPH_ENABLE_SMS_LOCAL_MERGE_FORCE=ON # experimental force staging (slower on RTX 5090)
-DGSPH_ENABLE_SAME_CELL_PAIR_FORCE=ON # pair-once research prototype (substantially slower)Benchmark output reports the number and shape of paired tasks. In GUI builds the historical
density-pass coloring is preserved: isSame SMS tasks are cyan, independent SMS tasks are
yellow, and TRA particles remain magenta.
Space– pause / resumew/s– move forward / backwarda/d– move left / rightq/e– move down / upo/u– increase / decrease particle point size- Arrow keys – move the light source
t– advance one simulation step while paused (debug)/– toggle screenshot capture toscreenshot/
.
├── assets/ runtime JSON scenes and textures
├── shaders/ GL vertex/fragment shaders
├── src/ source code
│ ├── main.cpp common CLI/headless entry point
│ ├── core/ shared utilities (CUDA helpers, math, parameters, timers)
│ ├── cuda_prescan/ legacy recursive prescan helpers; the live path uses persistent CUB
│ ├── grid/ uniform-grid construction and particle sorting
│ ├── io/ GPU model loader/reader and statistics I/O
│ ├── particle/ particle buffer definitions and management
│ ├── render/ optional GLUT/GLEW GUI, excluded from headless builds
│ ├── simulation/ high-level simulation, marching cubes, PCISPH helpers
│ └── solver/ CUDA SPH kernels split by physics, plus dispatch
├── third_party/ GUI-only third-party code (lodepng)
├── agent_docs/ code analysis, optimization reports, known issues (dev notes)
├── data/ placeholder for runtime output
├── CMakeLists.txt
└── README.md
Reference headless Release results for assets/scene_default.json (~4M particles):
| GPU / CUDA | Variant | Mean frame | FPS |
|---|---|---|---|
RTX 4090 / CUDA 12.4 / sm_89 |
Historical optimized register build | ~28.6 ms | ~35.0 |
RTX 5090 / CUDA 13.2 / sm_120 |
Shared-memory reference | 22.901 ms | 43.67 |
RTX 5090 / CUDA 13.2 / sm_120 |
Register path (default) | 21.833 ms | 45.80 |
The RTX 5090 values are the means of two interleaved 1000-frame runs per variant. The register path delivered 4.89% higher throughput (4.66% lower frame time). Replacing the remaining Thrust code with persistent CUB storage measured 22.792 ms versus 22.835 ms before the change in a separate interleaved 750-frame A/B, effectively no online regression.
Performance varies with driver state, clocks, scene evolution, and background GPU load. Use
interleaved runs and compare means rather than selecting a single best result. See
agent_docs/optimization_report.md for the complete history and rejected experiments.
The current source has been verified with:
- Register headless Release (
GSPH_USE_REGISTER_SMS=ON) - Shared-memory headless Release (
GSPH_USE_REGISTER_SMS=OFF) - GUI Release with CUDA-OpenGL interop
- Compute Sanitizer
memcheckandsynccheckon both register and shared builds: 0 errors cuobjdump -res-usageon nativesm_120: density 39/38 registers, force 67 registers, no stack/local spills
Example sanitizer invocation:
compute-sanitizer --tool memcheck build-headless/Release/gsph.exe --benchmark 6
compute-sanitizer --tool synccheck build-headless/Release/gsph.exe --benchmark 6This project is licensed under the Mozilla Public License 2.0. See LICENSE.
Please cite the following papers if this work helps your research.
@article{HUANG2020101088,
title = {Novel hierarchical strategies for SPH-centric algorithms on GPGPU},
journal = {Graphical Models},
volume = {111},
pages = {101088},
year = {2020},
issn = {1524-0703},
doi = {https://doi.org/10.1016/j.gmod.2020.101088},
url = {https://www.sciencedirect.com/science/article/pii/S152407032030028X},
author = {Kemeng Huang and Zipeng Zhao and Chen Li and Changbo Wang and Hong Qin}
}
@article{10.1145/3321360,
author = {Huang, Kemeng and Ruan, Jiming and Zhao, Zipeng and Li, Chen and Wang, Changbo and Qin, Hong},
title = {A General Novel Parallel Framework for SPH-Centric Algorithms},
year = {2019},
issue_date = {May 2019},
publisher = {Association for Computing Machinery},
address = {New York, NY, USA},
volume = {2},
number = {1},
url = {https://doi.org/10.1145/3321360},
doi = {10.1145/3321360},
journal = {Proc. ACM Comput. Graph. Interact. Tech.},
month = {jun},
articleno = {7},
numpages = {16}
}