Merge remote-tracking branch 'origin/main' into HEAD

This commit is contained in:
bflatastic
2026-10-30 00:27:26 +07:00
350 changed files with 15530 additions and 6022 deletions
+12 -8
View File
@@ -2,7 +2,6 @@
//! Set EFFECTCRAFT_REQUIRE_GPU=1 for acceptance: unavailable adapters/devices then fail
//! instead of taking the explicitly logged headless-CI skip path.
#![cfg(not(target_arch = "wasm32"))]
#![allow(clippy::expect_used)] // Test helpers require successful setup to exercise rejection.
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -149,23 +148,28 @@ fn opengl_adapters_get_cpu_compositing_at_once() {
assert!(t.elapsed() < std::time::Duration::from_secs(1), "{:?}", t.elapsed());
}
/// #613: FXC (DirectX 12's default shader compiler) builds Classic 3D, so the compositor starts
/// on DirectX 12 with its 3D kernel, and initialization leaves the caller's error scope and the
/// host's error handler untouched. Ignored by default: FXC compiles every kernel, which takes
/// minutes. Skips where there is no DirectX 12 adapter.
#[test]
#[cfg(target_os = "windows")]
#[ignore = "requires the reproduced Windows DX12/FXC pointwise compiler failure; no adapter skips"]
fn reproduced_fxc_pointwise_failure_returns_error_without_escaping_or_notifying_host() {
#[ignore = "slow: FXC compiles every kernel"]
fn fxc_builds_classic3d_and_initialization_errors_stay_scoped() {
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
desc.backends = wgpu::Backends::DX12;
desc.backend_options.dx12.shader_compiler = wgpu::Dx12Compiler::Fxc;
let (adapter, device, queue) = native_device(desc, true).expect("required DX12 device");
let Some((adapter, device, queue)) = native_device(desc, false) else { return };
assert_eq!(adapter.get_info().backend, wgpu::Backend::Dx12);
let host = count_host_errors(&device);
let outer = device.push_error_scope(wgpu::ErrorFilter::Validation);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| GpuContext::new(&adapter, device.clone(), queue)));
let error = match result.expect("FXC compiler failure must not escape the constructor") {
Err(error) => error,
Ok(_) => panic!("the platform-specific FXC failure no longer reproduces; reassess this diagnostic"),
let ctx = match result.expect("an FXC compiler failure must not escape the constructor") {
Ok(ctx) => ctx,
Err(error) => panic!("the compositor starts on DirectX 12 with FXC: {}", brief(error)),
};
assert!(error.contains("GPU initialization (pointwise)") && error.contains("FXC D3DCompile"), "{}", brief(error));
let missing: Vec<&str> = ctx.unavailable_kernels().iter().map(|(e, _)| *e).collect();
assert!(ctx.has_kernel("classic3d"), "FXC builds classic3d (unavailable: {missing:?})");
assert!(pollster::block_on(outer.pop()).is_none(), "constructor must drain its own scopes and leave the caller's scope clean");
assert_eq!(host.load(Ordering::SeqCst), 0);
valid_pipeline(&device);
+48 -2
View File
@@ -11,7 +11,7 @@ use effectcraft_project::{BitDepth, Comp, ItemKind, Layer, LayerSource, LightKin
use effectcraft_render::{Backend, RenderOpts, Renderer};
use effectcraft_time::{FrameRate, Tick};
use crate::tests::{Pattern, Scene, check, compare_at, gpu, n, opts, set};
use crate::tests::{Pattern, Scene, check, compare_at, diff, gpu, n, opts, set, tolerance};
const W: u32 = 160;
const H: u32 = 100;
@@ -133,6 +133,22 @@ fn lights_specular_transmission_and_shadows() {
}
}
#[test]
fn wide_shadow_diffusion() {
// #2444: a wide penumbra takes more than the 3 × 3 kernel's samples; both sides pick the
// same count.
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
let mut s = crossing(depth, BlendMode::Normal);
for l in &mut s.p.comp_mut(s.cid).unwrap().layers {
if l.switches.three_d {
set(l, "materialOptions/castsShadows", Value::Enum(1));
}
}
light(&mut s, LightKind::Point, [150.0, 20.0, -80.0], &[("castsShadows", Value::Bool(true)), ("shadowDiffusion", n(60.0))]);
check(&format!("wide shadow diffusion {depth:?}"), compare_at(&s, opts(), Tick::ZERO), 0.005);
}
}
#[test]
fn depth_of_field_and_motion_blur() {
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
@@ -209,6 +225,10 @@ fn the_gpu_draws_classic_3d_runs_itself() {
assert_eq!(prep.planes.iter().filter(|p| p.draw).count(), 3);
let mut e = crate::context::Enc::new(g.context());
let canvas = e.image(W, H);
if !g.context().has_kernel("classic3d") {
// FXC (DirectX 12) can't build the kernel (#613); the CPU fallback is tested below.
return;
}
let out = crate::classic3d::draw_run(&mut e, &prep, &canvas).expect("the kernel draws the run");
let img = e.download(&out).unwrap();
let mut cpu = effectcraft_raster::Image::new(W, H);
@@ -253,7 +273,9 @@ fn bokeh_depth_of_field_runs_on_the_gpu() {
assert!(prep.planes.iter().filter(|p| p.draw).all(|p| p.dof.is_some()), "{label}: depth of field deferred to the GPU");
let mut e = crate::context::Enc::new(g.context());
let canvas = e.image(W, H);
assert!(crate::classic3d::draw_run(&mut e, &prep, &canvas).is_some(), "{label}: drawn on the GPU");
if g.context().has_kernel("classic3d") {
assert!(crate::classic3d::draw_run(&mut e, &prep, &canvas).is_some(), "{label}: drawn on the GPU");
}
}
}
}
@@ -288,3 +310,27 @@ fn two_d_layer_with_a_three_d_track_matte() {
}
}
}
/// #613: FXC on DirectX 12 fails to build `classic3d` (it can't unroll its loops). The
/// compositor still starts without it, lists it, and 3D runs render on the CPU, matching the
/// CPU render.
#[test]
fn classic_3d_runs_render_on_the_cpu_without_the_kernel() {
let Some(base) = gpu() else {
return;
};
let ctx = base.context().without_kernel("classic3d");
assert!(!ctx.has_kernel("classic3d"));
let g = crate::Gpu::from_context(ctx);
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
let mut s = crossing(depth, BlendMode::Normal);
camera(&mut s, [30.0, 20.0, -260.0], None);
let cpu = Renderer::new(&s.p, &Pattern, RenderOpts { backend: Backend::Cpu, ..opts() }).comp_frame_cpu(s.cid, Tick::ZERO);
let mut r = Renderer::new(&s.p, &Pattern, RenderOpts { backend: Backend::Gpu, ..opts() });
r.accel = Some(&g);
let img = g.render(&r, s.cid, Tick::ZERO).expect("the GPU renders the frame");
let mut d = diff(&cpu, &img, tolerance(depth));
d.quantized = depth != BitDepth::Bpc32;
check(&format!("classic 3D without its kernel {depth:?}"), Some(d), 0.0);
}
}