canvas: Add vello backend (#36821)

Add vello backend by implementing Backend traits in canvas crate (so
this lives in canvas_paint_thread - embedded process). Current
implementation uses normal wgpu, so we block on GPU work. Vello backend
is gated behind `vello` feature and `dom_canvas_vello_enabled` pref.

Feature-wise this backend is on on par with raqote (sometimes better
sometimes worse), but performance wise it's worse.

## Known vello problems:

- image roundtrip does not work (fixed in
https://github.com/linebender/vello/pull/974)
- https://github.com/linebender/vello/issues/1066 (fixed)
- clip layers are not working properly:
https://github.com/linebender/vello/issues/1061
  - `/html/canvas/element/pixel-manipulation/2d.imageData.put.*`
  - `/html/canvas/element/path-objects/2d.path.clip.intersect.html`
- https://github.com/linebender/vello/issues/1056
-
`/html/canvas/element/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.html`
- `kurbo::Cap::Butt` is defect (only visible with big lineWidth)
https://github.com/linebender/vello/issues/1063
  - `/html/canvas/element/line-styles/2d.line.cross.html`
  - `/html/canvas/element/line-styles/2d.line.miter.acute.html`
- other lack of strong correct problems
(https://github.com/linebender/vello/issues/1063#issuecomment-2998084736):
  - `/html/canvas/element/path-objects/2d.path.rect.selfintersect.html`
- There is currently no way to do put image properly in vello as we
would need to ignore all clips and other stuff (we try to work around
this on best effort basis)
https://github.com/linebender/vello/issues/1088
  - `/html/canvas/element/pixel-manipulation/2d.imageData.put.*`
- precision problems
  - `/html/canvas/element/path-objects/2d.path.stroke.scale2.html`
  - `/html/canvas/element/path-objects/2d.path.arc.scale.1.html`

## Known servo problems

- bad performance due to blocking on GPU work
  - some get/put intensive tests `TIMEOUT`
- proper shadow support (non-blocker as we already are living without it
now)
- support for rect shadow is there but unimplemented currently as that's
the state in raqote

Testing: `mach try vello` will run normal WPT (with raqote) +
vello_canvas subsuite that runs only on `/html/canvas/element`. All
subsuite expectations are stored separately.
Fixes: #36823
Fixes: #35230

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-07-26 06:53:10 +02:00 committed by GitHub
parent c2ed599eb1
commit d678901122
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 1779 additions and 165 deletions

138
Cargo.lock generated
View file

@ -1071,16 +1071,20 @@ dependencies = [
"euclid",
"font-kit",
"fonts",
"futures-intrusive",
"ipc-channel",
"kurbo",
"log",
"net_traits",
"pixels",
"pollster",
"range",
"raqote",
"servo_arc",
"servo_config",
"stylo",
"unicode-script",
"vello",
"webrender_api",
]
@ -1324,6 +1328,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "color"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698"
[[package]]
name = "color_quant"
version = "1.1.0"
@ -2798,6 +2808,17 @@ dependencies = [
"futures-util",
]
[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
]
[[package]]
name = "futures-io"
version = "0.3.31"
@ -3480,6 +3501,16 @@ dependencies = [
"system-deps",
]
[[package]]
name = "guillotiere"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
dependencies = [
"euclid",
"svg_fmt",
]
[[package]]
name = "h2"
version = "0.3.27"
@ -6181,6 +6212,17 @@ dependencies = [
"synstructure",
]
[[package]]
name = "peniko"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9"
dependencies = [
"color",
"kurbo",
"smallvec",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -6386,6 +6428,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "pollster"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "polyval"
version = "0.6.2"
@ -6821,6 +6869,12 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "renderdoc-sys"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "resvg"
version = "0.45.1"
@ -7805,6 +7859,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "skrifa"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607"
dependencies = [
"bytemuck",
"read-fonts",
]
[[package]]
name = "slab"
version = "0.4.10"
@ -9066,6 +9130,48 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "vello"
version = "0.5.0"
source = "git+https://github.com/linebender/vello?rev=ecf6b282dba01e5dc50e9463b87b6baeccdb3094#ecf6b282dba01e5dc50e9463b87b6baeccdb3094"
dependencies = [
"bytemuck",
"futures-intrusive",
"log",
"peniko",
"png",
"skrifa",
"static_assertions",
"thiserror 2.0.12",
"vello_encoding",
"vello_shaders",
"wgpu",
]
[[package]]
name = "vello_encoding"
version = "0.5.0"
source = "git+https://github.com/linebender/vello?rev=ecf6b282dba01e5dc50e9463b87b6baeccdb3094#ecf6b282dba01e5dc50e9463b87b6baeccdb3094"
dependencies = [
"bytemuck",
"guillotiere",
"peniko",
"skrifa",
"smallvec",
]
[[package]]
name = "vello_shaders"
version = "0.5.0"
source = "git+https://github.com/linebender/vello?rev=ecf6b282dba01e5dc50e9463b87b6baeccdb3094#ecf6b282dba01e5dc50e9463b87b6baeccdb3094"
dependencies = [
"bytemuck",
"log",
"naga",
"thiserror 2.0.12",
"vello_encoding",
]
[[package]]
name = "version-compare"
version = "0.2.0"
@ -9593,6 +9699,34 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
[[package]]
name = "wgpu"
version = "25.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9"
dependencies = [
"arrayvec",
"bitflags 2.9.1",
"cfg_aliases",
"document-features",
"hashbrown",
"js-sys",
"log",
"naga",
"parking_lot",
"portable-atomic",
"profiling",
"raw-window-handle",
"smallvec",
"static_assertions",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"wgpu-core",
"wgpu-hal",
"wgpu-types",
]
[[package]]
name = "wgpu-core"
version = "25.0.1"
@ -9611,7 +9745,9 @@ dependencies = [
"naga",
"once_cell",
"parking_lot",
"portable-atomic",
"profiling",
"raw-window-handle",
"rustc-hash 1.1.0",
"serde",
"smallvec",
@ -9683,9 +9819,11 @@ dependencies = [
"objc",
"ordered-float",
"parking_lot",
"portable-atomic",
"profiling",
"range-alloc",
"raw-window-handle",
"renderdoc-sys",
"smallvec",
"thiserror 2.0.12",
"wasm-bindgen",

View file

@ -166,6 +166,7 @@ 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" }
webdriver = "0.53.0"
webgpu_traits = { path = "components/shared/webgpu" }
webpki-roots = "1.0"

View file

@ -11,6 +11,9 @@ rust-version.workspace = true
name = "canvas"
path = "lib.rs"
[features]
vello = ["dep:vello", "dep:pollster", "dep:futures-intrusive"]
[dependencies]
app_units = { workspace = true }
canvas_traits = { workspace = true }
@ -31,3 +34,7 @@ servo_arc = { workspace = true }
stylo = { workspace = true }
unicode-script = { workspace = true }
webrender_api = { workspace = true }
servo_config = { path = "../config" }
vello = { workspace = true, optional = true }
pollster = { version = "0.4", optional = true }
futures-intrusive = { version = "0.5", optional = true }

View file

@ -110,10 +110,9 @@ impl CanvasPaintThread {
let canvas_id = self.next_canvas_id;
self.next_canvas_id.0 += 1;
let canvas_data =
CanvasData::new(size, self.compositor_api.clone(), self.font_context.clone());
let image_key = canvas_data.image_key();
self.canvases.insert(canvas_id, Canvas::Raqote(canvas_data));
let canvas = Canvas::new(size, self.compositor_api.clone(), self.font_context.clone());
let image_key = canvas.image_key();
self.canvases.insert(canvas_id, canvas);
(canvas_id, image_key)
}
@ -283,14 +282,39 @@ impl CanvasPaintThread {
}
}
#[allow(clippy::large_enum_variant)]
enum Canvas {
Raqote(CanvasData<raqote::DrawTarget>),
#[cfg(feature = "vello")]
Vello(CanvasData<crate::vello_backend::VelloDrawTarget>),
}
impl Canvas {
fn new(
size: Size2D<u64>,
compositor_api: CrossProcessCompositorApi,
font_context: Arc<FontContext>,
) -> Self {
#[cfg(feature = "vello")]
if servo_config::pref!(dom_canvas_vello_enabled) {
return Self::Vello(CanvasData::new(size, compositor_api, font_context));
}
Self::Raqote(CanvasData::new(size, compositor_api, font_context))
}
fn image_key(&self) -> ImageKey {
match self {
Canvas::Raqote(canvas_data) => canvas_data.image_key(),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.image_key(),
}
}
fn pop_clip(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.pop_clip(),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.pop_clip(),
}
}
@ -320,6 +344,19 @@ impl Canvas {
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.fill_text(
text,
x,
y,
max_width,
is_rtl,
style,
text_options,
shadow_options,
composition_options,
transform,
),
}
}
@ -335,6 +372,10 @@ impl Canvas {
Canvas::Raqote(canvas_data) => {
canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
},
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => {
canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
},
}
}
@ -356,6 +397,15 @@ impl Canvas {
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.stroke_rect(
rect,
style,
line_options,
shadow_options,
composition_options,
transform,
),
}
}
@ -371,6 +421,10 @@ impl Canvas {
Canvas::Raqote(canvas_data) => {
canvas_data.fill_path(path, style, shadow_options, composition_options, transform)
},
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => {
canvas_data.fill_path(path, style, shadow_options, composition_options, transform)
},
}
}
@ -392,12 +446,23 @@ impl Canvas {
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.stroke_path(
path,
style,
line_options,
shadow_options,
composition_options,
transform,
),
}
}
fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect, transform),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.clear_rect(rect, transform),
}
}
@ -421,42 +486,64 @@ impl Canvas {
composition_options,
transform,
),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.draw_image(
snapshot,
dest_rect,
source_rect,
smoothing_enabled,
shadow_options,
composition_options,
transform,
),
}
}
fn read_pixels(&mut self, read_rect: Option<Rect<u32>>) -> Snapshot {
match self {
Canvas::Raqote(canvas_data) => canvas_data.read_pixels(read_rect),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.read_pixels(read_rect),
}
}
fn measure_text(&mut self, text: String, text_options: TextOptions) -> TextMetrics {
match self {
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),
}
}
fn clip_path(&mut self, path: &Path, transform: Transform2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path, transform),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.clip_path(path, transform),
}
}
fn put_image_data(&mut self, snapshot: Snapshot, rect: Rect<u32>) {
match self {
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),
}
}
fn update_image_rendering(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.update_image_rendering(),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.update_image_rendering(),
}
}
fn recreate(&mut self, size: Option<Size2D<u64>>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.recreate(size),
#[cfg(feature = "vello")]
Canvas::Vello(canvas_data) => canvas_data.recreate(size),
}
}
}

