Hoplite
A creative coding framework for Rust that gets out of your way.
Write shaders, render 3D scenes, and build visualizations with a single closure. No boilerplate, no ceremony—just code for the screen.
use *;
Philosophy
Hoplite is built on three principles:
-
One closure, one call — Your setup and frame logic live in closures. No trait implementations, no engine lifecycle to memorize.
-
Hot reload everything — Edit your WGSL shaders and watch them update instantly. No restart required.
-
Escape hatches everywhere — Start simple, access the full wgpu API when you need it.
Features
Background Color (No Shader Required)
run;
Shader-First Rendering
run;
Effects and post-process passes chain automatically. The render graph handles ping-pong buffers, texture binding, and presentation.
3D Mesh Rendering (Fluent Builder API)
run;
Meshes render with depth testing, respecting effect passes and post-processing in the pipeline.
Textured Meshes
run;
Loading 3D Models
run;
Load STL files with automatic transformations. The fluent API chains centering, orientation fixes, and scaling before GPU upload.
2D Sprites
run;
Sprites render in the 2D overlay layer on top of all 3D content and effects.
Orbit Camera
let mut orbit = new
.target
.distance
.fov
.mode; // or AutoRotate { speed: 0.5 }
move |frame|
Interactive mode: drag to rotate, scroll to zoom. Auto-rotate mode for demos and visualizations.
Entity Component System (ECS)
run;
Built on hecs — a fast, minimal ECS. Use it for game objects, particles, or any dynamic entity management. The immediate-mode API still works alongside ECS.
Immediate-Mode 2D
move |frame|
All 2D draws are batched and rendered as an overlay after your render pipeline completes.
Runtime Hot Reload
Edit any .wgsl file passed to hot_effect* or hot_post_process* methods. Hoplite watches the filesystem and recompiles shaders on change. If compilation fails, the previous working shader stays active.
[hot-reload] Reloading shader: "shaders/nebula.wgsl"
[hot-reload] Shader compiled successfully
Quick Start
[]
= { = "https://github.com/xandwr/hoplite" }
use *;
Examples
Run the black hole demo with gravitational lensing:
API Reference
Setup Context (SetupContext)
| Method | Description |
|---|---|
default_font(size) |
Load the default font at given pixel size |
background_color(color) |
Set solid background color (no shader needed) |
effect(shader) |
Add a screen-space effect pass |
effect_world(shader) |
Add a world-space effect with camera uniforms |
post_process(shader) |
Add screen-space post-processing |
post_process_world(shader) |
Add world-space post-processing |
hot_effect(path) |
Hot-reloadable screen-space effect |
hot_effect_world(path) |
Hot-reloadable world-space effect |
hot_post_process(path) |
Hot-reloadable screen-space post-process |
hot_post_process_world(path) |
Hot-reloadable world-space post-process |
enable_mesh_rendering() |
Enable 3D mesh pipeline |
mesh_cube() |
Create a unit cube mesh, returns MeshId |
mesh_sphere(segments, rings) |
Create a UV sphere mesh, returns MeshId |
mesh_plane(size) |
Create a flat plane mesh, returns MeshId |
load(path) |
Load geometry from file, returns MeshLoader |
load_stl_bytes(bytes) |
Load STL from bytes, returns MeshLoader |
add_texture(texture) |
Add a texture, returns TextureId |
texture_from_file(path) |
Load texture from file, returns TextureId |
texture_from_bytes(bytes, label) |
Load texture from memory |
texture_blocky_noise(size, seed) |
Procedural dirt/stone texture |
texture_blocky_grass(size, seed) |
Procedural grass texture |
texture_blocky_stone(size, seed) |
Procedural stone texture |
add_sprite(sprite) |
Add a sprite, returns SpriteId |
sprite_from_file(path) |
Load sprite from file (linear filtering) |
sprite_from_file_nearest(path) |
Load sprite from file (pixel art) |
sprite_from_bytes(bytes, label) |
Load sprite from memory |
Frame Context (Frame)
| Method | Description |
|---|---|
fps() |
Current frames per second |
width() / height() |
Screen dimensions in pixels |
set_camera(camera) |
Set the camera (cleaner than *frame.camera = ...) |
mesh(id) |
Start a mesh builder chain (fluent API) |
draw_mesh(id, transform, color) |
Draw a 3D mesh (classic API) |
draw_mesh_textured(id, transform, color, tex) |
Draw a textured 3D mesh (classic API) |
text(x, y, str) |
Draw text at position |
text_color(x, y, str, color) |
Draw colored text |
rect(x, y, w, h, color) |
Draw filled rectangle |
panel(x, y, w, h) |
Draw a bordered panel |
panel_titled(x, y, w, h, title) |
Panel with title bar |
sprite(id, x, y) |
Draw sprite at position |
sprite_tinted(id, x, y, tint) |
Draw sprite with color tint |
sprite_scaled(id, x, y, w, h) |
Draw sprite at custom size |
sprite_scaled_tinted(id, x, y, w, h, tint) |
Draw scaled sprite with tint |
sprite_region(id, x, y, w, h, sx, sy, sw, sh) |
Draw sprite sub-region |
render_world() |
Render all ECS entities with Transform + RenderMesh |
Mesh Builder (MeshBuilder)
The fluent API for drawing meshes, created via frame.mesh(id):
| Method | Description |
|---|---|
.at(x, y, z) |
Set position |
.position(Vec3) |
Set position from Vec3 |
.transform(Transform) |
Set full transform (position, rotation, scale) |
.color(Color) |
Set color/tint |
.texture(TextureId) |
Apply texture |
.draw() |
Queue the mesh for rendering |
Mesh Loader (MeshLoader)
The fluent API for loading geometry, created via ctx.load(path):
| Method | Description |
|---|---|
.centered() |
Center geometry at origin |
.upright() |
Convert Z-up to Y-up orientation |
.normalized() |
Scale to fit in unit cube |
.scaled(factor) |
Apply uniform scale |
.translated(Vec3) |
Move geometry by offset |
.rotated_by(Quat) |
Apply custom rotation |
.smooth_normals() |
Recalculate vertex normals |
.unwrap() |
Finalize and return MeshId |
.build() |
Finalize and return Result<MeshId> |
Frame Fields
| Field | Type | Description |
|---|---|---|
time |
f32 |
Total elapsed time in seconds |
dt |
f32 |
Delta time since last frame |
input |
&Input |
Keyboard and mouse state |
camera |
&mut Camera |
Current camera (or use set_camera()) |
world |
&mut World |
ECS world for entity management |
gpu |
&GpuContext |
Low-level GPU access |
draw |
&mut Draw2d |
Low-level 2D API |
Shader Uniforms
World-space shaders receive these uniforms:
struct Uniforms {
resolution: vec2f,
time: f32,
fov: f32,
camera_pos: vec3f,
_pad1: f32,
camera_forward: vec3f,
_pad2: f32,
camera_right: vec3f,
_pad3: f32,
camera_up: vec3f,
aspect: f32,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
Post-process shaders also get the input texture:
@group(0) @binding(1) var input_texture: texture_2d<f32>;
@group(0) @binding(2) var input_sampler: sampler;
Dependencies
Hoplite builds on solid foundations:
- wgpu — Cross-platform GPU abstraction
- winit — Window creation and input handling
- glam — Fast math types (Vec3, Mat4, Quat)
- hecs — Fast, minimal Entity Component System
- fontdue — Font rasterization
- bytemuck — Safe casting for GPU buffers
- image — Image loading and handling
License
MIT