Skip to content

Commit 576c02f

Browse files
committed
feat(renderer): raise light caps 4+16 → 8+256 (the audit's top graphics blocker)
Every layer that mirrored the fixed arrays moves in lockstep: LightingUniforms (Rust consts, now the single source), both legacy 3D WGSL shaders, the material ABI's PerView block (ABI-VERSION 2 → 3 per the documented protocol, EXPECTED_ABI_VERSION bumped), and the material-system Rust mirror (population uses array::from_fn, so the new capacity flows through without copy-length edits). Deliberately a uniform buffer, not storage: 256×32B + 8×32B < 9KB fits WebGL2's 16KB minimum UBO, so the cap raise lands on every backend with zero shader permutations. Shaders loop only over the live count — small scenes pay nothing. Froxel clustering (per-pixel cost for genuinely large counts) is the follow-up optimization, now safe to build against the golden harness. New golden: 40 point lights in a hue ring over a floor — under the old cap 60% of the ring goes dark, far past tolerance. Existing goldens pass pixel-identical (capacity-only change).
1 parent 8c35cf9 commit 576c02f

7 files changed

Lines changed: 53 additions & 12 deletions

File tree

native/shared/shaders/material_abi.wgsl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Bloom shader ABI header — see docs/rfc/0001-material-render-graph.md.
22
//
3-
// ABI-VERSION: 2
3+
// ABI-VERSION: 3
44
//
55
// Included by every 3D shader (built-in PBR, sky, custom user materials).
66
// Defines the five bind groups and the vertex attribute layout that any
@@ -76,9 +76,9 @@ struct PerView {
7676

7777
// Arrayed lights
7878
dir_light_count: vec4<f32>, // x = count, yzw = 0
79-
dir_lights: array<DirLight, 4>,
79+
dir_lights: array<DirLight, 8>,
8080
point_light_count: vec4<f32>,
81-
point_lights: array<PointLight, 16>,
81+
point_lights: array<PointLight, 256>,
8282

8383
// Shadow data — three cascades, split by shadow_splits.xyz
8484
shadow_splits: vec4<f32>,

native/shared/src/renderer/material_system.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ pub struct PerViewUniforms {
6666
pub sun_dir: [f32; 4],
6767
pub sun_color: [f32; 4],
6868
pub dir_light_count: [f32; 4],
69-
pub dir_lights: [PerViewDirLight; 4],
69+
pub dir_lights: [PerViewDirLight; 8],
7070
pub point_light_count: [f32; 4],
71-
pub point_lights: [PerViewPointLight; 16],
71+
pub point_lights: [PerViewPointLight; 256],
7272
pub shadow_splits: [f32; 4],
7373
pub shadow_view: [[f32; 4]; 4],
7474
pub shadow_cascades: [[[f32; 4]; 4]; 3],

native/shared/src/renderer/shader_library.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn verify_abi_version(expected: u32) -> Result<(), String> {
5151
/// when MaterialFactors gained `shading_model` + `foliage_params`
5252
/// (foliage shading model). Bump together with the
5353
/// `ABI-VERSION:` comment in `shaders/material_abi.wgsl`.
54-
pub const EXPECTED_ABI_VERSION: u32 = 2;
54+
pub const EXPECTED_ABI_VERSION: u32 = 3;
5555

5656
#[cfg(test)]
5757
mod tests {

native/shared/src/renderer/shaders.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ struct Lighting {
7171
light_dir: vec4<f32>,
7272
light_color: vec4<f32>,
7373
dir_light_count: vec4<f32>,
74-
dir_lights: array<DirLight, 4>,
74+
dir_lights: array<DirLight, 8>,
7575
point_light_count: vec4<f32>,
76-
point_lights: array<PointLight, 16>,
76+
point_lights: array<PointLight, 256>,
7777
};
7878
7979
struct JointMatrices {
@@ -219,9 +219,9 @@ struct Lighting {
219219
light_dir: vec4<f32>,
220220
light_color: vec4<f32>,
221221
dir_light_count: vec4<f32>,
222-
dir_lights: array<DirLight, 4>,
222+
dir_lights: array<DirLight, 8>,
223223
point_light_count: vec4<f32>,
224-
point_lights: array<PointLight, 16>,
224+
point_lights: array<PointLight, 256>,
225225
camera_pos: vec4<f32>,
226226
shadow_cascade_vps: array<mat4x4<f32>, 3>,
227227
shadow_cascade_splits: vec4<f32>,

native/shared/src/renderer/types.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,15 @@ impl SceneMaterialUniforms {
7575
}
7676
}
7777

78-
pub(super) const MAX_DIR_LIGHTS: usize = 4;
79-
pub(super) const MAX_POINT_LIGHTS: usize = 16;
78+
// Raised from 4/16: scenes were hard-capped at 16 point lights, the
79+
// audit's top graphics blocker. Arrays stay in a uniform buffer so the
80+
// cap raise works on every backend including WebGL2 (whose 16KB minimum
81+
// UBO size this still fits: 256*32B + 8*32B + header < 9KB). Shaders
82+
// loop only over the live count, so small scenes pay nothing. Per-pixel
83+
// cost for genuinely large light counts is the follow-up (froxel
84+
// clustering); this change removes the capability ceiling.
85+
pub(crate) const MAX_DIR_LIGHTS: usize = 8;
86+
pub(crate) const MAX_POINT_LIGHTS: usize = 256;
8087

8188
#[repr(C)]
8289
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
56.9 KB
Loading

native/shared/tests/golden_render.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,37 @@ fn golden_lit_primitives_3d() {
176176
});
177177
compare_or_update("lit_primitives_3d", w, h, &rgba);
178178
}
179+
180+
#[test]
181+
fn golden_many_point_lights() {
182+
let Some(mut eng) = try_engine() else {
183+
eprintln!("skip: no GPU adapter");
184+
return;
185+
};
186+
// 40 colored point lights in a ring over a dark floor — far past the
187+
// old 16-light cap. If the cap regressed, lights 17..40 vanish and
188+
// the right side of the ring goes dark (well past tolerance).
189+
let (w, h, rgba) = render(&mut eng, 6, |eng| {
190+
let r = &mut eng.renderer;
191+
r.set_clear_color(2.0, 2.0, 4.0, 255.0);
192+
r.begin_mode_3d(
193+
0.0, 9.0, 0.01, // eye: straight above
194+
0.0, 0.0, 0.0,
195+
0.0, 1.0, 0.0,
196+
60.0, 0.0,
197+
);
198+
r.draw_plane(0.0, 0.0, 0.0, 14.0, 14.0, 110.0, 110.0, 110.0, 255.0);
199+
for i in 0..40u32 {
200+
let t = i as f32 / 40.0 * std::f32::consts::TAU;
201+
let (sx, sz) = (t.cos() * 4.0, t.sin() * 4.0);
202+
// hue cycles so neighboring lights are distinguishable
203+
let (lr, lg, lb) = (
204+
0.5 + 0.5 * (t).cos(),
205+
0.5 + 0.5 * (t + 2.094).cos(),
206+
0.5 + 0.5 * (t + 4.189).cos(),
207+
);
208+
r.add_point_light(sx, 1.2, sz, 3.5, lr, lg, lb, 1.6);
209+
}
210+
});
211+
compare_or_update("many_point_lights", w, h, &rgba);
212+
}

0 commit comments

Comments
 (0)