Skip to content

ENG2-P4-04: Bulk transform-upload API: SoA handle/pos/rot arrays in one call; renderer dirty integration #784

Description

@aram-devdocs

Parent

  • Program: ENG2 — GoudEngine v2 Rebuild (see the pinned master tracking issue)
  • Phase / Milestone: Phase 4 — FFI v2 Convergence (eng2-p4-ffi-v2)
  • Batch / Group: Batch 4.2 — Bulk surfaces, Grp A (parallel with Grp B / ENG2-P4-05, Grp C / ENG2-P4-06)
  • Runbook spec: docs/src/runbook/phases/phase-4.md (committed with the roadmap)

Summary

Add a bulk transform-upload FFI op — SoA handle/position/rotation arrays uploaded in one command-buffer call — writing directly into the ENG2-P2-13 SoA transform store, with renderer-side dirty-range integration.

Architecture Context

Layer: FFI (Layer 5) entry point over Engine/Libs (Layer 4/2) state — goud_engine/src/ffi/ calling into the ENG2-P2-13 SoA transform store and libs/graphics/renderer3d/.
Modules/types touched:

  • goud_engine/src/ffi/ — new bulk transform-upload opcode (routed through the ENG2-P4-02 command buffer) accepting SoA handle/position/rotation arrays.
  • goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs — the per-instance transform write path this feeds; must integrate with the renderer's dirty-range tracking instead of a full per-call rebuild.
  • The ENG2-P2-13 SoA transform store (introduced by that issue) — this is the write target; do not invent a second transform representation.

Boundary constraints (only those that apply):

  • Raw GPU calls stay in libs/graphics/backend/ — no wgpu:: outside it.
  • FFI exports: #[no_mangle] extern "C", #[repr(C)], null checks, // SAFETY: comments on the SoA array pointers this op accepts.

Pattern to follow: The existing batch pattern (goud_component_add_batch, ffi/component/batch.rs:72) for one-lock/N-item amortization; the target data shape is SoA (parallel handle/position/rotation arrays), not an array-of-structs, to match the ENG2-P2-13 store layout.

Scope

  • Add a bulk transform-upload opcode to the command buffer (ENG2-P4-02): SoA handle array + position array + rotation array (and scale, if the P2-13 store carries it) in one call.
  • Write path lands directly in the ENG2-P2-13 SoA transform store — no intermediate per-entity copy or FFI-side staging buffer beyond what the command-buffer decode already requires.
  • Renderer-side dirty-range integration: the renderer consumes the updated ranges without a full per-instance rebuild.
  • Tests: spec test at goud_engine/tests/spec/ for bulk upload correctness (N handles updated in one call, dirty ranges marked correctly) + unit tests for the SoA decode path.
  • Docs/rules updates: none expected beyond noting the new bulk op in .agents/rules/ffi-patterns.md's function inventory, if that file tracks one.

Acceptance Criteria

  • One FFI call updates N instances' transforms (position + rotation) via SoA arrays, verified against a test asserting all N land correctly in the P2-13 store.
  • No per-instance vertex clone or per-entity FFI crossing occurs on this path (grep/test-gated against the ENG2-P2-13 anti-pattern it targets).
  • PERF (area:performance): informational throughput number for N=10k/50k transform updates in one call, feeding the eventual S3 gate acceptance in ENG2-P4-08.
  • 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. This adds a new bulk op alongside (not replacing) existing per-object transform setters in this issue; ENG2-P4-05/07 handle the demotion/deprecation of the per-object hot calls. Throne follow-up once the whole convergence phase lands: THR-A-02 (adopt batch spawn + bulk transform upload + command-buffer submission; delete per-entity SetModelPosition/SetModelRotation loops).

Blocked By

ENG2-P4-02 (command-buffer core must exist before this rides on it as an opcode).

Files Likely Touched

  • New/Modified/Generated: goud_engine/src/ffi/ (bulk transform opcode), goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs, goud_engine/tests/spec/

Agent Notes

  • Throne calls SetModelPosition/SetModelRotation per entity per frame (throne_ge repo, Entities.cs:56,287 per the prior cross-repo audit cited in design-technical.md:322) — batch equivalents exist in GoudEngine today but were never made the primary path from the SDK side. This issue is the engine-side half that makes the batch path worth adopting; THR-A-02 is the Throne-side adoption that deletes those per-entity loops.
  • Per design-expansion.md:32: ENG2-P2-13 (SoA transform store) is "Feeds ENG2-P2-05 (instance buffers), ENG2-P4-04 (bulk transform upload writes straight into this store)." This issue's write target is the P2-13 store specifically, not a new ad hoc structure — coordinate with whoever lands P2-13 on the exact API shape (columnar view, per design-expansion.md:45, "ENG2-P3-01 (RFC-0006) MUST require the store expose a columnar transform view usable by P2-13 with no copy").
  • Verified current per-instance cost this bulk path must avoid reintroducing: goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs:39-77 clones vertices and allocates a new buffer per model instance today (instantiate_model-style path) — the bulk transform op is about updating existing instances' transforms, not re-running instance creation, so it must not touch this clone path at all.
  • Per design-expansion.md:46: RFC-0007 (ENG2-P4-01) "MUST specify bulk transform-upload ops writing directly into the P2-13 SoA store, and a 0-alloc decode path" — this issue implements that specific requirement; check the RFC output before diverging on encoding format.

Verification

cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings
cargo test
./codegen.sh && git diff --exit-code
# perf issues: python3 scripts/bench-gate.py (per perf-dod.md), scene capture attached
# spec test: goud_engine/tests/spec/{eng2_p4_04_slug}.rs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions