Skip to content
 
 

Repository files navigation

embedded-3dgfx

embedded-3dgfx

crates.io docs.rs CI codecov License: MIT OR Apache-2.0

A no_std 3D graphics and physics engine for embedded systems: software rasterization, rigid/soft-body physics, skeletal animation, and effects tuned for MCUs.

Fork of embedded-gfx by Kezii, extended with textures, fog/dithering, DMA swapchains, AA, physics, BSP, and more.

Highlights

  • A graphics pipeline, spelled out — five stages (pipeline::vertexassemblerasterizeshadeoutput), each with a StageKind marker and a documented dependency direction, plus a prelude for the common types
  • Two raster layers, one config — the built-in draw path (hand-specialised per RenderMode, driven by record/execute) shares its FogConfig/DitherConfig with a zero-cost FragmentShader seam for custom materials and decorators (FogShader, DitherShader, ScreenTintShader, PaletteShader, WaterReflectShader)
  • Unified raster statepipeline::rasterize::draw::RasterState bundles per-pass config (fog, dither, tint, palette, stipple, depth bias), so rasterizers take one borrowed context instead of a wide positional argument tail
  • One home per type — no glob re-exports; every type is reached through its owning module, so the public surface is explicit rather than accidental
  • Record / execute — traverse once, rasterize from a fixed-capacity command buffer (PrimitiveHeader + packed typed descriptors for low RAM footprint)
  • Rendering — MVP + frustum/backface cull, Z-buffer, flat/Gouraud/Blinn-Phong, perspective textures, Bayer dither, Reinhard tonemapping, sub-pixel Q16.16 rasterization, lights, particles, LOD, HUD
  • Physics & Navigation (features physics, scene) — rigid bodies, joints, soft body, ray primitives, NavMesh A* pathfinding
  • Animation — skeletal LBS, vertex morphs, ABSM state machines, transform tracks, spline curves / tweens
  • Embedded-friendlyheapless caps, Cortex-M SWAR/DSP SIMD optimizations, async-agnostic swapchain present, silicon hardware offloading hooks (HardwareAccelerator)

Screenshots

SSR Water Reflection
SSR water reflection + palette cycling
Blinn-Phong Suzanne
Blinn-Phong
Physics balls
Rigid body physics
Particles + fog
Particles + fog
Cloth
Soft-body cloth
Point lights
Point lights
cargo run --release --example screenshots --features "std,lighting,textured,raycast,scene,physics"

Installation

[dependencies]
# Embedded (no_std) — slim default is row_width_240
embedded-3dgfx = { version = "0.6", default-features = false, features = ["row_width_320", "depth-u16"] }

# Orientation-style lit meshes
embedded-3dgfx = { version = "0.6", default-features = false, features = ["row_width_320", "depth-u16", "lighting"] }

# Desktop / simulator
embedded-3dgfx = { version = "0.6", features = ["std", "physics"] }

MCU feature recipes

Recipe Features
Minimal wireframe default-features = false, row_width_320, depth-u16
Lit mesh (Gouraud / Blinn / Toon) add lighting
Retro / Doom-style lighting, textured, raycast, hud
Physics demo add physics

Quick start

use embedded_3dgfx::prelude::*;

let mut engine = K3dengine::new(320, 240);
engine.camera.set_position(Vector3::new(0.0, 0.0, 5.0).into());

let geometry = Geometry { vertices: &CUBE_VERTS, faces: &CUBE_FACES, /* ... */ };
let mut mesh = K3dMesh::new(geometry);
mesh.set_render_mode(RenderMode::Lines);

let mut commands = CommandBuffer::<512>::new();
engine.record(core::iter::once(&mesh), &mut commands, None).unwrap();
engine.execute(&mut display, &mut frame_ctx, &commands, None).unwrap();

Custom raster draws (RasterState)

Everything the engine applies to a frame — fog, dither, screen tint, palette quantization, stipple, depth bias — is lowered once into a RasterState:

use embedded_3dgfx::pipeline::rasterize::draw::draw_zbuffered_with_state;

