canvas: Fully stateless backend (#38214)

I think this simplifies canvas backends greatly. Before there were many
(needless) hoops from abstract to concrete (backend) and back, now we
can do most stuff on abstract types.

Testing: Existing WPT tests
Fixes: #38022 

try run: https://github.com/sagudev/servo/actions/runs/16450978211

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-07-24 20:11:29 +02:00 committed by GitHub
parent 86d8317460
commit d39e701b46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 650 additions and 806 deletions

View file

@ -2,6 +2,8 @@
* 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/. */
#![allow(clippy::too_many_arguments)]
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::sync::Arc;
@ -18,26 +20,24 @@ use ipc_channel::router::ROUTER;
use log::warn;
use net_traits::ResourceThreads;
use pixels::Snapshot;
use style::color::AbsoluteColor;
use style::properties::style_structs::Font as FontStyleStruct;
use webrender_api::ImageKey;
use crate::canvas_data::*;
use crate::raqote_backend::RaqoteBackend;
pub struct CanvasPaintThread<'a> {
canvases: HashMap<CanvasId, Canvas<'a>>,
pub struct CanvasPaintThread {
canvases: HashMap<CanvasId, Canvas>,
next_canvas_id: CanvasId,
compositor_api: CrossProcessCompositorApi,
font_context: Arc<FontContext>,
}
impl<'a> CanvasPaintThread<'a> {
impl CanvasPaintThread {
fn new(
compositor_api: CrossProcessCompositorApi,
system_font_service: Arc<SystemFontServiceProxy>,
resource_threads: ResourceThreads,
) -> CanvasPaintThread<'a> {
) -> CanvasPaintThread {
CanvasPaintThread {
canvases: HashMap::new(),
next_canvas_id: CanvasId(0),
@ -146,21 +146,27 @@ impl<'a> CanvasPaintThread<'a> {
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_fill_style(style);
canvas.set_text_options(text_options);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.fill_text(text, x, y, max_width, is_rtl);
self.canvas(canvas_id).fill_text(
text,
x,
y,
max_width,
is_rtl,
style,
text_options,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::FillRect(rect, style, shadow_options, composition_options, transform) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_fill_style(style);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.fill_rect(&rect);
self.canvas(canvas_id).fill_rect(
&rect,
style,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::StrokeRect(
rect,
@ -170,25 +176,26 @@ impl<'a> CanvasPaintThread<'a> {
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_stroke_style(style);
canvas.set_line_options(line_options);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.stroke_rect(&rect);
self.canvas(canvas_id).stroke_rect(
&rect,
style,
line_options,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::ClearRect(ref rect, transform) => {
self.canvas(canvas_id).set_transform(&transform);
self.canvas(canvas_id).clear_rect(rect)
self.canvas(canvas_id).clear_rect(rect, transform)
},
Canvas2dMsg::FillPath(style, path, shadow_options, composition_options, transform) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_fill_style(style);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.fill_path(&path);
self.canvas(canvas_id).fill_path(
&path,
style,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::StrokePath(
path,
@ -198,18 +205,17 @@ impl<'a> CanvasPaintThread<'a> {
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_stroke_style(style);
canvas.set_line_options(line_options);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.stroke_path(&path);
self.canvas(canvas_id).stroke_path(
&path,
style,
line_options,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::ClipPath(path, transform) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.clip_path(&path);
self.canvas(canvas_id).clip_path(&path, transform);
},
Canvas2dMsg::DrawImage(
snapshot,
@ -219,18 +225,15 @@ impl<'a> CanvasPaintThread<'a> {
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
canvas.draw_image(
snapshot.to_owned(),
dest_rect,
source_rect,
smoothing_enabled,
)
},
) => self.canvas(canvas_id).draw_image(
snapshot.to_owned(),
dest_rect,
source_rect,
smoothing_enabled,
shadow_options,
composition_options,
transform,
),
Canvas2dMsg::DrawEmptyImage(
image_size,
dest_rect,
@ -238,18 +241,15 @@ impl<'a> CanvasPaintThread<'a> {
shadow_options,
composition_options,
transform,
) => {
let canvas = self.canvas(canvas_id);
canvas.set_transform(&transform);
canvas.set_shadow_options(shadow_options);
canvas.set_composition_options(composition_options);
self.canvas(canvas_id).draw_image(
Snapshot::cleared(image_size),
dest_rect,
source_rect,
false,
)
},
) => self.canvas(canvas_id).draw_image(
Snapshot::cleared(image_size),
dest_rect,
source_rect,
false,
shadow_options,
composition_options,
transform,
),
Canvas2dMsg::DrawImageInOther(
other_canvas_id,
image_size,
@ -263,20 +263,20 @@ impl<'a> CanvasPaintThread<'a> {
let snapshot = self
.canvas(canvas_id)
.read_pixels(Some(source_rect.to_u32()), Some(image_size));
let canvas = self.canvas(other_canvas_id);
canvas.set_transform(&transform);
canvas.set_composition_options(composition_options);
canvas.set_shadow_options(shadow_options);
canvas.draw_image(snapshot, dest_rect, source_rect, smoothing);
self.canvas(other_canvas_id).draw_image(
snapshot,
dest_rect,
source_rect,
smoothing,
shadow_options,
composition_options,
transform,
);
},
Canvas2dMsg::MeasureText(text, sender, text_options) => {
let canvas = self.canvas(canvas_id);
canvas.set_text_options(text_options);
let metrics = canvas.measure_text(text);
let metrics = self.canvas(canvas_id).measure_text(text, text_options);
sender.send(metrics).unwrap();
},
Canvas2dMsg::RestoreContext => self.canvas(canvas_id).restore_context_state(),
Canvas2dMsg::SaveContext => self.canvas(canvas_id).save_context_state(),
Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender) => {
let snapshot = self
.canvas(canvas_id)
@ -291,64 +291,130 @@ impl<'a> CanvasPaintThread<'a> {
self.canvas(canvas_id).update_image_rendering();
sender.send(()).unwrap();
},
Canvas2dMsg::PopClip => self.canvas(canvas_id).pop_clip(),
}
}
fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas<'a> {
fn canvas(&mut self, canvas_id: CanvasId) -> &mut Canvas {
self.canvases.get_mut(&canvas_id).expect("Bogus canvas id")
}
}
enum Canvas<'a> {
Raqote(CanvasData<'a, RaqoteBackend>),
enum Canvas {
Raqote(CanvasData<RaqoteBackend>),
}
impl Canvas<'_> {
fn set_fill_style(&mut self, style: FillOrStrokeStyle) {
impl Canvas {
fn pop_clip(&mut self) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_fill_style(style),
Canvas::Raqote(canvas_data) => canvas_data.pop_clip(),
}
}
fn fill_text(&mut self, text: String, x: f64, y: f64, max_width: Option<f64>, is_rtl: bool) {
fn fill_text(
&mut self,
text: String,
x: f64,
y: f64,
max_width: Option<f64>,
is_rtl: bool,
style: FillOrStrokeStyle,
text_options: TextOptions,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill_text(text, x, y, max_width, is_rtl),
Canvas::Raqote(canvas_data) => canvas_data.fill_text(
text,
x,
y,
max_width,
is_rtl,
style,
text_options,
shadow_options,
composition_options,
transform,
),
}
}
fn fill_rect(&mut self, rect: &Rect<f32>) {
fn fill_rect(
&mut self,
rect: &Rect<f32>,
style: FillOrStrokeStyle,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill_rect(rect),
Canvas::Raqote(canvas_data) => {
canvas_data.fill_rect(rect, style, shadow_options, composition_options, transform)
},
}
}
fn set_stroke_style(&mut self, style: FillOrStrokeStyle) {
fn stroke_rect(
&mut self,
rect: &Rect<f32>,
style: FillOrStrokeStyle,
line_options: LineOptions,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_stroke_style(style),
Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(
rect,
style,
line_options,
shadow_options,
composition_options,
transform,
),
}
}
fn stroke_rect(&mut self, rect: &Rect<f32>) {
fn fill_path(
&mut self,
path: &Path,
style: FillOrStrokeStyle,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.stroke_rect(rect),
Canvas::Raqote(canvas_data) => {
canvas_data.fill_path(path, style, shadow_options, composition_options, transform)
},
}
}
fn fill_path(&mut self, path: &Path) {
fn stroke_path(
&mut self,
path: &Path,
style: FillOrStrokeStyle,
line_options: LineOptions,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.fill_path(path),
Canvas::Raqote(canvas_data) => canvas_data.stroke_path(
path,
style,
line_options,
shadow_options,
composition_options,
transform,
),
}
}
fn stroke_path(&mut self, path: &Path) {
fn clear_rect(&mut self, rect: &Rect<f32>, transform: Transform2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.stroke_path(path),
}
}
fn clear_rect(&mut self, rect: &Rect<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect),
Canvas::Raqote(canvas_data) => canvas_data.clear_rect(rect, transform),
}
}
@ -358,11 +424,20 @@ impl Canvas<'_> {
dest_rect: Rect<f64>,
source_rect: Rect<f64>,
smoothing_enabled: bool,
shadow_options: ShadowOptions,
composition_options: CompositionOptions,
transform: Transform2D<f32>,
) {
match self {
Canvas::Raqote(canvas_data) => {
canvas_data.draw_image(snapshot, dest_rect, source_rect, smoothing_enabled)
},
Canvas::Raqote(canvas_data) => canvas_data.draw_image(
snapshot,
dest_rect,
source_rect,
smoothing_enabled,
shadow_options,
composition_options,
transform,
),
}
}
@ -376,123 +451,15 @@ impl Canvas<'_> {
}
}
fn restore_context_state(&mut self) {
fn measure_text(&mut self, text: String, text_options: TextOptions) -> TextMetrics {
match self {
Canvas::Raqote(canvas_data) => canvas_data.restore_context_state(),
Canvas::Raqote(canvas_data) => canvas_data.measure_text(text, text_options),
}
}
fn save_context_state(&mut self) {
fn clip_path(&mut self, path: &Path, transform: Transform2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.save_context_state(),
}
}
fn set_line_width(&mut self, width: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_width(width),
}
}
fn set_line_cap(&mut self, cap: LineCapStyle) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_cap(cap),
}
}
fn set_line_join(&mut self, join: LineJoinStyle) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_join(join),
}
}
fn set_miter_limit(&mut self, limit: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_miter_limit(limit),
}
}
fn set_line_dash(&mut self, items: Vec<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_dash(items),
}
}
fn set_line_dash_offset(&mut self, offset: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_line_dash_offset(offset),
}
}
fn set_transform(&mut self, matrix: &Transform2D<f32>) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_transform(matrix),
}
}
fn set_global_alpha(&mut self, alpha: f32) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_global_alpha(alpha),
}
}
fn set_global_composition(&mut self, op: CompositionOrBlending) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_global_composition(op),
}
}
fn set_shadow_offset_x(&mut self, value: f64) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_x(value),
}
}
fn set_shadow_offset_y(&mut self, value: f64) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_offset_y(value),
}
}
fn set_shadow_blur(&mut self, value: f64) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_blur(value),
}
}
fn set_shadow_color(&mut self, color: AbsoluteColor) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_shadow_color(color),
}
}
fn set_font(&mut self, font_style: FontStyleStruct) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_font(font_style),
}
}
fn set_text_align(&mut self, text_align: TextAlign) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_text_align(text_align),
}
}
fn set_text_baseline(&mut self, text_baseline: TextBaseline) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.set_text_baseline(text_baseline),
}
}
fn measure_text(&mut self, text: String) -> TextMetrics {
match self {
Canvas::Raqote(canvas_data) => canvas_data.measure_text(text),
}
}
fn clip_path(&mut self, path: &Path) {
match self {
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path),
Canvas::Raqote(canvas_data) => canvas_data.clip_path(path, transform),
}
}
@ -513,41 +480,4 @@ impl Canvas<'_> {
Canvas::Raqote(canvas_data) => canvas_data.recreate(size),
}
}
fn set_text_options(&mut self, text_options: TextOptions) {
if let Some(font) = text_options.font {
self.set_font(font);
}
self.set_text_align(text_options.align);
self.set_text_baseline(text_options.baseline);
}
fn set_shadow_options(&mut self, shadow_options: ShadowOptions) {
self.set_shadow_color(shadow_options.color);
self.set_shadow_offset_x(shadow_options.offset_x);
self.set_shadow_offset_y(shadow_options.offset_y);
self.set_shadow_blur(shadow_options.blur);
}
fn set_composition_options(&mut self, composition_options: CompositionOptions) {
self.set_global_alpha(composition_options.alpha as f32);
self.set_global_composition(composition_options.composition_operation);
}
fn set_line_options(&mut self, line_options: LineOptions) {
let LineOptions {
width,
cap_style,
join_style,
miter_limit,
dash,
dash_offset,
} = line_options;
self.set_line_width(width as f32);
self.set_line_cap(cap_style);
self.set_line_join(join_style);
self.set_miter_limit(miter_limit as f32);
self.set_line_dash(dash);
self.set_line_dash_offset(dash_offset as f32);
}
}