Files
figcraft/crates/render/src/render_depth_tests.rs
T
BFlat 7fdbda9f09 M8: One depth cap for a whole render, and render threads with room for it (#1605)
* 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)
2026-10-30 10:43:49 +07:00

119 lines
5.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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");
}