let state = engine.raster_state(320, 240);
draw_zbuffered_with_state(primitive, &mut fb, &mut zbuffer, &state);

Architecture: a five-stage pipeline

The crate is organised as a graphics pipeline. Each stage owns one module and consumes the previous stage's output:

# Stage Module Input → output
1 Stage::Vertex pipeline::vertex model-space mesh → clip-space vertices
2 Stage::Assemble pipeline::assemble clip-space vertices → screen-space DrawPrimitive
3 Stage::Rasterize pipeline::rasterize screen primitives → covered pixels
4 Stage::Shade pipeline::shade fragments → Rgb565 colours
5 Stage::Output pipeline::output framebuffer → presented frame

pipeline::command_buffer is the transport between the record half (stages 1–2) and the execute half (stages 3–5); pipeline::renderer drives the execute half, and engine is the frame driver on top. Shared per-pass configuration lives in pipeline::effects.

The dependency direction is enforced by review, not the compiler: a stage may depend on lower-numbered stages, effects, and the crate-root core types — never on a higher-numbered stage. The single exception is that rasterize invokes the fragment programs defined in shade, so shade sits below rasterize in dependency order even though it runs afterwards.

Every stage has a StageKind marker so the pipeline is queryable at compile time (Camera: Stage::Vertex, DrawPrimitive: Stage::Assemble, RasterState: Stage::Rasterize, FlatColorShader: Stage::Shade, DisplayError: Stage::Output).

Module map

Tree Contents
pipeline::vertex mesh, shapes, bounds, camera, camera_controller, view_frustum, lod, transform
pipeline::assemble primitive (DrawPrimitive)
pipeline::rasterize raster, draw, coverage, texture, tilebin
pipeline::shade shader, retro, dither, lights
pipeline::output display_backend, swapchain, completion, hud
pipeline::{command_buffer, renderer, effects} record/execute transport, execute driver, shared config
engine K3dengine frame driver (record / execute)
core color, config, error, simd_dsp, plus the prelude
subsystems physics, raycast, bsp, navmesh, skeleton, animation, absm, tween, scene_format, scene_stream, …

Geometry & Surface Normals for Lighting

When using lit render modes (RenderMode::SolidLightDir, BlinnPhong, Toon, GouraudLightDir), the engine requires surface face normals in Geometry.normals (or vertex_normals) to evaluate light angles (N · L):

  • Static Flash ROM Storage (Recommended for MCUs): Precompute face normals offline or at compile-time and store them alongside vertices as &'static [[f32; 3]] (0 RAM overhead).
  • On-Demand Helper: If authoring procedural geometry in code, use Geometry::compute_face_normals_into(&verts, &faces, &mut out_normals) or Geometry::compute_face_normals(&verts, &faces):
let mut normals = [[0.0f32; 3]; CUBE_FACES.len()];
Geometry::compute_face_normals_into(&CUBE_VERTS, &CUBE_FACES, &mut normals);

let geometry = Geometry {
    vertices: &CUBE_VERTS,
    faces: &CUBE_FACES,
    normals: &normals,
    ..Default::default()
};

More patterns (particles, lights, fog, physics, skeleton, soft body, async present) live under examples/ and on docs.rs.

Feature flags

Flag Default Description
row_width_* 240 Row-buffer width (96 / 160 / 240 / 320). Meant to be mutually exclusive; if a build enables several, the widest wins deterministically
std off Desktop helpers / perfcounter
lighting off SolidLightDir / Gouraud / Blinn / Toon / SectorBright + lights
textured off Texture modes + texture module (implies lighting)
raycast off Doom-style raycaster, BSP helpers, sector_lights
scene off Skeleton, character, particles, billboard, animation / scene stream
hud off HUD helpers
painters off Painter's algorithm helpers (painters module)
physics off Rigid body, soft body, physics raycast
aa-heuristic / aa-coverage off Triangle edge AA (coverage needs a W×H buffer)
dsp / fixed-transform / fixed-raster off Shared Q16.16 / quat path via embedded-dsp
triple-buffering / embassy / dma2d off Swapchain / Embassy / DMA2D hooks
perfcounter / dwt-profiler / rtt-trace / itm-trace off Timing / trace sinks

