Merge upstream/main into pr/overlay-windows (#1027 landed)

GPU handles(): only output-device proofs stay on the CPU; Show All Masks now renders zoom windows (upstream); proof-boundary tolerance in the GPU equivalence test; device-profile draft test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrew Frahn
2026-10-20 10:57:07 +11:00
co-authored by Claude Sonnet 5.5
521 changed files with 85675 additions and 4257 deletions
+31 -9
View File
@@ -35,6 +35,8 @@ pub mod backend;
mod ctx;
#[cfg(not(target_arch = "wasm32"))]
mod exit;
#[cfg(not(target_arch = "wasm32"))]
pub mod faces;
#[cfg(all(feature = "denoise", not(target_arch = "wasm32")))]
pub mod nn;
#[cfg(not(target_arch = "wasm32"))]
@@ -239,9 +241,9 @@ pub fn wait_idle(timeout: std::time::Duration) -> bool {
}
}
/// Why GPU compute that has a device of its own (denoise) is switched off by the user's settings, or `None`: the
/// Why GPU compute that has a device of its own (denoise, faces) is switched off by the user's settings, or `None`: the
/// environment switches and the rendering preference, but not a render-device failure (the devices are separate).
#[cfg(all(feature = "denoise", not(target_arch = "wasm32")))]
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn switched_off() -> Option<String> {
if env_disabled() {
return Some(if backend::env_off() { "disabled by LIGHTCRAFT_GPU_BACKEND=off" } else { "disabled by LIGHTCRAFT_GPU=0" }.into());
@@ -437,6 +439,15 @@ pub fn render(src: &Arc<Rgb32f>, info: &SourceInfo, s: &DevelopSettings, req: &R
try_render(src, info, s, req, stages).ok()
}
/// Whether the GPU renders requests of this kind at all (a given render can still fall back to
/// the CPU): the kernel writes 8-bit output, so high-bit-depth exports render on the CPU, and so do
/// soft proofs against an output-device profile (`device_proof`: its 3D table is CPU-side) and
/// Visualize HDR, which needs the float HDR render. [`try_render`] follows it, and the loupe sizes
/// its slider-drag drafts by it (issue #330), so the two can't drift apart.
pub fn handles(depth: lightcraft_pipeline::OutputDepth, device_proof: bool, overlay: lightcraft_pipeline::Overlay) -> bool {
depth == lightcraft_pipeline::OutputDepth::U8 && !device_proof && overlay != lightcraft_pipeline::Overlay::HdrRange
}
/// [`render`], reporting why this render made no GPU image: [`Fallback::NotTried`] when the GPU
/// was not used (callers can ask [`unavailable_reason`]), [`Fallback::Failed`] with the reason
/// when the GPU render failed and the CPU must render it. The reason belongs to this call, unlike
@@ -453,13 +464,8 @@ pub fn try_render(
// sections switched off with their eye render as if at their defaults (issue #316)
let effective = s.effective();
let s: &DevelopSettings = &effective;
// the kernel writes 8-bit output: high-bit-depth exports render on the CPU, and so do soft
// proofs against an output-device profile (its 3D table); Visualize HDR needs the float HDR render: CPU too
if !enabled()
|| req.depth != lightcraft_pipeline::OutputDepth::U8
|| req.proof.is_some_and(|p| lightcraft_pipeline::proof_profile(p.profile).is_some())
|| req.overlay == lightcraft_pipeline::Overlay::HdrRange
{
let device_proof = req.proof.is_some_and(|p| lightcraft_pipeline::proof_profile(p.profile).is_some());
if !enabled() || !handles(req.depth, device_proof, req.overlay) {
return Err(Fallback::NotTried);
}
let s = &*lightcraft_pipeline::settings_for(s, req);
@@ -533,3 +539,19 @@ pub fn try_render(
Err(Fallback::NotTried)
}
}
#[cfg(test)]
mod tests {
use lightcraft_pipeline::{OutputDepth, Overlay};
// Given a request the 8-bit kernel can't draw (high bit depth, a soft proof, Visualize HDR),
// the GPU doesn't take it; the loupe and diagnostic overlays it can draw stay on the GPU
#[test]
fn the_gpu_handles_8_bit_unproofed_renders_without_the_hdr_range() {
assert!(super::handles(OutputDepth::U8, false, Overlay::None));
assert!(super::handles(OutputDepth::U8, false, Overlay::Spots(50)));
assert!(!super::handles(OutputDepth::U16, false, Overlay::None));
assert!(!super::handles(OutputDepth::U8, true, Overlay::None));
assert!(!super::handles(OutputDepth::U8, false, Overlay::HdrRange));
}
}