This guide covers everything needed to bring up a new board or display backend: architecture constraints, memory sizing, hardware profiling, smoke testing, and a validated compatibility matrix.
The crate root is #![no_std]. The std feature is opt-in.
Runtime frame APIs (record, execute, execute_tiled) operate on caller-provided buffers only:
- Command recording writes into a bounded
CommandBuffer<N>(heapless storage) - Execution writes into caller-provided framebuffer and z-buffer slices
- Tile binning uses compile-time-bounded
heaplessvectors - No heap allocation in frame-path functions
CI runs .github/scripts/check_no_alloc_frame_path.py to reject obvious heap-allocation patterns in frame-path functions. --no-default-features checks remain in CI to validate constrained/no_std-style builds.
Expected frame loop:
- Pre-allocate command buffer and z-buffer
engine.record(meshes, &mut commands, telemetry)?engine.execute(&mut display, &mut frame_ctx, &commands, telemetry)?- Present full frame or region through backend/swapchain APIs
| Buffer | Bytes |
|---|---|
| Single framebuffer | width × height × 2 |
| Double framebuffer | 2 × width × height × 2 |
| Triple framebuffer | 3 × width × height × 2 |
Z-buffer (u32) |
width × height × 4 |
Example at 240×135:
- Single framebuffer: 64,800 bytes (~63 KiB)
- Double framebuffer: 129,600 bytes (~127 KiB)
- Z-buffer: 129,600 bytes (~127 KiB)
total ≈ framebuffers + zbuffer + command_buffer + scratch + scene_state
Where command_buffer depends on CommandBuffer<N> capacity, scratch includes temporary row/raster structures, and scene_state includes mesh arrays and transforms.
- Reserve 15–25% RAM for spikes and stack growth
- If physics/animation is enabled, prefer 25%+ headroom
- Avoid caps that leave <10% free RAM in normal scenes
| Error | Cause |
|---|---|
BudgetKind::MeshesPerFrame |
Too many visible meshes |
BudgetKind::TrianglesPerMesh |
Individual LOD too dense |
BudgetKind::VerticesPerMesh |
Per-mesh geometry too large |
BudgetKind::Textures |
Too many unique textures per frame |
BudgetKind::ZBufferLength |
Z-buffer slice size mismatch |
- RAM budget documented per target profile
- Z-buffer and framebuffer sizes validated against final resolution
- Telemetry snapshots captured for steady/stress/fail-soft cases
- Caps values and command-buffer capacity checked into source control
- Pick a baseline profile from
embedded_3dgfx::config(m3,m4,m33,m55) - Confirm target resolution and color format (
Rgb565) - Verify framebuffer + z-buffer fit within available RAM
- Verify
CommandBuffer<N>fits frame complexity
- Implement or adapt an
embedded_graphicsDrawTarget<Color = Rgb565> - Confirm correct origin/dimensions via
OriginDimensions - Validate full-frame present path first (no partial update optimization yet)
- Add a smoke render that draws points, lines, and filled triangles
- Use record/execute flow in frame loop:
engine.record(...)engine.execute(...)
- Reuse pre-allocated z-buffer and command buffer across frames
- Apply caps at startup (
apply_default_caps(...)orset_caps(...)) - Verify budget errors are surfaced and logged (
RenderError::OutOfBudget(...))
- Capture
RecordTelemetryandExecuteTelemetryfor representative scenes - Confirm fallback behavior for constrained profiles where expected
- Record at least one "steady" and one "stress" snapshot for your board
- Keep measurements with build flags and profile noted
- Run deterministic integration tests locally before hardware pass
- Validate no panics under camera movement, culling, and empty-scene cases
- Validate over-budget behavior is deterministic
- Confirm render output remains stable across repeated runs
- Add a board smoke command to CI docs or board-specific automation
- Include: startup render, moving camera, and one constrained-budget scene
- Define pass/fail signals (console output, telemetry line, screenshot hash)
- Link this backend row in the compatibility matrix below when validated
# 1. Constrained build check
EMBEDDED_3DGFX_CAPS=m4 cargo check --lib --no-default-features --features row_width_240
# 2. Deterministic telemetry snapshots
EMBEDDED_3DGFX_CAPS=m4 cargo test --test integration_tests test_ci_telemetry_snapshot_ -- --nocapture
# 3. Validate telemetry limits
# (pipe output to .github/scripts/check_telemetry_budget.py)
# 4. Run at least one interactive scene on hardware using record/execute path
# 5. Capture steady + stress snapshots in board notes
# 6. Mark backend in compatibility matrix once repeatableExit criteria: backend is complete when the board renders the canonical record/execute path, profile/cap limits are configured and validated, and a repeatable smoke procedure exists with actionable failure signals.
| Flag | Purpose |
|---|---|
dwt-profiler |
DWT cycle-counter sampling hooks |
rtt-trace |
Emits RTT_TRACE ... markers |
itm-trace |
Emits ITM_TRACE ... markers |
cargo check --lib --no-default-features --features "row_width_240 perfcounter dwt-profiler"PerformanceCounter::start_of_frame()initializes DWT counter hooksadd_measurement()records microseconds and cycle deltas where availableprint()records frame-level cycle totals- Hooks are implemented in
src/hardware_profile.rs - DWT cycle reads are active only on ARM targets with
dwt-profiler - RTT/ITM sinks emit textual markers suitable for board logging pipelines
- On non-ARM or when feature-disabled, hooks degrade safely to no-op
- Flash a profile-constrained build
- Capture
perf.frameandperf.measurementtraces for steady + stress scenes - Track cycle deltas across commits to detect regressions
- Attach trace snippets to PRs that modify rendering hot paths
.github/workflows/hardware-smoke.yml — designed for workflow_dispatch with self-hosted runners.
Expected runner labels: self-hosted, embedded-3dgfx-hw, board label (e.g. stm32-m4, stm32-m33, cortex-m55).
.github/scripts/hardware_smoke.sh emits standardized lines:
HARDWARE_SMOKE board=... profile=... stage=... status=...
Validated by .github/scripts/check_hardware_smoke.py.
- Build constrained profile for board
- Run deterministic telemetry snapshot tests
- Validate telemetry output format and expectations
- Persist smoke log artifact
Failure output should include: board identifier, profile, failed stage, and command context.
Legend: Validated = covered by CI and/or deterministic integration checks | Expected = should work, not yet formally validated | Planned = not yet implemented
| Profile | Status | Validation source |
|---|---|---|
std default |
Validated | rust.yml — check, test, examples |
--no-default-features --features row_width_240 |
Validated | rust.yml — embedded-budget matrix |
desktop-unbounded |
Expected | Local/demo usage |
row_width_96/160/320 |
Validated | rust.yml — embedded-budget matrix |
| Capability | Status | Validation source |
|---|---|---|
| Record/execute command buffer pipeline | Validated | Integration tests + examples |
| Deterministic command recording | Validated | tests/integration_tests.rs |
| Golden-output regression digests | Validated | tests/integration_tests.rs |
| Telemetry counters (record/execute) | Validated | Integration tests + demo HUD |
| Fail-soft fallback APIs | Validated | Integration tests + demos |
| Snapshot class | Status | Expected signals |
|---|---|---|
CI_TELEMETRY |
Validated | fallback_used=0 |
CI_TELEMETRY_STRESS |
Validated | fallback_used=0 |
CI_TELEMETRY_FAILSOFT |
Validated | fallback_used=1, expected budget kind |
| Target / Backend | Status | Notes |
|---|---|---|
embedded-graphics-simulator desktop |
Validated | Primary interactive demo environment |
Generic DrawTarget execution path |
Validated | Core renderer backend-agnostic path |
| Hardware SPI/LTDC/DMA2D board backends | Planned | Needs board-specific implementation and smoke tests |
| Hardware-in-the-loop CI | Planned | Not yet in CI |
| MCU class | Status | Validation source |
|---|---|---|
| Cortex-M33 balanced | Validated | Default caps + demos/tests + profile-budget CI matrix |
| Cortex-M4 balanced | Validated | rust.yml profile-budget matrix (EMBEDDED_3DGFX_CAPS=m4) |
| Cortex-M3 balanced | Validated | rust.yml profile-budget matrix (EMBEDDED_3DGFX_CAPS=m3) |
| Cortex-M55 perf | Validated | rust.yml profile-budget matrix + m55-perf-budget lane |
When adding a new backend/profile/board:
- Add a deterministic test or CI job that exercises it
- Add threshold/telemetry assertions where possible
- Update the row above from Expected/Planned to Validated
- Link the validating workflow step or test name in the notes column