mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 21:48:20 -06:00
Merge remote-tracking branch 'origin/main' into HEAD
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user