View file

@ -6,6 +6,8 @@
mod backend;
mod raqote_backend;
#[cfg(feature = "vello")]
mod vello_backend;
pub mod canvas_data;
pub mod canvas_paint_thread;

View file

@ -0,0 +1,752 @@
/* 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/. */
//! Vello implementation of 2D canvas backend.
//!
//! Vello only encodes commands for GPU, then runs rendering when
//! image is explicitly requested. This requires to copy image
//! from texture to buffer, then download buffer to CPU
//! (where we also need to un pad it).
//!
//! All Vello images are in no alpha premultiplied RGBA8 pixel format.
use std::cell::RefCell;
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::rc::Rc;
use canvas_traits::canvas::{
BlendingStyle, CanvasGradientStop, CompositionOptions, CompositionOrBlending, CompositionStyle,
FillOrStrokeStyle, LineCapStyle, LineJoinStyle, 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 pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
use range::Range;
use style::color::AbsoluteColor;
use vello::wgpu::{
BackendOptions, Backends, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Device,
Extent3d, Instance, InstanceDescriptor, InstanceFlags, MapMode, Queue, TexelCopyBufferInfo,
TexelCopyBufferLayout, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
TextureViewDescriptor,
};
use vello::{kurbo, peniko};
use webrender_api::{ImageDescriptor, ImageDescriptorFlags};
use crate::backend::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<HashMap<FontIdentifier, peniko::Font>> = RefCell::default();
}
pub(crate) struct VelloDrawTarget {
device: Device,
queue: Queue,
renderer: Rc<RefCell<vello::Renderer>>,
scene: vello::Scene,
size: Size2D<u32>,
}
fn options() -> vello::RendererOptions {
vello::RendererOptions {
use_cpu: false,
num_init_threads: NonZeroUsize::new(1),
antialiasing_support: vello::AaSupport::area_only(),
pipeline_cache: None,
}
}
impl VelloDrawTarget {
fn with_draw_options<F: FnOnce(&mut Self)>(&mut self, draw_options: &CompositionOptions, f: F) {
self.scene.push_layer(
draw_options.composition_operation.convert(),
1.0,
kurbo::Affine::IDENTITY,
&kurbo::Rect::ZERO.with_size(self.size.cast()),
);
f(self);
self.scene.pop_layer();
}
fn render_and_download<F, R>(&self, f: F) -> R
where
F: FnOnce(u32, Option<&[u8]>) -> R,
{
let size = Extent3d {
width: self.size.width,
height: self.size.height,
depth_or_array_layers: 1,
};
let target = self.device.create_texture(&TextureDescriptor {
label: Some("Target texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8Unorm,
usage: TextureUsages::STORAGE_BINDING | TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = target.create_view(&TextureViewDescriptor::default());
self.renderer
.borrow_mut()
.render_to_texture(
&self.device,
&self.queue,
&self.scene,
&view,
&vello::RenderParams {
base_color: peniko::color::AlphaColor::TRANSPARENT,
width: self.size.width,
height: self.size.height,
antialiasing_method: vello::AaConfig::Area,
},
)
.unwrap();
// TODO(perf): do a render pass that will multiply with alpha on GPU
let padded_byte_width = (self.size.width * 4).next_multiple_of(256);
let buffer_size = padded_byte_width as u64 * self.size.height as u64;
let buffer = self.device.create_buffer(&BufferDescriptor {
label: Some("val"),
size: buffer_size,
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut encoder = self
.device
.create_command_encoder(&CommandEncoderDescriptor {
label: Some("Copy out buffer"),
});
encoder.copy_texture_to_buffer(
target.as_image_copy(),
TexelCopyBufferInfo {
buffer: &buffer,
layout: TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_byte_width),
rows_per_image: None,
},
},
size,
);
self.queue.submit([encoder.finish()]);
let result = {
let buf_slice = buffer.slice(..);
let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
buf_slice.map_async(MapMode::Read, move |v| sender.send(v).unwrap());
if let Err(error) =
vello::util::block_on_wgpu(&self.device, receiver.receive()).unwrap()
{
log::warn!("VELLO WGPU MAP ASYNC ERROR {error}");
return f(padded_byte_width, None);
}
let data = buf_slice.get_mapped_range();
f(padded_byte_width, Some(&data))
};
buffer.unmap();
result
}
}
impl GenericDrawTarget for VelloDrawTarget {
type SourceSurface = Vec<u8>; // TODO: this should be texture
fn new(size: Size2D<u32>) -> Self {
// TODO: we should read prefs instead of env
// we forbid GL because it clashes with servo's GL usage
let backends = Backends::from_env().unwrap_or_default() - Backends::GL;
let flags = InstanceFlags::from_build_config().with_env();
let backend_options = BackendOptions::from_env_or_default();
let instance = Instance::new(&InstanceDescriptor {
backends,
flags,
backend_options,
});
let mut context = vello::util::RenderContext {
instance,
devices: Vec::new(),
};
let device_id = pollster::block_on(context.device(None)).unwrap();
let device_handle = &mut context.devices[device_id];
let device = device_handle.device.clone();
let queue = device_handle.queue.clone();
let renderer = vello::Renderer::new(&device, options()).unwrap();
let scene = vello::Scene::new();
device.on_uncaptured_error(Box::new(|error| {
log::error!("VELLO WGPU ERROR: {error}");
}));
Self {
device,
queue,
renderer: Rc::new(RefCell::new(renderer)),
scene,
size,
}
}
fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f32>) {
let rect: kurbo::Rect = rect.cast().into();
let transform = transform.cast().into();
self.scene
.push_layer(peniko::Compose::Clear, 0.0, transform, &rect);
self.scene.fill(
peniko::Fill::NonZero,
transform,
peniko::BrushRef::Solid(peniko::color::AlphaColor::TRANSPARENT),
None,
&rect,
);
self.scene.pop_layer();
}
fn copy_surface(&mut self, surface: Vec<u8>, source: Rect<i32>, destination: Point2D<i32>) {
let destination: kurbo::Point = destination.cast::<f64>().into();
let rect = kurbo::Rect::from_origin_size(destination, source.size.cast());
// TODO: ignore clip from prev layers
// this will require creating a stacks of applicable clips
// that will be popped and reinserted after
// or we could impl this in vello directly
// then there is also this nasty vello bug where clipping does not work correctly:
// https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Servo.202D.20canvas.20backend/near/525153593
self.scene
.push_layer(peniko::Compose::Copy, 1.0, kurbo::Affine::IDENTITY, &rect);
self.scene.fill(
peniko::Fill::NonZero,
kurbo::Affine::IDENTITY,
&peniko::Image {
data: peniko::Blob::from(surface),
format: peniko::ImageFormat::Rgba8,
width: source.size.width as u32,
height: source.size.height as u32,
x_extend: peniko::Extend::Pad,
y_extend: peniko::Extend::Pad,
quality: peniko::ImageQuality::Low,
alpha: 1.0,
},
Some(kurbo::Affine::translate(destination.to_vec2())),
&rect,
);
self.scene.pop_layer();
}
fn create_similar_draw_target(&self, size: &Size2D<i32>) -> Self {
Self {
device: self.device.clone(),
queue: self.queue.clone(),
renderer: self.renderer.clone(),
scene: vello::Scene::new(),
size: size.cast(),
}
}
fn draw_surface(
&mut self,
surface: Vec<u8>,
dest: Rect<f64>,
source: Rect<f64>,
filter: Filter,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
let scale_up = dest.size.width > source.size.width || dest.size.height > source.size.height;
let shape: kurbo::Rect = dest.into();
self.with_draw_options(&composition_options, move |self_| {
self_.scene.fill(
peniko::Fill::NonZero,
transform.cast().into(),
&peniko::Image {
data: peniko::Blob::from(surface),
format: peniko::ImageFormat::Rgba8,
width: source.size.width as u32,
height: source.size.height as u32,
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
},
alpha: composition_options.alpha as f32,
},
Some(
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,
),
),
&shape,
)
})
}
fn draw_surface_with_shadow(
&self,
_surface: Vec<u8>,
_dest: &Point2D<f32>,
_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,
style: FillOrStrokeStyle,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
self.with_draw_options(&composition_options, |self_| {
self_.scene.fill(
peniko::Fill::NonZero,
transform.cast().into(),
&style
.convert()
.multiply_alpha(composition_options.alpha as f32),
None,
&path.0,
);
})
}
fn fill_text(
&mut self,
text_runs: Vec<TextRun>,
start: Point2D<f32>,
style: FillOrStrokeStyle,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
let pattern = style
.convert()
.multiply_alpha(composition_options.alpha as f32);
let transform = transform.cast().into();
self.with_draw_options(&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_
.scene
.draw_glyphs(font)
.transform(transform)
.brush(&pattern)
.font_size(run.font.descriptor.pt_size.to_f32_px())
.draw(
peniko::Fill::NonZero,
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::Glyph {
id: glyph.id(),
x,
y,
}
}),
);
});
}
})
}
fn fill_rect(
&mut self,
rect: &Rect<f32>,
style: FillOrStrokeStyle,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
let pattern = style
.convert()
.multiply_alpha(composition_options.alpha as f32);
let transform = transform.cast().into();
let rect: kurbo::Rect = rect.cast().into();
self.with_draw_options(&composition_options, |self_| {
self_
.scene
.fill(peniko::Fill::NonZero, transform, &pattern, None, &rect);
})
}
fn get_size(&self) -> Size2D<i32> {
self.size.cast()
}
fn pop_clip(&mut self) {
self.scene.pop_layer();
}
fn push_clip(&mut self, path: &Path, transform: Transform2D<f32>) {
self.scene
.push_layer(peniko::Mix::Clip, 1.0, transform.cast().into(), &path.0);
}
fn push_clip_rect(&mut self, rect: &Rect<i32>) {
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, Transform2D::identity());
}
fn stroke(
&mut self,
path: &Path,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
self.with_draw_options(&composition_options, |self_| {
self_.scene.stroke(
&line_options.convert(),
transform.cast().into(),
&style
.convert()
.multiply_alpha(composition_options.alpha as f32),
None,
&path.0,
);
})
}
fn stroke_rect(
&mut self,
rect: &Rect<f32>,
style: FillOrStrokeStyle,
line_options: LineOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
let rect: kurbo::Rect = rect.cast().into();
self.with_draw_options(&composition_options, |self_| {
self_.scene.stroke(
&line_options.convert(),
transform.cast().into(),
&style
.convert()
.multiply_alpha(composition_options.alpha as f32),
None,
&rect,
);
})
}
fn image_descriptor_and_serializable_data(
&mut self,
) -> (ImageDescriptor, SerializableImageData) {
let size = self.size;
self.render_and_download(|stride, data| {
let image_desc = ImageDescriptor {
format: webrender_api::ImageFormat::RGBA8,
size: size.cast().cast_unit(),
stride: data.map(|_| stride as i32),
offset: 0,
flags: ImageDescriptorFlags::empty(),
};
let data = SerializableImageData::Raw(if let Some(data) = data {
let mut data = IpcSharedMemory::from_bytes(data);
#[allow(unsafe_code)]
unsafe {
pixels::generic_transform_inplace::<1, false, false>(data.deref_mut());
};
data
} else {
IpcSharedMemory::from_byte(0, size.area() as usize * 4)
});
(image_desc, data)
})
}
fn snapshot(&mut self) -> pixels::Snapshot {
let size = self.size;
self.render_and_download(|padded_byte_width, data| {
let data = data
.map(|data| {
let mut result_unpadded = Vec::<u8>::with_capacity(size.area() as usize * 4);
for row in 0..self.size.height {
let start = (row * padded_byte_width).try_into().unwrap();
result_unpadded
.extend(&data[start..start + (self.size.width * 4) as usize]);
}
result_unpadded
})
.unwrap_or_else(|| vec![0; size.area() as usize * 4]);
Snapshot::from_vec(
size,
SnapshotPixelFormat::RGBA,
SnapshotAlphaMode::Transparent {
premultiplied: false,
},
data,
)
})
}
fn surface(&mut self) -> Vec<u8> {
self.snapshot().to_vec(None, None).0
}
fn create_source_surface_from_data(&self, data: Snapshot) -> Option<Vec<u8>> {
let (data, _, _) = data.to_vec(
Some(SnapshotAlphaMode::Transparent {
premultiplied: false,
}),
Some(SnapshotPixelFormat::RGBA),
);
Some(data)
}
}
/// A version of the `Into<T>` trait from the standard library that can be used
/// to convert between two types that are not defined in the canvas crate.
pub(crate) trait Convert<T> {
fn convert(self) -> T;
}
impl Convert<kurbo::Join> for LineJoinStyle {
fn convert(self) -> kurbo::Join {
match self {
LineJoinStyle::Round => kurbo::Join::Round,
LineJoinStyle::Bevel => kurbo::Join::Bevel,
LineJoinStyle::Miter => kurbo::Join::Miter,
}
}
}
impl Convert<kurbo::Cap> for LineCapStyle {
fn convert(self) -> kurbo::Cap {
match self {
LineCapStyle::Butt => kurbo::Cap::Butt,
LineCapStyle::Round => kurbo::Cap::Round,
LineCapStyle::Square => kurbo::Cap::Square,
}
}
}
impl Convert<peniko::Color> for AbsoluteColor {
fn convert(self) -> peniko::Color {
let srgb = self.into_srgb_legacy();
peniko::Color::new([
srgb.components.0,
srgb.components.1,
srgb.components.2,
srgb.alpha,
])
}
}
impl Convert<peniko::BlendMode> for CompositionOrBlending {
fn convert(self) -> peniko::BlendMode {
match self {
CompositionOrBlending::Composition(composition_style) => {
composition_style.convert().into()
},
CompositionOrBlending::Blending(blending_style) => blending_style.convert().into(),
}
}
}
impl Convert<peniko::Compose> for CompositionStyle {
fn convert(self) -> peniko::Compose {
match self {
CompositionStyle::SourceIn => peniko::Compose::SrcIn,
CompositionStyle::SourceOut => peniko::Compose::SrcOut,
CompositionStyle::SourceOver => peniko::Compose::SrcOver,
CompositionStyle::SourceAtop => peniko::Compose::SrcAtop,
CompositionStyle::DestinationIn => peniko::Compose::DestIn,
CompositionStyle::DestinationOut => peniko::Compose::DestOut,
CompositionStyle::DestinationOver => peniko::Compose::DestOver,
CompositionStyle::DestinationAtop => peniko::Compose::DestAtop,
CompositionStyle::Copy => peniko::Compose::Copy,
CompositionStyle::Lighter => peniko::Compose::Plus,
CompositionStyle::Xor => peniko::Compose::Xor,
CompositionStyle::Clear => peniko::Compose::Clear,
}
}
}
impl Convert<peniko::Mix> for BlendingStyle {
fn convert(self) -> peniko::Mix {
match self {
BlendingStyle::Multiply => peniko::Mix::Multiply,
BlendingStyle::Screen => peniko::Mix::Screen,
BlendingStyle::Overlay => peniko::Mix::Overlay,
BlendingStyle::Darken => peniko::Mix::Darken,
BlendingStyle::Lighten => peniko::Mix::Lighten,
BlendingStyle::ColorDodge => peniko::Mix::ColorDodge,
BlendingStyle::ColorBurn => peniko::Mix::ColorBurn,
BlendingStyle::HardLight => peniko::Mix::HardLight,
BlendingStyle::SoftLight => peniko::Mix::SoftLight,
BlendingStyle::Difference => peniko::Mix::Difference,
BlendingStyle::Exclusion => peniko::Mix::Exclusion,
BlendingStyle::Hue => peniko::Mix::Hue,
BlendingStyle::Saturation => peniko::Mix::Saturation,
BlendingStyle::Color => peniko::Mix::Color,
BlendingStyle::Luminosity => peniko::Mix::Luminosity,
}
}
}
impl Convert<kurbo::Stroke> for LineOptions {
fn convert(self) -> kurbo::Stroke {
let LineOptions {
width,
cap_style,
join_style,
miter_limit,
dash,
dash_offset,
} = self;
kurbo::Stroke {
width,
join: join_style.convert(),
miter_limit,
start_cap: cap_style.convert(),
end_cap: cap_style.convert(),
dash_pattern: dash.iter().map(|x| *x as f64).collect(),
dash_offset,
}
}
}
impl Convert<peniko::Brush> for FillOrStrokeStyle {
fn convert(self) -> peniko::Brush {
use canvas_traits::canvas::FillOrStrokeStyle::*;
match self {
Color(absolute_color) => peniko::Brush::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();
peniko::Brush::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();
peniko::Brush::Gradient(gradient)
},
Surface(surface_style) => {
let data = surface_style
.surface_data
.to_owned()
.to_vec(
Some(SnapshotAlphaMode::Transparent {
premultiplied: false,
}),
Some(SnapshotPixelFormat::RGBA),
)
.0;
peniko::Brush::Image(peniko::Image {
data: peniko::Blob::from(data),
format: peniko::ImageFormat::Rgba8,
width: surface_style.surface_size.width,
height: surface_style.surface_size.height,
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,
alpha: 1.0,
})
},
}
}
}
impl Convert<peniko::color::DynamicColor> for AbsoluteColor {
fn convert(self) -> peniko::color::DynamicColor {
peniko::color::DynamicColor::from_alpha_color(self.convert())
}
}
impl Convert<peniko::ColorStop> for CanvasGradientStop {
fn convert(self) -> peniko::ColorStop {
peniko::ColorStop {
offset: self.offset as f32,
color: self.color.convert(),
}
}
}
impl Convert<peniko::ColorStops> for Vec<CanvasGradientStop> {
fn convert(self) -> peniko::ColorStops {
let mut stops = peniko::ColorStops(self.into_iter().map(|item| item.convert()).collect());
// https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap
stops
.0
.sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap());
stops
}
}
impl Convert<peniko::ImageQuality> for Filter {
fn convert(self) -> peniko::ImageQuality {
match self {
Filter::Bilinear => peniko::ImageQuality::Medium,
Filter::Nearest => peniko::ImageQuality::Low,
}
}
}

