#323: a drag keeps the original its superseded drafts decoded; textures convert faster
A drag supersedes every request before it finishes, and the source a superseded draft had decoded was thrown away with it, so a window cut from a full-size original decoded it again for every tick. A view slot's superseded draft that is newer than what the view shows now also hands its source to the session (late results of other slots stay ignored, as before). Render results become textures with a plain copy for the opaque case instead of egui's per-pixel premultiplying conversion, on the UI thread. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f7d248d61
commit
ea33301e6e
|
|
@ -84,6 +84,18 @@ struct Queued {
|
|||
job: RenderJob,
|
||||
}
|
||||
|
||||
/// The texture of a render: its pixels as egui wants them. Renders are opaque, so the common case
|
||||
/// is a plain copy; only a pixel with alpha pays for premultiplying. (The generic conversion, on
|
||||
/// the UI thread, was most of the time a 5 MP zoom window took to show.)
|
||||
pub(crate) fn color_image(img: &lightcraft_raster::Rgba8) -> egui::ColorImage {
|
||||
let pixels = img
|
||||
.data
|
||||
.iter()
|
||||
.map(|p| if p[3] == 255 { egui::Color32::from_rgb(p[0], p[1], p[2]) } else { egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]) })
|
||||
.collect();
|
||||
egui::ColorImage { size: [img.width, img.height], source_size: egui::vec2(img.width as f32, img.height as f32), pixels }
|
||||
}
|
||||
|
||||
/// Which stage caches to clear, in order, so that the rest fit `budget` bytes: the cheapest to
|
||||
/// rebuild and least reused first (hover, before, compare, second window, the zoom window), the
|
||||
/// open photo's own view last. Empty caches are never named. The budget is hard.
|
||||
|
|
@ -509,7 +521,9 @@ impl Renderer {
|
|||
for (mut slot, r, ms) in finished {
|
||||
self.completed += 1;
|
||||
// Ignore obsolete pixels, failures and decoded sources, even for the same render key;
|
||||
// an interactive view still takes a superseded draft that is newer than what it shows.
|
||||
// an interactive view still takes a superseded draft that is newer than what it
|
||||
// shows, and the source it decoded: a drag supersedes every request before it
|
||||
// finishes, and a source thrown away with them is decoded again for every tick.
|
||||
if self.request_ids.get(&slot) != Some(&r.request_id) {
|
||||
let newer_draft = slot.is_view()
|
||||
&& self.request_ids.contains_key(&slot)
|
||||
|
|
@ -519,6 +533,7 @@ impl Renderer {
|
|||
if !newer_draft {
|
||||
continue;
|
||||
}
|
||||
session.accept(&r);
|
||||
} else {
|
||||
self.request_ids.remove(&slot);
|
||||
session.accept(&r);
|
||||
|
|
@ -558,7 +573,7 @@ impl Renderer {
|
|||
self.textures.remove(&Slot::ThumbQuick(id));
|
||||
}
|
||||
let img = &rendered.image;
|
||||
let color = std::sync::Arc::new(egui::ColorImage::from_rgba_unmultiplied([img.width, img.height], &img.as_bytes()));
|
||||
let color = std::sync::Arc::new(color_image(img));
|
||||
let pixels = self.keep_pixels.then(|| color.clone());
|
||||
let name = format!("{slot:?}");
|
||||
match self.textures.get_mut(&slot) {
|
||||
|
|
@ -1179,3 +1194,32 @@ mod stage_budget_tests {
|
|||
assert_eq!(m["stageCaches"]["trimmed"], 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod color_image_tests {
|
||||
use super::*;
|
||||
|
||||
// The texture a render becomes is what egui would make of its bytes, whatever the alpha
|
||||
#[test]
|
||||
fn the_fast_conversion_equals_eguis() {
|
||||
let img = lightcraft_raster::Rgba8::from_fn(37, 29, |x, y| {
|
||||
let a = match (x + y) % 5 {
|
||||
0 => 0,
|
||||
1 => 17,
|
||||
2 => 128,
|
||||
_ => 255,
|
||||
};
|
||||
[(x * 7) as u8, (y * 9) as u8, (x * y) as u8, a]
|
||||
});
|
||||
let want = egui::ColorImage::from_rgba_unmultiplied([img.width, img.height], &img.as_bytes());
|
||||
let got = color_image(&img);
|
||||
assert_eq!(got.size, want.size);
|
||||
assert_eq!(got.pixels, want.pixels);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_image_is_an_empty_texture() {
|
||||
let img = lightcraft_raster::Rgba8::new(0, 0);
|
||||
assert!(color_image(&img).pixels.is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue