From 20822fa513d08fda989ec808301510a086b3cb18 Mon Sep 17 00:00:00 2001 From: Matthew James Sivak Date: Sun, 11 Oct 2026 17:32:53 +0200 Subject: [PATCH] LR-VIEW-ZOOM: one rule for which renders the GPU draws (#330) The loupe sized its slider-drag drafts by its own copy of try_render's "CPU only" list (soft proof, HDR range). A new CPU-only overlay added to try_render (e.g. #995's sharpening mask) would have left the loupe drafting full size on the CPU for its first drag. lightcraft_gpu::handles is now the single rule: try_render returns NotTried by it and the loupe asks it, so the two can't drift apart. Co-Authored-By: Claude Opus 5.5 --- crates/gpu/src/lib.rs | 32 ++++++++++++++++++++++------- crates/ui-egui/src/panels/detail.rs | 7 +++---- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/crates/gpu/src/lib.rs b/crates/gpu/src/lib.rs index fb9785ba6..76fb0f2c1 100644 --- a/crates/gpu/src/lib.rs +++ b/crates/gpu/src/lib.rs @@ -437,6 +437,14 @@ pub fn render(src: &Arc, 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 and soft proofs render on +/// the CPU, and so does 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, proofed: bool, overlay: lightcraft_pipeline::Overlay) -> bool { + depth == lightcraft_pipeline::OutputDepth::U8 && !proofed && 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 +461,7 @@ 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 (and soft proofs) render on the CPU - // Visualize HDR needs the float HDR render: CPU too - if !enabled() - || req.depth != lightcraft_pipeline::OutputDepth::U8 - || req.proof.is_some() - || req.overlay == lightcraft_pipeline::Overlay::HdrRange - { + if !enabled() || !handles(req.depth, req.proof.is_some(), req.overlay) { return Err(Fallback::NotTried); } let s = &*lightcraft_pipeline::settings_for(s, req); @@ -533,3 +535,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)); + } +} diff --git a/crates/ui-egui/src/panels/detail.rs b/crates/ui-egui/src/panels/detail.rs index 22173a61d..0b17684d3 100644 --- a/crates/ui-egui/src/panels/detail.rs +++ b/crates/ui-egui/src/panels/detail.rs @@ -437,11 +437,10 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { // request renders: the loupe at display resolution (drafts during drags) let interacting = app.session.interaction.is_some(); let overlay = view_overlay(app, &d); - // a drag drafts at full size where the GPU draws the view (soft proofs and the HDR range - // render on the CPU; `ready` first: `available` would wait for a device still being created) + // a drag drafts at full size where the GPU draws the view (by the GPU's own rule, `handles`; + // `ready` first: `available` would wait for a device still being created) let gpu = interacting - && !app.ui.soft_proof - && overlay != lightcraft_pipeline::Overlay::HdrRange + && lightcraft_engine::gpu::handles(lightcraft_pipeline::OutputDepth::U8, app.ui.soft_proof, overlay) && lightcraft_engine::gpu::ready() && lightcraft_engine::gpu::available(); let shown = app.renderer.textures.get(&Slot::Main).filter(|t| t.photo == id && t.quick.is_none()).map(|t| (t.key, t.ms));