From 4acfa0e8bca1ebb6005c8b4a13fc46839a51bfe4 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Fri, 3 Apr 2026 09:28:24 -0600 Subject: [PATCH 1/3] fix(renderer3d): populate Object3D.vertices for models and instances Models and model instances were created with empty `vertices` fields, causing the static batch rebuild to produce zero vertices while static objects were excluded from the dynamic render pass -- making them invisible when `SetModelStatic(true)` was called. Primitives already populated this field correctly, which is why `SetObjectStatic` on primitives worked. This fix applies the same pattern to models and model instances (non-skinned only, since skinned models use a 16-FPV layout incompatible with the 8-FPV static batch). Fixes #630 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../libs/graphics/renderer3d/core_model_instances.rs | 12 +++++++++--- .../src/libs/graphics/renderer3d/core_models/mod.rs | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs b/goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs index 2d3880393..6eae1b6e5 100644 --- a/goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs +++ b/goud_engine/src/libs/graphics/renderer3d/core_model_instances.rs @@ -29,9 +29,15 @@ impl Renderer3D { let mut instance_material_ids = Vec::with_capacity(source.mesh_material_ids.len()); for (i, &src_obj_id) in source.mesh_object_ids.iter().enumerate() { - let (src_buffer, vertex_count, texture_id, src_bounds) = + let (src_buffer, vertex_count, texture_id, src_bounds, src_vertices) = match self.objects.get(&src_obj_id) { - Some(o) => (o.buffer, o.vertex_count, o.texture_id, o.bounds), + Some(o) => ( + o.buffer, + o.vertex_count, + o.texture_id, + o.bounds, + o.vertices.clone(), + ), None => continue, }; @@ -68,7 +74,7 @@ impl Renderer3D { Object3D { buffer, vertex_count, - vertices: Vec::new(), + vertices: src_vertices, position: Vector3::new(0.0, 0.0, 0.0), rotation: Vector3::new(0.0, 0.0, 0.0), scale: Vector3::new(1.0, 1.0, 1.0), diff --git a/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs b/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs index 8da685da0..ac423e174 100644 --- a/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs +++ b/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs @@ -127,7 +127,11 @@ impl Renderer3D { Object3D { buffer, vertex_count: tri_vert_count as i32, - vertices: Vec::new(), + vertices: if is_skinned { + Vec::new() + } else { + verts.clone() + }, position: Vector3::new(0.0, 0.0, 0.0), rotation: Vector3::new(0.0, 0.0, 0.0), scale: Vector3::new(1.0, 1.0, 1.0), From 98a8b47d8db8ad3ca058725a07ba2a37aaaaae6d Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Fri, 3 Apr 2026 09:33:05 -0600 Subject: [PATCH 2/3] test(renderer3d): add regression test for static batch rendering (#630) Add test verifying that primitives marked static still render via the static batch path. Also add clarifying comment on why skinned models keep an empty vertices vec (16 FPV layout incompatible with 8 FPV static batch format). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../graphics/renderer3d/core_models/mod.rs | 2 ++ .../src/libs/graphics/renderer3d/tests.rs | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs b/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs index ac423e174..8cf8c9bc4 100644 --- a/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs +++ b/goud_engine/src/libs/graphics/renderer3d/core_models/mod.rs @@ -127,6 +127,8 @@ impl Renderer3D { Object3D { buffer, vertex_count: tri_vert_count as i32, + // CPU-side vertex copy for static batching (8 FPV layout). + // Skinned models use 16 FPV and are never static-batched. vertices: if is_skinned { Vec::new() } else { diff --git a/goud_engine/src/libs/graphics/renderer3d/tests.rs b/goud_engine/src/libs/graphics/renderer3d/tests.rs index 27e3d3e49..bdf99219b 100644 --- a/goud_engine/src/libs/graphics/renderer3d/tests.rs +++ b/goud_engine/src/libs/graphics/renderer3d/tests.rs @@ -379,3 +379,37 @@ fn test_scene_filtering_limits_rendered_objects() { renderer.render(None); assert_eq!(renderer.stats().draw_calls, 2); } + +/// Regression test for #630: primitives marked static must still render via +/// the static batch path instead of disappearing. +#[test] +fn test_static_primitive_renders_via_batch() { + let mut renderer = make_renderer(); + let cube = renderer.create_primitive(PrimitiveCreateInfo { + primitive_type: PrimitiveType::Cube, + width: 1.0, + height: 1.0, + depth: 1.0, + segments: 1, + texture_id: 0, + }); + assert_ne!(cube, 0); + + // Dynamic path: one draw call, one visible object. + renderer.render(None); + assert_eq!(renderer.stats().draw_calls, 1); + assert_eq!(renderer.stats().visible_objects, 1); + + // Mark static: should render via static batch, not dynamic pass. + assert!(renderer.set_object_static(cube, true)); + renderer.render(None); + let stats = renderer.stats(); + assert!( + stats.draw_calls >= 1, + "static object must produce at least one draw call" + ); + assert_eq!( + stats.visible_objects, 0, + "static object should not appear in dynamic pass" + ); +} From 5e573a5a354d29db5adb669ee3fe1dcabb39bf08 Mon Sep 17 00:00:00 2001 From: Aram Hammoudeh Date: Fri, 3 Apr 2026 10:29:24 -0600 Subject: [PATCH 3/3] fix(ci): increase Android build pipeline timeout to 25min Rust cross-compilation for Android takes ~12min without cache hits, leaving insufficient time for both Gradle builds within the 15min timeout. Matches the pattern from the iOS timeout fix (8a6ddd5f). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08e8904ce..00d5fcb2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1142,7 +1142,7 @@ jobs: name: Android Build Pipeline runs-on: ubuntu-latest needs: [preflight] - timeout-minutes: 15 + timeout-minutes: 25 steps: - uses: actions/checkout@v6