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 <noreply@anthropic.com>
This commit is contained in:
Matthew James Sivak
2026-10-11 17:32:53 +02:00
co-authored by Claude Opus 5.5
parent c0a1a6443a
commit 20822fa513
2 changed files with 28 additions and 11 deletions
+25 -7
View File
@@ -437,6 +437,14 @@ 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 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));
}
}
+3 -4
View File
@@ -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));