diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb376327..9bf6784aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,10 +41,13 @@ Check out our Wordle game demo, now running smoothly in your browser! - **gpu**: Updated the `wgpu` implementation of the GPU backend to support WebGL. (#578, @M-Adoo) - **ci**: add wasm test (#583 @wjian23) - **ci**: wasm server watch file change (#586 @wjian23) +- **painter**: Introduced support for `Resource` for drawing. This indicates that the `Path` may be shared with others, allowing the backend to cache it. (#589 @M-Adoo) +- **painter**: Introduced support for bundled commands, enabling the backend to process these commands as a single entity and cache the resulting output. (#589 @M-Adoo) ### Fixed - **gpu**: Retrieve the texture limit size from the GPU instead of using a hardcoded value. (#578, @M-Adoo) +- **ribir**: fixed the crash issue when the shell window is zero-sized at startup. (#582, @M-Adoo) ### Changed @@ -56,11 +59,8 @@ Check out our Wordle game demo, now running smoothly in your browser! - **painter**: Removed the AntiAliasing feature from the `painter` package, This responsibility now lies with the painter backend. (#584 @M-Adoo) - **gpu**: The GPU backend no longer relies on MSAA, which is dependent on the graphics API. Instead, it uses the alpha atlas to provide a solution similar to SSAA.(#584, @M-Adoo) - **example**: run example in web wasm (#571 @wjian23) - - -### Fixed - -- **ribir**: fixed the crash issue when the shell window is zero-sized at startup. (#582, @M-Adoo) +- **gpu**: The GPU backend now only caches the path command if it is a `Resource`. This change reduces GPU memory usage and accelerates cache detection. (#589 @M-Adoo) +- **text**: Implemented caching of the glyph path as a `Resource` to improve performance. (#589 @M-Adoo) ### Documented diff --git a/core/src/test_helper.rs b/core/src/test_helper.rs index ec4469814..1d86baef5 100644 --- a/core/src/test_helper.rs +++ b/core/src/test_helper.rs @@ -161,8 +161,9 @@ impl ShellWindow for TestShellWindow { fn begin_frame(&mut self, surface: Color) { self.surface_color = surface; } - fn draw_commands(&mut self, viewport: Rect, commands: Vec) { - self.last_frame = Some(Frame { commands, viewport, surface: self.surface_color }); + fn draw_commands(&mut self, viewport: Rect, commands: &[PaintCommand]) { + self.last_frame = + Some(Frame { commands: commands.to_owned(), viewport, surface: self.surface_color }); } fn end_frame(&mut self) {} diff --git a/core/src/window.rs b/core/src/window.rs index 63a2aa015..a876ff0a4 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -85,7 +85,7 @@ pub trait ShellWindow { /// device. fn device_pixel_ratio(&self) -> f32; fn begin_frame(&mut self, surface_color: Color); - fn draw_commands(&mut self, viewport: Rect, commands: Vec); + fn draw_commands(&mut self, viewport: Rect, commands: &[PaintCommand]); fn end_frame(&mut self); } @@ -202,8 +202,8 @@ impl Window { let mut shell = self.shell_wnd.borrow_mut(); let inner_size = shell.inner_size(); - let paint_cmds = self.painter.borrow_mut().finish(); - shell.draw_commands(Rect::from_size(inner_size), paint_cmds); + let mut painter = self.painter.borrow_mut(); + shell.draw_commands(Rect::from_size(inner_size), &painter.finish()); shell.end_frame(); } diff --git a/dev-helper/src/image_test.rs b/dev-helper/src/image_test.rs index d616bb008..517148c7c 100644 --- a/dev-helper/src/image_test.rs +++ b/dev-helper/src/image_test.rs @@ -1,3 +1,4 @@ +use ribir_geom::Transform; use ribir_painter::{image::ColorFormat, PixelImage}; /// This macro generates image tests for the painter with every backend. Accept @@ -26,7 +27,7 @@ macro_rules! painter_backend_eq_image_test { fn []() { let mut painter = $painter_fn(); let viewport = painter.viewport().to_i32().cast_unit(); - let img = wgpu_render_commands(painter.finish(), viewport, Color::TRANSPARENT); + let img = wgpu_render_commands(&painter.finish(), viewport, Color::TRANSPARENT); let name = format!("{}_wgpu", std::stringify!($painter_fn)); let file_path = test_case_name!(name, "png"); ImageTest::new(img, &file_path) @@ -174,7 +175,7 @@ pub fn assert_texture_eq_png(test_img: PixelImage, ref_path: &std::path::Path) { /// Render painter by wgpu backend, and return the image. pub fn wgpu_render_commands( - commands: Vec, viewport: ribir_geom::DeviceRect, + commands: &[ribir_painter::PaintCommand], viewport: ribir_geom::DeviceRect, surface: ribir_painter::Color, ) -> PixelImage { use futures::executor::block_on; @@ -189,7 +190,7 @@ pub fn wgpu_render_commands( let mut backend = GPUBackend::new(gpu_impl); backend.begin_frame(surface); - backend.draw_commands(rect, commands, &mut texture); + backend.draw_commands(rect, commands, &Transform::identity(), &mut texture); let img = texture.copy_as_image(&rect, backend.get_impl_mut()); backend.end_frame(); block_on(img).unwrap() diff --git a/dev-helper/src/widget_test.rs b/dev-helper/src/widget_test.rs index 2db43abe7..46c621d79 100644 --- a/dev-helper/src/widget_test.rs +++ b/dev-helper/src/widget_test.rs @@ -224,7 +224,7 @@ macro_rules! widget_image_test { wnd.draw_frame(); let Frame { commands, viewport, surface} = wnd.take_last_frame().unwrap(); let viewport = viewport.to_i32().cast_unit(); - let img = wgpu_render_commands(commands, viewport, surface); + let img = wgpu_render_commands(&commands, viewport, surface); let name = format!("{}_with_default_by_wgpu", std::stringify!($widget_fn)); let file_path = test_case_name!(name, "png"); ImageTest::new(img, &file_path) @@ -241,7 +241,7 @@ macro_rules! widget_image_test { wnd.draw_frame(); let Frame { commands, viewport, surface} = wnd.take_last_frame().unwrap(); let viewport = viewport.to_i32().cast_unit(); - let img = wgpu_render_commands(commands, viewport, surface); + let img = wgpu_render_commands(&commands, viewport, surface); let name = format!("{}_with_material_by_wgpu", std::stringify!($widget_fn)); let file_path = test_case_name!(name, "png"); ImageTest::new(img, &file_path) diff --git a/geom/src/lib.rs b/geom/src/lib.rs index 304d9840f..9de6dac7b 100644 --- a/geom/src/lib.rs +++ b/geom/src/lib.rs @@ -42,3 +42,12 @@ where Point2D::new(rect.min_x(), rect.max_y()), ] } + +/// Apply the transform to the rect and return the new rect as device unit. +pub fn transform_to_device_rect(rect: &Rect, matrix: &Transform) -> DeviceRect { + matrix + .outer_transformed_rect(rect) + .round_out() + .to_i32() + .cast_unit() +} diff --git a/gpu/src/gpu_backend.rs b/gpu/src/gpu_backend.rs index 8296783e4..ecea5cde7 100644 --- a/gpu/src/gpu_backend.rs +++ b/gpu/src/gpu_backend.rs @@ -1,8 +1,11 @@ use std::error::Error; -use ribir_geom::{rect_corners, DeviceRect, DeviceSize, Point}; +use guillotiere::euclid::Vector2D; +use ribir_geom::{ + rect_corners, transform_to_device_rect, DeviceRect, DeviceSize, Point, Transform, +}; use ribir_painter::{ - image::ColorFormat, Color, PaintCommand, PaintPathAction, PainterBackend, PathCommand, + image::ColorFormat, Color, PaintCommand, PaintPath, PaintPathAction, PainterBackend, PathCommand, PixelImage, Vertex, VertexBuffers, }; @@ -93,36 +96,20 @@ where } fn draw_commands( - &mut self, viewport: DeviceRect, commands: Vec, output: &mut Self::Texture, + &mut self, viewport: DeviceRect, commands: &[PaintCommand], global_matrix: &Transform, + output: &mut Self::Texture, ) { + let clips = self.clip_layer_stack.len(); self.viewport = viewport; self.begin_draw_phase(); let output_size = output.size(); - for cmd in commands.into_iter() { - let max_tex_per_draw = self.gpu_impl.limits().max_tex_load; - let maybe_used = match cmd { - PaintCommand::Path(PathCommand { action: PaintPathAction::Image { .. }, .. }) => 2, - PaintCommand::PopClip => 0, - _ => 1, - }; - if !self.can_batch(&cmd) { - // if the next command may hit the texture limit, submit the current draw phase. - // And start a new draw phase. - self.draw_triangles(output); - self.end_draw_phase(); - self.begin_draw_phase(); - - assert!( - self.tex_ids_map.all_textures().len() + maybe_used < max_tex_per_draw, - "The GPUBackend implementation does not provide a sufficient texture limit per draw." - ) - } - self.draw_command(cmd, output_size); + for cmd in commands { + self.draw_command(cmd, global_matrix, output_size, output); } self.draw_triangles(output); self.end_draw_phase(); - assert!(self.clip_layer_stack.is_empty()); + assert_eq!(self.clip_layer_stack.len(), clips); } fn end_frame(&mut self) { @@ -166,27 +153,37 @@ where #[inline] pub fn get_impl_mut(&mut self) -> &mut Impl { &mut self.gpu_impl } - fn draw_command(&mut self, cmd: PaintCommand, output_tex_size: DeviceSize) { + fn draw_command( + &mut self, cmd: &PaintCommand, global_matrix: &Transform, output_tex_size: DeviceSize, + output: &mut Impl::Texture, + ) { match cmd { - PaintCommand::Path(path) => { + PaintCommand::Path(cmd @ PathCommand { path, paint_bounds, transform, action }) => { if self.skip_clip_cnt > 0 { - if matches!(path.action, PaintPathAction::Clip) { + if matches!(action, PaintPathAction::Clip) { self.skip_clip_cnt += 1; } // Skip the commands if the clip layer is not visible. return; } + let bounds = transform_to_device_rect(paint_bounds, global_matrix); - let bounds = path.paint_bounds.round_out().to_i32().cast_unit(); - let Some((rect, mask_head)) = self.new_mask_layer(&path) else { - if matches!(path.action, PaintPathAction::Clip) { + let Some(viewport) = self.viewport().intersection(&bounds) else { + if matches!(action, PaintPathAction::Clip) { self.skip_clip_cnt += 1; } - // Skip the command if its mask is not visible. + // Skip the command if it is not visible. return; }; - match path.action { + if !self.can_batch_path_command(cmd) { + self.new_draw_phase(output); + } + + let matrix = transform.then(global_matrix); + let (rect, mask_head) = self.new_mask_layer(&viewport, &matrix, path); + + match &action { PaintPathAction::Color(color) => { let color = color.into_components(); let color_attr = ColorAttr { color, mask_head }; @@ -195,27 +192,13 @@ where self.current_phase = CurrentPhase::Color; } PaintPathAction::Image { img, opacity } => { - let img_slice = self.tex_mgr.store_image(&img, &mut self.gpu_impl); - let img_start = img_slice.rect.origin.to_f32().to_array(); - let img_size = img_slice.rect.size.to_f32().to_array(); - let mask_head_and_tex_idx = - mask_head << 16 | self.tex_ids_map.tex_idx(img_slice.tex_id) as i32; - let prim_idx = self.img_prims.len() as u32; - let prim = ImgPrimitive { - transform: path.transform.inverse().unwrap().to_array(), - img_start, - img_size, - mask_head_and_tex_idx, - opacity, - }; - self.img_prims.push(prim); - let buffer = &mut self.img_vertices_buffer; - add_rect_vertices(rect, output_tex_size, ImagePrimIndex(prim_idx), buffer); - self.current_phase = CurrentPhase::Img; + let slice = self.tex_mgr.store_image(img, &mut self.gpu_impl); + let ts = matrix.inverse().unwrap(); + self.draw_img_slice(slice, &ts, mask_head, *opacity, output_tex_size, rect); } PaintPathAction::Radial(radial) => { let prim: RadialGradientPrimitive = RadialGradientPrimitive { - transform: path.transform.inverse().unwrap().to_array(), + transform: matrix.inverse().unwrap().to_array(), stop_start: self.radial_gradient_stops.len() as u32, stop_cnt: radial.stops.len() as u32, start_center: radial.start_center.to_array(), @@ -227,8 +210,8 @@ where }; let stops = radial .stops - .into_iter() - .map(Into::::into); + .iter() + .map(GradientStopPrimitive::new); self.radial_gradient_stops.extend(stops); let prim_idx = self.radial_gradient_prims.len() as u32; self.radial_gradient_prims.push(prim); @@ -241,7 +224,7 @@ where let stop = (self.linear_gradient_stops.len() << 16 | linear.stops.len()) as u32; let mask_head_and_spread = mask_head << 16 | linear.spread_method as i32; let prim: LinearGradientPrimitive = LinearGradientPrimitive { - transform: path.transform.inverse().unwrap().to_array(), + transform: matrix.inverse().unwrap().to_array(), stop, start_position: linear.start.to_array(), end_position: linear.end.to_array(), @@ -249,8 +232,8 @@ where }; let stops = linear .stops - .into_iter() - .map(Into::::into); + .iter() + .map(GradientStopPrimitive::new); self.linear_gradient_stops.extend(stops); let prim_idx = self.linear_gradient_prims.len() as u32; self.linear_gradient_prims.push(prim); @@ -258,13 +241,9 @@ where add_rect_vertices(rect, output_tex_size, LinearGradientPrimIndex(prim_idx), buffer); self.current_phase = CurrentPhase::LinearGradient; } - PaintPathAction::Clip => { - if let Some(viewport) = bounds.intersection(self.viewport()) { - self - .clip_layer_stack - .push(ClipLayer { viewport, mask_head }); - } - } + PaintPathAction::Clip => self + .clip_layer_stack + .push(ClipLayer { viewport, mask_head }), } } PaintCommand::PopClip => { @@ -274,21 +253,109 @@ where self.clip_layer_stack.pop(); } } + PaintCommand::Bundle { transform, opacity, bounds, cmds } => { + let matrix = transform.then(global_matrix); + let scale = self.tex_mgr.cache_scale(&bounds.size, &matrix); + let cache_size = bounds.size * scale; + + let this = self as *mut Self; + let (cache_scale, slice) = self.tex_mgr.store_commands( + cache_size.to_i32().cast_unit(), + cmds.clone().into_any(), + scale, + &mut self.gpu_impl, + |slice, tex, _| { + // SAFETY: We already hold a mut reference to the texture in the texture + // manager, so we cant use `self` here, but this texture should always exist + // within the frame, and no modifications will be made to the slice + // that has already been allocated. + let this = unsafe { &mut *this }; + + // Initiate a new drawing phase to ensure a clean state for rendering in a new + // texture. + this.new_draw_phase(output); + + // store the viewport + let viewport = self.viewport; + // Overwrite the viewport to the slice bounds. + self + .clip_layer_stack + .push(ClipLayer { viewport, mask_head: -1 }); + + let matrix = Transform::translation(bounds.origin.x, bounds.origin.y) + .then_scale(scale, scale) + .then_translate(slice.origin.to_f32().cast_unit().to_vector()); + this.draw_commands(*slice, cmds, &matrix, tex); + + // restore the clip layer and viewport + self.clip_layer_stack.pop(); + this.viewport = viewport; + this.begin_draw_phase(); + }, + ); + + let mut points: [_; 4] = rect_corners(&bounds.to_f32().cast_unit()); + for p in points.iter_mut() { + *p = matrix.transform_point(*p); + } + + let view_to_slice = matrix + // point back to the bundle commands axis. + .inverse() + .unwrap() + // align to the zero point, draw image slice is start from zero. + .then_translate(Vector2D::new(-bounds.origin.x, -bounds.origin.y)) + // scale to the cache size. + .then_scale(cache_scale, cache_scale); + + if !self.can_batch_img_path() { + self.new_draw_phase(output); + } + let mask_head = self + .clip_layer_stack + .last() + .map_or(-1, |l| l.mask_head); + self.draw_img_slice(slice, &view_to_slice, mask_head, *opacity, output_tex_size, points); + } } } + fn can_batch_img_path(&self) -> bool { + let limits = self.gpu_impl.limits(); + self.current_phase == CurrentPhase::None + || (self.current_phase == CurrentPhase::Img + && self.tex_ids_map.len() < limits.max_tex_load - 1 + && self.img_prims.len() < limits.max_image_primitives) + } + + // end current draw phase and start a new draw phase. + fn new_draw_phase(&mut self, output: &mut Impl::Texture) { + self.draw_triangles(output); + self.end_draw_phase(); + self.begin_draw_phase(); + } + fn begin_draw_phase(&mut self) { - self.current_phase = CurrentPhase::None; if !self.clip_layer_stack.is_empty() { // clear unused mask layers and update mask index. let mut retain_masks = Vec::with_capacity(self.clip_layer_stack.len()); + let mut mask_new_idx = vec![-1; self.mask_layers.len()]; for s in self.clip_layer_stack.iter_mut() { - retain_masks.push(s.mask_head); - s.mask_head = retain_masks.len() as i32 - 1; + if s.mask_head != -1 { + retain_masks.push(s.mask_head); + mask_new_idx[s.mask_head as usize] = retain_masks.len() as i32 - 1; + s.mask_head = retain_masks.len() as i32 - 1; + } } self.mask_layers = retain_masks .iter() - .map(|&idx| self.mask_layers[idx as usize].clone()) + .map(|&idx| { + let mut mask = self.mask_layers[idx as usize].clone(); + if mask.prev_mask_idx != -1 { + mask.prev_mask_idx = mask_new_idx[mask.prev_mask_idx as usize]; + } + mask + }) .collect(); // update the texture index of mask layers in new draw phase. @@ -305,6 +372,7 @@ where } fn end_draw_phase(&mut self) { + self.current_phase = CurrentPhase::None; self.color_vertices_buffer.vertices.clear(); self.color_vertices_buffer.indices.clear(); self.img_vertices_buffer.vertices.clear(); @@ -328,12 +396,30 @@ where self.linear_gradient_stops.clear(); } - fn can_batch(&self, cmd: &PaintCommand) -> bool { - let limits = self.gpu_impl.limits(); - let tex_used = self.tex_ids_map.all_textures().len(); - // Pop commands can always be batched. - let PaintCommand::Path(cmd) = cmd else { return true }; + fn draw_img_slice( + &mut self, img_slice: TextureSlice, transform: &Transform, mask_head: i32, opacity: f32, + output_tex_size: DeviceSize, rect: [Point; 4], + ) { + let img_start = img_slice.rect.origin.to_f32().to_array(); + let img_size = img_slice.rect.size.to_f32().to_array(); + let mask_head_and_tex_idx = mask_head << 16 | self.tex_ids_map.tex_idx(img_slice.tex_id) as i32; + let prim_idx = self.img_prims.len() as u32; + let prim = ImgPrimitive { + transform: transform.to_array(), + img_start, + img_size, + mask_head_and_tex_idx, + opacity, + }; + self.img_prims.push(prim); + let buffer = &mut self.img_vertices_buffer; + add_rect_vertices(rect, output_tex_size, ImagePrimIndex(prim_idx), buffer); + self.current_phase = CurrentPhase::Img; + } + fn can_batch_path_command(&self, cmd: &PathCommand) -> bool { + let limits = self.gpu_impl.limits(); + let tex_used = self.tex_ids_map.len(); match (self.current_phase, &cmd.action) { (CurrentPhase::None, _) => true, (_, PaintPathAction::Clip) | (CurrentPhase::Color, PaintPathAction::Color(_)) => { @@ -370,13 +456,13 @@ where .map_or(&self.viewport, |l| &l.viewport) } - fn new_mask_layer(&mut self, path: &PathCommand) -> Option<([Point; 4], i32)> { - let paint_bounds = path.paint_bounds.round_out().to_i32().cast_unit(); - let view = paint_bounds.intersection(self.viewport())?; - - let (mask, mask_to_view) = self - .tex_mgr - .store_alpha_path(path, view, &mut self.gpu_impl); + fn new_mask_layer( + &mut self, view: &DeviceRect, matrix: &Transform, path: &PaintPath, + ) -> ([Point; 4], i32) { + let (mask, mask_to_view) = + self + .tex_mgr + .store_alpha_path(path, matrix, view, &mut self.gpu_impl); let mut points = rect_corners(&mask.rect.to_f32().cast_unit()); for p in points.iter_mut() { @@ -393,7 +479,7 @@ where mask_tex_idx: self.tex_ids_map.tex_idx(mask.tex_id), prev_mask_idx: self.current_clip_mask_index(), }); - Some((points, index as i32)) + (points, index as i32) } fn draw_triangles(&mut self, output: &mut Impl::Texture) { @@ -401,44 +487,64 @@ where let gpu_impl = &mut self.gpu_impl; self.tex_mgr.draw_alpha_textures(gpu_impl); - gpu_impl.load_mask_layers(&self.mask_layers); + if !self.mask_layers.is_empty() { + gpu_impl.load_mask_layers(&self.mask_layers); + } let textures = self.tex_ids_map.all_textures(); let max_textures = gpu_impl.limits().max_tex_load; let mut tex_buffer = Vec::with_capacity(max_textures); - textures.iter().take(max_textures).for_each(|id| { - tex_buffer.push(self.tex_mgr.texture(*id)); - }); - + if textures.is_empty() { + tex_buffer.push(self.tex_mgr.texture(TextureID::Alpha(0))); + } else { + textures.iter().take(max_textures).for_each(|id| { + tex_buffer.push(self.tex_mgr.texture(*id)); + }); + } gpu_impl.load_textures(&tex_buffer); match self.current_phase { - CurrentPhase::None => gpu_impl.draw_color_triangles(output, 0..0, color.take()), - CurrentPhase::Color => { + CurrentPhase::None => { + if color.is_some() { + gpu_impl.draw_color_triangles(output, 0..0, color.take()) + } + } + CurrentPhase::Color if !self.color_vertices_buffer.indices.is_empty() => { gpu_impl.load_color_vertices(&self.color_vertices_buffer); let rg = 0..self.color_vertices_buffer.indices.len() as u32; gpu_impl.draw_color_triangles(output, rg, color.take()) } - CurrentPhase::Img => { + CurrentPhase::Img if !self.img_vertices_buffer.indices.is_empty() => { gpu_impl.load_img_primitives(&self.img_prims); gpu_impl.load_img_vertices(&self.img_vertices_buffer); let rg = 0..self.img_vertices_buffer.indices.len() as u32; gpu_impl.draw_img_triangles(output, rg, color.take()) } - CurrentPhase::RadialGradient => { + CurrentPhase::RadialGradient + if !self + .radial_gradient_vertices_buffer + .indices + .is_empty() => + { gpu_impl.load_radial_gradient_primitives(&self.radial_gradient_prims); gpu_impl.load_radial_gradient_stops(&self.radial_gradient_stops); gpu_impl.load_radial_gradient_vertices(&self.radial_gradient_vertices_buffer); let rg = 0..self.radial_gradient_vertices_buffer.indices.len() as u32; gpu_impl.draw_radial_gradient_triangles(output, rg, color.take()) } - CurrentPhase::LinearGradient => { + CurrentPhase::LinearGradient + if !self + .linear_gradient_vertices_buffer + .indices + .is_empty() => + { gpu_impl.load_linear_gradient_primitives(&self.linear_gradient_prims); gpu_impl.load_linear_gradient_stops(&self.linear_gradient_stops); gpu_impl.load_linear_gradient_vertices(&self.linear_gradient_vertices_buffer); let rg = 0..self.linear_gradient_vertices_buffer.indices.len() as u32; gpu_impl.draw_linear_gradient_triangles(output, rg, color.take()) } + _ => {} } } } @@ -458,6 +564,8 @@ impl TextureIdxMap { self.texture_map.clear(); self.textures.clear(); } + + fn len(&self) -> usize { self.textures.len() } } pub fn vertices_coord(pos: Point, tex_size: DeviceSize) -> [f32; 2] { @@ -580,7 +688,7 @@ mod tests { painter } - painter_backend_eq_image_test!(stroke_include_border, comparison = 0.00035); + painter_backend_eq_image_test!(stroke_include_border, comparison = 0.0004); fn stroke_include_border() -> Painter { let mut painter = painter(Size::new(100., 100.)); painter @@ -664,4 +772,25 @@ mod tests { } #[cfg(not(target_os = "windows"))] painter_backend_eq_image_test!(multi_draw_phase, comparison = 0.001); + + fn draw_bundle_svg() -> Painter { + let mut painter = painter(Size::new(512., 512.)); + let circle = Resource::new(Path::circle(Point::new(4., 4.), 100.)); + let commands = (0..64) + .map(|i| { + let color = if i % 2 == 0 { Color::GREEN } else { Color::RED }; + PaintCommand::Path(PathCommand { + paint_bounds: *circle.bounds(), + path: circle.clone().into(), + transform: Transform::translation(i as f32 * 8., i as f32 * 8.), + action: PaintPathAction::Color(color), + }) + }) + .collect(); + + let svg = Svg { size: Size::new(512., 512.), commands: Resource::new(commands) }; + painter.draw_svg(&svg); + painter + } + painter_backend_eq_image_test!(draw_bundle_svg, comparison = 0.001); } diff --git a/gpu/src/gpu_backend/atlas.rs b/gpu/src/gpu_backend/atlas.rs index 25e3777ab..e6a50d1a0 100644 --- a/gpu/src/gpu_backend/atlas.rs +++ b/gpu/src/gpu_backend/atlas.rs @@ -32,7 +32,9 @@ pub(crate) struct Atlas { atlas_allocator: AtlasAllocator, texture: T, cache: FrameCache, AtlasHandle>, + /// Extra textures which store only single allocation. extras: Slab, + /// All allocations in the current frame and not cached. islands: ahash::HashSet, } @@ -184,7 +186,7 @@ where impl AtlasConfig { pub fn new(label: &'static str, max_size: DeviceSize) -> Self { - Self { label, min_size: max_size / 4, max_size } + Self { label, min_size: max_size / 8, max_size } } } diff --git a/gpu/src/gpu_backend/textures_mgr.rs b/gpu/src/gpu_backend/textures_mgr.rs index 7113ed1da..37e824655 100644 --- a/gpu/src/gpu_backend/textures_mgr.rs +++ b/gpu/src/gpu_backend/textures_mgr.rs @@ -1,12 +1,10 @@ -use std::{cmp::Ordering, hash::Hash, ops::Range}; +use std::{any::Any, cmp::Ordering, hash::Hash, ops::Range}; use guillotiere::euclid::SideOffsets2D; use rayon::{prelude::ParallelIterator, slice::ParallelSlice}; use ribir_algo::Resource; -use ribir_geom::{DeviceRect, DeviceSize, Transform}; -use ribir_painter::{ - image::ColorFormat, PaintPath, Path, PathCommand, PixelImage, Vertex, VertexBuffers, -}; +use ribir_geom::{transform_to_device_rect, DeviceRect, DeviceSize, Size, Transform}; +use ribir_painter::{image::ColorFormat, PaintPath, Path, PixelImage, Vertex, VertexBuffers}; use super::{ atlas::{Atlas, AtlasConfig, AtlasDist}, @@ -20,11 +18,19 @@ const PAR_CHUNKS_SIZE: usize = 64; pub(super) enum TextureID { Alpha(usize), Rgba(usize), + Bundle(usize), } pub(super) struct TexturesMgr { alpha_atlas: Atlas, rgba_atlas: Atlas, + /// Similar to the `rgba_atlas`, this is used to allocate the target texture + /// for drawing commands. + /// + /// We keep it separate from `rgba_atlas` because the backend may not permit a + /// texture to be used both as a target and as a sampled resource in the same + /// draw call. + target_atlas: Atlas, fill_task: Vec, fill_task_buffers: VertexBuffers<()>, need_clear_areas: Vec, @@ -49,6 +55,7 @@ macro_rules! id_to_texture_mut { match $id { TextureID::Alpha(id) => $mgr.alpha_atlas.get_texture_mut(id), TextureID::Rgba(id) => $mgr.rgba_atlas.get_texture_mut(id), + TextureID::Bundle(id) => $mgr.target_atlas.get_texture_mut(id), } }; } @@ -58,6 +65,7 @@ macro_rules! id_to_texture { match $id { TextureID::Alpha(id) => $mgr.alpha_atlas.get_texture(id), TextureID::Rgba(id) => $mgr.rgba_atlas.get_texture(id), + TextureID::Bundle(id) => $mgr.target_atlas.get_texture(id), } }; } @@ -81,6 +89,11 @@ where ColorFormat::Rgba8, gpu_impl, ), + target_atlas: Atlas::new( + AtlasConfig::new("Bundle atlas", max_size), + ColorFormat::Rgba8, + gpu_impl, + ), fill_task: <_>::default(), fill_task_buffers: <_>::default(), need_clear_areas: vec![], @@ -90,28 +103,25 @@ where /// Store an alpha path in texture and return the texture and a transform that /// can transform the mask to viewport pub(super) fn store_alpha_path( - &mut self, path: &PathCommand, viewport: DeviceRect, gpu: &mut T::Host, + &mut self, path: &PaintPath, matrix: &Transform, viewport: &DeviceRect, gpu: &mut T::Host, ) -> (TextureSlice, Transform) { - match &path.path { + match path { PaintPath::Share(p) => { - let cache_scale: f32 = self.alpha_cache_path_scale(path); + let cache_scale: f32 = self.cache_scale(&path.bounds().size, matrix); let key = p.clone().into_any(); let (slice, scale) = if let Some(h) = self.alpha_atlas.get(&key, cache_scale).copied() { let mask_slice = self.alpha_atlas_dist_to_tex_silice(&h.dist); (mask_slice, h.scale) } else { - let scale_bounds = p - .bounds() - .scale(cache_scale, cache_scale) - .round_out(); - let (dist, slice) = self.alpha_allocate(scale_bounds.size.to_i32().cast_unit(), gpu); + let scale_bounds = p.bounds().scale(cache_scale, cache_scale); + let (dist, slice) = + self.alpha_allocate(scale_bounds.round_out().size.to_i32().cast_unit(), gpu); let _ = self.alpha_atlas.cache(key, cache_scale, dist); let offset = slice.rect.origin.to_f32().cast_unit() - scale_bounds.origin; let transform = Transform::scale(cache_scale, cache_scale).then_translate(offset); - let path = path.path.clone(); self .fill_task - .push(FillTask { slice, path, transform, clip_rect: None }); + .push(FillTask { slice, path: path.clone(), transform, clip_rect: None }); (slice, cache_scale) }; @@ -119,17 +129,17 @@ where let slice_origin = slice.rect.origin.to_vector().to_f32(); // back to slice origin let matrix = Transform::translation(-slice_origin.x, -slice_origin.y) - // move to cachedc path axis. + // move to cached path axis. .then_translate(path_origin.to_vector().cast_unit()) // scale back to path axis. .then_scale(1. / scale, 1. / scale) // apply path transform matrix to view. - .then(&path.transform); + .then(matrix); (slice.expand_for_paste(), matrix) } PaintPath::Own(_) => { - let paint_bounds = path.paint_bounds.round_out().to_i32().cast_unit(); + let paint_bounds = transform_to_device_rect(path.bounds(), matrix); let alloc_size = size_expand_blank(paint_bounds.size); let (visual_rect, clip_rect) = if self.alpha_atlas.is_good_size_to_alloc(alloc_size) { @@ -137,7 +147,7 @@ where } else { // We intersect the path bounds with the viewport to reduce the number of pixels // drawn for large paths. - let visual_rect = paint_bounds.intersection(&viewport).unwrap(); + let visual_rect = paint_bounds.intersection(viewport).unwrap(); (visual_rect, Some(visual_rect)) }; @@ -145,8 +155,8 @@ where let offset = (slice.rect.origin - visual_rect.origin) .to_f32() .cast_unit(); - let ts = path.transform.then_translate(offset); - let task = FillTask { slice, transform: ts, path: path.path.clone(), clip_rect }; + let ts = matrix.then_translate(offset); + let task = FillTask { slice, transform: ts, path: path.clone(), clip_rect }; self.fill_task.push(task); let offset = (visual_rect.origin - slice.rect.origin).to_f32(); @@ -171,6 +181,22 @@ where TextureSlice { tex_id: TextureID::Rgba(h.tex_id()), rect: h.tex_rect(atlas) } } + pub(super) fn store_commands( + &mut self, size: DeviceSize, target: Resource, scale: f32, gpu: &mut T::Host, + init: impl FnOnce(&DeviceRect, &mut T, &mut T::Host), + ) -> (f32, TextureSlice) { + let dist = self + .target_atlas + .get_or_cache(target, scale, size, gpu, init); + ( + dist.scale, + TextureSlice { + tex_id: TextureID::Bundle(dist.tex_id()), + rect: dist.tex_rect(&self.target_atlas), + }, + ) + } + pub(super) fn texture(&self, tex_id: TextureID) -> &T { id_to_texture!(self, tex_id) } fn alpha_allocate( @@ -191,18 +217,17 @@ where TextureSlice { tex_id: TextureID::Alpha(dist.tex_id()), rect: rect.inner_rect(blank_side) } } - fn alpha_cache_path_scale(&self, path: &PathCommand) -> f32 { - let Transform { m11, m12, m21, m22, .. } = path.transform; + pub(super) fn cache_scale(&self, size: &Size, matrix: &Transform) -> f32 { + let Transform { m11, m12, m21, m22, .. } = matrix; let scale = (m11.abs() + m12.abs()).max(m21.abs() + m22.abs()); - let path_size = path.path.bounds().size; - let dis = path_size.width.max(path_size.height); + let dis = size.width.max(size.height); if dis * scale < 32. { // If the path is too small, set a minimum tessellation size of 32 pixels. 32. / dis } else { // 2 * BLANK_EDGE is the blank edge for each side. let max_size = size_shrink_blank(self.alpha_atlas.max_size()).to_f32(); - let max_scale = (max_size.width / path_size.width).min(max_size.width / path_size.height); + let max_scale = (max_size.width / size.width).min(max_size.width / size.height); scale.min(max_scale) } } @@ -338,6 +363,7 @@ where self.need_clear_areas.push(rect); }); self.rgba_atlas.end_frame(); + self.target_atlas.end_frame(); } } @@ -384,7 +410,7 @@ pub mod tests { use futures::executor::block_on; use ribir_geom::*; - use ribir_painter::{Color, PaintPathAction}; + use ribir_painter::Color; use super::*; use crate::{WgpuImpl, WgpuTexture}; @@ -452,15 +478,12 @@ pub mod tests { let p = Resource::new(Path::rect(&rect(0., 0., 300., 300.))); let p = PaintPath::Share(p.clone()); - let path1 = - PathCommand::new(p.clone(), PaintPathAction::Color(Color::RED), Transform::scale(2., 2.)); - let path2 = - PathCommand::new(p, PaintPathAction::Color(Color::BLUE), Transform::translation(100., 100.)); let viewport = rect(0, 0, 1024, 1024); - let (slice1, ts1) = mgr.store_alpha_path(&path1, viewport, &mut wgpu); + let (slice1, ts1) = mgr.store_alpha_path(&p, &Transform::scale(2., 2.), &viewport, &mut wgpu); - let (slice2, ts2) = mgr.store_alpha_path(&path2, viewport, &mut wgpu); + let (slice2, ts2) = + mgr.store_alpha_path(&p, &Transform::translation(100., 100.), &viewport, &mut wgpu); assert_eq!(slice1, slice2); assert_eq!(ts1, Transform::new(1., 0., 0., 1., -2., -2.)); diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index 0c84efcd6..440290054 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -211,8 +211,8 @@ pub struct GradientStopPrimitive { pub offset: f32, } -impl From for GradientStopPrimitive { - fn from(stop: GradientStop) -> Self { +impl GradientStopPrimitive { + fn new(stop: &GradientStop) -> Self { GradientStopPrimitive { color: stop.color.into_u32(), offset: stop.offset } } } diff --git a/painter/Cargo.toml b/painter/Cargo.toml index b64077579..6accacf9d 100644 --- a/painter/Cargo.toml +++ b/painter/Cargo.toml @@ -22,7 +22,7 @@ material-color-utilities-rs = {workspace = true} rctree.workspace = true ribir_algo = {path = "../algo", version = "0.3.0-alpha.5" } ribir_geom = {path = "../geom", version = "0.3.0-alpha.5" } -serde = {version = "1.0", features = ["rc", "derive"]} +serde = {version = "1.0", features = ["derive"]} serde_json.workspace = true tiny-skia-path = {workspace = true} usvg.workspace = true diff --git a/painter/src/painter.rs b/painter/src/painter.rs index 667311b27..e60a9b85e 100644 --- a/painter/src/painter.rs +++ b/painter/src/painter.rs @@ -22,6 +22,8 @@ pub struct Painter { path_builder: PathBuilder, } +pub struct PainterResult<'a>(&'a mut Vec); + /// `PainterBackend` use to draw textures for every frame, All `draw_commands` /// will called between `begin_frame` and `end_frame` /// @@ -49,9 +51,9 @@ pub trait PainterBackend { /// You should guarantee the output be same one in the same frame, otherwise /// it may cause undefined behavior. fn draw_commands( - &mut self, viewport: DeviceRect, commands: Vec, output: &mut Self::Texture, + &mut self, viewport: DeviceRect, commands: &[PaintCommand], global_matrix: &Transform, + output: &mut Self::Texture, ); - /// A frame end. fn end_frame(&mut self); } @@ -111,14 +113,16 @@ pub struct PathCommand { pub enum PaintCommand { Path(PathCommand), PopClip, - // /// A Bundle of paint commands that can be assumed as a single command, that - // /// means the backend can cache it. - // Bundle { - // transform: Transform, - // opacity: f32, - // paint_bounds: Rect, - // cmds: Resource>, - // }, + /// A Bundle of paint commands that can be assumed as a single command, that + /// means the backend can cache it. + Bundle { + transform: Transform, + opacity: f32, + /// the bounds of the bundle commands. This is the union of all paint + /// command + bounds: Rect, + cmds: Resource>, + }, } #[derive(Clone)] @@ -180,11 +184,9 @@ impl Painter { } #[inline] - pub fn finish(&mut self) -> Vec { + pub fn finish(&mut self) -> PainterResult { self.fill_all_pop_clips(); - let commands = self.commands.clone(); - self.commands.clear(); - commands + PainterResult(&mut self.commands) } /// Saves the entire state and return a guard to auto restore the state when @@ -494,22 +496,58 @@ impl Painter { self } + /// Draws a bundle of paint commands that can be treated as a single command. + /// This allows the backend to cache it. + /// + /// - **bounds** - The bounds of the bundle commands. This is the union of all + /// paint command bounds. It does not configure where the bundle is placed. + /// If you want to change the position of the bundle, you should call + /// `Painter::translate` before calling this method. + /// - **cmds** - The list of paint commands to draw. + pub fn draw_bundle_commands( + &mut self, bounds: Rect, cmds: Resource>, + ) -> &mut Self { + invisible_return!(self); + let transform = *self.get_transform(); + let opacity = self.alpha(); + let cmd = PaintCommand::Bundle { transform, opacity, bounds, cmds }; + self.commands.push(cmd); + self + } + pub fn draw_svg(&mut self, svg: &Svg) -> &mut Self { invisible_return!(self); - let transform = *self.get_transform(); - let alpha = self.alpha(); - - for cmd in svg.paint_commands.iter() { - let cmd = match cmd.clone() { - PaintCommand::Path(mut path) => { - path.transform(&transform); - path.action.apply_alpha(alpha); - PaintCommand::Path(path) - } - PaintCommand::PopClip => PaintCommand::PopClip, - }; - self.commands.push(cmd); + // For a large number of path commands (more than 16), bundle them + // together as a single resource. This allows the backend to cache + // them collectively. + // For a small number of path commands (less than 16), store them + // individually as multiple resources. This means the backend doesn't + // need to perform a single draw operation for an SVG. + if svg.commands.len() <= 16 { + let transform = *self.get_transform(); + let alpha = self.alpha(); + + for cmd in svg.commands.iter() { + let cmd = match cmd.clone() { + PaintCommand::Path(mut path) => { + path.transform(&transform); + path.action.apply_alpha(alpha); + PaintCommand::Path(path) + } + PaintCommand::PopClip => PaintCommand::PopClip, + PaintCommand::Bundle { transform: b_ts, opacity, bounds, cmds } => PaintCommand::Bundle { + transform: transform.then(&b_ts), + opacity: alpha * opacity, + bounds, + cmds, + }, + }; + self.commands.push(cmd); + } + } else { + let rect = Rect::from_size(svg.size); + self.draw_bundle_commands(rect, svg.commands.clone()); } self @@ -613,6 +651,19 @@ impl Painter { } } +impl Drop for PainterResult<'_> { + fn drop(&mut self) { self.0.clear() } +} + +impl<'a> Deref for PainterResult<'a> { + type Target = [PaintCommand]; + fn deref(&self) -> &Self::Target { self.0 } +} + +impl<'a> DerefMut for PainterResult<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { self.0 } +} + /// An RAII implementation of a "scoped state" of the render layer. When this /// structure is dropped (falls out of scope), changed state will auto restore. /// The data can be accessed through this guard via its Deref and DerefMut @@ -751,10 +802,12 @@ mod test { .fill() .finish(); - assert_eq!(painter.current_state().clip_cnt, 0); - assert!(matches!(commands[commands.len() - 1], PaintCommand::PopClip)); assert!(matches!(commands[commands.len() - 2], PaintCommand::PopClip)); + + std::mem::drop(commands); + + assert_eq!(painter.current_state().clip_cnt, 0); } #[test] diff --git a/painter/src/svg.rs b/painter/src/svg.rs index 09fad61e2..a4df48796 100644 --- a/painter/src/svg.rs +++ b/painter/src/svg.rs @@ -1,5 +1,6 @@ use std::{error::Error, io::Read}; +use ribir_algo::Resource; use ribir_geom::{Point, Rect, Size, Transform}; use serde::{Deserialize, Serialize}; use usvg::{Options, Stop, Tree, TreeParsing}; @@ -12,7 +13,7 @@ use crate::{ #[derive(Serialize, Deserialize, Clone)] pub struct Svg { pub size: Size, - pub paint_commands: Vec, + pub commands: Resource>, } /// Fits size into a viewbox. copy from resvg @@ -54,14 +55,13 @@ impl Svg { if let Some(ref fill) = p.fill { let (brush, transform) = brush_from_usvg_paint(&fill.paint, fill.opacity, &size); let mut painter = painter.save_guard(); + + let inverse_ts = transform.inverse().unwrap(); + let path = Resource::new(path.clone().transform(&inverse_ts)); painter .set_brush(brush.clone()) .apply_transform(&transform) - .fill_path( - path - .clone() - .transform(&transform.inverse().unwrap()), - ); + .fill_path(path); //&o_ts.then(&n_ts.inverse().unwrap()))); } @@ -86,11 +86,18 @@ impl Svg { let (brush, transform) = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size); let mut painter = painter.save_guard(); + painter .set_brush(brush.clone()) - .apply_transform(&transform) - .set_strokes(options) - .stroke_path(path.transform(&transform.inverse().unwrap())); + .apply_transform(&transform); + + let path = path + .transform(&transform.inverse().unwrap()) + .stroke(&options, Some(painter.get_transform())); + + if let Some(p) = path { + painter.fill_path(Resource::new(p)); + } }; } NodeKind::Image(_) => { @@ -123,7 +130,12 @@ impl Svg { } }); - Ok(Svg { size: Size::new(size.width(), size.height()), paint_commands: painter.finish() }) + let paint_commands = painter.finish().to_owned().into_boxed_slice(); + + Ok(Svg { + size: Size::new(size.width(), size.height()), + commands: Resource::new(paint_commands), + }) } pub fn open>(path: P) -> Result> { diff --git a/ribir/src/backends/wgpu_backend.rs b/ribir/src/backends/wgpu_backend.rs index e44e06a6a..25fbae302 100644 --- a/ribir/src/backends/wgpu_backend.rs +++ b/ribir/src/backends/wgpu_backend.rs @@ -1,4 +1,4 @@ -use ribir_core::prelude::{Color, DeviceRect, DeviceSize, PaintCommand, PainterBackend}; +use ribir_core::prelude::{Color, DeviceRect, DeviceSize, PaintCommand, PainterBackend, Transform}; use ribir_gpu::Surface; use crate::winit_shell_wnd::WinitBackend; @@ -28,10 +28,15 @@ impl<'a> WinitBackend<'a> for WgpuBackend<'a> { fn begin_frame(&mut self, surface_color: Color) { self.backend.begin_frame(surface_color); } - fn draw_commands(&mut self, viewport: DeviceRect, commands: Vec) { - self - .backend - .draw_commands(viewport, commands, self.surface.get_current_texture()); + fn draw_commands( + &mut self, viewport: DeviceRect, global_matrix: &Transform, commands: &[PaintCommand], + ) { + self.backend.draw_commands( + viewport, + commands, + global_matrix, + self.surface.get_current_texture(), + ); } fn end_frame(&mut self) { diff --git a/ribir/src/winit_shell_wnd.rs b/ribir/src/winit_shell_wnd.rs index 116bcf75c..a976236ef 100644 --- a/ribir/src/winit_shell_wnd.rs +++ b/ribir/src/winit_shell_wnd.rs @@ -20,7 +20,9 @@ pub trait WinitBackend<'a>: Sized { fn begin_frame(&mut self, surface_color: Color); - fn draw_commands(&mut self, viewport: DeviceRect, commands: Vec); + fn draw_commands( + &mut self, viewport: DeviceRect, global_matrix: &Transform, commands: &[PaintCommand], + ); fn end_frame(&mut self); } @@ -147,13 +149,7 @@ impl ShellWindow for WinitShellWnd { fn begin_frame(&mut self, surface: Color) { self.backend.begin_frame(surface) } #[inline] - fn draw_commands(&mut self, viewport: Rect, mut commands: Vec) { - for c in &mut commands { - if let PaintCommand::Path(path) = c { - path.scale(self.winit_wnd.scale_factor() as f32); - } - } - + fn draw_commands(&mut self, viewport: Rect, commands: &[PaintCommand]) { let scale = self.winit_wnd.scale_factor() as f32; let viewport: DeviceRect = viewport .scale(scale, scale) @@ -162,7 +158,9 @@ impl ShellWindow for WinitShellWnd { .cast_unit(); self.winit_wnd.pre_present_notify(); - self.backend.draw_commands(viewport, commands); + self + .backend + .draw_commands(viewport, &Transform::scale(scale, scale), commands); } #[inline] diff --git a/test_cases/ribir_gpu/gpu_backend/tests/draw_bundle_svg_wgpu.png b/test_cases/ribir_gpu/gpu_backend/tests/draw_bundle_svg_wgpu.png new file mode 100644 index 000000000..42edbb318 Binary files /dev/null and b/test_cases/ribir_gpu/gpu_backend/tests/draw_bundle_svg_wgpu.png differ diff --git a/test_cases/ribir_widgets/checkbox/tests/checked_with_default_by_wgpu.png b/test_cases/ribir_widgets/checkbox/tests/checked_with_default_by_wgpu.png index e704ec9bc..6ea1dd801 100644 Binary files a/test_cases/ribir_widgets/checkbox/tests/checked_with_default_by_wgpu.png and b/test_cases/ribir_widgets/checkbox/tests/checked_with_default_by_wgpu.png differ diff --git a/test_cases/ribir_widgets/checkbox/tests/checked_with_material_by_wgpu.png b/test_cases/ribir_widgets/checkbox/tests/checked_with_material_by_wgpu.png index e704ec9bc..6ea1dd801 100644 Binary files a/test_cases/ribir_widgets/checkbox/tests/checked_with_material_by_wgpu.png and b/test_cases/ribir_widgets/checkbox/tests/checked_with_material_by_wgpu.png differ diff --git a/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_default_by_wgpu.png b/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_default_by_wgpu.png index cf6d79e17..a41e6a137 100644 Binary files a/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_default_by_wgpu.png and b/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_default_by_wgpu.png differ diff --git a/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_material_by_wgpu.png b/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_material_by_wgpu.png index cf6d79e17..a41e6a137 100644 Binary files a/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_material_by_wgpu.png and b/test_cases/ribir_widgets/checkbox/tests/indeterminate_with_material_by_wgpu.png differ diff --git a/test_cases/ribir_widgets/checkbox/tests/unchecked_with_default_by_wgpu.png b/test_cases/ribir_widgets/checkbox/tests/unchecked_with_default_by_wgpu.png index 6f5d16db6..03d3ff4ff 100644 Binary files a/test_cases/ribir_widgets/checkbox/tests/unchecked_with_default_by_wgpu.png and b/test_cases/ribir_widgets/checkbox/tests/unchecked_with_default_by_wgpu.png differ diff --git a/test_cases/ribir_widgets/checkbox/tests/unchecked_with_material_by_wgpu.png b/test_cases/ribir_widgets/checkbox/tests/unchecked_with_material_by_wgpu.png index 6f5d16db6..03d3ff4ff 100644 Binary files a/test_cases/ribir_widgets/checkbox/tests/unchecked_with_material_by_wgpu.png and b/test_cases/ribir_widgets/checkbox/tests/unchecked_with_material_by_wgpu.png differ diff --git a/tests/include_svg_test.rs b/tests/include_svg_test.rs index 5c84871b2..cfcb31c43 100644 --- a/tests/include_svg_test.rs +++ b/tests/include_svg_test.rs @@ -4,7 +4,7 @@ use ribir_dev_helper::*; #[test] fn include_svg() { let svg: Svg = include_crate_svg!("./assets/test1.svg"); - assert_eq!(svg.paint_commands.len(), 2); + assert_eq!(svg.commands.len(), 2); } fn fix_draw_svg_not_apply_alpha() -> Painter { diff --git a/text/src/font_db.rs b/text/src/font_db.rs index 686b51530..09508da55 100644 --- a/text/src/font_db.rs +++ b/text/src/font_db.rs @@ -299,7 +299,7 @@ impl Face { bounds.and_then(move |b| { let mut path = builder.build(rect(b.x_min, b.y_min, b.width(), b.height()).to_f32()); if let PathStyle::Stroke(options) = style { - path = path.stroke(&options, None)?; + path = path.stroke(options, None)?; } Some(Resource::new(path)) })