From cc5f2f57adb78aaad3fb2ef6ac0fcc0ebdd053d7 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 7 Oct 2026 12:08:33 +0200 Subject: [PATCH] :arrow_up: Upgrade skia-safe to 0.153.3 with wasm SIMD (#12148) Point SKIA_BINARIES_URL at the 0.153.3 simd prebuild and adapt fonts, path stroke expansion, and gradients to the new API. --- render-wasm/Cargo.lock | 67 ++++++++++++++++---------- render-wasm/Cargo.toml | 2 +- render-wasm/_build_env | 2 +- render-wasm/lint | 3 +- render-wasm/src/render.rs | 3 +- render-wasm/src/render/fonts.rs | 13 +++-- render-wasm/src/render/images.rs | 5 +- render-wasm/src/render/strokes.rs | 4 +- render-wasm/src/render/ui/rulers.rs | 14 +++--- render-wasm/src/shapes/fills.rs | 33 ++++++++----- render-wasm/src/shapes/stroke_paths.rs | 7 +-- render-wasm/src/shapes/text_paths.rs | 2 +- render-wasm/test | 2 +- render-wasm/watch_test | 2 +- 14 files changed, 96 insertions(+), 63 deletions(-) diff --git a/render-wasm/Cargo.lock b/render-wasm/Cargo.lock index 05fbd20e6a..a5c139b930 100644 --- a/render-wasm/Cargo.lock +++ b/render-wasm/Cargo.lock @@ -23,6 +23,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bezier-rs" version = "0.4.0" @@ -49,7 +55,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.106", ] [[package]] @@ -334,7 +340,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -393,7 +399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "910d41a655dac3b764f1ade94821093d3610248694320cd072303a8eedcf221d" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.106", ] [[package]] @@ -425,9 +431,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -437,9 +443,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -448,15 +454,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "render" version = "0.1.0" dependencies = [ - "base64", + "base64 0.22.1", "bezier-rs", "gl", "glam", @@ -525,7 +531,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -563,9 +569,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "skia-bindings" -version = "0.93.1" +version = "0.153.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2359f7e30c9da3f322f8ca3d4ec0abbc12a40035ce758309db0cdab07b5d4476" +checksum = "eb325787c91b3f6c34a7c6f2b8a2297b20292d733ca53a08c233e5028aec49af" dependencies = [ "bindgen", "cc", @@ -580,11 +586,11 @@ dependencies = [ [[package]] name = "skia-safe" -version = "0.93.1" +version = "0.153.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e837ea9d531c9efee8f980bfcdb7226b21db0285b0c3171d8be745829f940" +checksum = "c61cb643dac43067f3eb76c866635a74377ac0abca0ff2590674e1e295514c3d" dependencies = [ - "base64", + "base64 0.23.1", "bitflags", "percent-encoding", "skia-bindings", @@ -593,13 +599,13 @@ dependencies = [ [[package]] name = "skia-svg-macros" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044dd2233c9717a74f75197f3e7f0a966db2127c0ffb5e05013b480a9b75b2c7" +checksum = "fec38aa7a389d249e1ce357b050ae5bdb302fd9943661fa650f39cf6ea51be97" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.6", ] [[package]] @@ -614,10 +620,21 @@ dependencies = [ ] [[package]] -name = "tar" -version = "0.4.42" +name = "syn" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -654,7 +671,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", ] [[package]] @@ -740,7 +757,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -762,7 +779,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/render-wasm/Cargo.toml b/render-wasm/Cargo.toml index 8c0be81049..7b2951a2a1 100644 --- a/render-wasm/Cargo.toml +++ b/render-wasm/Cargo.toml @@ -26,7 +26,7 @@ gl = "0.14.0" glam = "0.24.2" indexmap = "2.7.1" macros = { path = "macros" } -skia-safe = { version = "0.93.1", default-features = false, features = [ +skia-safe = { version = "0.153.3", default-features = false, features = [ "gl", "svg", "textlayout", diff --git a/render-wasm/_build_env b/render-wasm/_build_env index dc03bebd89..2673c81ba4 100644 --- a/render-wasm/_build_env +++ b/render-wasm/_build_env @@ -20,7 +20,7 @@ fi export BUILD_NAME="${BUILD_NAME:-render-wasm}" export CARGO_BUILD_TARGET=${CARGO_BUILD_TARGET:-"wasm32-unknown-emscripten"}; export CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-"target/$RENDER_TARGET"}; -export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.93.1/skia-binaries-319323662b1685a112f5-wasm32-unknown-emscripten-gl-svg-textlayout-binary-cache-webp-pdf-simd.tar.gz"} +export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.153.3/skia-binaries-b7f043e0b1e2a850e702-wasm32-unknown-emscripten-gl-svg-textlayout-binary-cache-webp-pdf-simd.tar.gz"} # 256 MB of initial heap to perform less # initial calls to memory grow. diff --git a/render-wasm/lint b/render-wasm/lint index 0489e1040d..aeacf782c4 100755 --- a/render-wasm/lint +++ b/render-wasm/lint @@ -10,8 +10,7 @@ fi . ./_build_env frontend -export CARGO_BUILD_TARGET=${CARGO_BUILD_TARGET:-"wasm32-unknown-emscripten"}; -export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.93.1/skia-binaries-319323662b1685a112f5-wasm32-unknown-emscripten-gl-svg-textlayout-binary-cache-webp-pdf-simd.tar.gz"} +export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.153.3/skia-binaries-b7f043e0b1e2a850e702-wasm32-unknown-emscripten-gl-svg-textlayout-binary-cache-webp-pdf-simd.tar.gz"} ALLOWED_RULES="-D static_mut_refs" diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 65f7665dec..44835b88f4 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -767,8 +767,9 @@ impl RenderState { paint.set_style(skia::PaintStyle::Stroke); paint.set_stroke_width(stroke_outset * 2.0); - let mut outline = skia::Path::default(); + let mut outline = skia::PathBuilder::new(); if skia::path_utils::fill_path_with_paint(&base, &paint, &mut outline, None, None) { + let outline = outline.detach(); if let Some(united) = base.op(&outline, skia::PathOp::Union) { return united; } diff --git a/render-wasm/src/render/fonts.rs b/render-wasm/src/render/fonts.rs index 78ae886064..b1a09531cc 100644 --- a/render-wasm/src/render/fonts.rs +++ b/render-wasm/src/render/fonts.rs @@ -33,7 +33,12 @@ pub struct FontStore { impl FontStore { pub fn try_new() -> Result { - let font_mgr = FontMgr::new(); + // custom_empty: FreeType-backed manager with no system scan. FontMgr::new() + // calls SkFontMgr_NewSystem, which on wasm tries to opendir font paths and + // aborts when Emscripten is built with -sFILESYSTEM=0 (exporter + browser). + let font_mgr = FontMgr::custom_empty().ok_or(Error::CriticalError( + "Failed to create empty FontMgr".to_string(), + ))?; let font_provider = load_default_provider(&font_mgr); let mut font_collection = skia::textlayout::FontCollection::new(); font_collection.set_default_font_manager(FontMgr::from(font_provider.clone()), None); @@ -47,7 +52,7 @@ impl FontStore { let debug_font = skia::Font::new(debug_typeface, 12.0); let ui_typeface = font_mgr - .new_from_data(UI_FONT_BYTES, None) + .new_from_data(skia::Data::new_copy(UI_FONT_BYTES), None) .ok_or(Error::CriticalError("Failed to load UI font".to_string()))?; let ui_font = skia::Font::new(ui_typeface, 12.0); @@ -96,7 +101,7 @@ impl FontStore { let typeface = self .font_mgr - .new_from_data(font_data, None) + .new_from_data(skia::Data::new_copy(font_data), None) .ok_or(Error::CriticalError( "Failed to create typeface".to_string(), ))?; @@ -265,7 +270,7 @@ fn load_default_provider(font_mgr: &FontMgr) -> skia::textlayout::TypefaceFontPr let family = FontFamily::new(default_font_uuid(), 400, FontStyle::Normal); let font = font_mgr - .new_from_data(DEFAULT_FONT_BYTES, None) + .new_from_data(skia::Data::new_copy(DEFAULT_FONT_BYTES), None) .expect("Failed to load font"); font_provider.register_typeface(font, family.alias().as_str()); diff --git a/render-wasm/src/render/images.rs b/render-wasm/src/render/images.rs index 4b1ce66754..5b59b3620a 100644 --- a/render-wasm/src/render/images.rs +++ b/render-wasm/src/render/images.rs @@ -186,8 +186,9 @@ const DEFAULT_SVG_SIZE: f32 = 512.0; fn parse_svg(raw_data: &[u8]) -> Option<(skia::svg::Dom, Size)> { // An empty font manager: elements inside SVG image fills won't // resolve typefaces. Wire the render state's font provider here if that - // ever becomes a need. - let font_mgr = skia::FontMgr::new(); + // ever becomes a need. Must be custom_empty (not FontMgr::new): NewSystem + // scans font dirs and aborts under -sFILESYSTEM=0. + let font_mgr = skia::FontMgr::custom_empty()?; let mut dom = skia::svg::Dom::from_bytes(raw_data, font_mgr).ok()?; let mut size = dom.root().intrinsic_size(); diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 48efeada92..8a89015034 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -439,7 +439,7 @@ fn expand_side_patterns( line_pb.detach() }; - let mut outline = skia::Path::default(); + let mut outline = skia::PathBuilder::new(); if skia::path_utils::fill_path_with_paint( &line_path, &stamp_paint, @@ -447,7 +447,7 @@ fn expand_side_patterns( None, None, ) { - pb.add_path(&outline); + pb.add_path(&outline.detach(), None); expanded = true; } } diff --git a/render-wasm/src/render/ui/rulers.rs b/render-wasm/src/render/ui/rulers.rs index 16bf6f4acc..18e9d0a4e3 100644 --- a/render-wasm/src/render/ui/rulers.rs +++ b/render-wasm/src/render/ui/rulers.rs @@ -364,14 +364,12 @@ fn draw_mask(ctx: &RenderCtx, rect: Rect, axis: MaskAxis, fade_to_end: bool) { MaskAxis::Horizontal => (rect.right, rect.top), MaskAxis::Vertical => (rect.left, rect.bottom), }; - let shader = skia::gradient_shader::linear( - ((rect.left, rect.top), end), - &colors[..], - Some(&offsets[..]), - skia::TileMode::Clamp, - None, - None, - ); + let colors4f: Vec = colors.iter().map(|c| skia::Color4f::from(*c)).collect(); + let gradient_colors = + skia::gradient::Colors::new(&colors4f, Some(&offsets[..]), skia::TileMode::Clamp, None); + let gradient = + skia::gradient::Gradient::new(gradient_colors, skia::gradient::Interpolation::default()); + let shader = skia::shaders::linear_gradient(((rect.left, rect.top), end), &gradient, None); let mut paint = Paint::default(); paint.set_anti_alias(false); paint.set_style(PaintStyle::Fill); diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 1faeb54185..94d33e7dd2 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -74,14 +74,20 @@ impl Gradient { rect.left + self.end.0 * rect.width(), rect.top + self.end.1 * rect.height(), ); - skia::gradient_shader::linear( - (start, end), - self.colors.as_slice(), + let colors4f: Vec = self + .colors + .iter() + .map(|c| skia::Color4f::from(*c)) + .collect(); + let colors = skia::gradient::Colors::new( + &colors4f, Some(self.offsets.as_slice()), skia::TileMode::Clamp, None, - None, - ) + ); + let gradient = + skia::gradient::Gradient::new(colors, skia::gradient::Interpolation::default()); + skia::shaders::linear_gradient((start, end), &gradient, None) } pub fn to_radial_shader(&self, rect: &Rect) -> Option { @@ -106,15 +112,20 @@ impl Gradient { transform.pre_scale((self.width * rect.width() / rect.height(), 1.), None); transform.pre_translate((-center.x, -center.y)); - skia::gradient_shader::radial( - center, - distance, - self.colors.as_slice(), + let colors4f: Vec = self + .colors + .iter() + .map(|c| skia::Color4f::from(*c)) + .collect(); + let colors = skia::gradient::Colors::new( + &colors4f, Some(self.offsets.as_slice()), skia::TileMode::Clamp, None, - Some(&transform), - ) + ); + let gradient = + skia::gradient::Gradient::new(colors, skia::gradient::Interpolation::default()); + skia::shaders::radial_gradient((center, distance), &gradient, Some(&transform)) } } diff --git a/render-wasm/src/shapes/stroke_paths.rs b/render-wasm/src/shapes/stroke_paths.rs index 52dc58e762..bcdbdbeac4 100644 --- a/render-wasm/src/shapes/stroke_paths.rs +++ b/render-wasm/src/shapes/stroke_paths.rs @@ -48,7 +48,7 @@ pub fn stroke_to_path( paint.set_stroke_cap(cap); } - let mut stroke_outline = skia::Path::default(); + let mut stroke_outline = skia::PathBuilder::new(); let success = skia::path_utils::fill_path_with_paint( &transformed_shape_path, &paint, @@ -60,6 +60,7 @@ pub fn stroke_to_path( if !success { return None; } + let stroke_outline = stroke_outline.detach(); // For inner/outer strokes, use boolean ops to clip // the 2×-width stroke outline to the correct region. @@ -198,9 +199,9 @@ fn stroke_region(path: &skia::Path, width: f32) -> Option { paint.set_style(skia::PaintStyle::Stroke); paint.set_stroke_width(width); - let mut outline = skia::Path::default(); + let mut outline = skia::PathBuilder::new(); skia::path_utils::fill_path_with_paint(path, &paint, &mut outline, None, None) - .then_some(outline) + .then(|| outline.detach()) } /// Filled geometry of a single stroke cap, matching what `handle_stroke_caps` diff --git a/render-wasm/src/shapes/text_paths.rs b/render-wasm/src/shapes/text_paths.rs index 777181a3a4..44cc21a115 100644 --- a/render-wasm/src/shapes/text_paths.rs +++ b/render-wasm/src/shapes/text_paths.rs @@ -68,7 +68,7 @@ impl TextPaths { let Some(glyph_path) = font.get_path(*glyph) else { continue; }; - builder.add_path(&glyph_path.with_offset(run_origin + *position)); + builder.add_path(&glyph_path.with_offset(run_origin + *position), None); has_glyphs = true; } diff --git a/render-wasm/test b/render-wasm/test index a94cf8025d..c27d5ea533 100755 --- a/render-wasm/test +++ b/render-wasm/test @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -x -export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.93.1/skia-binaries-319323662b1685a112f5-x86_64-unknown-linux-gnu-gl-svg-textlayout-binary-cache-webp.tar.gz"} +export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.153.3/skia-binaries-b7f043e0b1e2a850e702-x86_64-unknown-linux-gnu-gl-svg-textlayout-binary-cache-webp-pdf.tar.gz"} export CARGO_BUILD_TARGET=${CARGO_BUILD_TARGET:-"x86_64-unknown-linux-gnu"}; _SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) diff --git a/render-wasm/watch_test b/render-wasm/watch_test index 798eb84bf0..27106e49bd 100755 --- a/render-wasm/watch_test +++ b/render-wasm/watch_test @@ -1,7 +1,7 @@ #!/usr/bin/env bash _SCRIPT_DIR=$(dirname $0); -export SKIA_BINARIES_URL="https://github.com/penpot/skia-binaries/releases/download/0.93.1/skia-binaries-319323662b1685a112f5-x86_64-unknown-linux-gnu-gl-svg-textlayout-binary-cache-webp.tar.gz" +export SKIA_BINARIES_URL="https://github.com/penpot/skia-binaries/releases/download/0.153.3/skia-binaries-b7f043e0b1e2a850e702-x86_64-unknown-linux-gnu-gl-svg-textlayout-binary-cache-webp-pdf.tar.gz" pushd $_SCRIPT_DIR; cargo watch -x "test --bin render_wasm -- --show-output"