Native runtime for Three.js games. Write Three.js, ship to Steam as a small native binary. Same codebase runs in browser.
No Electron. No Chromium. No bullshit.
three-native is a Zig-based runtime that hosts an unmodified Three.js build
on top of a lightweight JavaScript engine and a browser API shim. The goal is
to make Three.js apps run natively with minimal footprint while keeping the
existing Three.js codebase intact.
┌─────────────────────────────────┐
│ Your Game (Three.js) │ ← Unmodified Three.js code
├─────────────────────────────────┤
│ Three.js (submodule) │
├─────────────────────────────────┤
│ Browser Shim │ ← WebGL, DOM, etc.
├─────────────────────────────────┤
│ mquickjs │ ← 100KB JS engine
├─────────────────────────────────┤
│ Native Backend (Zig) │ ← sokol or wgpu-native
└─────────────────────────────────┘
- 10KB RAM, 100KB ROM
- ES5 subset encourages clean code
- JS is not the hot path; GPU calls are
- Can swap to V8/JSC later if needed
Three.js touches these browser APIs:
- WebGL:
WebGLRenderingContext/WebGL2RenderingContext - Canvas:
HTMLCanvasElement,getContext, width/height - Timing:
requestAnimationFrame,performance.now() - Assets:
Image,createImageBitmap,fetch,FileReader,URL.* - Input:
addEventListener, pointer lock, gamepad API - Audio (phase 2):
AudioContext,AudioBuffer,GainNode, etc. - Misc:
document.createElement('canvas'),TextDecoder,console.*
git clone --recursive https://github.com/mattneel/three-native
cd three-native
zig build run -- examples/creating-a-scene.jsRequires:
- Zig 0.15.2+
- Node.js + npm (used to build the ES5 Three.js bundle on first run)
Notes:
zig build runautomatically runsnpm installand buildsexamples/three.es5.js.- You can rebuild the bundle manually with
zig build three-es5. - Three.js is a submodule, so
--recursiveis required on clone.
deps/sokol-zigis vendored to keep Sokol patches reproducible. We raiseSG_MAX_UNIFORMBLOCK_MEMBERSto 64 to match current Three.js shader needs.
Early stage. Milestones are tracked in KICKSTART.md.
When running Three.js examples, you may see Sokol warnings like:
GL_UNIFORMBLOCK_NAME_NOT_FOUND_IN_SHADER: uniform block name not found in shader
GL_VERTEX_ATTRIBUTE_NOT_FOUND_IN_SHADER: vertex attribute not found in shader
These are benign. They occur because:
- Three.js shaders declare many standard uniforms/attributes for optional features
- The GLSL compiler optimizes out unused ones
- Sokol logs a warning when
glGetUniformLocation/glGetAttribLocationreturns -1
The runtime uses preprocessor-aware filtering to minimize these, but the GLSL compiler's dead code elimination is more aggressive than our heuristics. Unused uniforms simply won't be applied, which is correct behavior.
- Design docs (GitHub Pages): https://mattneel.github.io/three-native/docs/design/
- Local mdbook source:
docs/design
If you want to help:
- Pick an unimplemented
gl.*function - Implement it in
src/shim/webgl.zig - Add a test
- Open a PR
Issues and ideas are welcome.
If this saved you from having to wrangle Electron, please consider sponsoring. Every little bit helps.
MIT