forked from 1b-agent/opencadstudio-upstream
perf: allocate a shadow map only for viewports that cast shadows
`gpu-live` made this the obvious next one. Once idle slots started giving their memory back, the shadow maps were the largest thing that never did: 128 MiB across eight slots, 37 % of the 341 MiB floor, and none of it sampled — `shadow_enabled` defaults to false and most visual styles never turn it on. Every slot allocated `SHADOW_MAP_SIZE` squared at `Depth32Float` in `Pipeline::new` and held it for the life of the slot, because the frame bind group needs *something* at its shadow binding and the field was not optional. It is now `Option<(Texture, TextureView)>` alongside a 1x1 placeholder that satisfies the binding while shadows are off — nothing samples it then, so one texel is the whole requirement. `sync_shadow_target` allocates on the transition into shadows and drops on the way out, and only on the transition, so a viewport that keeps its setting does not rebuild a bind group every frame. The frame bind group had two inline builders — one in `Pipeline::new`, one in `upload_background_images` — and this adds a third caller, so they are now one `rebuild_frame_bind_group`. A shadow transition cannot forget the background image the slot already had. The shadow pass now tests for the target rather than the flag: wanting shadows and having somewhere to put them are different questions, and without the second one a failed 16 MiB allocation would have rendered the pass into the 1x1 placeholder. Releasing a cold slot hands the target back too, and `gpu-live` reports what is actually held rather than a constant. Covered by a GPU test that toggles the setting off/on/off/on against a real adapter with a validation scope armed, then releases the slot — the transition is the risky path and the other GPU tests never take it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
deebc14012
commit
49318a1c55
3 files changed
+199
-67
No files matched your search
@@ -268,3 +268,54 @@ fn bounded_gpu_uploads_preserve_geometry_and_instancing() {
|
||||
"GPU validation failed"
|
||||
);
|
||||
}
|
||||
|
||||
/// The shadow map is 16 MiB and every slot used to hold one whether or not its
|
||||
/// visual style enabled shadows. Now it is allocated on the transition, which
|
||||
/// means the frame bind group is rebuilt while the device is watching — the one
|
||||
/// path the other GPU tests never take.
|
||||
#[test]
|
||||
#[ignore = "requires a GPU adapter"]
|
||||
fn toggling_shadows_reallocates_without_upsetting_the_device() {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
|
||||
let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default()))
|
||||
.expect("GPU adapter");
|
||||
let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
required_limits: adapter.limits(),
|
||||
..Default::default()
|
||||
}))
|
||||
.expect("GPU device");
|
||||
let validation = device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let mut pipeline = Pipeline::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb);
|
||||
|
||||
assert!(
|
||||
pipeline.shadow_full.is_none(),
|
||||
"a fresh slot must not hold a shadow map"
|
||||
);
|
||||
let camera = crate::scene::view::camera::Camera::default();
|
||||
let bounds = iced::Rectangle::new(iced::Point::ORIGIN, iced::Size::new(64.0, 64.0));
|
||||
let with_shadows = |on: bool| {
|
||||
let mut u = Uniforms::new(&camera, bounds, false);
|
||||
u.shadow_params[0] = if on { 1.0 } else { 0.0 };
|
||||
u
|
||||
};
|
||||
|
||||
// Off → on → off → on, so both transitions are exercised twice and the
|
||||
// second `on` proves the release did not leave the bind group stale.
|
||||
for expected in [true, false, true] {
|
||||
pipeline.upload_uniforms(&device, &queue, &with_shadows(expected));
|
||||
assert_eq!(
|
||||
pipeline.shadow_full.is_some(),
|
||||
expected,
|
||||
"shadow target should follow shadow_params[0]"
|
||||
);
|
||||
}
|
||||
// Releasing a cold slot gives it back and must rebuild the bind group too.
|
||||
pipeline.release_heavy_resources(&device);
|
||||
assert!(pipeline.shadow_full.is_none(), "a released slot holds none");
|
||||
|
||||
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
|
||||
assert!(
|
||||
block_on(validation.pop()).is_none(),
|
||||
"GPU validation failed"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user