mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 22:06:49 -06:00
* M8: one depth cap for a whole render; render threads get 8 MiB stacks (#1082) A pattern fill drew its source as a fresh render from depth 0, so patterns in patterns could stack three renders of 256 levels. The source now goes on from the depth of the layer it fills. A render level takes about 6.5 KB of stack (measured), about 1.7 MB at the cap: the canvas render, preview and save threads and a band pool of their own now get 8 MiB stacks instead of the default 2 MiB. * M8: SVG and PDF exports carry the depth into pattern sources and embedded renders (#1082)
119 lines
5.6 KiB
Rust
119 lines
5.6 KiB
Rust
//! One depth cap for the whole render (#1082): a pattern fill's source is drawn one level below
|
||
//! the layer it fills, not as a fresh render, so patterns in patterns can't stack three times the
|
||
//! cap; and the deepest render the cap allows fits a render thread's stack ([`RENDER_STACK`]).
|
||
|
||
use figcraft_color::{Color, Paint, PaintKind, PatternPaint};
|
||
use figcraft_doc::{Document, Effect, NodeId, NodeKind};
|
||
use tiny_skia::Pixmap;
|
||
|
||
use crate::backdrop::render_beneath;
|
||
use crate::{Alone, MAX_EXPORT_DEPTH, Options, RENDER_STACK, View, at_layer_depth, layer_depth, render_alone, render_view, render_view_single};
|
||
|
||
/// `levels` 100 × 100 frames under `parent`, each inside the last (with drop shadows when
|
||
/// `heavy`, which keeps every level drawn); the outermost and the innermost.
|
||
fn chain(d: &mut Document, parent: NodeId, levels: usize, heavy: bool) -> (NodeId, NodeId) {
|
||
let mut at = parent;
|
||
let mut first = None;
|
||
for _ in 0..levels {
|
||
at = d.create(at, NodeKind::Frame, "F", 0.0, 0.0, 100.0, 100.0).unwrap();
|
||
let n = d.get_mut(at).unwrap();
|
||
n.fills.clear();
|
||
if heavy {
|
||
n.effects = vec![Effect::of_type("DROP_SHADOW").unwrap()];
|
||
}
|
||
first.get_or_insert(at);
|
||
}
|
||
(first.unwrap(), at)
|
||
}
|
||
|
||
fn pattern_of(source: NodeId) -> Paint {
|
||
Paint { kind: PaintKind::Pattern(PatternPaint::new(source.0)), ..Paint::solid(Color::WHITE) }
|
||
}
|
||
|
||
/// A page whose `outer`-deep chain ends in a frame filled with a pattern of another chain,
|
||
/// `inner` deep and ending in red (kept on a second page).
|
||
fn pattern_at_depth(outer: usize, inner: usize) -> (Document, NodeId) {
|
||
let mut d = Document::new("depth");
|
||
let page = d.first_page().unwrap();
|
||
let sources = d.add_page("Sources");
|
||
let (source, red) = chain(&mut d, sources, inner, false);
|
||
d.get_mut(red).unwrap().fills = vec![Paint::solid(Color::from_hex("FF0000").unwrap())];
|
||
let (_, filled) = chain(&mut d, page, outer, false);
|
||
d.get_mut(filled).unwrap().fills = vec![pattern_of(source)];
|
||
(d, page)
|
||
}
|
||
|
||
fn red_at_centre(pix: &Pixmap) -> bool {
|
||
let p = pix.pixel(50, 50).unwrap();
|
||
p.red() > 200 && p.green() < 60
|
||
}
|
||
|
||
fn render(d: &Document, page: NodeId) -> Pixmap {
|
||
render_view_single(d, page, &View { origin: (0.0, 0.0), scale: 1.0, width: 100, height: 100 }, &Options::default()).unwrap()
|
||
}
|
||
|
||
#[test]
|
||
fn a_pattern_source_goes_on_from_the_depth_of_the_layer_it_fills() {
|
||
// 200 levels, then 40 more in the pattern's source: within the cap, so the red shows.
|
||
let (d, page) = pattern_at_depth(200, 40);
|
||
assert!(red_at_centre(&render(&d, page)));
|
||
// 200, then 100 more: past the cap. The source used to start again at depth 0 and draw.
|
||
let (d, page) = pattern_at_depth(200, 100);
|
||
const { assert!(200 + 100 > MAX_EXPORT_DEPTH) };
|
||
assert!(!red_at_centre(&render(&d, page)), "the levels past the cap are not drawn");
|
||
// A pattern at the top of the page still draws its whole source.
|
||
let (d, page) = pattern_at_depth(1, 100);
|
||
assert!(red_at_centre(&render(&d, page)));
|
||
}
|
||
|
||
#[test]
|
||
fn a_layer_drawn_alone_for_an_exporter_goes_on_from_the_exporters_depth() {
|
||
let (d, page) = pattern_at_depth(1, 100);
|
||
let filled = d.node(page).unwrap().children[0];
|
||
let view = View { origin: (0.0, 0.0), scale: 1.0, width: 100, height: 100 };
|
||
let alone = |depth| at_layer_depth(depth, || render_alone(&d, filled, &view, Alone::Plain)).unwrap();
|
||
assert!(red_at_centre(&alone(0)), "a root layer draws its whole pattern source");
|
||
// Exported 200 levels down: its source's 100 levels pass the cap.
|
||
assert!(!red_at_centre(&alone(200)));
|
||
assert_eq!(layer_depth(), 0, "the depth is put back");
|
||
}
|
||
|
||
#[test]
|
||
fn what_lies_beneath_a_layer_counts_from_the_page_whatever_the_exporters_depth() {
|
||
let mut d = Document::new("beneath");
|
||
let page = d.first_page().unwrap();
|
||
let (root, red) = chain(&mut d, page, 100, false);
|
||
d.get_mut(red).unwrap().fills = vec![Paint::solid(Color::from_hex("FF0000").unwrap())];
|
||
let top = d.create(page, NodeKind::Rectangle, "Top", 0.0, 0.0, 100.0, 100.0).unwrap();
|
||
let view = View { origin: (0.0, 0.0), scale: 1.0, width: 100, height: 100 };
|
||
let beneath = at_layer_depth(200, || render_beneath(&d, &[root, top], top, &view)).unwrap();
|
||
assert!(red_at_centre(&beneath), "the page's own layers keep their depth");
|
||
}
|
||
|
||
#[test]
|
||
fn patterns_in_patterns_as_deep_as_the_cap_render_on_a_render_threads_stack() {
|
||
let drawn = std::thread::Builder::new()
|
||
.stack_size(RENDER_STACK)
|
||
.spawn(|| {
|
||
// Three chains of 250 heavy levels, each ending in a pattern of the next: what used to
|
||
// be three renders 250 deep on top of each other is one, cut at the cap.
|
||
let mut d = Document::new("patterns");
|
||
let page = d.first_page().unwrap();
|
||
let sources = d.add_page("Sources");
|
||
let (c, c_end) = chain(&mut d, sources, 250, true);
|
||
d.get_mut(c_end).unwrap().fills = vec![Paint::solid(Color::from_hex("FF0000").unwrap())];
|
||
let (b, b_end) = chain(&mut d, sources, 250, true);
|
||
d.get_mut(b_end).unwrap().fills = vec![pattern_of(c)];
|
||
let (_, a_end) = chain(&mut d, page, 250, true);
|
||
d.get_mut(a_end).unwrap().fills = vec![pattern_of(b)];
|
||
let pix = render(&d, page);
|
||
// A view large enough to render in bands, on the band pool.
|
||
let big = render_view(&d, page, &View { origin: (0.0, 0.0), scale: 1.0, width: 600, height: 600 }, &Options::default());
|
||
(red_at_centre(&pix), big.is_some())
|
||
})
|
||
.unwrap()
|
||
.join()
|
||
.unwrap();
|
||
assert_eq!(drawn, (false, true), "rendered, and the red 750 levels down is past the cap");
|
||
}
|