Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
639 changes: 202 additions & 437 deletions Cargo.lock

Large diffs are not rendered by default.

42 changes: 24 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,28 @@ authors = ["FastTrack Studios"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/FastTrackStudios/frame"

# The versions Studio pinned at the cut (studio@fc6b1954), so Frame builds
# with exactly what it built with there.
# architect v0.10.0's pins (its Cargo.toml and lock): vox rc.7, facet rc.7,
# vello 0.11, wgpu 30, anyrender 0.14 / anyrender_vello 0.15, Blitz and
# nice-plug at fts-upstream-2026-11. A Frame consumer repeats the [patch]
# tables below.
[workspace.dependencies]
anyrender_0_11 = { package = "anyrender", git = "https://github.com/FastTrackStudios/blitz", tag = "fts-pointer-2026-10" }
anyrender_svg = { git = "https://github.com/DioxusLabs/anyrender.git", branch = "main" }
anyrender_svg_0_12 = { version = "0.12", package = "anyrender_svg" }
architect = { git = "https://github.com/FastTrackStudios/architect", tag = "v0.9.31" }
anyrender = "0.14"
anyrender_vello = "0.15"
anyrender_svg = "0.15"
architect = { git = "https://github.com/FastTrackStudios/architect", tag = "v0.10.0" }
base64 = "0.22.1"
blake3 = { version = "1.8" }
chrono = { version = "0.4", default-features = false, features = ["clock", "std", "wasmbind"] }
crdt = { git = "https://github.com/FastTrackStudios/architect", tag = "v0.9.31" }
facet = { version = "0.50.0-rc.5", features = ["reflect", "chrono", "uuid"] }
crdt = { git = "https://github.com/FastTrackStudios/architect", tag = "v0.10.0" }
facet = { version = "0.50.0-rc.7", features = ["reflect", "chrono", "uuid"] }
facet-json = { version = "0.50.0-rc.7" }
facet-styx = { version = "5.0.0-rc.7" }
figma-api = { git = "https://github.com/gridaco/figma-api-rust.git" }
fixed = "1.28"
image = { version = "0.25", default-features = false, features = ["png"] }
kurbo = "0.13"
miniz_oxide = "0.8"
nice-plug-core = { git = "https://github.com/FastTrackStudios/nice-plug.git", branch = "fts-baseview-03" }
nice-plug-core = { git = "https://github.com/FastTrackStudios/nice-plug.git", tag = "fts-upstream-2026-11" }
peniko = "0.6"
pollster = "0.4.0"
proptest = "1"
Expand All @@ -67,22 +69,26 @@ thiserror = "2.0"
tokio = { version = "1.48", features = ["full"] }
uuid = { version = "1", features = ["v4", "v5", "v7", "serde"] }
vello_cpu = { version = "0.0.9", default-features = false }
vox = { version = "0.10.0-rc.5", default-features = false, features = ["runtime"] }
vox-core = { version = "0.10.0-rc.5" }
vox = { version = "0.10.0-rc.7", default-features = false, features = ["runtime"] }
vox-core = { version = "0.10.0-rc.7" }
vox-websocket = { version = "0.10.0-rc.5" }
winit = "0.30"
vello = "0.11"
wgpu = "30"
zip = { version = "2", default-features = false }

# Studio's patch tables, the entries that reach Frame's graph.
# architect v0.10.0's patch tables (FastTrackStudios/vendor at v0.7.1 where
# architect has v0.7.0), the entries that reach Frame's graph.
[patch.crates-io]
anyrender = { git = "https://github.com/FastTrackStudios/blitz", tag = "fts-pointer-2026-10" }
wgpu-hal = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.6.1" }
raw-window-metal = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.6.1" }
anyrender = { git = "https://github.com/FastTrackStudios/blitz", tag = "fts-upstream-2026-11" }
anyrender_vello = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.7.1" }
wgpu-hal = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.7.1" }
raw-window-metal = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.7.1" }
styx-format = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.1.0" }
vox-phon = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.3.0" }
phon = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.4.1" }
facet-core = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.4.1" }
phon = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.4.0" }
phon-jit = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.2.3" }
facet-core = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.4.0" }
vox-phon = { git = "https://github.com/FastTrackStudios/vendor", tag = "v0.3.0" }

[workspace.lints.rust]
# Flipped from "allow" 2026-07-28 (task cleanup): the blanket allows are
Expand Down
21 changes: 9 additions & 12 deletions crates/frame/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ workspace = true

[features]
default = ["vox"]
# A host's anyrender `PaintScene` (Blitz's) as a paint target: the
# anyrender crate alone, the same 0.14 Blitz and architect use.
host-scene = ["dep:anyrender"]
anyrender = ["dep:anyrender", "dep:anyrender_svg", "dep:anyrender_vello", "dep:image", "dep:peniko", "dep:pollster", "dep:skrifa", "dep:vello", "dep:winit", "svg_export"]
anyrender-0-11 = ["dep:anyrender_0_11", "dep:anyrender_svg_0_12"]
codec = ["io"]
cpu = ["dep:image", "dep:vello_cpu"]
design = ["io", "render", "runtime"]
Expand All @@ -36,14 +38,9 @@ theme = ["render"]
vox = ["architect/vox", "dep:vox", "rpc"]

[dependencies]
anyrender = { git = "https://github.com/DioxusLabs/anyrender.git", branch = "main", optional = true }
# The host scene anyrender (0.11): the blitz fork the workspace patches
# crates.io anyrender to, named directly — dx's metadata reader cannot
# match a renamed dependency to a patched source.
anyrender_0_11 = { workspace = true, optional = true }
anyrender_svg_0_12 = { workspace = true, optional = true }
anyrender = { workspace = true, optional = true }
anyrender_svg = { workspace = true, optional = true }
anyrender_vello = { git = "https://github.com/DioxusLabs/anyrender.git", branch = "main", optional = true }
anyrender_vello = { workspace = true, optional = true }
architect = { workspace = true, optional = true }
base64 = { workspace = true, optional = true }
blake3 = { workspace = true }
Expand All @@ -69,7 +66,7 @@ taffy = { workspace = true, optional = true }
thiserror = { workspace = true }
tokio = { workspace = true, optional = true }
uuid = { workspace = true }
vello = { version = "0.10.0", optional = true }
vello = { workspace = true, optional = true }
vello_cpu = { workspace = true, features = ["std", "u8_pipeline"], optional = true }
vox = { workspace = true, optional = true }
vox-core = { workspace = true, optional = true }
Expand All @@ -80,7 +77,7 @@ zip = { workspace = true, features = ["deflate"], optional = true }
[dev-dependencies]
# Its own tests and examples, with the features they need (see their
# required-features): a plain `cargo test` of the workspace builds them all.
frame = { path = ".", features = ["anyrender", "cpu", "design", "dsp", "embed", "figma", "io", "library", "live", "render", "runtime", "surface-runtime-rpc", "theme"] }
frame = { path = ".", features = ["anyrender", "cpu", "design", "dsp", "embed", "figma", "host-scene", "io", "library", "live", "nice-plug", "render", "runtime", "surface-runtime-rpc", "theme"] }
architect = { workspace = true, features = ["vox", "local"] }
facet = { workspace = true }
facet-json = { workspace = true }
Expand All @@ -90,10 +87,10 @@ pollster = { workspace = true }
proptest = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
vello = { version = "0.10.0" }
vello = { workspace = true }
vox-core = { workspace = true }
vox-websocket = { workspace = true }
wgpu = { version = "29" }
wgpu = { workspace = true }

[[example]]
name = "strips"
Expand Down
4 changes: 3 additions & 1 deletion crates/frame/examples/face_frames.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ fn read(device: &wgpu::Device, queue: &wgpu::Queue, texture: &wgpu::Texture) ->
let slice = buffer.slice(..);
slice.map_async(wgpu::MapMode::Read, |_| {});
let _ = device.poll(wgpu::PollType::wait_indefinitely());
let mapped = slice.get_mapped_range();
let mapped = slice
.get_mapped_range()
.expect("the buffer is mapped: the poll above waited for it");
let mut rgba = vec![0_u8; (unpadded * rows) as usize];
for row in 0..rows as usize {
rgba[row * unpadded as usize..(row + 1) * unpadded as usize].copy_from_slice(
Expand Down
4 changes: 2 additions & 2 deletions crates/frame/src/embed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! two paint passes (params, recorded once; feeds and motion, live),
//! pointer routing in strip-local coordinates.
//! - [`Canvas`]: what it paints into — the host's own scene. An anyrender
//! 0.11 `PaintScene` (feature `anyrender-0-11`, session's mixer), frame's
//! `PaintScene` (feature `host-scene`, session's mixer), frame's
//! replayable [`Recording`], or the CPU rasteriser ([`cpu`], REAPER's
//! LICE bitmap, tests).
//! - [`tone`]: session's Tone rack (EQ, gate, compressor, saturator,
Expand Down Expand Up @@ -50,7 +50,7 @@ pub mod tone;
// The painting seam, shared with the theme runtime.
pub use crate::render::canvas::{canvas, paint, text};

#[cfg(feature = "anyrender-0-11")]
#[cfg(feature = "host-scene")]
pub use canvas::AnyRender;
pub use canvas::{Canvas, CanvasExt, Recording, Tally};
pub use embed::{
Expand Down
16 changes: 8 additions & 8 deletions crates/frame/src/live/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//! hand. A host with an engine feeds the engine's values in with
//! [`LiveSurface::apply_values`].

#[cfg(feature = "anyrender-0-11")]
#[cfg(feature = "host-scene")]
mod vectors;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -764,13 +764,13 @@ impl LiveSurface {

/// Paint the surface's vectors at `width × height` physical px
/// (`scale` physical px per element px) into `scene`, a host's anyrender
/// 0.11 scene — the path for a host whose renderer gives no Vello GPU to
/// scene — the path for a host whose renderer gives no Vello GPU to
/// [`render`](Self::render) with (vello-hybrid on the iOS simulator,
/// whose Metal has no indirect execution). The same frame as `render`,
/// painted by the same painter; the GPU effects stage is left out (a
/// glow is drawn as a soft copy, as frame's fallback mode draws it).
#[cfg(feature = "anyrender-0-11")]
pub fn paint_vectors<S: anyrender_0_11::PaintScene>(
#[cfg(feature = "host-scene")]
pub fn paint_vectors<S: anyrender::PaintScene>(
&mut self,
scene: &mut S,
width: u32,
Expand Down Expand Up @@ -802,7 +802,7 @@ impl LiveSurface {
ty: physical.ty / scale,
};
let doc = self.rt.frame(t);
let mut target = vectors::AnyRender011(scene);
let mut target = vectors::HostScene(scene);
let bounds = kurbo::Rect::new(0.0, 0.0, f64::from(w), f64::from(h));
crate::render::PaintTarget::fill(
&mut target,
Expand Down Expand Up @@ -835,8 +835,8 @@ impl LiveSurface {
/// drawn over a host's own picture, as one block of its grid, its
/// transparent margin showing what is under it. The same frame as
/// [`paint_vectors`](Self::paint_vectors), by the same painter.
#[cfg(feature = "anyrender-0-11")]
pub fn paint_vectors_at<S: anyrender_0_11::PaintScene>(
#[cfg(feature = "host-scene")]
pub fn paint_vectors_at<S: anyrender::PaintScene>(
&mut self,
scene: &mut S,
width: f64,
Expand All @@ -854,7 +854,7 @@ impl LiveSurface {
let view = View::fit(self.content, width.max(1.0), height.max(1.0));
self.view = view;
let doc = self.rt.frame(t);
let mut target = vectors::AnyRender011(scene);
let mut target = vectors::HostScene(scene);
let prims = crate::render::build_scene_primitives_with(
&doc,
self.page,
Expand Down
69 changes: 36 additions & 33 deletions crates/frame/src/live/vectors.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
//! A host's anyrender 0.11 scene as frame's paint target — Blitz's, drawn
//! A host's anyrender scene as frame's paint target — Blitz's, drawn
//! by whatever renderer the host runs: vello-hybrid on the iOS simulator,
//! whose Metal has no indirect execution for Vello's compute stages. What
//! that API has no word for is approximated: a luminance mask draws its
//! content unmasked, and clips are non-zero.
//! content unmasked.
//!
//! Here rather than in `render`, under the crates' own names: `render` also
//! links frame's anyrender (git main), and a renamed dependency that a host
//! redirects with `[patch]` (signal's, to the blitz fork) breaks dx's
//! dependency scan.
//! Frame and its hosts share one anyrender (0.14, Blitz's fork, the
//! version architect pins), so a host's scene and frame's are one type.

use kurbo::{Affine, Rect, Shape, Stroke};
use peniko::{BlendMode, BrushRef, Color, Fill};

use crate::render::{GlyphRun, PaintTarget};

/// An anyrender 0.11 `PaintScene` as a [`PaintTarget`].
pub struct AnyRender011<'s, S: anyrender_0_11::PaintScene>(pub &'s mut S);
/// A host's anyrender `PaintScene` as a [`PaintTarget`].
pub struct HostScene<'s, S: anyrender::PaintScene>(pub &'s mut S);

impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
impl<S: anyrender::PaintScene> PaintTarget for HostScene<'_, S> {
fn fill<'b>(
&mut self,
style: Fill,
Expand All @@ -32,9 +30,10 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
if let BrushRef::Image(image) = &brush
&& image.sampler.alpha < 1.0
{
let mut opaque = image.clone();
let mut opaque = *image;
opaque.sampler.alpha = 1.0;
self.0.push_layer(
Fill::NonZero,
BlendMode::default(),
image.sampler.alpha,
transform,
Expand All @@ -45,7 +44,7 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
self.0.fill(
style,
transform,
anyrender_0_11::PaintRef::Image(opaque),
anyrender::PaintRef::Image(opaque),
brush_transform,
shape,
);
Expand All @@ -55,7 +54,7 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
self.0.fill(
style,
transform,
anyrender_0_11::PaintRef::from(brush),
anyrender::PaintRef::from(brush),
brush_transform,
shape,
);
Expand All @@ -73,37 +72,45 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
self.0.stroke(
style,
transform,
anyrender_0_11::PaintRef::from(brush),
anyrender::PaintRef::from(brush),
brush_transform,
shape,
);
}

fn push_layer(
&mut self,
_clip_style: Fill,
clip_style: Fill,
blend: impl Into<BlendMode>,
alpha: f32,
transform: Affine,
clip: &impl Shape,
) {
self.0.push_layer(blend, alpha, transform, clip, None, None);
self.0
.push_layer(clip_style, blend, alpha, transform, clip, None, None);
}

fn push_clip_layer(&mut self, _clip_style: Fill, transform: Affine, clip: &impl Shape) {
self.0.push_clip_layer(transform, clip);
fn push_clip_layer(&mut self, clip_style: Fill, transform: Affine, clip: &impl Shape) {
self.0.push_clip_layer(clip_style, transform, clip);
}

fn push_luminance_mask_layer(
&mut self,
_clip_style: Fill,
clip_style: Fill,
alpha: f32,
transform: Affine,
clip: &impl Shape,
) {
// No luminance masks here: the mask's content draws unmasked.
self.0
.push_layer(BlendMode::default(), alpha, transform, clip, None, None);
self.0.push_layer(
clip_style,
BlendMode::default(),
alpha,
transform,
clip,
None,
None,
);
}

fn pop_layer(&mut self) {
Expand All @@ -123,7 +130,7 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
}

fn draw_glyph_run(&mut self, run: &GlyphRun<'_>) {
let glyphs = run.glyphs.iter().map(|g| anyrender_0_11::Glyph {
let glyphs = run.glyphs.iter().map(|g| anyrender::Glyph {
id: g.id,
x: g.x,
y: g.y,
Expand All @@ -135,7 +142,7 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
run.normalized_coords,
run.embolden,
Fill::NonZero,
anyrender_0_11::PaintRef::from(run.brush.clone()),
anyrender::PaintRef::from(run.brush),
run.brush_alpha,
run.transform,
run.glyph_transform,
Expand All @@ -157,18 +164,17 @@ impl<S: anyrender_0_11::PaintScene> PaintTarget for AnyRender011<'_, S> {
let Some(tree) = svg_tree(svg_text) else {
return false;
};
anyrender_svg_0_12::render_svg_tree(&mut *self.0, &tree, transform);
anyrender_svg::render_svg_tree(&mut *self.0, &tree, transform);
true
}
}

/// SVG trees parsed by anyrender_svg 0.12's usvg, by text — frame's own
/// cache holds the other usvg's.
fn svg_tree(svg_text: &str) -> Option<std::sync::Arc<anyrender_svg_0_12::usvg::Tree>> {
/// SVG trees parsed by anyrender_svg's usvg, by text.
fn svg_tree(svg_text: &str) -> Option<std::sync::Arc<anyrender_svg::usvg::Tree>> {
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, LazyLock, Mutex};
static TREES: LazyLock<Mutex<HashMap<u64, Option<Arc<anyrender_svg_0_12::usvg::Tree>>>>> =
static TREES: LazyLock<Mutex<HashMap<u64, Option<Arc<anyrender_svg::usvg::Tree>>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
let mut hasher = std::collections::hash_map::DefaultHasher::new();
svg_text.hash(&mut hasher);
Expand All @@ -177,12 +183,9 @@ fn svg_tree(svg_text: &str) -> Option<std::sync::Arc<anyrender_svg_0_12::usvg::T
trees
.entry(key)
.or_insert_with(|| {
anyrender_svg_0_12::usvg::Tree::from_str(
svg_text,
&anyrender_svg_0_12::usvg::Options::default(),
)
.ok()
.map(Arc::new)
anyrender_svg::usvg::Tree::from_str(svg_text, &anyrender_svg::usvg::Options::default())
.ok()
.map(Arc::new)
})
.clone()
}
Loading
Loading