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
Acceptance Criteria
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
Parent
eng2-p4-ffi-v2)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 andlibs/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.Boundary constraints (only those that apply):
libs/graphics/backend/— nowgpu::outside it.#[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
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..agents/rules/ffi-patterns.md's function inventory, if that file tracks one.Acceptance Criteria
cargo check && cargo fmt --all -- --check && cargo clippy -- -D warningsclean;cargo testgreen;./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/SetModelRotationloops).Blocked By
ENG2-P4-02 (command-buffer core must exist before this rides on it as an opcode).
Files Likely Touched
goud_engine/src/ffi/(bulk transform opcode),goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs,goud_engine/tests/spec/Agent Notes
SetModelPosition/SetModelRotationper entity per frame (throne_gerepo,Entities.cs:56,287per the prior cross-repo audit cited indesign-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.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, perdesign-expansion.md:45, "ENG2-P3-01 (RFC-0006) MUST require the store expose a columnar transform view usable by P2-13 with no copy").goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs:39-77clones 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.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