mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 03:06:57 -06:00
Each pointer move of a drag applies the move to the interaction's snapshot, so the dragged object is a new node in every frame. The renderer kept the rasters of an object's drop shadows, outer glows and filtered content (Gaussian Blur, Feather and the Photoshop-style filters) only for the same node, so every frame of a drag ran those effects again. On main, a moved frame of texture_drag_perf (a CMYK rounded rectangle at 190.07% and DPI 1.5 in an 1800 x 1500 view) took 112 to 197 ms in a release build. The canvas now renders with RenderOptions::interacting while a drag or a dialog's preview is in progress. When an object misses its cached raster in such a frame, the renderer takes the raster an earlier frame made for an object with the same id, slot and ink at the same zoom and rotation, made by the same effects from content of the same size (to within 0.000001 pt). It draws the object's content into a raster placed as that one was and compares the pixels byte for byte (only the alpha under a shadow or glow). For Feather it also compares the points of the clip outline, to within 0.000001 pt. When they match, the old raster is drawn at the new place, rounded to a whole pixel, as a pan draws a cached raster. Content under De-Interlace never takes a moved raster, and a raster made over moved rasters counts as moved itself. A frame rendered without interacting never draws such a raster, or a raster made over one: the frame after the drag runs the effects again and draws what a fresh renderer draws. The canvas's cache key includes whether an interaction is in progress. On the desktop, the canvas sends the frame after an interaction to the render worker when the frame shown drew moved rasters. Each cached raster keeps the content it was made from, within the existing 512 MiB budget. The pixels kept with a filtered raster take as many bytes as the raster, and the alpha kept with a shadow or glow takes a quarter as many as the shadow. texture_drag_perf now renders a drag's frames as the canvas does, then the frame after the drag, and that frame once more with every raster cached. A moved frame now takes 20 to 26 ms, close to the 18 to 25 ms of the cached frame; the frame after the drag takes 110 to 189 ms. Docs: docs/target-app-parity.md.
87 lines
2.5 KiB
Rust
87 lines
2.5 KiB
Rust
//! Background rendering (native): heavy documents render off the UI thread while the canvas shows
|
|
//! the previous frame reprojected to the current view. Light documents render synchronously so
|
|
//! edits never lag behind overlays.
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::mpsc::{Receiver, Sender, channel};
|
|
|
|
use vectorcraft_doc::Document;
|
|
use vectorcraft_geom::Affine;
|
|
use vectorcraft_render::{RenderOptions, Rendered, Renderer};
|
|
|
|
use crate::CacheKey;
|
|
|
|
pub struct Job {
|
|
pub key: CacheKey,
|
|
pub doc: Arc<Document>,
|
|
pub w: u32,
|
|
pub h: u32,
|
|
pub view: Affine,
|
|
pub opts: RenderOptions,
|
|
}
|
|
|
|
pub struct Done {
|
|
pub key: CacheKey,
|
|
pub img: Rendered,
|
|
pub ms: f64,
|
|
/// [`vectorcraft_render::FrameStats::moved`] of the frame.
|
|
pub moved: usize,
|
|
}
|
|
|
|
pub struct Worker {
|
|
tx: Sender<Job>,
|
|
rx: Receiver<Done>,
|
|
pub pending: Option<CacheKey>,
|
|
}
|
|
|
|
impl Worker {
|
|
/// Spawn the worker thread (None on wasm, where rendering stays synchronous).
|
|
pub fn spawn(ctx: egui::Context) -> Option<Self> {
|
|
if cfg!(target_arch = "wasm32") {
|
|
return None;
|
|
}
|
|
let (tx, jobs) = channel::<Job>();
|
|
let (done_tx, rx) = channel::<Done>();
|
|
std::thread::Builder::new()
|
|
.name("vectorcraft-render".into())
|
|
.spawn(move || {
|
|
let mut r = Renderer::new();
|
|
while let Ok(mut job) = jobs.recv() {
|
|
// Skip stale jobs: only the newest matters.
|
|
while let Ok(newer) = jobs.try_recv() {
|
|
job = newer;
|
|
}
|
|
let t0 = crate::now_ms();
|
|
let img = r.render(&job.doc, job.w, job.h, job.view, &job.opts);
|
|
if done_tx.send(Done { key: job.key, img, ms: crate::now_ms() - t0, moved: r.stats.moved }).is_err() {
|
|
break;
|
|
}
|
|
ctx.request_repaint();
|
|
}
|
|
})
|
|
.ok()?;
|
|
Some(Self { tx, rx, pending: None })
|
|
}
|
|
|
|
pub fn submit(&mut self, job: Job) {
|
|
if self.pending.as_ref() == Some(&job.key) {
|
|
return;
|
|
}
|
|
self.pending = Some(job.key.clone());
|
|
let _ = self.tx.send(job);
|
|
}
|
|
|
|
pub fn poll(&mut self) -> Option<Done> {
|
|
let mut last = None;
|
|
while let Ok(d) = self.rx.try_recv() {
|
|
last = Some(d);
|
|
}
|
|
if let Some(d) = &last
|
|
&& self.pending.as_ref() == Some(&d.key)
|
|
{
|
|
self.pending = None;
|
|
}
|
|
last
|
|
}
|
|
}
|