diff --git a/Cargo.lock b/Cargo.lock index f7f6ede07a..c9952d4471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,8 +295,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "anyrender" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00e1af5c6ac9b1e8ed8e431529a1024b7386d5385dce2a83494da3d39a6bab5" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "kurbo", "peniko", @@ -308,8 +307,7 @@ dependencies = [ [[package]] name = "anyrender_serialize" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa3ca49ef372eb4477a70623b77f95d71eb5bdc40d71433459dc2dd9e00add82" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "image", @@ -327,8 +325,7 @@ dependencies = [ [[package]] name = "anyrender_skia" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fd22e1d5b834ec2da56a85f9f27c578e00335f5b135a753ae8963828f0d460" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "ash", @@ -355,8 +352,7 @@ dependencies = [ [[package]] name = "anyrender_svg" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a06a9cab20563ae5b392239db09d42222820fd3706fe483db7d3cb25546c24" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "image", @@ -368,8 +364,7 @@ dependencies = [ [[package]] name = "anyrender_vello" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1301d68b8d7a9333c242225bb026dc08f70a5b0ab096087866c06ceab227de5" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -387,8 +382,7 @@ dependencies = [ [[package]] name = "anyrender_vello_cpu" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb392bf738b1e50c50fc82824ff3f875f15632717ba732fe68e2fe6e1011e6c" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "bytemuck", @@ -406,8 +400,7 @@ dependencies = [ [[package]] name = "anyrender_vello_hybrid" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acface501bfa9d5b2abf9413ee5728ded41bd7f9c63b24162b9ea54f431d7e4f" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6187,8 +6180,7 @@ dependencies = [ [[package]] name = "pixels_window_renderer" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320b195ebb5b4513d7c538fc7686ebd4ee3fa1f945e5995c70338f6d78fb07de" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -7358,9 +7350,9 @@ dependencies = [ [[package]] name = "skia-bindings" -version = "0.99.0" +version = "0.153.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2d1c3ebd697c0cbded0145e9204a38fa6b268446051b7196d0a096414ea7f3" +checksum = "eb325787c91b3f6c34a7c6f2b8a2297b20292d733ca53a08c233e5028aec49af" dependencies = [ "bindgen", "cc", @@ -7375,14 +7367,32 @@ dependencies = [ [[package]] name = "skia-safe" -version = "0.99.0" +version = "0.153.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f512ac418a64194842dd05566320805dad1c957c521039db2486fd6368865bc" +checksum = "c61cb643dac43067f3eb76c866635a74377ac0abca0ff2590674e1e295514c3d" dependencies = [ "bitflags 2.13.1", "skia-bindings", ] +[[package]] +name = "skia_paint_bench" +version = "0.1.0" +dependencies = [ + "anyrender", + "anyrender_skia", + "blitz-dom", + "blitz-html", + "blitz-net", + "blitz-paint", + "blitz-traits", + "objc2 0.6.4", + "objc2-metal 0.3.2", + "reqwest", + "skia-safe", + "tokio", +] + [[package]] name = "skrifa" version = "0.44.0" @@ -7542,8 +7552,7 @@ dependencies = [ [[package]] name = "softbuffer_window_renderer" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ce2a1c61716161a23530df2c507a7dd3f132a149789c5700f24f2893a3a877" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "anyrender", "debug_timer 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -9466,8 +9475,7 @@ dependencies = [ [[package]] name = "wgpu_context" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6c2553d5b114dfd941fa3372c7df1e1cde7bd81800e89aa13b7e3af2e4be9b" +source = "git+https://github.com/DioxusLabs/anyrender?branch=devin%2F1790696745-skia-graphite-metal#ba464c2d3527bd375e55c456d17368e14d3bb3e8" dependencies = [ "futures-intrusive", "wgpu", diff --git a/Cargo.toml b/Cargo.toml index eee2191719..f878590614 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = [ "examples/seven_guis", "examples/todomvc", "examples/wgpu_texture", + "examples/skia_paint_bench", "examples/wasm_hello", ] exclude = ["sites"] @@ -115,7 +116,7 @@ skrifa = { version = "0.44", default-features = false, features = [ # AnyRender anyrender = { version = "0.13.0" } anyrender_serialize = { version = "0.7.0" } -anyrender_skia = { version = "0.11.0" } +anyrender_skia = { version = "0.11.0", default-features = false } anyrender_vello = { version = "0.14.0" } anyrender_vello_cpu = { version = "0.17.0" } anyrender_vello_hybrid = { version = "0.10.0" } @@ -288,6 +289,17 @@ wgpu = { workspace = true } wgpu_context = { workspace = true } rustc-hash = { workspace = true } +# Testing https://github.com/DioxusLabs/anyrender/pull/103 (Skia Graphite) +[patch.crates-io] +anyrender = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +anyrender_serialize = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +anyrender_skia = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +anyrender_vello = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +anyrender_vello_cpu = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +anyrender_vello_hybrid = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +anyrender_svg = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } +wgpu_context = { git = "https://github.com/DioxusLabs/anyrender", branch = "devin/1790696745-skia-graphite-metal" } + # [patch.crates-io] # anyrender = { path = "../anyrender/crates/anyrender" } # anyrender_skia = { path = "../anyrender/crates/anyrender_skia" } diff --git a/apps/browser/Cargo.toml b/apps/browser/Cargo.toml index 593263974f..e75ad62a3b 100644 --- a/apps/browser/Cargo.toml +++ b/apps/browser/Cargo.toml @@ -18,6 +18,7 @@ capture = ["dep:anyrender_serialize", "dep:blitz-paint", "dep:png", "dep:peniko" vello = ["dioxus-native/vello"] hybrid = ["dioxus-native/vello-hybrid"] skia = ["dioxus-native/skia"] +skia-graphite = ["dioxus-native/skia-graphite"] skia-raster = ["skia-raster-pixels"] skia-raster-pixels = ["dioxus-native/skia-pixels"] skia-raster-softbuffer = ["dioxus-native/skia-softbuffer"] diff --git a/apps/readme/Cargo.toml b/apps/readme/Cargo.toml index 31cd2c1273..964e08c07c 100644 --- a/apps/readme/Cargo.toml +++ b/apps/readme/Cargo.toml @@ -15,9 +15,10 @@ default = ["hybrid", "comrak", "floats", "scrollbars"] # Renderers gpu = ["dep:anyrender_vello"] hybrid = ["dep:anyrender_vello_hybrid"] -skia = ["dep:anyrender_skia"] -skia-pixels = ["dep:anyrender_skia", "anyrender_skia/pixels_window_renderer"] -skia-softbuffer = ["dep:anyrender_skia", "anyrender_skia/softbuffer_window_renderer"] +skia = ["dep:anyrender_skia", "anyrender_skia/ganesh"] +skia-graphite = ["dep:anyrender_skia", "anyrender_skia/graphite"] +skia-pixels = ["dep:anyrender_skia", "anyrender_skia/ganesh", "anyrender_skia/pixels_window_renderer"] +skia-softbuffer = ["dep:anyrender_skia", "anyrender_skia/ganesh", "anyrender_skia/softbuffer_window_renderer"] cpu = ["cpu-pixels"] cpu-pixels = ["cpu-base", "anyrender_vello_cpu/pixels_window_renderer"] cpu-softbuffer = ["cpu-base", "anyrender_vello_cpu/softbuffer_window_renderer"] diff --git a/apps/readme/src/main.rs b/apps/readme/src/main.rs index 43103f20bb..11c2187b1e 100644 --- a/apps/readme/src/main.rs +++ b/apps/readme/src/main.rs @@ -15,7 +15,7 @@ mod markdown { pub(crate) use pulldown_cmark::*; } -#[cfg(feature = "skia")] +#[cfg(any(feature = "skia", feature = "skia-graphite"))] use anyrender_skia::SkiaWindowRenderer as WindowRenderer; #[cfg(feature = "skia-pixels")] use anyrender_skia::raster::SkiaRasterWindowRenderer as WindowRenderer; diff --git a/examples/skia_paint_bench/Cargo.toml b/examples/skia_paint_bench/Cargo.toml new file mode 100644 index 0000000000..e023a7a355 --- /dev/null +++ b/examples/skia_paint_bench/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "skia_paint_bench" +version = "0.1.0" +edition = "2024" +license.workspace = true +publish = false + +[features] +default = ["ganesh"] +# Pick one: rust-skia only publishes prebuilt binaries with a single GPU engine enabled. +ganesh = ["anyrender_skia/ganesh"] +graphite = ["anyrender_skia/graphite"] + +[target.'cfg(target_os = "macos")'.dependencies] +anyrender = { workspace = true } +anyrender_skia = { workspace = true } +blitz-dom = { workspace = true, features = ["default"] } +blitz-html = { workspace = true } +blitz-net = { workspace = true } +blitz-paint = { workspace = true, features = ["default"] } +blitz-traits = { workspace = true } +reqwest = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +skia-safe = { version = "0.153.3", features = ["metal"] } +objc2 = "0.6" +objc2-metal = { version = "0.3", default-features = false, features = ["std", "MTLDevice", "MTLCommandQueue"] } diff --git a/examples/skia_paint_bench/src/main.rs b/examples/skia_paint_bench/src/main.rs new file mode 100644 index 0000000000..18e2ae6bd9 --- /dev/null +++ b/examples/skia_paint_bench/src/main.rs @@ -0,0 +1,230 @@ +//! Benchmark painting a page with anyrender_skia on Metal, offscreen. +//! +//! Uses Ganesh by default, or Graphite with `--no-default-features --features graphite`. +//! +//! Usage: skia_paint_bench [width] [height] [scale] [iters] + +#[cfg(not(target_os = "macos"))] +fn main() { + eprintln!("skia_paint_bench only supports macOS (Metal)"); +} + +#[cfg(target_os = "macos")] +#[tokio::main] +async fn main() { + bench::main().await +} + +#[cfg(target_os = "macos")] +mod bench { + use anyrender::PaintScene as _; + use anyrender_skia::{SkiaSceneCache, SkiaScenePainter}; + use blitz_dom::DocumentConfig; + use blitz_html::HtmlDocument; + use blitz_net::Provider; + use blitz_paint::paint_scene; + use blitz_traits::shell::{ColorScheme, Viewport}; + use objc2::rc::Retained; + use objc2_metal::{MTLCreateSystemDefaultDevice, MTLDevice as _}; + use reqwest::Url; + use skia_safe::ImageInfo; + use std::ffi::c_void; + use std::sync::Arc; + use std::time::Instant; + + const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"; + const WARMUP_ITERS: usize = 5; + + fn print_stats(label: &str, times_us: &mut [u128]) { + times_us.sort_unstable(); + let min = times_us[0]; + let max = times_us[times_us.len() - 1]; + let median = times_us[times_us.len() / 2]; + let mean: u128 = times_us.iter().sum::() / times_us.len() as u128; + println!("{label}: min {min}us / median {median}us / mean {mean}us / max {max}us"); + } + + /// Run `iters` iterations of a two-phase (encode, rasterize) frame and report + /// per-phase timing statistics. + fn bench(iters: usize, mut frame: impl FnMut() -> (u128, u128)) { + for _ in 0..WARMUP_ITERS { + frame(); + } + + let mut encode_us = Vec::with_capacity(iters); + let mut raster_us = Vec::with_capacity(iters); + let mut total_us = Vec::with_capacity(iters); + for _ in 0..iters { + let (encode, raster) = frame(); + encode_us.push(encode); + raster_us.push(raster); + total_us.push(encode + raster); + } + + print_stats("paint_scene", &mut encode_us); + print_stats("rasterize ", &mut raster_us); + print_stats("total ", &mut total_us); + } + + pub async fn main() { + let mut args = std::env::args().skip(1); + let url_string = args.next().unwrap_or_else(|| "https://servo.org".into()); + let width: u32 = args.next().and_then(|a| a.parse().ok()).unwrap_or(1366); + let height: u32 = args.next().and_then(|a| a.parse().ok()).unwrap_or(768); + let scale: f64 = args.next().and_then(|a| a.parse().ok()).unwrap_or(2.0); + let iters: usize = args.next().and_then(|a| a.parse().ok()).unwrap_or(100); + + let url = Url::parse(&url_string) + .unwrap_or_else(|_| Url::parse(&format!("https://{url_string}")).expect("Invalid url")); + let url_string = url.to_string(); + + let html = match url.scheme() { + "file" => String::from_utf8(std::fs::read(url.path()).unwrap()).unwrap(), + _ => reqwest::Client::new() + .get(url) + .header("User-Agent", USER_AGENT) + .send() + .await + .unwrap() + .text() + .await + .unwrap(), + }; + + let net = Arc::new(Provider::new(None)); + let render_width = (width as f64 * scale) as u32; + let render_height = (height as f64 * scale) as u32; + let mut document = HtmlDocument::from_html( + &html, + DocumentConfig { + base_url: Some(url_string.clone()), + net_provider: Some(Arc::clone(&net) as _), + viewport: Some(Viewport::new( + render_width, + render_height, + scale as f32, + ColorScheme::Light, + )), + ..Default::default() + }, + ); + loop { + document.resolve(0.0); + if net.is_empty() { + break; + } + } + document.as_mut().resolve(0.0); + + let engine = if cfg!(feature = "graphite") { + "graphite" + } else { + "ganesh" + }; + println!( + "Loaded {url_string} at {width}x{height}@{scale}x; running {iters} paint iterations (skia {engine})" + ); + + let device = MTLCreateSystemDefaultDevice().expect("no Metal device"); + let queue = device + .newCommandQueue() + .expect("unable to create command queue"); + let device_ptr = Retained::as_ptr(&device) as *mut c_void; + let queue_ptr = Retained::as_ptr(&queue) as *mut c_void; + let image_info = + ImageInfo::new_n32_premul((render_width as i32, render_height as i32), None); + let mut cache = SkiaSceneCache::default(); + + #[cfg(feature = "graphite")] + { + use skia_safe::gpu::{ + Mipmapped, + graphite::{self, InsertRecordingInfo, mtl}, + }; + + let backend = unsafe { mtl::BackendContext::new(device_ptr, queue_ptr) }; + let mut context = + mtl::context_factory::make_metal(&backend, None).expect("no Graphite context"); + let mut recorder = context.make_recorder(None).expect("no Graphite recorder"); + let mut surface = graphite::surfaces::render_target( + &mut recorder, + &image_info, + Mipmapped::No, + None, + None, + ) + .expect("unable to create surface"); + + bench(iters, || { + let encode_start = Instant::now(); + { + let mut painter = + SkiaScenePainter::new_graphite(surface.canvas(), &mut cache, &mut recorder); + painter.reset(); + paint_scene( + &mut painter, + document.as_mut(), + scale, + render_width, + render_height, + 0, + 0, + ); + } + cache.next_gen(); + let encode = encode_start.elapsed().as_micros(); + + let raster_start = Instant::now(); + let mut recording = recorder.snap().expect("unable to snap recording"); + context.insert_recording(&InsertRecordingInfo::new(&mut recording)); + context.submit_and_wait(); + (encode, raster_start.elapsed().as_micros()) + }); + } + + #[cfg(not(feature = "graphite"))] + { + use skia_safe::gpu::{self, Budgeted, SurfaceOrigin, mtl}; + + let backend = unsafe { + mtl::BackendContext::new(device_ptr as mtl::Handle, queue_ptr as mtl::Handle) + }; + let mut context = + gpu::direct_contexts::make_metal(&backend, None).expect("no Ganesh context"); + let mut surface = gpu::surfaces::render_target( + &mut context, + Budgeted::Yes, + &image_info, + None, + SurfaceOrigin::TopLeft, + None, + false, + None, + ) + .expect("unable to create surface"); + + bench(iters, || { + let encode_start = Instant::now(); + { + let mut painter = SkiaScenePainter::new(surface.canvas(), &mut cache); + painter.reset(); + paint_scene( + &mut painter, + document.as_mut(), + scale, + render_width, + render_height, + 0, + 0, + ); + } + cache.next_gen(); + let encode = encode_start.elapsed().as_micros(); + + let raster_start = Instant::now(); + context.flush_submit_and_sync_cpu(); + (encode, raster_start.elapsed().as_micros()) + }); + } + } +} diff --git a/packages/dioxus-native/Cargo.toml b/packages/dioxus-native/Cargo.toml index ff4367c4e7..e9c25ed1df 100644 --- a/packages/dioxus-native/Cargo.toml +++ b/packages/dioxus-native/Cargo.toml @@ -33,10 +33,11 @@ data-uri = ["blitz-shell/data-uri"] # Renderer features vello = ["dep:anyrender_vello", "dep:wgpu_context"] vello-hybrid = ["dep:anyrender_vello_hybrid", "dep:wgpu_context"] -skia = ["dep:anyrender_skia"] +skia = ["dep:anyrender_skia", "anyrender_skia/ganesh"] +skia-graphite = ["dep:anyrender_skia", "anyrender_skia/graphite"] skia-pixels = ["skia-raster-base", "anyrender_skia/pixels_window_renderer"] skia-softbuffer = ["skia-raster-base", "anyrender_skia/softbuffer_window_renderer"] -skia-raster-base = ["dep:anyrender_skia"] +skia-raster-base = ["dep:anyrender_skia", "anyrender_skia/ganesh"] vello-cpu = ["vello-cpu-pixels"] vello-cpu-pixels = ["vello-cpu-base", "anyrender_vello_cpu/pixels_window_renderer"] vello-cpu-softbuffer = ["vello-cpu-base", "anyrender_vello_cpu/softbuffer_window_renderer"] diff --git a/packages/dioxus-native/src/dioxus_renderer.rs b/packages/dioxus-native/src/dioxus_renderer.rs index f404f63506..67007bcd6e 100644 --- a/packages/dioxus-native/src/dioxus_renderer.rs +++ b/packages/dioxus-native/src/dioxus_renderer.rs @@ -14,7 +14,7 @@ cfg_if::cfg_if! { }; } else if #[cfg(feature = "vello-cpu-base")] { use anyrender_vello_cpu::VelloCpuWindowRenderer as InnerRenderer; - } else if #[cfg(feature = "skia")] { + } else if #[cfg(any(feature = "skia", feature = "skia-graphite"))] { use anyrender_skia::SkiaWindowRenderer as InnerRenderer; } else if #[cfg(feature = "skia-raster-base")] { use anyrender_skia::raster::SkiaRasterWindowRenderer as InnerRenderer;