diff --git a/Cargo.lock b/Cargo.lock index eef58c1decb..2c6c718a122 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1087,6 +1087,7 @@ dependencies = [ "stylo", "unicode-script", "vello", + "vello_cpu", "webrender_api", ] @@ -1335,6 +1336,9 @@ name = "color" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698" +dependencies = [ + "bytemuck", +] [[package]] name = "color_quant" @@ -1355,7 +1359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2464,6 +2468,14 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fearless_simd" +version = "0.2.0" +source = "git+https://github.com/linebender/fearless_simd?rev=3d1a77c#3d1a77cfb4515c0da307d50dc782c08840b90c70" +dependencies = [ + "bytemuck", +] + [[package]] name = "filetime" version = "0.2.25" @@ -4833,7 +4845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -6220,6 +6232,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9" dependencies = [ + "bytemuck", "color", "kurbo", "smallvec", @@ -9135,7 +9148,7 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "vello" version = "0.5.0" -source = "git+https://github.com/linebender/vello?rev=ecf6b282dba01e5dc50e9463b87b6baeccdb3094#ecf6b282dba01e5dc50e9463b87b6baeccdb3094" +source = "git+https://github.com/linebender/vello?rev=daf940230a24cbb123a458b6de95721af47aef98#daf940230a24cbb123a458b6de95721af47aef98" dependencies = [ "bytemuck", "futures-intrusive", @@ -9150,10 +9163,33 @@ dependencies = [ "wgpu", ] +[[package]] +name = "vello_common" +version = "0.0.1" +source = "git+https://github.com/linebender/vello?rev=daf940230a24cbb123a458b6de95721af47aef98#daf940230a24cbb123a458b6de95721af47aef98" +dependencies = [ + "bytemuck", + "fearless_simd", + "log", + "peniko", + "png", + "skrifa", + "smallvec", +] + +[[package]] +name = "vello_cpu" +version = "0.0.1" +source = "git+https://github.com/linebender/vello?rev=daf940230a24cbb123a458b6de95721af47aef98#daf940230a24cbb123a458b6de95721af47aef98" +dependencies = [ + "bytemuck", + "vello_common", +] + [[package]] name = "vello_encoding" version = "0.5.0" -source = "git+https://github.com/linebender/vello?rev=ecf6b282dba01e5dc50e9463b87b6baeccdb3094#ecf6b282dba01e5dc50e9463b87b6baeccdb3094" +source = "git+https://github.com/linebender/vello?rev=daf940230a24cbb123a458b6de95721af47aef98#daf940230a24cbb123a458b6de95721af47aef98" dependencies = [ "bytemuck", "guillotiere", @@ -9165,7 +9201,7 @@ dependencies = [ [[package]] name = "vello_shaders" version = "0.5.0" -source = "git+https://github.com/linebender/vello?rev=ecf6b282dba01e5dc50e9463b87b6baeccdb3094#ecf6b282dba01e5dc50e9463b87b6baeccdb3094" +source = "git+https://github.com/linebender/vello?rev=daf940230a24cbb123a458b6de95721af47aef98#daf940230a24cbb123a458b6de95721af47aef98" dependencies = [ "bytemuck", "log", @@ -9885,7 +9921,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 699190c2b94..d52806e6f64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -168,7 +168,8 @@ unicode-segmentation = "1.12.0" url = "2.5" urlpattern = "0.3" uuid = { version = "1.12.1", features = ["v4"] } -vello = { git = "https://github.com/linebender/vello", rev = "ecf6b282dba01e5dc50e9463b87b6baeccdb3094" } +vello = { git = "https://github.com/linebender/vello", rev = "daf940230a24cbb123a458b6de95721af47aef98" } +vello_cpu = { git = "https://github.com/linebender/vello", rev = "daf940230a24cbb123a458b6de95721af47aef98" } webdriver = "0.53.0" webgpu_traits = { path = "components/shared/webgpu" } webpki-roots = "1.0" diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 1df66df404c..89a2a310ee9 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -13,6 +13,7 @@ path = "lib.rs" [features] vello = ["dep:vello", "dep:pollster", "dep:futures-intrusive", "dep:peniko"] +vello_cpu = ["dep:vello_cpu", "dep:peniko"] [dependencies] app_units = { workspace = true } @@ -38,5 +39,6 @@ unicode-script = { workspace = true } webrender_api = { workspace = true } servo_config = { path = "../config" } vello = { workspace = true, optional = true } +vello_cpu = { workspace = true, optional = true } pollster = { version = "0.4", optional = true } futures-intrusive = { version = "0.5", optional = true } diff --git a/components/canvas/canvas_paint_thread.rs b/components/canvas/canvas_paint_thread.rs index 09e34bd3ed7..3f96af28179 100644 --- a/components/canvas/canvas_paint_thread.rs +++ b/components/canvas/canvas_paint_thread.rs @@ -296,6 +296,8 @@ enum Canvas { Raqote(CanvasData), #[cfg(feature = "vello")] Vello(CanvasData), + #[cfg(feature = "vello_cpu")] + VelloCPU(CanvasData), } impl Canvas { @@ -308,6 +310,10 @@ impl Canvas { if servo_config::pref!(dom_canvas_vello_enabled) { return Self::Vello(CanvasData::new(size, compositor_api, font_context)); } + #[cfg(feature = "vello_cpu")] + if servo_config::pref!(dom_canvas_vello_cpu_enabled) { + return Self::VelloCPU(CanvasData::new(size, compositor_api, font_context)); + } Self::Raqote(CanvasData::new(size, compositor_api, font_context)) } @@ -316,6 +322,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.image_key(), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.image_key(), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.image_key(), } } @@ -324,6 +332,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.pop_clip(), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.pop_clip(), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.pop_clip(), } } @@ -366,6 +376,19 @@ impl Canvas { composition_options, transform, ), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.fill_text( + text, + x, + y, + max_width, + is_rtl, + style, + text_options, + shadow_options, + composition_options, + transform, + ), } } @@ -385,6 +408,10 @@ impl Canvas { Canvas::Vello(canvas_data) => { canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform) }, + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => { + canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform) + }, } } @@ -415,6 +442,15 @@ impl Canvas { composition_options, transform, ), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.stroke_rect( + rect, + style, + line_options, + shadow_options, + composition_options, + transform, + ), } } @@ -445,6 +481,15 @@ impl Canvas { composition_options, transform, ), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.fill_path( + path, + fill_rule, + style, + shadow_options, + composition_options, + transform, + ), } } @@ -475,6 +520,15 @@ impl Canvas { composition_options, transform, ), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.stroke_path( + path, + style, + line_options, + shadow_options, + composition_options, + transform, + ), } } @@ -483,6 +537,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect, transform), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.clear_rect(rect, transform), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.clear_rect(rect, transform), } } @@ -516,6 +572,16 @@ impl Canvas { composition_options, transform, ), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.draw_image( + snapshot, + dest_rect, + source_rect, + smoothing_enabled, + shadow_options, + composition_options, + transform, + ), } } @@ -524,6 +590,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.read_pixels(read_rect), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.read_pixels(read_rect), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.read_pixels(read_rect), } } @@ -532,6 +600,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.measure_text(text, text_options), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.measure_text(text, text_options), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.measure_text(text, text_options), } } @@ -540,6 +610,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.clip_path(path, fill_rule, transform), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.clip_path(path, fill_rule, transform), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.clip_path(path, fill_rule, transform), } } @@ -548,6 +620,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.put_image_data(snapshot, rect), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.put_image_data(snapshot, rect), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.put_image_data(snapshot, rect), } } @@ -556,6 +630,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.update_image_rendering(), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.update_image_rendering(), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.update_image_rendering(), } } @@ -564,6 +640,8 @@ impl Canvas { Canvas::Raqote(canvas_data) => canvas_data.recreate(size), #[cfg(feature = "vello")] Canvas::Vello(canvas_data) => canvas_data.recreate(size), + #[cfg(feature = "vello_cpu")] + Canvas::VelloCPU(canvas_data) => canvas_data.recreate(size), } } } diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs index 00f0b1d5a8e..bbdc8bbab67 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -5,11 +5,13 @@ #![deny(unsafe_code)] mod backend; -#[cfg(feature = "vello")] +#[cfg(any(feature = "vello", feature = "vello_cpu"))] mod peniko_conversions; mod raqote_backend; #[cfg(feature = "vello")] mod vello_backend; +#[cfg(feature = "vello_cpu")] +mod vello_cpu_backend; pub mod canvas_data; pub mod canvas_paint_thread; diff --git a/components/canvas/vello_cpu_backend.rs b/components/canvas/vello_cpu_backend.rs new file mode 100644 index 00000000000..d2fc3f36bae --- /dev/null +++ b/components/canvas/vello_cpu_backend.rs @@ -0,0 +1,452 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::sync::Arc; + +use canvas_traits::canvas::{ + CompositionOptions, FillOrStrokeStyle, FillRule, LineOptions, Path, ShadowOptions, +}; +use compositing_traits::SerializableImageData; +use euclid::default::{Point2D, Rect, Size2D, Transform2D}; +use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods as _}; +use ipc_channel::ipc::IpcSharedMemory; +use kurbo::Shape; +use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; +use range::Range; +use vello_cpu::{kurbo, peniko}; +use webrender_api::{ImageDescriptor, ImageDescriptorFlags}; + +use crate::backend::{Convert, GenericDrawTarget}; +use crate::canvas_data::{Filter, TextRun}; + +thread_local! { + /// The shared font cache used by all canvases that render on a thread. It would be nicer + /// to have a global cache, but it looks like font-kit uses a per-thread FreeType, so + /// in order to ensure that fonts are particular to a thread we have to make our own + /// cache thread local as well. + static SHARED_FONT_CACHE: RefCell> = RefCell::default(); +} + +pub(crate) struct VelloCPUDrawTarget { + /// Because this is stateful context + /// caller cannot assume anything about transform, paint, stroke, + /// so it should provide it's own used by each command + /// but it can assume paint_transform to be identity + /// and fill rule to be `peniko::Fill::NonZero` + /// + /// This is because paint_transform is rarely set, + /// so it's cheaper to always reset it after use. + ctx: vello_cpu::RenderContext, + pixmap: vello_cpu::Pixmap, + clips: Vec, +} + +impl VelloCPUDrawTarget { + fn with_composition( + &mut self, + composition_options: &CompositionOptions, + f: impl FnOnce(&mut Self), + ) { + self.ctx.push_layer( + None, + Some(composition_options.composition_operation.convert()), + Some(composition_options.alpha as f32), + None, + ); + f(self); + self.ctx.pop_layer(); + } + + fn ignore_clips(&mut self, f: impl FnOnce(&mut Self)) { + // pop all clip layers + for _ in &self.clips { + self.ctx.pop_layer(); + } + f(self); + // push all clip layers back + for path in &self.clips { + self.ctx.push_clip_layer(&path.0); + } + } + + fn pixmap(&mut self) -> &[u8] { + self.ignore_clips(|self_| { + self_.ctx.flush(); + self_ + .ctx + .render_to_pixmap(&mut self_.pixmap, vello_cpu::RenderMode::OptimizeQuality) + }); + + self.pixmap.data_as_u8_slice() + } + + fn size(&self) -> Size2D { + Size2D::new(self.ctx.width(), self.ctx.height()).cast() + } +} + +impl GenericDrawTarget for VelloCPUDrawTarget { + type SourceSurface = Arc; + + fn new(size: Size2D) -> Self { + let size = size.cast(); + Self { + ctx: vello_cpu::RenderContext::new(size.width, size.height), + pixmap: vello_cpu::Pixmap::new(size.width, size.height), + clips: Vec::new(), + } + } + + fn clear_rect(&mut self, rect: &Rect, transform: Transform2D) { + let rect: kurbo::Rect = rect.cast().into(); + let mut clip_path = rect.to_path(0.1); + clip_path.apply_affine(transform.cast().into()); + let blend_mode = peniko::Compose::Clear; + self.ctx.push_layer( + Some(&clip_path.to_path(0.1)), + Some(blend_mode.into()), + None, + None, + ); + self.ctx.pop_layer(); + } + + fn copy_surface( + &mut self, + surface: Self::SourceSurface, + source: Rect, + destination: Point2D, + ) { + let destination: kurbo::Point = destination.cast::().into(); + let rect = kurbo::Rect::from_origin_size(destination, source.size.cast()); + self.ctx.set_transform(kurbo::Affine::IDENTITY); + self.ignore_clips(|self_| { + // Clipped blending does not work correctly: + // https://github.com/linebender/vello/issues/1119 + //self_.push_layer(Some(rect.to_path(0.1)), Some(peniko::Compose::Copy.into()), None, None); + + self_.ctx.set_paint(vello_cpu::Image { + source: vello_cpu::ImageSource::Pixmap(surface), + x_extend: peniko::Extend::Pad, + y_extend: peniko::Extend::Pad, + quality: peniko::ImageQuality::Low, + }); + self_.ctx.fill_rect(&rect); + + //self_.ctx.pop_layer(); + }); + } + + fn create_similar_draw_target(&self, size: &Size2D) -> Self { + Self::new(size.cast()) + } + + fn draw_surface( + &mut self, + surface: Self::SourceSurface, + dest: Rect, + source: Rect, + filter: Filter, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + let scale_up = dest.size.width > source.size.width || dest.size.height > source.size.height; + self.with_composition(&composition_options, move |self_| { + self_.ctx.set_transform(transform.cast().into()); + self_.ctx.set_paint(vello_cpu::Image { + source: vello_cpu::ImageSource::Pixmap(surface), + x_extend: peniko::Extend::Pad, + y_extend: peniko::Extend::Pad, + // we should only do bicubic when scaling up + quality: if scale_up { + filter.convert() + } else { + peniko::ImageQuality::Low + }, + }); + self_.ctx.set_paint_transform( + kurbo::Affine::translate((dest.origin.x, dest.origin.y)).pre_scale_non_uniform( + dest.size.width / source.size.width, + dest.size.height / source.size.height, + ), + ); + self_.ctx.fill_rect(&dest.into()); + self_.ctx.reset_paint_transform(); + }) + } + + fn draw_surface_with_shadow( + &self, + _surface: Self::SourceSurface, + _dest: &Point2D, + _shadow_options: ShadowOptions, + _composition_options: CompositionOptions, + ) { + log::warn!("no support for drawing shadows"); + /* + We will need to do some changes to support drawing shadows with vello, as current abstraction is made for azure. + In vello we do not need new draw target (we will use layers) and we need to pass whole rect. + offsets will be applied to rect directly. shadow blur will be passed directly to let backend do transforms. + */ + //self_.scene.draw_blurred_rounded_rect(self_.transform, rect, color, 0.0, sigma); + } + + fn fill( + &mut self, + path: &Path, + fill_rule: FillRule, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + let paint: vello_cpu::PaintType = style.convert(); + self.with_composition(&composition_options, |self_| { + self_.ctx.set_transform(transform.cast().into()); + self_.ctx.set_fill_rule(fill_rule.convert()); + self_.ctx.set_paint(paint); + self_.ctx.fill_path(&path.0); + }); + self.ctx.set_fill_rule(peniko::Fill::NonZero); + } + + fn fill_text( + &mut self, + text_runs: Vec, + start: Point2D, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + let style: vello_cpu::PaintType = style.convert(); + self.ctx.set_paint(style); + self.ctx.set_transform(transform.cast().into()); + self.with_composition(&composition_options, |self_| { + let mut advance = 0.; + for run in text_runs.iter() { + let glyphs = &run.glyphs; + + let template = &run.font.template; + + SHARED_FONT_CACHE.with(|font_cache| { + let identifier = template.identifier(); + if !font_cache.borrow().contains_key(&identifier) { + font_cache.borrow_mut().insert( + identifier.clone(), + peniko::Font::new( + peniko::Blob::from(run.font.data().as_ref().to_vec()), + identifier.index(), + ), + ); + } + + let font_cache = font_cache.borrow(); + let Some(font) = font_cache.get(&identifier) else { + return; + }; + + self_ + .ctx + .glyph_run(font) + .font_size(run.font.descriptor.pt_size.to_f32_px()) + .fill_glyphs( + glyphs + .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len())) + .map(|glyph| { + let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); + let x = advance + start.x + glyph_offset.x.to_f32_px(); + let y = start.y + glyph_offset.y.to_f32_px(); + advance += glyph.advance().to_f32_px(); + vello_cpu::Glyph { + id: glyph.id(), + x, + y, + } + }), + ); + }); + } + }) + } + + fn fill_rect( + &mut self, + rect: &Rect, + style: FillOrStrokeStyle, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + let paint: vello_cpu::PaintType = style.convert(); + self.with_composition(&composition_options, |self_| { + self_.ctx.set_transform(transform.cast().into()); + self_.ctx.set_paint(paint); + self_.ctx.fill_rect(&rect.cast().into()); + }) + } + + fn get_size(&self) -> Size2D { + self.size().cast() + } + + fn pop_clip(&mut self) { + if self.clips.pop().is_some() { + self.ctx.pop_layer(); + } + } + + fn push_clip(&mut self, path: &Path, fill_rule: FillRule, transform: Transform2D) { + self.ctx.set_transform(transform.cast().into()); + let mut path = path.clone(); + path.transform(transform.cast()); + self.ctx.set_fill_rule(fill_rule.convert()); + self.ctx.push_clip_layer(&path.0); + self.clips.push(path); + self.ctx.set_fill_rule(peniko::Fill::NonZero); + } + + fn push_clip_rect(&mut self, rect: &Rect) { + let mut path = Path::new(); + let rect = rect.cast(); + path.rect( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + ); + self.push_clip(&path, FillRule::Nonzero, Transform2D::identity()); + } + + fn stroke( + &mut self, + path: &Path, + style: FillOrStrokeStyle, + line_options: LineOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + let paint: vello_cpu::PaintType = style.convert(); + self.with_composition(&composition_options, |self_| { + self_.ctx.set_transform(transform.cast().into()); + self_.ctx.set_paint(paint); + self_.ctx.set_stroke(line_options.convert()); + self_.ctx.stroke_path(&path.0); + }) + } + + fn stroke_rect( + &mut self, + rect: &Rect, + style: FillOrStrokeStyle, + line_options: LineOptions, + composition_options: CompositionOptions, + transform: Transform2D, + ) { + let paint: vello_cpu::PaintType = style.convert(); + self.with_composition(&composition_options, |self_| { + self_.ctx.set_transform(transform.cast().into()); + self_.ctx.set_paint(paint); + self_.ctx.set_stroke(line_options.convert()); + self_.ctx.stroke_rect(&rect.cast().into()); + }) + } + + fn image_descriptor_and_serializable_data( + &mut self, + ) -> (ImageDescriptor, SerializableImageData) { + let image_desc = ImageDescriptor { + format: webrender_api::ImageFormat::RGBA8, + size: self.size().cast().cast_unit(), + stride: None, + offset: 0, + flags: ImageDescriptorFlags::empty(), + }; + let data = SerializableImageData::Raw(IpcSharedMemory::from_bytes(self.pixmap())); + (image_desc, data) + } + + fn snapshot(&mut self) -> pixels::Snapshot { + Snapshot::from_vec( + self.size().cast(), + SnapshotPixelFormat::RGBA, + SnapshotAlphaMode::Transparent { + premultiplied: true, + }, + self.pixmap().to_vec(), + ) + } + + fn surface(&mut self) -> Self::SourceSurface { + self.pixmap(); // sync pixmap + Arc::new(vello_cpu::Pixmap::from_parts( + self.pixmap.clone().take(), + self.pixmap.width(), + self.pixmap.height(), + )) + } + + fn create_source_surface_from_data(&self, data: Snapshot) -> Option { + Some(snapshot_as_pixmap(data)) + } +} + +fn snapshot_as_pixmap(data: Snapshot) -> Arc { + let size = data.size().cast(); + let (data, _, _) = data.to_vec( + Some(SnapshotAlphaMode::Transparent { + premultiplied: true, + }), + Some(SnapshotPixelFormat::RGBA), + ); + Arc::new(vello_cpu::Pixmap::from_parts( + bytemuck::cast_vec(data), + size.width, + size.height, + )) +} + +impl Convert for FillOrStrokeStyle { + fn convert(self) -> vello_cpu::PaintType { + use canvas_traits::canvas::FillOrStrokeStyle::*; + match self { + Color(absolute_color) => vello_cpu::PaintType::Solid(absolute_color.convert()), + LinearGradient(style) => { + let start = kurbo::Point::new(style.x0, style.y0); + let end = kurbo::Point::new(style.x1, style.y1); + let mut gradient = peniko::Gradient::new_linear(start, end); + gradient.stops = style.stops.convert(); + vello_cpu::PaintType::Gradient(gradient) + }, + RadialGradient(style) => { + let center1 = kurbo::Point::new(style.x0, style.y0); + let center2 = kurbo::Point::new(style.x1, style.y1); + let mut gradient = peniko::Gradient::new_two_point_radial( + center1, + style.r0 as f32, + center2, + style.r1 as f32, + ); + gradient.stops = style.stops.convert(); + vello_cpu::PaintType::Gradient(gradient) + }, + Surface(surface_style) => { + let pixmap = snapshot_as_pixmap(surface_style.surface_data.to_owned()); + vello_cpu::PaintType::Image(vello_cpu::Image { + source: vello_cpu::ImageSource::Pixmap(pixmap), + x_extend: if surface_style.repeat_x { + peniko::Extend::Repeat + } else { + peniko::Extend::Pad + }, + y_extend: if surface_style.repeat_y { + peniko::Extend::Repeat + } else { + peniko::Extend::Pad + }, + quality: peniko::ImageQuality::Low, + }) + }, + } + } +} diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 0ce2d76eb5f..fe375b618db 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -78,6 +78,8 @@ pub struct Preferences { pub dom_canvas_text_enabled: bool, /// Uses vello as canvas backend pub dom_canvas_vello_enabled: bool, + /// Uses vello_cpu as canvas backend + pub dom_canvas_vello_cpu_enabled: bool, pub dom_clipboardevent_enabled: bool, pub dom_composition_event_enabled: bool, pub dom_cookiestore_enabled: bool, @@ -258,6 +260,7 @@ impl Preferences { dom_canvas_capture_enabled: false, dom_canvas_text_enabled: true, dom_canvas_vello_enabled: false, + dom_canvas_vello_cpu_enabled: false, dom_clipboardevent_enabled: true, dom_composition_event_enabled: false, dom_cookiestore_enabled: false, diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index 0a615f7479a..f7b6e73cb66 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -17,6 +17,7 @@ default = [] tracing = ["dep:tracing"] webgpu = ["script_traits/webgpu"] vello = ["canvas/vello"] +vello_cpu = ["canvas/vello_cpu"] [dependencies] background_hang_monitor = { path = "../background_hang_monitor" } diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index b7a07b58913..4033b1ad489 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -63,6 +63,7 @@ webgpu = [ "constellation_traits/webgpu", ] vello = ["constellation/vello"] +vello_cpu = ["constellation/vello_cpu"] [dependencies] background_hang_monitor = { path = "../background_hang_monitor" } diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml index 9a6a42b7866..ff1f292d8ff 100644 --- a/ports/servoshell/Cargo.toml +++ b/ports/servoshell/Cargo.toml @@ -53,6 +53,7 @@ webgl_backtrace = ["libservo/webgl_backtrace"] webgpu = ["libservo/webgpu"] webxr = ["libservo/webxr"] vello = ["libservo/vello"] +vello_cpu = ["libservo/vello_cpu"] [dependencies] cfg-if = { workspace = true } diff --git a/python/servo/try_parser.py b/python/servo/try_parser.py index bbaf8fe2c6f..c05a6f6ed8f 100644 --- a/python/servo/try_parser.py +++ b/python/servo/try_parser.py @@ -125,6 +125,19 @@ def handle_preset(s: str) -> Optional[JobConfig]: unit_tests=False, number_of_wpt_chunks=2, ) + elif any(word in s for word in ["vello-cpu", "vello_cpu"]): + return JobConfig( + "Vello-CPU WPT", + Workflow.LINUX, + wpt=True, + wpt_args=" ".join( + [ + "--subsuite-file ./tests/wpt/vello_cpu_canvas_subsuite.json", + "--subsuite vello_cpu_canvas", + ] + ), + build_args="--features 'vello_cpu'", + ) elif any(word in s for word in ["vello"]): return JobConfig( "Vello WPT", diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.clear.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.clear.html.ini new file mode 100644 index 00000000000..2308d9a7c9a --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.clear.html.ini @@ -0,0 +1,4 @@ +[2d.composite.clip.clear.html] + [fill() does not affect pixels outside the clip region.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.copy.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.copy.html.ini new file mode 100644 index 00000000000..8c00e22528e --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.copy.html.ini @@ -0,0 +1,4 @@ +[2d.composite.clip.copy.html] + [fill() does not affect pixels outside the clip region.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.destination-atop.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.destination-atop.html.ini new file mode 100644 index 00000000000..765e7767ce4 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.destination-atop.html.ini @@ -0,0 +1,4 @@ +[2d.composite.clip.destination-atop.html] + [fill() does not affect pixels outside the clip region.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.destination-in.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.destination-in.html.ini new file mode 100644 index 00000000000..9bfe54a0950 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.destination-in.html.ini @@ -0,0 +1,4 @@ +[2d.composite.clip.destination-in.html] + [fill() does not affect pixels outside the clip region.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.source-in.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.source-in.html.ini new file mode 100644 index 00000000000..c1aaa465e69 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.source-in.html.ini @@ -0,0 +1,4 @@ +[2d.composite.clip.source-in.html] + [fill() does not affect pixels outside the clip region.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.source-out.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.source-out.html.ini new file mode 100644 index 00000000000..f17b0e37338 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.clip.source-out.html.ini @@ -0,0 +1,4 @@ +[2d.composite.clip.source-out.html] + [fill() does not affect pixels outside the clip region.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.copy.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.copy.html.ini new file mode 100644 index 00000000000..9866506fd48 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.copy.html.ini @@ -0,0 +1,4 @@ +[2d.composite.uncovered.pattern.copy.html] + [Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.destination-atop.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.destination-atop.html.ini new file mode 100644 index 00000000000..dd893014410 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.destination-atop.html.ini @@ -0,0 +1,4 @@ +[2d.composite.uncovered.pattern.destination-atop.html] + [Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.destination-in.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.destination-in.html.ini new file mode 100644 index 00000000000..5fcaf9e164f --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.destination-in.html.ini @@ -0,0 +1,4 @@ +[2d.composite.uncovered.pattern.destination-in.html] + [Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.source-in.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.source-in.html.ini new file mode 100644 index 00000000000..776c24cbc17 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.source-in.html.ini @@ -0,0 +1,4 @@ +[2d.composite.uncovered.pattern.source-in.html] + [Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.source-out.html.ini b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.source-out.html.ini new file mode 100644 index 00000000000..2dcf65aba03 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/compositing/2d.composite.uncovered.pattern.source-out.html.ini @@ -0,0 +1,4 @@ +[2d.composite.uncovered.pattern.source-out.html] + [Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.clip.html.ini b/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.clip.html.ini new file mode 100644 index 00000000000..cba3e72d6a1 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.clip.html.ini @@ -0,0 +1,4 @@ +[2d.clearRect.clip.html] + [clearRect is affected by clipping regions] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.shadow.html.ini b/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.shadow.html.ini new file mode 100644 index 00000000000..8e379529c99 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.shadow.html.ini @@ -0,0 +1,4 @@ +[2d.clearRect.shadow.html] + [clearRect does not draw shadows] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.zero.html.ini b/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.zero.html.ini new file mode 100644 index 00000000000..44b93d83457 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/drawing-rectangles-to-the-canvas/2d.clearRect.zero.html.ini @@ -0,0 +1,4 @@ +[2d.clearRect.zero.html] + [clearRect of zero pixels has no effect] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.html.ini index 45482761b5d..9b8c5c40112 100644 --- a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.html.ini +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.html.ini @@ -3,3 +3,4 @@ bug: https://github.com/linebender/vello/issues/1056 expected: if subsuite == "vello_canvas": FAIL + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.cone.front.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.cone.front.html.ini new file mode 100644 index 00000000000..c36ccbdf128 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.cone.front.html.ini @@ -0,0 +1,4 @@ +[2d.gradient.radial.cone.front.html] + [Canvas test: 2d.gradient.radial.cone.front] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.cone.top.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.cone.top.html.ini new file mode 100644 index 00000000000..96d94db7802 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.cone.top.html.ini @@ -0,0 +1,4 @@ +[2d.gradient.radial.cone.top.html] + [Canvas test: 2d.gradient.radial.cone.top] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.inside2.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.inside2.html.ini new file mode 100644 index 00000000000..9ad9e462939 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.inside2.html.ini @@ -0,0 +1,4 @@ +[2d.gradient.radial.inside2.html] + [Canvas test: 2d.gradient.radial.inside2] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini new file mode 100644 index 00000000000..7296e891037 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.inside3.html.ini @@ -0,0 +1,4 @@ +[2d.gradient.radial.inside3.html] + [Canvas test: 2d.gradient.radial.inside3] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside1.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside1.html.ini new file mode 100644 index 00000000000..24714c7ae88 --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside1.html.ini @@ -0,0 +1,4 @@ +[2d.gradient.radial.outside1.html] + [Canvas test: 2d.gradient.radial.outside1] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside3.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside3.html.ini index 4786f083031..9205277c4dc 100644 --- a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside3.html.ini +++ b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.gradient.radial.outside3.html.ini @@ -2,3 +2,4 @@ [Canvas test: 2d.gradient.radial.outside3] expected: if subsuite == "": FAIL + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/line-styles/2d.line.cross.html.ini b/tests/wpt/meta/html/canvas/element/line-styles/2d.line.cross.html.ini index 9fc308d6baa..f838bfd07f1 100644 --- a/tests/wpt/meta/html/canvas/element/line-styles/2d.line.cross.html.ini +++ b/tests/wpt/meta/html/canvas/element/line-styles/2d.line.cross.html.ini @@ -3,3 +3,4 @@ bug: https://github.com/linebender/vello/issues/1063 expected: if subsuite == "vello_canvas": FAIL + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.scale.1.html.ini b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.scale.1.html.ini index ed3b9b0e199..be3f56ccf83 100644 --- a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.scale.1.html.ini +++ b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.arc.scale.1.html.ini @@ -2,3 +2,4 @@ [Non-uniformly scaled arcs are the right shape] expected: if subsuite == "vello_canvas": FAIL + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.rect.selfintersect.html.ini b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.rect.selfintersect.html.ini index 5ac5b9c8bcc..b18bf07387f 100644 --- a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.rect.selfintersect.html.ini +++ b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.rect.selfintersect.html.ini @@ -1,5 +1,6 @@ [2d.path.rect.selfintersect.html] [Canvas test: 2d.path.rect.selfintersect] - bug: https://github.com/linebender/vello/issues/1063#issuecomment-2998084736 + bug: https://github.com/linebender/vello/issues/1063 #issuecomment-2998084736 expected: if subsuite == "vello_canvas": FAIL + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.stroke.scale2.html.ini b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.stroke.scale2.html.ini index ecde0bfb25d..a19300881ff 100644 --- a/tests/wpt/meta/html/canvas/element/path-objects/2d.path.stroke.scale2.html.ini +++ b/tests/wpt/meta/html/canvas/element/path-objects/2d.path.stroke.scale2.html.ini @@ -2,3 +2,4 @@ [Stroke line widths are scaled by the current transformation matrix] expected: if subsuite == "vello_canvas": FAIL + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.alpha.html.ini b/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.alpha.html.ini new file mode 100644 index 00000000000..52a87f20e0c --- /dev/null +++ b/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.alpha.html.ini @@ -0,0 +1,4 @@ +[2d.imageData.put.alpha.html] + [putImageData() puts non-solid image data correctly] + expected: + if subsuite == "vello_cpu_canvas": FAIL diff --git a/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.unchanged.html.ini b/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.unchanged.html.ini index bcc95019e98..124b05bd597 100644 --- a/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.unchanged.html.ini +++ b/tests/wpt/meta/html/canvas/element/pixel-manipulation/2d.imageData.put.unchanged.html.ini @@ -1,4 +1,3 @@ [2d.imageData.put.unchanged.html] [putImageData(getImageData(...), ...) has no effect] - expected: - if subsuite == "": FAIL + expected: FAIL diff --git a/tests/wpt/vello_cpu_canvas_subsuite.json b/tests/wpt/vello_cpu_canvas_subsuite.json new file mode 100644 index 00000000000..a01f8c2b766 --- /dev/null +++ b/tests/wpt/vello_cpu_canvas_subsuite.json @@ -0,0 +1,8 @@ +{ + "vello_cpu_canvas": { + "config": { + "binary_args": ["--pref", "dom_canvas_vello_cpu_enabled"] + }, + "include": ["/html/canvas/element"] + } +}