mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 21:27:00 -06:00
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:
@@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user