Flash impact of the slim recipes is tracked in docs/feature-size.md (size_harness + CI budget).

Optional scene extras (off by default — keeps MCU binaries lean)

Feature What you get
aabb-cull Cached AABB, two-stage frustum cull, raycast broadphase
render-layers Camera ↔ mesh layer bitmasks
record-sort Priority / distance sort in record
lod-crossfade LOD fade margins
anim-blend Clip blending, bone slerp, skinned AABBs (also enables scene)
gizmos AABB / frustum debug wireframes
visibility-extras aabb-cull + render-layers + record-sort + lod-crossfade
scene-extras All of the above
embedded-3dgfx = { version = "0.6", features = ["std", "scene-extras"] }
cargo test --test scene_extras --features "std,scene-extras"

Examples

cargo run --example rotating_cube --features std
cargo run --example lighting_demo --features "std,lighting"
cargo run --example texture_mapping_demo --features "std,textured"
cargo run --example skeletal_animation_demo --features "std,scene"
cargo run --example star_striker_demo --features "std,lighting,scene"
# physics demos also need: --features "std,physics"

Rendering: basic_rendering, rotating_cube, scene_viewer, lighting_demo, gouraud_demo, blinn_phong_demo, fog_dithering_demo, texture_mapping_demo, mesh_texture_demo, retro_presets_demo, bsp_builder_demo, dma_rendering_demo, billboard_demo, lod_demo, vertex_animation_demo, painters_algorithm_demo, boot_menu, stl_viewer, water_reflection_ssr_demo, hybrid_hud_sprite_demo, star_striker_demo, …

Physics: physics_rolling_ball, physics_bouncing_balls, physics_pendulum, physics_newtons_cradle, physics_stack_tower, cloth_simulation, jelly_cube_demo, raycast_demo, walkable_demo, capsule_physics_demo, …

Docs & tools

Resource Topic
MIGRATION.md Upgrading from the pre-pipeline module layout
docs/app-integration.md Adding the engine to your application (with runnable templates)
docs/caps-and-telemetry.md Caps, telemetry, CI budgets
docs/feature-size.md Slim vs full flash (.text) budgets
docs/backend-integration.md Board bring-up, memory sizing
docs/asset-pipeline.md Offline assets / scene streaming
tools/blender_addon Blender mesh / animation export add-on

Typical target: Cortex-M4F/M33 with FPU; ~128 KB RAM minimum, ~512 KB+ recommended for double-buffer + Z + physics at 240×135.

Testing

cargo test --lib
cargo test --lib --features dma2d,depth-u16
cargo test --test scene_extras --features "std,scene-extras"

Git hooks (fmt on commit / push): ./scripts/install-git-hooks.sh

Contributing

PRs welcome — especially board backends, broad-phase spatial structures, and extra joint / collider types.

Import policy

Aggregating surfaces — the prelude and the per-stage facades such as pipeline::output — are public API for downstream crates. Code inside src/ must not import from them: internal modules name the module that actually defines an item, so trimming an aggregator can never silently reshape the internals. Glob imports are confined to #[cfg(test)] modules.

python3 .github/scripts/check_internal_imports.py   # run before pushing

The check runs as the import-policy CI job. tests/, examples/ and benches/ are consumers of the public API and may use the facades freely.

Feature gate policy

A module reached only through #[cfg(feature = "F")] mod x; cannot be compiled without F, so a #[cfg(feature = "F")] inside it does nothing. Those gates read as if they mattered and hide which ones are load-bearing, so they are rejected:

python3 .github/scripts/check_feature_gates.py   # run before pushing

The check runs as the feature-gate-policy CI job. It only reports gates that are provably redundant — any(...) and not(...) establish nothing, and a gate that still constrains a second feature is kept.

License

Dual-licensed under MIT OR Apache-2.0. See LICENSE-MIT, LICENSE-APACHE, and NOTICE.

About

Lightweight 3D graphics rasterization, software shading, matrix transforms, and vector geometry for embedded-graphics

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages