From 95e6ca9a2d456b88e8db97ac79d48c4200f99379 Mon Sep 17 00:00:00 2001 From: Minher0 Date: Mon, 26 Oct 2026 19:15:29 +0000 Subject: [PATCH] Test that an Environment light visibly lights the scene (#2436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end regression tests for the issue's report, through the real command path (what the Light Settings dialog runs) and the real viewer: adding an Environment light to a Classic 3D comp switches it to Advanced 3D and changes the viewer's pixels — a red light at full intensity, the default white light, and the same on the GPU compositor (skips without an adapter, like ui_gpu). --- crates/ui-egui/tests/it/main.rs | 2 + crates/ui-egui/tests/it/ui_env_light_gpu.rs | 79 +++++++++++++++++++ crates/ui-egui/tests/it/ui_env_light_repro.rs | 75 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 crates/ui-egui/tests/it/ui_env_light_gpu.rs create mode 100644 crates/ui-egui/tests/it/ui_env_light_repro.rs diff --git a/crates/ui-egui/tests/it/main.rs b/crates/ui-egui/tests/it/main.rs index 6b4c08912..724b64b08 100644 --- a/crates/ui-egui/tests/it/main.rs +++ b/crates/ui-egui/tests/it/main.rs @@ -20,6 +20,8 @@ mod ui_drag_drop; mod ui_effect_controls; mod ui_effect_drop_highlight; mod ui_effect_names; +mod ui_env_light_gpu; +mod ui_env_light_repro; mod ui_font_search; mod ui_form_fit; mod ui_frame_step; diff --git a/crates/ui-egui/tests/it/ui_env_light_gpu.rs b/crates/ui-egui/tests/it/ui_env_light_gpu.rs new file mode 100644 index 000000000..171cc5728 --- /dev/null +++ b/crates/ui-egui/tests/it/ui_env_light_gpu.rs @@ -0,0 +1,79 @@ +//! The Environment light through the app's GPU compositor (egui_kittest with wgpu): adding one +//! visibly changes the viewer's GPU frames (#2436). Skips without a GPU adapter (CI). + +use effectcraft_engine::Session; +use effectcraft_engine::project::Renderer; +use effectcraft_ui_egui::EffectcraftApp; +use egui_kittest::Harness; +use serde_json::json; + +fn settle(h: &mut Harness<'_, EffectcraftApp>) { + for _ in 0..600 { + h.step(); + if h.state().frames.inflight() == 0 && h.state().frames.last_ms.lock().map(|v| *v > 0.0).unwrap_or(false) { + break; + } + std::thread::sleep(std::time::Duration::from_millis(20)); + } + for _ in 0..4 { + h.step(); + } +} + +/// egui_kittest's wgpu setup on the adapter the headless compositor picked, so the viewer's GPU +/// compositor attaches (see ui_gpu.rs). +fn same_adapter(name: String) -> eframe::egui_wgpu::WgpuSetup { + let mut setup = egui_kittest::wgpu::default_wgpu_setup(); + if let eframe::egui_wgpu::WgpuSetup::CreateNew(c) = &mut setup { + let fallback = c.native_adapter_selector.take(); + c.native_adapter_selector = Some(std::sync::Arc::new(move |adapters, surface| { + let named = |a: &&eframe::wgpu::Adapter| format!("{} ({:?})", a.get_info().name, a.get_info().backend) == name; + match (adapters.iter().find(named), &fallback) { + (Some(a), _) => Ok(a.clone()), + (None, Some(f)) => f(adapters, surface), + (None, None) => adapters.first().cloned().ok_or_else(|| "No adapter found".to_owned()), + } + })); + } + setup +} + +/// A red Environment light at full intensity lights the scene on the GPU compositor too: the +/// viewer's GPU frames change when the light is added. +#[test] +fn environment_light_lights_the_scene_on_the_gpu() { + let Some(probe) = effectcraft_gpu::Gpu::headless() else { + eprintln!("no GPU adapter: skipping"); + return; + }; + let adapter = effectcraft_engine::render::Accelerator::name(&probe); + eprintln!("headless compositor adapter: {adapter}"); + let gl = adapter.ends_with("(Gl)"); + drop(probe); + let mut s = Session::default(); + s.execute("file.openDemoProject", json!({})).unwrap(); + s.execute("comp.open", json!({"comp": "3D Showcase"})).unwrap(); + s.execute("time.set", json!({"time": 5.0})).unwrap(); + s.execute("comp.settings", json!({"renderer": "classic3D"})).unwrap(); + let mut h = Harness::builder() + .with_size(egui::vec2(1400.0, 900.0)) + .with_pixels_per_point(1.0) + .wgpu_setup(same_adapter(adapter)) + .build_eframe(|_| EffectcraftApp::new(s)); + settle(&mut h); + if gl && h.state().gpu_adapter().is_none() { + eprintln!("egui-wgpu's GL device has WebGL2 limits: the viewer stays on the CPU, skipping"); + return; + } + assert!(h.state().gpu_adapter().is_some(), "GPU compositor on egui-wgpu's device"); + let before = h.state_mut().viewer_pixels().expect("GPU frame before adding the light").pixels.clone(); + + h.state_mut().session.execute("layer.newLight", json!({"kind": "Environment", "color": [1.0, 0.0, 0.0], "intensity": 100.0})).unwrap(); + assert_eq!(h.state().session.active_comp().unwrap().renderer, Renderer::Advanced3D); + settle(&mut h); + let after = h.state_mut().viewer_pixels().expect("GPU frame after adding the light").pixels.clone(); + + assert_eq!(before.len(), after.len()); + let diff = before.iter().zip(&after).filter(|(a, b)| a != b).count(); + assert!(diff > 500, "a red Environment light at 100 % changed only {diff} GPU pixels"); +} diff --git a/crates/ui-egui/tests/it/ui_env_light_repro.rs b/crates/ui-egui/tests/it/ui_env_light_repro.rs new file mode 100644 index 000000000..78a26160f --- /dev/null +++ b/crates/ui-egui/tests/it/ui_env_light_repro.rs @@ -0,0 +1,75 @@ +//! Reproduction of issue #2436's report: an Environment light added through the real command +//! path (what the Light Settings dialog runs) must visibly light the scene in the viewer. + +use effectcraft_engine::Session; +use effectcraft_engine::project::Renderer; +use effectcraft_ui_egui::EffectcraftApp; +use egui_kittest::Harness; +use serde_json::json; + +fn app() -> EffectcraftApp { + let mut s = Session::default(); + s.execute("file.openDemoProject", json!({})).unwrap(); + s.execute("comp.open", json!({"comp": "3D Showcase"})).unwrap(); + s.execute("time.set", json!({"time": 5.0})).unwrap(); + EffectcraftApp::new(s) +} + +/// Step the UI until background frame renders have landed. +fn settle(h: &mut Harness<'_, EffectcraftApp>) { + for _ in 0..600 { + h.step(); + if h.state().frames.inflight() == 0 && h.state().frames.last_ms.lock().map(|v| *v > 0.0).unwrap_or(false) { + break; + } + std::thread::sleep(std::time::Duration::from_millis(20)); + } + for _ in 0..4 { + h.step(); + } +} + +fn changed_pixels(before: &[egui::Color32], after: &[egui::Color32]) -> usize { + before.iter().zip(after).filter(|(a, b)| a != b).count() +} + +/// An Environment light (red, full intensity, no source: the flat default environment) added to +/// a Classic 3D comp switches it to Advanced 3D and visibly changes the viewer's pixels. +#[test] +fn environment_light_lights_the_scene() { + let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| app()); + h.state_mut().session.execute("comp.settings", json!({"renderer": "classic3D"})).unwrap(); + h.run_steps(3); + settle(&mut h); + let before = h.state_mut().viewer_pixels().expect("frame before adding the light").pixels.clone(); + + h.state_mut().session.execute("layer.newLight", json!({"kind": "Environment", "color": [1.0, 0.0, 0.0], "intensity": 100.0})).unwrap(); + + assert_eq!(h.state().session.active_comp().unwrap().renderer, Renderer::Advanced3D, "the comp switched to Advanced 3D"); + settle(&mut h); + let after = h.state_mut().viewer_pixels().expect("frame after adding the light").pixels.clone(); + assert_eq!(before.len(), after.len()); + let diff = changed_pixels(&before, &after); + assert!(diff > 500, "a red Environment light at 100 % changed only {diff} pixels"); +} + +/// The literal action from the issue: default white Environment light, defaults everywhere — +/// even then the lighting model changes (Advanced 3D IBL instead of unlit/Classic), so pixels +/// must change somewhere in the comp frame. +#[test] +fn default_environment_light_changes_the_scene() { + let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| app()); + h.state_mut().session.execute("comp.settings", json!({"renderer": "classic3D"})).unwrap(); + h.run_steps(3); + settle(&mut h); + let before = h.state_mut().viewer_pixels().expect("frame before adding the light").pixels.clone(); + + h.state_mut().session.execute("layer.newLight", json!({"kind": "Environment"})).unwrap(); + + assert_eq!(h.state().session.active_comp().unwrap().renderer, Renderer::Advanced3D); + settle(&mut h); + let after = h.state_mut().viewer_pixels().expect("frame after adding the light").pixels.clone(); + assert_eq!(before.len(), after.len()); + let diff = changed_pixels(&before, &after); + assert!(diff > 500, "a default white Environment light changed only {diff} pixels"); +}