fix(gpu): refresh patterns when defaults or formats change (#5013)
Co-authored-by: haranishi <223786623+haranishi@users.noreply.github.com>
This commit is contained in:
parent
752f611264
commit
66e07cbf79
|
|
@ -178,6 +178,13 @@ type ResKey = (LayerId, Role, Cell);
|
|||
/// larger than the texture limit.
|
||||
type FxKey = (LayerId, Option<Cell>);
|
||||
|
||||
struct CachedPattern {
|
||||
fingerprint: u64,
|
||||
format: PixelFormat,
|
||||
default_bits: Vec<u32>,
|
||||
texture: Tex,
|
||||
}
|
||||
|
||||
struct Resident {
|
||||
texture: wgpu::Texture,
|
||||
view: wgpu::TextureView,
|
||||
|
|
@ -412,7 +419,7 @@ pub struct Compositor {
|
|||
prefix: prefix::Prefix,
|
||||
/// Effect temporaries (R32F, region sized) and the frame they were last used.
|
||||
temps: Vec<(Tex, u64)>,
|
||||
patterns: HashMap<String, (u64, Tex)>,
|
||||
patterns: HashMap<String, CachedPattern>,
|
||||
/// The colour mode of the document being encoded: patterns paint in it
|
||||
/// (`photocraft_compose::pattern::pattern_rgba`).
|
||||
pattern_mode: photocraft_color::ColorMode,
|
||||
|
|
@ -1538,10 +1545,15 @@ impl Compositor {
|
|||
});
|
||||
let mode = self.pattern_mode;
|
||||
let key = format!("{}\u{0}{}\u{0}{mode:?}", pat.id, pat.name);
|
||||
if let Some((f, t)) = self.patterns.get(&key)
|
||||
&& *f == fp
|
||||
let format = pat.surface.format();
|
||||
// Unstored tiles read the default pixel, interpreted in the source format.
|
||||
let default_bits: Vec<u32> = pat.surface.default_pixel().iter().map(|v| v.to_bits()).collect();
|
||||
if let Some(cached) = self.patterns.get(&key)
|
||||
&& cached.fingerprint == fp
|
||||
&& cached.format == format
|
||||
&& cached.default_bits == default_bits
|
||||
{
|
||||
return t.view.clone();
|
||||
return cached.texture.view.clone();
|
||||
}
|
||||
let (w, h) = (pat.width, pat.height);
|
||||
let px = photocraft_compose::pattern::pattern_rgba(pat, mode);
|
||||
|
|
@ -1554,7 +1566,7 @@ impl Compositor {
|
|||
wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
|
||||
);
|
||||
let v = t.view.clone();
|
||||
self.patterns.insert(key, (fp, t));
|
||||
self.patterns.insert(key, CachedPattern { fingerprint: fp, format, default_bits, texture: t });
|
||||
v
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2109,6 +2109,76 @@ fn artboards() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sparse patterns may have no stored tiles: their defaults still determine every pixel.
|
||||
#[test]
|
||||
fn pattern_cache_tracks_default_pixels() {
|
||||
let Some(mut g) = gpu() else { return };
|
||||
for depth in [SampleType::U8, SampleType::U16, SampleType::F32] {
|
||||
let fmt = PixelFormat::RGBA8.with_sample(depth);
|
||||
let mut d = Document::new("pattern defaults", Size::new(8, 6), ColorMode::Rgb, depth);
|
||||
let mut pat = Pattern::new("cache identity", photocraft_raster::Surface::with_default(fmt, &[1.0, 0.0, 0.0, 1.0]), 2, 2);
|
||||
assert_eq!(pat.surface.tiles().count(), 0);
|
||||
d.layers.push(Layer::new(
|
||||
"pattern",
|
||||
LayerContent::Fill(Fill::Pattern { name: pat.name.clone(), id: pat.id.clone(), scale: 1.0, angle: 0.0, link: true, phase: (0.0, 0.0) }),
|
||||
));
|
||||
d.patterns.push(pat.clone());
|
||||
check(&mut g, &d, &format!("red default {depth:?}"));
|
||||
pat.surface = photocraft_raster::Surface::with_default(fmt, &[0.0, 0.0, 1.0, 1.0]);
|
||||
assert_eq!(pat.surface.tiles().count(), 0);
|
||||
d.patterns[0] = pat;
|
||||
let cpu = photocraft_compose::flatten(&d);
|
||||
let mut fresh = Compositor::new(&g.device);
|
||||
let out = render_to_vec(&mut fresh, &g.device, &g.queue, &d, d.bounds()).unwrap();
|
||||
assert!(worst_diff(&cpu.px, &out).0 <= TOL, "fresh compositor matches blue default");
|
||||
check(&mut g, &d, &format!("blue default {depth:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_cache_tracks_source_format_and_cow_tiles() {
|
||||
let Some(mut g) = gpu() else { return };
|
||||
let defaults = [0.5, 0.25, 0.75, 1.0];
|
||||
let mut pat = Pattern::new("format identity", photocraft_raster::Surface::with_default(PixelFormat::RGBA32F, &defaults), 2, 2);
|
||||
let mut d = Document::new("pattern formats", Size::new(8, 6), ColorMode::Rgb, SampleType::F32);
|
||||
d.layers.push(Layer::new(
|
||||
"pattern",
|
||||
LayerContent::Fill(Fill::Pattern { name: pat.name.clone(), id: pat.id.clone(), scale: 1.0, angle: 0.0, link: true, phase: (0.0, 0.0) }),
|
||||
));
|
||||
let mut previous = None;
|
||||
for fmt in [PixelFormat::RGBA32F, PixelFormat::new(ColorMode::Lab, SampleType::F32, true), PixelFormat::RGBA16, PixelFormat::RGBA8] {
|
||||
pat.surface = photocraft_raster::Surface::with_default(fmt, &defaults);
|
||||
if fmt.sample == SampleType::F32 {
|
||||
assert_eq!(pat.surface.default_pixel(), defaults);
|
||||
}
|
||||
d.patterns = vec![pat.clone()];
|
||||
let cpu = photocraft_compose::flatten(&d);
|
||||
if fmt.mode == ColorMode::Lab {
|
||||
let old: Vec<[f32; 4]> = previous.take().unwrap();
|
||||
assert!(worst_diff(&old, &cpu.px).0 > 0.1, "same default bits have different source-format colours");
|
||||
}
|
||||
previous = Some(cpu.px.clone());
|
||||
let mut fresh = Compositor::new(&g.device);
|
||||
let out = render_to_vec(&mut fresh, &g.device, &g.queue, &d, d.bounds()).unwrap();
|
||||
assert!(worst_diff(&cpu.px, &out).0 <= TOL, "fresh compositor matches {fmt:?}");
|
||||
check(&mut g, &d, &format!("source format {fmt:?}"));
|
||||
check(&mut g, &d, &format!("unchanged source format {fmt:?}"));
|
||||
}
|
||||
pat.surface.fill_rect(pat.rect(), &[1.0, 0.0, 0.0, 1.0]);
|
||||
d.patterns = vec![pat.clone()];
|
||||
check(&mut g, &d, "stored red tile");
|
||||
// Keep the old tile alive: this exercises COW invalidation, not allocator address reuse.
|
||||
let old = pat.clone();
|
||||
pat.surface.fill_rect(Rect::new(0, 0, 1, 1), &[0.0, 0.0, 1.0, 1.0]);
|
||||
assert!(!std::sync::Arc::ptr_eq(old.surface.tiles().next().unwrap().1, pat.surface.tiles().next().unwrap().1));
|
||||
d.patterns = vec![pat];
|
||||
check(&mut g, &d, "COW blue tile edit");
|
||||
let mut fresh = Compositor::new(&g.device);
|
||||
let out = render_to_vec(&mut fresh, &g.device, &g.queue, &d, d.bounds()).unwrap();
|
||||
assert!(worst_diff(&photocraft_compose::flatten(&d).px, &out).0 <= TOL, "fresh compositor matches COW edit");
|
||||
assert_eq!(old.surface.tile_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_fill_layers() {
|
||||
let Some(mut g) = gpu() else { return };
|
||||
|
|
|
|||
Loading…
Reference in New Issue