Render: brush art is made again when a symbol or pattern it can show changes

Brush art leaves out the symbol instances and pattern paints whose art
draws a brushed stroke. The brush cache now records whether a brush's art
or the stroke's paint can show a symbol or a pattern, and drops those
entries when the art of the document's symbols or patterns changes, so an
edit that makes a symbol or pattern start or stop drawing a brushed stroke
draws as a new renderer does.

The parity line gives the median and fastest-frame figures of the
benchmark for both documents it names.
This commit is contained in:
dotCooCoo
2026-10-24 17:29:33 +07:00
committed by bflatastic
parent bba0618f55
commit dfbc1ee866
4 changed files with 102 additions and 6 deletions
+38 -5
View File
@@ -6,13 +6,16 @@
//! any of them regenerate the art and nothing else does. The copies of an object that share its id
//! (symbol instances, inline graphics, repeat copies) have their own paths, so each keeps its own
//! pieces. The pieces keep their `Arc`s while they are cached, so their stroke outlines, live-effect
//! geometry and the evaluations of live objects come from the renderer's caches too.
//! geometry and the evaluations of live objects come from the renderer's caches too. Brush art
//! leaves out the symbol instances and pattern paints whose art draws a brushed stroke, so when
//! the art of a symbol or pattern changes, the pieces of brushes and strokes that can show one are
//! made again.
use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Arc;
use std::sync::{Arc, Weak};
use vectorcraft_brush::Brush;
use vectorcraft_brush::{Brush, BrushKind};
use vectorcraft_doc::{AppearanceItem, Document, Node, NodeKind, StrokeLayer, SymbolBoxes};
use vectorcraft_geom::{BezPath, PathEl, Point, Rect};
use vello_cpu::RenderContext;
@@ -25,6 +28,27 @@ pub(crate) struct Entry {
stroke: StrokeLayer,
brush: Arc<Brush>,
pieces: Arc<Vec<Arc<Node>>>,
/// Can the pieces show symbol or pattern art ([`shows_definitions`])?
definitions: bool,
}
/// Can the brush art of `brush` along a stroke `st` show symbol or pattern art: does the stroke
/// paint with a pattern, or does the brush's own art show a symbol or a pattern?
fn shows_definitions(brush: &Brush, st: &StrokeLayer) -> bool {
let art: Vec<&Node> = match &brush.kind {
BrushKind::Art(a) => vec![&a.art],
BrushKind::Scatter(s) => vec![&s.art],
BrushKind::Pattern(p) => {
[Some(&p.side), p.outer_corner.as_ref(), p.inner_corner.as_ref(), p.start.as_ref(), p.end.as_ref()].into_iter().flatten().collect()
}
BrushKind::Calligraphic(_) | BrushKind::Bristle(_) => vec![],
};
matches!(st.paint, vectorcraft_color::Paint::Pattern { .. }) || crate::fx::Reads::of_all(art.into_iter()).shows_definitions()
}
/// The art of `doc`'s patterns and symbols, in order.
fn definitions(doc: &Document) -> impl Iterator<Item = &Arc<Node>> {
doc.patterns.iter().flat_map(|p| &p.art).chain(doc.symbols.iter().map(|s| &s.art))
}
/// Parsed brush library and generated brush art.
@@ -37,6 +61,9 @@ pub(crate) struct BrushCache {
/// (frame stamp, document address) the library was last validated for.
checked: (u64, usize),
lib: HashMap<String, Arc<Brush>>,
/// The art of the document's patterns and symbols ([`definitions`]) when the library was last
/// validated, held weakly as the pattern tiles hold theirs.
definitions: Vec<Weak<Node>>,
/// Brush art per (node id, stroke item index, [`path_key`] of the stroked path).
pub(crate) entries: FrameCache<(u64, usize, u64), Entry>,
/// How many times [`Renderer::draw_brush`] made brush art.
@@ -51,6 +78,7 @@ impl Default for BrushCache {
key: 0,
checked: (0, 0),
lib: HashMap::new(),
definitions: Vec::new(),
entries: FrameCache::new(lru::BRUSH_BUDGET, 512),
#[cfg(test)]
made: 0,
@@ -65,6 +93,10 @@ impl BrushCache {
return;
}
self.checked = key;
if !self.definitions.iter().map(Weak::as_ptr).eq(definitions(doc).map(Arc::as_ptr)) {
self.definitions = definitions(doc).map(Arc::downgrade).collect();
self.entries.retain(|e| !e.definitions);
}
let cur = doc.unknown.get(vectorcraft_brush::DOC_KEY);
if self.src.as_ref().is_some_and(|s| s.as_ref() == cur) {
return;
@@ -149,8 +181,9 @@ impl Renderer {
let pieces: Arc<Vec<Arc<Node>>> =
Arc::new(vectorcraft_brush::stroke_pieces(f.doc, &brush, bp, st).into_iter().map(Arc::new).collect());
let bytes = size_of::<Entry>() + lru::path_bytes(bp) + pieces.iter().map(|p| lru::node_bytes(p)).sum::<usize>();
let kept =
self.brushes.entries.insert(key, Entry { path: bp.clone(), stroke: st.clone(), brush, pieces: pieces.clone() }, bytes, stamp);
let definitions = shows_definitions(&brush, st);
let entry = Entry { path: bp.clone(), stroke: st.clone(), brush, pieces: pieces.clone(), definitions };
let kept = self.brushes.entries.insert(key, entry, bytes, stamp);
(pieces, kept)
}
};
+5
View File
@@ -977,6 +977,11 @@ impl Reads {
self.plugins || self.symbols || self.patterns
}
/// Does the content show symbol or pattern art?
pub(crate) fn shows_definitions(self) -> bool {
self.symbols || self.patterns
}
fn add(&mut self, n: &Node) {
let pattern = |p: &vectorcraft_color::Paint| matches!(p, vectorcraft_color::Paint::Pattern { .. });
n.walk(&mut |c| {
+58
View File
@@ -743,6 +743,64 @@ fn brush_art_of_copies_draws_symbol_brush_and_stroke_edits() {
});
}
/// A path stroked with scatter brush `Dots`, whose art is an instance of symbol `Dot` (a blue
/// square) and a square painted with pattern `Fill` (a green square). After a frame, the next one
/// draws as a new renderer does when the art of `Dot` or `Fill` starts or stops drawing a brushed
/// stroke, which decides whether the brush art shows that instance or that pattern.
#[test]
fn brush_art_draws_edits_of_the_symbols_and_patterns_it_shows() {
let _fonts = fonts_steady();
use vectorcraft_brush::{Brush, BrushKind, Scatter};
use vectorcraft_doc::pattern::{PatternDef, pattern_paint};
use vectorcraft_geom::shapes::{line, rectangle};
let square = |d: &mut Document, paint: Paint| filled(d, rectangle(Rect::new(-4.0, -4.0, 4.0, 4.0)), paint, vec![]);
// A square painted `paint`, with a brushed line beside it when `brushed`.
let art = |d: &mut Document, paint: Paint, brushed: bool| {
let mut members = vec![Arc::new(square(d, paint))];
if brushed {
members.push(Arc::new(brushed_line(d)));
}
Node::group(d.alloc_id(), members)
};
let doc = |dot_brushed: bool, fill_brushed: bool| {
let mut d = Document::new(200.0, 100.0);
let dot = art(&mut d, Paint::solid(Color::rgb(0.0, 0.0, 1.0)), dot_brushed);
d.symbols.push(vectorcraft_doc::Symbol { name: "Dot".into(), art: Arc::new(dot) });
let fill = art(&mut d, Paint::solid(Color::rgb(0.0, 0.6, 0.0)), fill_brushed);
let mut def = PatternDef::new("Fill", vec![Arc::new(fill)]);
def.tile = Rect::new(-6.0, -6.0, 6.0, 6.0);
d.patterns.push(def);
let id = d.alloc_id();
let instance = Node::new(id, NodeKind::SymbolInstance { symbol: "Dot".into(), xf: Affine::translate((0.0, -8.0)) });
let mut painted = square(&mut d, pattern_paint("Fill"));
painted.transform(Affine::translate((0.0, 8.0)), true);
let scatter = Node::group(d.alloc_id(), vec![Arc::new(instance), Arc::new(painted)]);
let mut lib = vectorcraft_brush::library(&d);
lib.push(Brush { name: "Dots".into(), kind: BrushKind::Scatter(Scatter { art: scatter, spacing: (150.0, 150.0), ..Default::default() }) });
vectorcraft_brush::store(&mut d, &lib);
let mut ap = vectorcraft_doc::Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0);
if let Some(s) = ap.stroke_mut() {
s.brush = Some("Dots".into());
}
let path = Node::path(d.alloc_id(), line((20.0, 50.0).into(), (180.0, 50.0).into()), ap);
let l = d.layers[0].id;
d.insert(Some(l), usize::MAX, path).unwrap();
d
};
for brushed in [false, true] {
edit_draws_as_new(doc(brushed, false), |d| {
let dot = art(d, Paint::solid(Color::rgb(0.0, 0.0, 1.0)), !brushed);
d.symbols[0].art = Arc::new(dot);
});
edit_draws_as_new(doc(false, brushed), |d| {
let fill = art(d, Paint::solid(Color::rgb(0.0, 0.6, 0.0)), !brushed);
if let Some(def) = d.pattern_mut("Fill") {
def.art = vec![Arc::new(fill)];
}
});
}
}
/// A path of the document and an instance of symbol `Wave` (a group holding another path) are
/// stroked with scatter brush `Bent`, whose art is an arc envelope around a green band: the second
/// frame evaluates none of the envelopes in their brush art again, and draws what the first drew