Compare commits

...
11 Commits
Author SHA1 Message Date
bflatastic 59fe9b5ea8 Record #326, #317 and #306's rebased heads (same content as landed in #530 and #529) 2026-10-16 01:17:17 +07:00
bflatastic 862d73bf9f Merge #312: test(canvas): grow the slow zoom-preview scene until it crosses the threshold (@tylerjenningsw) 2026-10-16 01:17:16 +07:00
BFlat 9dcfe4ecbc Community batch 4: 11 reviewed PRs (#530)
Lands 11 reviewed community PRs with their own commits plus maintainer fixes on their branches: #305 #363 #400 #338 #272 #368 #352 #308 #322 #317 #306.
2026-10-16 01:11:44 +07:00
BFlat 2fe7a9449a Community batch 3: 20 reviewed PRs (#529)
Lands 20 reviewed community PRs with their own commits plus maintainer fixes on their branches: #257 #263 #312 #253 #303 #331 #336 #280 #313 #332 #279 #281 #300 #326 #361 #310 #474 #346 #469 #477. Also: the FreeBSD CI VM installs DejaVu, so its text tests measure with a real font (they failed on main's nightly too).
2026-10-16 01:11:29 +07:00
bflatastic 916ee3276c #306: say why the window can't open in the interface language
The message now comes from the UI crate (dialogs::window_failed) through
tr() and trf(), with entries in every catalog, and the app picks the
language from the preferences before the event loop starts, so the box
reads in it even though no frame was ever drawn. The commands to type stay
as written.
2026-10-15 20:24:05 +03:00
Barzabel 97f9c2fc5d M1: say why when the window can't open (#261)
Release builds on Windows have no console, so an eframe::run_native error
(no usable graphics adapter, a device the driver refuses) went to an unseen
standard error and the app quit silently. The error is now logged and shown
in a message box with a hint: unset WGPU_BACKEND when it is set, otherwise
update the driver or try another graphics API (OpenGL on Windows), and
where to report it. With --control the box is skipped so agents aren't
blocked.
2026-10-15 20:24:04 +03:00
bflatastic 4a2d19f0db #326: decode at most the image crate's default 512 MiB per image; a full image cache drops its least recently used images, not all of them 2026-10-15 20:23:07 +03:00
bflatastic a7bcc6d6ff #317: compute the backdrop once under a casting layer
With drop shadows the backdrop was read twice: before the content and
again after the shadows. The first read is only what backdrop effects
inside the content see, so it now runs only when the layer has such
descendants, and the content is copied for the shadow caster only when it
gets a layer blur, noise or texture. The output is unchanged.
2026-10-15 20:23:07 +03:00
Barzabel f4d53a659b M1: memory budgets for decoded images and a file's images (#203, #189) 2026-10-15 20:23:07 +03:00
Barzabel 3074db1cbb M1: drop shadows under a background blur (#255)
A layer with a background blur (or glass) drew its blurred backdrop into
its own pixels before casting its drop shadows from them, so a translucent
fill cast a shadow as dark as an opaque one, and with "show behind
transparent areas" the shadow was cast from the backdrop's pixels. The
backdrop was also read before the shadows were drawn, so a shadow showing
behind was never seen through the blur.

When a layer has both, it now casts its shadows from what it paints (with
the layer blur, noise and texture it will get), draws them, and only then
reads the backdrop, which goes under its content; the content still sees
the first backdrop under it while it's drawn. Layers without both draw as
before.
2026-10-15 20:23:07 +03:00
tylerjenningsw d769320d45 test(canvas): grow the slow zoom-preview scene until it crosses the threshold (M8)
The browser-worker test assumed a fixed 2400x2400 glass scene renders
in at least 12 ms; a faster machine or renderer (the glass speedups)
would make it quick and the test fail. The scene now gets more and
more blurred rectangles until renders from scratch of the views the
test uses take three times the threshold.

Refs #295
2026-10-15 10:04:09 -04:00
2 changed files with 37 additions and 8 deletions

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ use web_time::Instant;
/// A preview has this many times fewer pixels each way than the view it stands in for.
pub const PREVIEW_FACTOR: f64 = 4.0;
/// Full renders quicker than this (about a frame) show without a preview first.
const PREVIEW_AFTER_MS: f64 = 12.0;
pub const PREVIEW_AFTER_MS: f64 = 12.0;
#[derive(Clone)]
pub struct Job {
+36 -7
View File
@@ -13,7 +13,7 @@ use figcraft_render::{View, Work};
use serde_json::json;
use super::{CanvasState, shows};
use crate::render_worker::{Done, Inline, Job, PREVIEW_FACTOR, Renders, coarse_view, render_now};
use crate::render_worker::{Done, Inline, Job, PREVIEW_AFTER_MS, PREVIEW_FACTOR, Renders, coarse_view, render_now};
/// A page of ellipses with a rectangle on top; the session and the rectangle.
fn document() -> (Session, NodeId) {
@@ -27,6 +27,35 @@ fn document() -> (Session, NodeId) {
(s, rect)
}
/// A page whose renders from scratch of each of `views` take at least `ms`: the rectangle with glass
/// and a background blur, and more and more blurred rectangles over the views until they do (a
/// fixed scene would turn quick on a faster machine or renderer).
fn slow_scene(views: &[View], ms: f64) -> Session {
let (mut s, rect) = document();
s.execute("effect.add", &json!({"ids": [rect], "type": "GLASS"})).unwrap();
s.execute("effect.add", &json!({"ids": [rect], "type": "BACKGROUND_BLUR"})).unwrap();
let area = views.iter().map(View::page_rect).reduce(|a, b| a.union(b)).unwrap();
let mut more = 8;
for _ in 0..12 {
let ds = s.ds().unwrap();
let quickest = views.iter().map(|v| render_now(&job(&ds.doc, ds.page, ds.uid, v.clone())).unwrap().ms).fold(f64::INFINITY, f64::min);
if quickest >= ms {
return s;
}
let (w, h) = (area.width() / 4.0, area.height() / 4.0);
let ids: Vec<NodeId> = (0..more)
.map(|k| {
let (fx, fy) = (f64::from(k * 37 % 101) / 101.0, f64::from(k * 59 % 103) / 103.0);
let p = json!({"x": area.x0 + fx * (area.width() - w), "y": area.y0 + fy * (area.height() - h), "width": w, "height": h});
id_of(&s.execute("shape.rectangle", &p).unwrap()["id"]).unwrap()
})
.collect();
s.execute("effect.add", &json!({"ids": ids, "type": "BACKGROUND_BLUR"})).unwrap();
more *= 2;
}
panic!("no scene renders slower than {ms} ms");
}
/// A `w` × `h` device-pixel view at `scale` centred on page point `c`.
fn view_at(c: (f64, f64), scale: f64, w: u32, h: u32) -> View {
View { origin: (c.0 - f64::from(w) / scale / 2.0, c.1 - f64::from(h) / scale / 2.0), scale, width: w, height: h }
@@ -254,18 +283,18 @@ fn a_preview_replaces_a_refined_image_of_the_same_zoom_elsewhere_on_the_page() {
#[test]
fn the_browser_worker_previews_by_measured_render_time_then_refines_the_next_frame() {
// Big enough that a render from scratch takes well over a frame: blurs and glass on 2400².
let (mut s, rect) = document();
s.execute("effect.add", &json!({"ids": [rect], "type": "GLASS"})).unwrap();
s.execute("effect.add", &json!({"ids": [rect], "type": "BACKGROUND_BLUR"})).unwrap();
// Renders from scratch at 200% and 100% (whose times decide the previews below) well over the
// threshold, wherever this runs.
let view = |scale| view_at((300.0, 200.0), scale, 1200, 1200);
let s = slow_scene(&[view(2.0), view(1.0)], 3.0 * PREVIEW_AFTER_MS);
let ds = s.ds().unwrap();
let at = |scale| job(&ds.doc, ds.page, ds.uid, view_at((300.0, 200.0), scale, 2400, 2400));
let at = |scale| job(&ds.doc, ds.page, ds.uid, view(scale));
// The production threshold, as the browser has it.
let mut w = Inline::new(egui::Context::default());
w.submit(at(2.0));
let first = w.poll().expect("the first image");
assert!(!first.coarse && !w.in_flight(), "the first render has no measure to preview by");
assert!(first.ms >= 12.0, "renders are timed in the browser worker too (and this one is slow): {} ms", first.ms);
assert!(first.ms >= PREVIEW_AFTER_MS, "renders are timed in the browser worker too (and this one is slow): {} ms", first.ms);
// A new zoom: the preview this frame, the refined image the next.
w.submit(at(1.0));
let p = w.poll().expect("a preview");