mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 03:49:34 -06:00
Metafile: stop shading on a full canvas, charge nested pictures and clip regions to the budgets
This commit is contained in:
@@ -369,6 +369,11 @@ impl Canvas {
|
||||
}
|
||||
|
||||
/// True once the item cap is hit; the readers stop there and keep what they have.
|
||||
/// Bytes the canvas has retained so far.
|
||||
pub fn used(&self) -> usize {
|
||||
self.used
|
||||
}
|
||||
|
||||
pub fn full(&self) -> bool {
|
||||
self.full
|
||||
}
|
||||
@@ -551,6 +556,10 @@ impl Canvas {
|
||||
/// of at most [`SHADE_RES`] cells on the longer side, laid out so that its one repeat covers
|
||||
/// the shape.
|
||||
pub fn shade_shape(&mut self, ops: &[Op], shade: &dyn Fn(P) -> [u8; 4]) {
|
||||
// A full canvas keeps nothing more, so do not sample a tile for it.
|
||||
if self.full {
|
||||
return;
|
||||
}
|
||||
let dev: Vec<Op> = ops.iter().map(|o| self.map_op(*o)).collect();
|
||||
let Some([l, t, r, b]) = crate::cliprect::ops_bbox(&dev) else { return };
|
||||
let side = (r - l).max(b - t);
|
||||
|
||||
@@ -289,7 +289,7 @@ impl Plus {
|
||||
return None;
|
||||
}
|
||||
match typ {
|
||||
0x4008 => self.object(flags, r),
|
||||
0x4008 => self.object(flags, r, cv.used()),
|
||||
0x4009 | 0x4013 | 0x401A..=0x401C | 0x4036 => self.record_image_text_region(cv, typ, flags, r),
|
||||
0x4025..=0x4035 => self.record_state(cv, typ, flags, r),
|
||||
0x4010..=0x4012 | 0x4016..=0x4019 => self.record_curve(cv, typ, flags, r),
|
||||
@@ -559,7 +559,7 @@ impl Plus {
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn object(&mut self, flags: u16, r: Bytes) -> Option<()> {
|
||||
fn object(&mut self, flags: u16, r: Bytes, canvas_used: usize) -> Option<()> {
|
||||
let id = usize::from(flags & 0xFF);
|
||||
let kind = (flags >> 8) & 0x7F;
|
||||
if flags & 0x8000 != 0 {
|
||||
@@ -575,7 +575,7 @@ impl Plus {
|
||||
p.data.extend_from_slice(&chunk[..chunk.len().min(room)]);
|
||||
if p.data.len() >= p.total {
|
||||
let obj = parse_object(p.kind, Bytes(&p.data));
|
||||
self.store(p.id, obj);
|
||||
self.store(p.id, obj, canvas_used);
|
||||
} else {
|
||||
self.partial = Some(p);
|
||||
}
|
||||
@@ -589,22 +589,23 @@ impl Plus {
|
||||
let room = p.total.saturating_sub(p.data.len());
|
||||
p.data.extend_from_slice(&r.0[..r.0.len().min(room)]);
|
||||
let obj = parse_object(kind, Bytes(&p.data));
|
||||
self.store(id, obj);
|
||||
self.store(id, obj, canvas_used);
|
||||
return Some(());
|
||||
}
|
||||
let obj = parse_object(kind, r);
|
||||
self.store(id, obj);
|
||||
self.store(id, obj, canvas_used);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn store(&mut self, id: usize, obj: Option<Obj>) {
|
||||
/// `canvas_used` is what the canvas already retains: objects and items share one budget.
|
||||
fn store(&mut self, id: usize, obj: Option<Obj>, canvas_used: usize) {
|
||||
let Some(slot) = self.objects.get_mut(id) else { return };
|
||||
if let Some(old) = slot.take() {
|
||||
self.object_bytes = self.object_bytes.saturating_sub(Self::obj_cost(&old));
|
||||
}
|
||||
if let Some(new_obj) = obj {
|
||||
let cost = Self::obj_cost(&new_obj);
|
||||
if self.object_bytes.saturating_add(cost) <= crate::MAX_BYTES {
|
||||
if self.object_bytes.saturating_add(cost).saturating_add(canvas_used) <= crate::MAX_BYTES {
|
||||
self.object_bytes += cost;
|
||||
*slot = Some(new_obj);
|
||||
}
|
||||
@@ -615,7 +616,7 @@ impl Plus {
|
||||
match obj {
|
||||
Obj::Image(img) => match img.as_ref() {
|
||||
ImageData::Bitmap(rgba) => rgba.px.len(),
|
||||
ImageData::Meta(pic) => pic.items.len().saturating_mul(128),
|
||||
ImageData::Meta(pic) => pic.items.iter().map(item_cost).sum(),
|
||||
},
|
||||
Obj::Brush(Fill::Texture { img, .. }) => img.px.len(),
|
||||
Obj::Path(ops) => ops.len().saturating_mul(std::mem::size_of::<Op>()),
|
||||
@@ -1276,6 +1277,18 @@ fn metafile(r: Bytes) -> Option<Picture> {
|
||||
pic.filter(|p| p.width > 0.0 && p.height > 0.0)
|
||||
}
|
||||
|
||||
/// Bytes an item of a nested picture owns: its path segments, clip rectangles, pixels and text.
|
||||
fn item_cost(item: &crate::Item) -> usize {
|
||||
use crate::Item;
|
||||
let region = |r: &Option<Vec<[f32; 4]>>| r.as_ref().map_or(0, |v| v.len() * 16);
|
||||
128 + match item {
|
||||
Item::Path { segs, region: r, .. } => segs.len() * std::mem::size_of::<crate::Seg>() + region(r),
|
||||
Item::Bitmap { rgba, region: r, .. } => rgba.len() + region(r),
|
||||
Item::Pattern { segs, tile, region: r, .. } => segs.len() * std::mem::size_of::<crate::Seg>() + tile.rgba.len() + region(r),
|
||||
Item::Text(t) => t.text.len() + region(&t.region),
|
||||
}
|
||||
}
|
||||
|
||||
fn bitmap(r: Bytes) -> Option<Rgba> {
|
||||
let (w, h, stride) = (r.u32(8)? as i32, r.u32(12)? as i32, r.u32(16)? as i32);
|
||||
let format = r.u32(20)?;
|
||||
|
||||
@@ -58,6 +58,7 @@ impl Picture {
|
||||
let k = (c[0].abs() + c[3].abs()) / 2.0;
|
||||
let mut out = Vec::with_capacity(self.items.len());
|
||||
let mut total_elements = 0usize;
|
||||
let region_elements = std::cell::Cell::new(0usize);
|
||||
let mut cached_region: Option<(*const [f32; 4], usize, Option<Option<Arc<BezPath>>>)> = None;
|
||||
let mut get_region = |reg: &Option<Vec<[f32; 4]>>| -> Option<Option<Arc<BezPath>>> {
|
||||
let (ptr, len) = match reg {
|
||||
@@ -71,12 +72,15 @@ impl Picture {
|
||||
return res.clone();
|
||||
}
|
||||
let res = placed_region(&tf, reg);
|
||||
// Each distinct clip region is expanded once; its elements count against the placement budget.
|
||||
let n = res.as_ref().and_then(Option::as_ref).map_or(0, |p| p.elements().len());
|
||||
region_elements.set(region_elements.get().saturating_add(n));
|
||||
cached_region = Some((ptr, len, res.clone()));
|
||||
res
|
||||
};
|
||||
|
||||
for (index, item) in self.items.iter().enumerate() {
|
||||
if total_elements >= MAX_PLACED_ELEMENTS {
|
||||
if total_elements.saturating_add(region_elements.get()) >= MAX_PLACED_ELEMENTS {
|
||||
break;
|
||||
}
|
||||
match item {
|
||||
|
||||
@@ -2074,3 +2074,15 @@ fn clip_polygon_work_bound() {
|
||||
let clipped = crate::cliprect::clip_polygon(&poly, &clip);
|
||||
assert!(clipped.is_empty(), "excessive polygon clip product rejected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_canvas_does_not_sample_gradient_tiles() {
|
||||
let mut cv = Canvas::new();
|
||||
let big = || Rgba { w: 1, h: 1, bottom_up: false, px: vec![0u8; MAX_BYTES / 2] };
|
||||
for _ in 0..3 {
|
||||
cv.bitmap([0.0, 0.0, 1.0, 1.0], big());
|
||||
}
|
||||
assert!(cv.full());
|
||||
cv.shade_shape(&crate::geom::rect(0.0, 0.0, 10.0, 10.0), &|_| panic!("sampled a full canvas"));
|
||||
assert_eq!(cv.items.len(), 2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user