View file

@ -76,6 +76,8 @@ pub struct Preferences {
pub dom_allow_scripts_to_close_windows: bool,
pub dom_canvas_capture_enabled: bool,
pub dom_canvas_text_enabled: bool,
/// Uses vello as canvas backend
pub dom_canvas_vello_enabled: bool,
pub dom_clipboardevent_enabled: bool,
pub dom_composition_event_enabled: bool,
pub dom_cookiestore_enabled: bool,
@ -255,6 +257,7 @@ impl Preferences {
dom_bluetooth_testing_enabled: false,
dom_canvas_capture_enabled: false,
dom_canvas_text_enabled: true,
dom_canvas_vello_enabled: false,
dom_clipboardevent_enabled: true,
dom_composition_event_enabled: false,
dom_cookiestore_enabled: false,

View file

@ -16,6 +16,7 @@ bluetooth = ["bluetooth_traits"]
default = []
tracing = ["dep:tracing"]
webgpu = ["script_traits/webgpu"]
vello = ["canvas/vello"]
[dependencies]
background_hang_monitor = { path = "../background_hang_monitor" }

View file

@ -62,6 +62,7 @@ webgpu = [
"constellation/webgpu",
"constellation_traits/webgpu",
]
vello = ["constellation/vello"]
[dependencies]
background_hang_monitor = { path = "../background_hang_monitor" }

View file

@ -193,4 +193,4 @@ skip = [
# github.com organizations to allow git sources for
[sources.allow-org]
github = ["pcwalton", "servo"]
github = ["pcwalton", "servo", "linebender"]

View file

@ -52,6 +52,7 @@ webdriver = ["libservo/webdriver"]
webgl_backtrace = ["libservo/webgl_backtrace"]
webgpu = ["libservo/webgpu"]
webxr = ["libservo/webxr"]
vello = ["libservo/vello"]
[dependencies]
cfg-if = { workspace = true }

View file

@ -104,7 +104,7 @@ def handle_preset(s: str) -> Optional[JobConfig]:
return JobConfig(
"WebGPU CTS",
Workflow.LINUX,
wpt=True, # reftests are mode for new layout
wpt=True,
wpt_args="_webgpu", # run only webgpu cts
profile="production", # WebGPU works to slow with debug assert
unit_tests=False,
@ -125,6 +125,20 @@ def handle_preset(s: str) -> Optional[JobConfig]:
unit_tests=False,
number_of_wpt_chunks=2,
)
elif any(word in s for word in ["vello"]):
return JobConfig(
"Vello WPT",
Workflow.LINUX,
wpt=True,
wpt_args=" ".join(
[
"--subsuite-file ./tests/wpt/vello_canvas_subsuite.json",
"--subsuite vello_canvas",
"--processes 1",
]
),
build_args="--features 'vello'",
)
elif any(word in s for word in ["lint", "tidy"]):
return JobConfig("Lint", Workflow.LINT)
else:

View file

@ -1,2 +1,3 @@
[2d.composite.grid.no_filter.no_shadow.drawImage.html]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,2 +1,3 @@
[2d.composite.grid.no_filter.no_shadow.fillRect.html]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,2 +1,3 @@
[2d.composite.grid.no_filter.no_shadow.pattern.html]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -0,0 +1,5 @@
[2d.gradient.interpolate.coloralpha.html]
[Canvas test: 2d.gradient.interpolate.coloralpha]
bug: https://github.com/linebender/vello/issues/1056
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -1,4 +1,4 @@
[2d.gradient.radial.cone.behind.html]
[Canvas test: 2d.gradient.radial.cone.behind]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.gradient.radial.cone.shape2.html]
[Canvas test: 2d.gradient.radial.cone.shape2]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.gradient.radial.outside2.html]
[Canvas test: 2d.gradient.radial.outside2]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.gradient.radial.outside3.html]
[Canvas test: 2d.gradient.radial.outside3]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -0,0 +1,4 @@
[2d.layer.globalCompositeOperation.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
PASS

View file

@ -0,0 +1,5 @@
[2d.line.cross.html]
[Canvas test: 2d.line.cross]
bug: https://github.com/linebender/vello/issues/1063
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,5 @@
[2d.line.miter.acute.html]
[Miter joins are drawn correctly with acute angles]
bug: https://github.com/linebender/vello/issues/1063
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -1,3 +1,15 @@
[canvas-createImageBitmap-resize.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
[createImageBitmap from a HTMLImageElement of svg with no specified size with resize option.]
expected: FAIL
expected:
if subsuite == "vello_canvas": TIMEOUT
FAIL
[createImageBitmap from an ImageBitmap with resize option.]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from an ImageData with resize option.]
expected:
if subsuite == "vello_canvas": NOTRUN

View file

@ -1,4 +1,6 @@
[createImageBitmap-drawImage.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
[createImageBitmap from a vector HTMLImageElement resized, and drawImage on the created ImageBitmap]
expected: FAIL
@ -73,3 +75,39 @@
[createImageBitmap from a vector HTMLImageElement scaled up, and drawImage on the created ImageBitmap]
expected: FAIL
[createImageBitmap from an ImageBitmap scaled down, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": [PASS, TIMEOUT]
[createImageBitmap from an ImageBitmap scaled up, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": [TIMEOUT, NOTRUN]
[createImageBitmap from an ImageBitmap resized, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from an ImageBitmap with negative sw/sh, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from a Blob, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from a Blob scaled down, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from a Blob scaled up, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from a Blob resized, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN
[createImageBitmap from a Blob with negative sw/sh, and drawImage on the created ImageBitmap]
expected:
if subsuite == "vello_canvas": NOTRUN

View file

@ -0,0 +1,3 @@
[canvas.2d.lang.dynamic.html]
expected:
if subsuite == "vello_canvas": [PASS, FAIL]

View file

@ -1,4 +1,6 @@
[canvas-display-p3-drawImage-ImageBitmap-Blob.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
[sRGB-FF0000FF.png, Context srgb, ImageData display-p3, cropSource=false]
expected: FAIL

View file

@ -0,0 +1,3 @@
[canvas-display-p3-drawImage-ImageBitmap-ImageBitmap.html]
expected:
if subsuite == "vello_canvas": TIMEOUT

View file

@ -1,4 +1,6 @@
[canvas-display-p3-drawImage-ImageBitmap-cloned.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
[sRGB-FF0000FF.png, Context srgb, ImageData display-p3, cropSource=false]
expected: FAIL

View file

@ -1,4 +1,6 @@
[canvas-display-p3-drawImage-ImageBitmap-image.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
[sRGB-FF0000FF.png, Context srgb, ImageData display-p3, cropSource=false]
expected: FAIL

View file

@ -1,4 +1,6 @@
[canvas-display-p3-pattern-image.html]
expected:
if subsuite == "vello_canvas": TIMEOUT
[sRGB-FF0000FF.png, Context srgb, ImageData display-p3]
expected: FAIL

View file

@ -0,0 +1,4 @@
[2d.path.arc.scale.1.html]
[Non-uniformly scaled arcs are the right shape]
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -1,4 +1,4 @@
[2d.path.arc.scale.2.html]
[Highly scaled arcs are the right shape]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.path.arc.selfintersect.1.html]
[arc() with lineWidth > 2*radius is drawn sensibly]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.path.arc.selfintersect.2.html]
[arc() with lineWidth > 2*radius is drawn sensibly]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.path.arc.shape.3.html]
[arc() from 0 to -pi/2 does not draw anything in the wrong quadrant]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.path.arc.shape.4.html]
[arc() from 0 to -pi/2 draws stuff in the right quadrant]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -0,0 +1,5 @@
[2d.path.clip.intersect.html]
[Canvas test: 2d.path.clip.intersect]
bug: https://github.com/linebender/vello/issues/1061
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,5 @@
[2d.path.rect.selfintersect.html]
[Canvas test: 2d.path.rect.selfintersect]
bug: https://github.com/linebender/vello/issues/1063#issuecomment-2998084736
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,4 @@
[2d.path.stroke.scale2.html]
[Stroke line widths are scaled by the current transformation matrix]
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,5 @@
[2d.imageData.put.clip.html]
[putImageData() is not affected by clipping regions]
bug: https://github.com/linebender/vello/issues/1088
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,5 @@
[2d.imageData.put.dirty.negative.html]
[putImageData() handles negative-sized dirty rectangles correctly]
bug: https://github.com/linebender/vello/issues/1061
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,5 @@
[2d.imageData.put.dirty.rect1.html]
[putImageData() only modifies areas inside the dirty rectangle, using width and height]
bug: https://github.com/linebender/vello/issues/1061
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -0,0 +1,5 @@
[2d.imageData.put.dirty.rect2.html]
[putImageData() only modifies areas inside the dirty rectangle, using x and y]
bug: https://github.com/linebender/vello/issues/1061
expected:
if subsuite == "vello_canvas": FAIL

View file

@ -1,4 +1,4 @@
[2d.imageData.put.unchanged.html]
[putImageData(getImageData(...), ...) has no effect]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,4 +1,4 @@
[2d.transformation.scale.large.html]
[scale() with large scale factors works]
expected: FAIL
expected:
if subsuite == "": FAIL

View file

@ -1,2 +1,3 @@
[2d.layer.globalCompositeOperation.html]
expected: FAIL
expected:
FAIL

8
tests/wpt/vello_canvas_subsuite.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"vello_canvas": {
"config": {
"binary_args": ["--pref", "dom_canvas_vello_enabled"]
},
"include": ["/html/canvas/element"]
}
}