Real-time 3D rendering engine with ECS architecture, built on pure wgpu
Project description
ManifoldX
A real-time 3D rendering engine built on pure wgpu with an Entity Component System (ECS) architecture. Written in Python with numpy for high-performance data handling.
⚠️ Beta / Academic Project — This is an experimental proof-of-concept exploring the extent to which Python can be used for high-performance graphics via wgpu. Not recommended for production use. Expect bugs, breaking changes, and missing features.
Motivation
Domain researchers need to run large-scale simulations (10⁴-10⁶ entities) and visualize results in 3D. Currently they can't do both in pure Python.
| Tool | Simulation | Visualization | Language |
|---|---|---|---|
| Matplotlib/Plotly | ✅ | 2D only, slow at scale | Pure Python |
| VisPy | ❌ | OpenGL, steep curve | Python + GLSL |
| PyGfx | ❌ | Excellent rendering | Pure Python |
| Game engines (PyGame) | ✅ | 2D mostly | Pure Python |
| Specialized (OpenMM, SUMO) | ✅ | Data export only | Fortran/C++ |
The gap: No Python tool combines data-driven simulation with accessible 3D visualization in one package.
The Vision
ManifoldX gives researchers both:
- Simulation in pure NumPy — vectorized operations over entity arrays, no Python loops in the hot path
- 3D visualization without graphics knowledge — spawn entities with meshes/materials, the engine handles GPU rendering
# Write physics in pure numpy (data-driven, not OOP)
@engine.system
def nbody_physics(query, dt):
forces = compute_gravity_all_pairs(query[Transform].pos.data)
velocities += forces * dt
query[Transform].pos += velocities * dt
# Engine handles GPU buffers, WGSL shaders, instanced draw calls
# You get 3D visualization of your simulation instantly
Target domains: Astrophysics (galaxy formation), molecular dynamics (protein folding), epidemiology (disease spread), crowd science (evacuation flows), traffic engineering (vehicle flow), weather (particle advection).
Works in: Jupyter notebooks, Quarto documents, Streamlit dashboards, standalone scripts.
⚠️ Spoiler: Python is surprisingly capable at real-time 3D. The N-body demo runs 500 bodies with N² gravity — 250,000 force pairs per frame in pure numpy.
Why Not PyGfx?
PyGfx is an excellent rendering engine and has been a key source of inspiration — we've learned a lot from its WGSL patterns, material system, and API design. However, it's fundamentally a scene graph engine (object-oriented hierarchy of transforms), not a data-driven ECS. For large-scale simulations with vectorized physics, the OOP overhead of traversing a scene graph becomes a bottleneck. ManifoldX uses a flat SoA data layout where simulation logic operates directly on numpy arrays.
Technically this is a game engine — the ECS + instanced rendering + PBR pipeline is exactly what you'd use for a game. But that's not the focus. The goal is making 3D visualization accessible to researchers who don't know anything about graphics.
Installation
pip install manifold-gfx
# or
uv add manifold-gfx
Requirements:
- Python 3.13+
- GPU with WebGPU support (via wgpu backend)
- Vulkan on Linux
- Metal on macOS
- D3D12 on Windows
Quick Start
import manifoldx as mx
import numpy as np
from manifoldx.components import Transform, Mesh, Material
from manifoldx.resources import StandardMaterial, PointLight, cube, sphere
# Create engine with default settings
engine = mx.Engine("My First Scene")
# Create a cube and sphere
cube_geo = cube(1, 1, 1)
sphere_geo = sphere(0.7, 32)
# Create PBR materials (roughness: 0-1, metallic: 0-1)
red_shiny = StandardMaterial(color="#ff3333", roughness=0.15, metallic=0.9)
blue_dull = StandardMaterial(color="#3366ff", roughness=0.8, metallic=0.0)
# Spawn entities
engine.spawn(
Mesh(cube_geo),
Material(red_shiny),
Transform(pos=(-1.5, 0, 0)),
)
engine.spawn(
Mesh(sphere_geo),
Material(blue_dull),
Transform(pos=(1.5, 0, 0)),
)
# Add an orbiting light
light = PointLight(color="#ffffff", intensity=15.0, position=(5, 5, 5))
engine.set_lights([light])
# Animate
@engine.system
def animate_lights(query: mx.Query[Transform], dt: float):
t = engine.elapsed
light.position = (
5 * np.cos(t * 0.7),
3 + np.sin(t * 0.5) * 2,
5 * np.sin(t * 0.7),
)
# Auto-fit camera to view the scene
engine.camera.fit(radius=5.0, azimuth=30, elevation=35)
# Run!
engine.run()
Save as my_scene.py and run:
python my_scene.py
N-Body Simulation
A pure-numpy gravitational simulation running 500 bodies in real-time at a single draw call (instanced rendering).
Physics: All pairwise forces are computed with a single vectorized numpy expression — no Python loops in the hot path. For N bodies this means N² = 250,000 force computations per frame, each a 3-component vector.
@engine.system
def nbody_physics(query: mx.Query[Transform], dt: float):
global velocities
pos = query[Transform].pos.data
# All-pairs position differences (N, N, 3) — one numpy broadcast
diff = pos[None, :] - pos[:, None]
# Pairwise distances (N, N)
dist = np.linalg.norm(diff, axis=2)
dist = np.maximum(dist, SOFTENING)
# Gravitational force magnitude for every pair
force_mag = G * (masses[None, :] * masses[:, None]) / dist**2
# Net force on each body: sum over all other bodies
direction = diff / dist[:, :, None]
forces = force_mag[:, :, None] * direction
net_force = forces.sum(axis=1)
# Integrate: F = ma → a = F/m
velocities += (net_force / masses[:, None]) * dt
query[Transform].pos += velocities * dt
See
examples/nbody.pyfor the full implementation with velocity damping and speed clamping.
Ideal Gas Simulation
The examples/gas.py demo shows the other side: no gravity, all collisions. 500 particles bounce inside an invisible box with elastic collisions and wall reflections — a kinetic theory simulation in pure numpy.
Collisions find overlapping pairs with a vectorized comparison, filter with np.where(np.triu(...)), then resolve impulse with np.add.at for safe accumulation. Wall reflections are a single vectorized mask: velocities[next_pos < wall] = np.abs(...).
See
examples/gas.pyfor the full implementation.
Boids Flocking Simulation
The examples/boids.py demo shows emergent behavior from simple local rules: 300 boids with separation, alignment, cohesion, plus 4 wandering predators they flee from.
Flocking rules (all vectorized):
- Separation — boids repel neighbors weighted by 1/dist²
- Alignment — match average velocity of nearby boids
- Cohesion — steer toward center of mass of neighbors
Predator avoidance — boids detect predators within 10 units and flee with 20x the force of any flocking rule. Fleeing boids get a speed boost (15 vs 10).
Spatial optimization — uses squared distances to avoid sqrt in the hot path. All three rules computed via masked tensor sums over axis=1 of an (N, N, 3) difference tensor.
See
examples/boids.pyfor the full implementation.
Three Demos, Three Vectorization Patterns
| Demo | Entities | Physics Pattern | Operations/Frame |
|---|---|---|---|
nbody.py |
500 | All-pairs gravity | N² = 250,000 force pairs |
gas.py |
500 | Pair collisions + walls | O(N²) pair checks + wall masks |
boids.py |
300 + 4 | Neighbor flocking + predator-flee | (N,N,3) tensor sums + (N,P,3) |
All three use pure numpy — zero Python loops in the hot path. The ECS overhead is ~microseconds/frame; the bottleneck is GPU fill-rate, not CPU physics.
| Example | Description |
|---|---|
hello_world.py |
Minimal empty window |
cube.py |
Rotating cube with Phong material |
pbr_demo.py |
3×2 grid demonstrating PBR materials + 3 orbiting lights |
spheres.py |
Many spheres with physics-like behavior |
nbody.py |
500-body gravitational simulation with pure-numpy physics |
gas.py |
500-particle ideal gas with collisions and virtual walls |
boids.py |
300-agent flocking simulation with emergent swarm behavior |
Run an example:
python -m examples.nbody # N-body gravitational simulation
python -m examples.gas # Ideal gas with elastic collisions
python -m examples.boids # Boids flocking with soft boundary
python -m examples.pbr_demo
Features
ECS Architecture
- Structure of Arrays (SoA) layout for each component
- Vectorized numpy operations for batch transforms
- Free-list for efficient entity reuse
- Component view with operator overloads (
+=,*=, etc.)
Rendering
- Instanced drawing — single draw call per (geometry, material) batch
- Material-specific pipelines — each material type compiles its own WGSL shader
- Transform caching — dirty-flag optimization to avoid recomputing matrices
- Shared transform buffer — all instance transforms uploaded once per frame
Materials & Lighting
- BasicMaterial — unlit flat color with simple diffuse
- StandardMaterial — full PBR with GGX BRDF
- Roughness/metallic workflow
- Multiple point lights with inverse-square attenuation
- Reinhard tonemapping + gamma correction
- External lights — passed to engine like camera (not in ECS)
Camera
- Perspective projection (WebGPU NDC)
- Spherical coordinate orbit controls
- Fit/fit_bounds for automatic framing
Geometries
- Cube (with normals)
- UV Sphere (with normals, CCW winding)
- Plane (with normals)
Architecture Highlights
The ECS uses numpy arrays for all component data. When you call query[Transform].pos += velocity * dt, it's a single vectorized numpy operation spanning thousands of entities.
Real-world examples: The N-body demo (examples/nbody.py) simulates 500 bodies with 250,000 pairwise gravitational force computations per frame. The ideal gas demo (examples/gas.py) runs 500 particles with elastic collisions and wall reflections. Both are pure numpy with zero Python loops.
Limitations (Known)
- ❌ No shadows
- ❌ No texture support
- ❌ No environment/IBL mapping
- ❌ Single material params per draw call (not per-instance)
- ❌ Only point lights in PBR shader
- ❌ Limited to ~100k entities
Future Ideas
This is an academic/experimental project. Ideas for future development:
- Per-instance material data — Storage buffer for varying roughness/metallic per instance in a single draw
- Shadow mapping — Shadow pass + PCF sampling
- Texture maps — Diffuse, normal, roughness textures via storage buffers
- Spot/Directional lights — Extend PBR shader
- Environment mapping — IBL with prefiltered radiance
- Skinned animation — Bone transforms in vertex shader
- Post-processing — Bloom, DOF, TAA
- Deferred rendering — Forward+ / clustered lighting for many lights
Contributing
Contributions welcome! This is an educational project — all skill levels encouraged.
Areas needing work:
- Bug fixes and stability improvements
- Additional geometry types (torus, cylinder, etc.)
- More material types (toon, unlit with texture)
- Shadow implementation
- Performance profiling and optimization
Getting started:
# Clone and set up
git clone https://github.com/apiad/manifoldx.git
cd manifoldx
pip install -e ".[dev]"
# Run tests
make test
# Run an example
python -m examples.cube
Testing
# Run all tests
make test
# Run specific test file
python -m pytest tests/test_ecs.py -v
Current test coverage: 150+ tests covering ECS operations, components, materials, rendering, and camera.
License
MIT License — See LICENSE file.
Credits
- wgpu — Pure Python WebGPU bindings
- PyGfx — Reference for WGSL shader patterns
- rendercanvas — Window management
Disclaimer: This project is for educational and research purposes. Not optimized for production use. Performance characteristics will vary by hardware and Python version.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file manifold_gfx-0.2.0.tar.gz.
File metadata
- Download URL: manifold_gfx-0.2.0.tar.gz
- Upload date:
- Size: 171.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b962fbdeb56d9bf831cdd0af78e51341b75b478587a7a25ef14bd9bed80cd34c
|
|
| MD5 |
9933487b10c2ed3c615e1883d25c8812
|
|
| BLAKE2b-256 |
2a8707aec30ce4eae6b81417e2ac0c6874a9009458f0f7e11a3e9623168931d9
|
File details
Details for the file manifold_gfx-0.2.0-py3-none-any.whl.
File metadata
- Download URL: manifold_gfx-0.2.0-py3-none-any.whl
- Upload date:
- Size: 36.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21dab61b462dadcd756a88443fde0a0f5e47c74676532e41c154fd615752facf
|
|
| MD5 |
88ae3414c1aa8e9a9a550c256b483f42
|
|
| BLAKE2b-256 |
0846eed0626076bfb5f57c90d3f91ff26b622fea7a0c54cd9c1717c5de1e221b
|