canvas: Use bytemuck to remove unsafe from raqote backend (#38283)

This PR changes surface type from `Vec<u8>` to `Vec<u32>` as this is
native type of raqote's backend. We used unsafe for `Vec<u8>` <->
`Vec<u32>` casting that is replaced with bytemuck. Bytemuck is already
in cargo lock.

Testing: Existing WPT tests.

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-07-26 15:16:16 +02:00 committed by GitHub
parent daebb5a033
commit f8d6c5646c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 41 additions and 57 deletions

1
Cargo.lock generated
View file

@ -1064,6 +1064,7 @@ name = "canvas"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"app_units", "app_units",
"bytemuck",
"canvas_traits", "canvas_traits",
"compositing_traits", "compositing_traits",
"crossbeam-channel", "crossbeam-channel",

View file

@ -33,6 +33,7 @@ base64 = "0.22.1"
bincode = "1" bincode = "1"
bitflags = "2.9" bitflags = "2.9"
bluetooth_traits = { path = "components/shared/bluetooth" } bluetooth_traits = { path = "components/shared/bluetooth" }
bytemuck = "1"
byteorder = "1.5" byteorder = "1.5"
canvas_traits = { path = "components/shared/canvas" } canvas_traits = { path = "components/shared/canvas" }
cbc = "0.1.2" cbc = "0.1.2"

View file

@ -16,6 +16,7 @@ vello = ["dep:vello", "dep:pollster", "dep:futures-intrusive", "dep:peniko"]
[dependencies] [dependencies]
app_units = { workspace = true } app_units = { workspace = true }
bytemuck = { workspace = true, features = ["extern_crate_alloc"] }
canvas_traits = { workspace = true } canvas_traits = { workspace = true }
compositing_traits = { workspace = true } compositing_traits = { workspace = true }
crossbeam-channel = { workspace = true } crossbeam-channel = { workspace = true }

View file

@ -154,26 +154,16 @@ pub fn source(pattern: &Pattern) -> raqote::Source {
pattern.radius2, pattern.radius2,
raqote::Spread::Pad, raqote::Spread::Pad,
), ),
Pattern::Surface(pattern) => { Pattern::Surface(pattern) => raqote::Source::Image(
#[allow(unsafe_code)] raqote::Image {
let data = unsafe { width: pattern.image.size().width as i32,
let data = pattern.image.as_raw_bytes(); height: pattern.image.size().height as i32,
std::slice::from_raw_parts( data: bytemuck::cast_slice(pattern.image.as_raw_bytes()),
data.as_ptr() as *const u32, },
data.len() / std::mem::size_of::<u32>(), pattern.extend,
) pattern.filter,
}; pattern.transform,
raqote::Source::Image( ),
raqote::Image {
width: pattern.image.size().width as i32,
height: pattern.image.size().height as i32,
data,
},
pattern.extend,
pattern.filter,
pattern.transform,
)
},
} }
} }
@ -188,7 +178,7 @@ fn create_gradient_stops(gradient_stops: Vec<CanvasGradientStop>) -> Vec<raqote:
} }
impl GenericDrawTarget for raqote::DrawTarget { impl GenericDrawTarget for raqote::DrawTarget {
type SourceSurface = Vec<u8>; // TODO: See if we can avoid the alloc (probably?) type SourceSurface = Vec<u32>; // TODO: See if we can avoid the alloc (probably?)
fn new(size: Size2D<u32>) -> Self { fn new(size: Size2D<u32>) -> Self {
raqote::DrawTarget::new(size.width as i32, size.height as i32) raqote::DrawTarget::new(size.width as i32, size.height as i32)
@ -206,17 +196,13 @@ impl GenericDrawTarget for raqote::DrawTarget {
transform, transform,
); );
} }
#[allow(unsafe_code)]
fn copy_surface( fn copy_surface(
&mut self, &mut self,
surface: Self::SourceSurface, surface: Self::SourceSurface,
source: Rect<i32>, source: Rect<i32>,
destination: Point2D<i32>, destination: Point2D<i32>,
) { ) {
let mut dt = raqote::DrawTarget::new(source.size.width, source.size.height); let dt = raqote::DrawTarget::from_vec(source.size.width, source.size.height, surface);
let data = surface;
let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u32, data.len() / 4) };
dt.get_data_mut().copy_from_slice(s);
raqote::DrawTarget::copy_surface(self, &dt, source.to_box2d(), destination); raqote::DrawTarget::copy_surface(self, &dt, source.to_box2d(), destination);
} }
@ -225,16 +211,18 @@ impl GenericDrawTarget for raqote::DrawTarget {
} }
fn create_source_surface_from_data(&self, data: Snapshot) -> Option<Self::SourceSurface> { fn create_source_surface_from_data(&self, data: Snapshot) -> Option<Self::SourceSurface> {
Some( Some(
data.to_vec( bytemuck::try_cast_vec(
Some(SnapshotAlphaMode::Transparent { data.to_vec(
premultiplied: true, Some(SnapshotAlphaMode::Transparent {
}), premultiplied: true,
Some(SnapshotPixelFormat::BGRA), }),
Some(SnapshotPixelFormat::BGRA),
)
.0,
) )
.0, .unwrap_or_else(|(_, surface)| bytemuck::pod_collect_to_vec(&surface)),
) )
} }
#[allow(unsafe_code)]
fn draw_surface( fn draw_surface(
&mut self, &mut self,
surface: Self::SourceSurface, surface: Self::SourceSurface,
@ -244,15 +232,6 @@ impl GenericDrawTarget for raqote::DrawTarget {
composition_options: CompositionOptions, composition_options: CompositionOptions,
transform: Transform2D<f32>, transform: Transform2D<f32>,
) { ) {
let image = Snapshot::from_vec(
src.size.cast(),
SnapshotPixelFormat::BGRA,
SnapshotAlphaMode::Transparent {
premultiplied: true,
},
surface,
);
let paint_transform = let paint_transform =
raqote::Transform::translation(-dest.origin.x as f32, -dest.origin.y as f32) raqote::Transform::translation(-dest.origin.x as f32, -dest.origin.y as f32)
.then_scale( .then_scale(
@ -260,13 +239,6 @@ impl GenericDrawTarget for raqote::DrawTarget {
src.size.height as f32 / dest.size.height as f32, src.size.height as f32 / dest.size.height as f32,
); );
let pattern = Pattern::Surface(SurfacePattern::new(
image,
filter.to_raqote(),
Repetition::NoRepeat,
paint_transform,
));
self.set_transform(&transform); self.set_transform(&transform);
let dest = dest.cast(); let dest = dest.cast();
let mut pb = raqote::PathBuilder::new(); let mut pb = raqote::PathBuilder::new();
@ -276,10 +248,20 @@ impl GenericDrawTarget for raqote::DrawTarget {
dest.size.width, dest.size.width,
dest.size.height, dest.size.height,
); );
let size = src.size.cast();
fill_draw_target( fill_draw_target(
self, self,
draw_options(composition_options), draw_options(composition_options),
pattern, &raqote::Source::Image(
raqote::Image {
width: size.width,
height: size.height,
data: &surface,
},
raqote::ExtendMode::Pad,
filter.to_raqote(),
paint_transform,
),
pb.finish(), pb.finish(),
); );
} }
@ -303,7 +285,7 @@ impl GenericDrawTarget for raqote::DrawTarget {
let draw_options = draw_options(composition_options); let draw_options = draw_options(composition_options);
let pattern = style.to_raqote_pattern(); let pattern = style.to_raqote_pattern();
let path = to_path(path); let path = to_path(path);
fill_draw_target(self, draw_options, pattern, path); fill_draw_target(self, draw_options, &source(&pattern), path);
} }
fn fill_text( fn fill_text(
@ -402,7 +384,7 @@ impl GenericDrawTarget for raqote::DrawTarget {
self.push_clip_rect(rect.to_box2d()); self.push_clip_rect(rect.to_box2d());
} }
fn surface(&mut self) -> Self::SourceSurface { fn surface(&mut self) -> Self::SourceSurface {
self.get_data_u8().to_vec() self.get_data().to_vec()
} }
fn stroke( fn stroke(
&mut self, &mut self,
@ -481,13 +463,13 @@ impl GenericDrawTarget for raqote::DrawTarget {
fn fill_draw_target( fn fill_draw_target(
draw_target: &mut raqote::DrawTarget, draw_target: &mut raqote::DrawTarget,
draw_options: DrawOptions, draw_options: DrawOptions,
pattern: Pattern, source: &raqote::Source<'_>,
path: raqote::Path, path: raqote::Path,
) { ) {
match draw_options.blend_mode { match draw_options.blend_mode {
raqote::BlendMode::Src => { raqote::BlendMode::Src => {
draw_target.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0)); draw_target.clear(raqote::SolidSource::from_unpremultiplied_argb(0, 0, 0, 0));
draw_target.fill(&path, &source(&pattern), &draw_options); draw_target.fill(&path, source, &draw_options);
}, },
raqote::BlendMode::Clear | raqote::BlendMode::Clear |
raqote::BlendMode::SrcAtop | raqote::BlendMode::SrcAtop |
@ -496,7 +478,7 @@ fn fill_draw_target(
raqote::BlendMode::Xor | raqote::BlendMode::Xor |
raqote::BlendMode::DstOver | raqote::BlendMode::DstOver |
raqote::BlendMode::SrcOver => { raqote::BlendMode::SrcOver => {
draw_target.fill(&path, &source(&pattern), &draw_options); draw_target.fill(&path, source, &draw_options);
}, },
raqote::BlendMode::SrcIn | raqote::BlendMode::SrcIn |
raqote::BlendMode::SrcOut | raqote::BlendMode::SrcOut |
@ -505,7 +487,7 @@ fn fill_draw_target(
let mut options = draw_options; let mut options = draw_options;
draw_target.push_layer_with_blend(1., options.blend_mode); draw_target.push_layer_with_blend(1., options.blend_mode);
options.blend_mode = raqote::BlendMode::SrcOver; options.blend_mode = raqote::BlendMode::SrcOver;
draw_target.fill(&path, &source(&pattern), &options); draw_target.fill(&path, source, &options);
draw_target.pop_layer(); draw_target.pop_layer();
}, },
_ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode), _ => warn!("unrecognized blend mode: {:?}", draw_options.blend_mode),
@ -620,7 +602,6 @@ impl ToRaqoteGradientStop for CanvasGradientStop {
} }
impl ToRaqotePattern for FillOrStrokeStyle { impl ToRaqotePattern for FillOrStrokeStyle {
#[allow(unsafe_code)]
fn to_raqote_pattern(self) -> Pattern { fn to_raqote_pattern(self) -> Pattern {
use canvas_traits::canvas::FillOrStrokeStyle::*; use canvas_traits::canvas::FillOrStrokeStyle::*;