Skip to content

ENG2-P8-02: Particles: FFI exposure of the existing 3D emitter + new 2D emitter on the sprite core #800

Description

@aram-devdocs

Parent

  • Program: ENG2 — GoudEngine v2 Rebuild (see the pinned master tracking issue)
  • Phase / Milestone: Phase 8 — Capability Gaps (eng2-p8-capabilities)
  • Batch / Group: Batch 8.1 — Highest-demand capabilities, Group B
  • Runbook spec: docs/src/runbook/phases/phase-8.md (committed with the roadmap)

Summary

Expose the already-implemented 3D ParticleEmitter through a new ffi/renderer3d/particles.rs module and all 10 SDKs, and add a new 2D particle emitter built on the Phase 6 instanced sprite core.

Architecture Context

Layer: Layer 4 (Engine, libs/graphics/renderer3d/ — 2D emitter logic reuses the sprite core) for the 2D emitter internals; Layer 5 (FFI) for both new export surfaces.
Modules/types touched:

  • goud_engine/src/ffi/renderer3d/particles.rs (new) — wraps the existing Renderer3D::create_particle_emitter / set_particle_emitter_position / remove_particle_emitter engine API.
  • goud_engine/src/libs/graphics/renderer2d/ — new 2D particle emitter type, driven by the Phase 6 instanced sprite core's per-instance buffer rather than a bespoke draw path.
  • goud_engine/src/ffi/renderer/ — new FFI exports for the 2D emitter (naming to mirror the 3D particles.rs module).

Boundary constraints (only those that apply):

  • Raw GPU calls stay in libs/graphics/backend/ — the 2D emitter must go through the sprite core's existing instance-buffer upload path, not a new wgpu:: call site.
  • FFI exports: #[no_mangle] extern "C", #[repr(C)], null checks, // SAFETY: comments.

Pattern to follow: goud_engine/src/ffi/spatial_grid/mod.rs handle-registry pattern for exposing engine-internal u32 IDs safely across FFI (the 3D emitter already returns opaque u32 IDs from create_particle_emitter — mirror how existing FFI modules validate and scope such IDs per context). For the 2D emitter, follow whatever per-instance buffer API Phase 6's ENG2-P6-02 sprite core exposes internally — do not build a second CPU-side vertex-expansion path.

Scope

  • ffi/renderer3d/particles.rs: #[no_mangle] wrappers for create/configure/set-position/remove on the existing Renderer3D particle emitter API (core_primitives.rs:133-172).
  • New 2D particle emitter type in libs/graphics/renderer2d/, spawning/aging/killing particles on the CPU and feeding positions/colors/sizes into the sprite core's per-instance buffer (per ENG2-P6-02).
  • FFI exports for the 2D emitter (create/configure/position/remove), structurally parallel to the 3D surface.
  • Tests: spec test + unit tests for particle lifecycle (spawn rate, aging, death at lifetime) for both 2D and 3D paths.
  • Docs/rules updates: .agents/rules/graphics-patterns.md gains a short particle-system note once both paths exist.

Acceptance Criteria

  • 3D particle FFI surface is callable end-to-end from at least one SDK smoke test (create emitter, position it, observe particles render, remove it).
  • 2D particle emitter renders through the sprite core's instanced path (verified: no per-particle immediate-mode draw calls).
  • FFI particle exports have wrappers in all 10 SDKs and pass python codegen/validate_coverage.py.
  • Docs: an mdBook page documents both particle FFI surfaces with example usage.
  • cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings clean; cargo test green; ./codegen.sh && git diff --exit-code (drift gate)

Breaking Change & Throne Follow-up

None — additive/internal (new FFI exports only; no existing exports change).

Blocked By

ENG2-P6-02 (instanced sprite core — the new 2D particle emitter must render through the unified sprite core's per-instance buffer, not a fifth parallel 2D rendering path).

Files Likely Touched

  • New: goud_engine/src/ffi/renderer3d/particles.rs, goud_engine/src/libs/graphics/renderer2d/particles.rs (or equivalent), new 2D particle FFI file under goud_engine/src/ffi/renderer/
  • Generated: SDK bindings under sdks/*/ for both particle FFI surfaces
  • Modified: goud_engine/src/ffi/renderer3d/mod.rs (register particles module), codegen/goud_sdk.schema.json

Agent Notes

  • ParticleEmitterConfig (goud_engine/src/libs/graphics/renderer3d/types.rs:107-129), Particle (types.rs:267-273), and ParticleEmitter (types.rs:276-283) are fully implemented and already rendered every frame: render_instanced_and_particles (libs/graphics/renderer3d/render_instanced.rs:13) checks self.particle_emitters at lines 23 and 72 and draws them via the instanced path. This is real, working engine functionality, not a stub.
  • The engine-level public API already exists and needs no new engine logic for the 3D half of this issue: Renderer3D::create_particle_emitter (libs/graphics/renderer3d/core_primitives.rs:133-142), set_particle_emitter_position (core_primitives.rs:157-164), and remove_particle_emitter (core_primitives.rs:167-172) are all pub fn on Renderer3D. The work here is exclusively adding the FFI wrapper layer + SDK codegen — confirmed by listing goud_engine/src/ffi/renderer3d/: it contains animation.rs, camera.rs, environment/, lighting.rs, materials.rs, mod.rs, model/, postprocess.rs, primitives.rs, scene.rs, skinned.rs, state.rs and no particles.rs.
  • Note the internal types (ParticleEmitterConfig, Particle, ParticleEmitter) are scoped pub(in crate::libs::graphics::renderer3d) — FFI code cannot touch them directly and must go exclusively through the Renderer3D public methods above (which take/return plain u32 IDs and primitive floats already, so no new #[repr(C)] struct should be needed for the 3D half).
  • 2D has no particle system at all today; it must be built new on top of whatever the Phase 6 sprite core (ENG2-P6-02) exposes for per-instance buffer updates — do not build it against the pre-Phase-6 immediate or rebuild-per-call sprite paths, since those are slated for deletion in ENG2-P6-03.

Verification

cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings
cargo test
./codegen.sh && git diff --exit-code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions