From 65fbce1755efbbd45cbf74034633e249dbe9fcff Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Jan 2025 15:03:02 +0100 Subject: [PATCH 1/4] Add tests for shape size --- crates/epaint/src/shapes/rect_shape.rs | 8 ++++++++ crates/epaint/src/shapes/shape.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/crates/epaint/src/shapes/rect_shape.rs b/crates/epaint/src/shapes/rect_shape.rs index a36ae08c920..4780941e9a9 100644 --- a/crates/epaint/src/shapes/rect_shape.rs +++ b/crates/epaint/src/shapes/rect_shape.rs @@ -42,6 +42,14 @@ pub struct RectShape { pub uv: Rect, } +#[test] +fn rect_shape_size() { + assert_eq!( + std::mem::size_of::(), 72, + "RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." + ); +} + impl RectShape { /// The stroke extends _outside_ the [`Rect`]. #[inline] diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 945e5563dad..30654cc7418 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -68,6 +68,14 @@ pub enum Shape { Callback(PaintCallback), } +#[test] +fn shape_size() { + assert_eq!( + std::mem::size_of::(), 72, + "Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." + ); +} + #[test] fn shape_impl_send_sync() { fn assert_send_sync() {} From ebb5136bed38acf3f8875db88e51570ccffabe23 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Jan 2025 15:16:30 +0100 Subject: [PATCH 2/4] Shrink size of `RectShape` from 72 bytes to 64 bytes --- crates/egui/src/widgets/image.rs | 15 ++---- crates/epaint/src/brush.rs | 19 ++++++++ crates/epaint/src/lib.rs | 2 + crates/epaint/src/shape_transform.rs | 3 +- crates/epaint/src/shapes/rect_shape.rs | 67 +++++++++++++------------- crates/epaint/src/shapes/shape.rs | 2 +- crates/epaint/src/tessellator.rs | 10 ++-- 7 files changed, 69 insertions(+), 49 deletions(-) create mode 100644 crates/epaint/src/brush.rs diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 3abcf5fd5f0..d1976a12c45 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -9,7 +9,7 @@ use epaint::{ use crate::{ load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll}, pos2, Color32, Context, Id, Mesh, Painter, Rect, Response, Rounding, Sense, Shape, Spinner, - Stroke, TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType, + TextStyle, TextureOptions, Ui, Vec2, Widget, WidgetInfo, WidgetType, }; /// A widget which displays an image. @@ -822,15 +822,10 @@ pub fn paint_texture_at( painter.add(Shape::mesh(mesh)); } None => { - painter.add(RectShape { - rect, - rounding: options.rounding, - fill: options.tint, - stroke: Stroke::NONE, - blur_width: 0.0, - fill_texture_id: texture.id, - uv: options.uv, - }); + painter.add( + RectShape::filled(rect, options.rounding, options.tint) + .with_texture(texture.id, options.uv), + ); } } } diff --git a/crates/epaint/src/brush.rs b/crates/epaint/src/brush.rs new file mode 100644 index 00000000000..a414194a15e --- /dev/null +++ b/crates/epaint/src/brush.rs @@ -0,0 +1,19 @@ +use crate::{Rect, TextureId}; + +/// Controls texturing of a [`crate::RectShape`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct Brush { + /// If the rect should be filled with a texture, which one? + /// + /// The texture is multiplied with [`crate::RectShape::fill`]. + pub fill_texture_id: TextureId, + + /// What UV coordinates to use for the texture? + /// + /// To display a texture, set [`Self::fill_texture_id`], + /// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`. + /// + /// Use [`Rect::ZERO`] to turn off texturing. + pub uv: Rect, +} diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 5aeb96762d4..226821c16eb 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -23,6 +23,7 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] +mod brush; pub mod color; pub mod image; mod margin; @@ -44,6 +45,7 @@ pub mod util; mod viewport; pub use self::{ + brush::Brush, color::ColorMode, image::{ColorImage, FontImage, ImageData, ImageDelta}, margin::Margin, diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 263f9cf07a6..5ff087d926a 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -64,8 +64,7 @@ pub fn adjust_colors( fill, stroke, blur_width: _, - fill_texture_id: _, - uv: _, + brush: _, }) => { adjust_color(fill); adjust_color(&mut stroke.color); diff --git a/crates/epaint/src/shapes/rect_shape.rs b/crates/epaint/src/shapes/rect_shape.rs index 4780941e9a9..2c7f216a73e 100644 --- a/crates/epaint/src/shapes/rect_shape.rs +++ b/crates/epaint/src/shapes/rect_shape.rs @@ -1,7 +1,9 @@ +use std::sync::Arc; + use crate::*; /// How to paint a rectangle. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct RectShape { pub rect: Rect, @@ -28,26 +30,23 @@ pub struct RectShape { /// The blur is currently implemented using a simple linear blur in sRGBA gamma space. pub blur_width: f32, - /// If the rect should be filled with a texture, which one? - /// - /// The texture is multiplied with [`Self::fill`]. - pub fill_texture_id: TextureId, - - /// What UV coordinates to use for the texture? + /// Controls texturing, if any. /// - /// To display a texture, set [`Self::fill_texture_id`], - /// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`. - /// - /// Use [`Rect::ZERO`] to turn off texturing. - pub uv: Rect, + /// Since most rectangles do not have a texture, this is optional and in an `Arc`, + /// so that [`RectShape`] is kept small.. + pub brush: Option>, } #[test] fn rect_shape_size() { assert_eq!( - std::mem::size_of::(), 72, + std::mem::size_of::(), 48, "RectShape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." ); + assert!( + std::mem::size_of::() <= 64, + "RectShape is getting way too big!" + ); } impl RectShape { @@ -65,8 +64,7 @@ impl RectShape { fill: fill_color.into(), stroke: stroke.into(), blur_width: 0.0, - fill_texture_id: Default::default(), - uv: Rect::ZERO, + brush: Default::default(), } } @@ -76,29 +74,14 @@ impl RectShape { rounding: impl Into, fill_color: impl Into, ) -> Self { - Self { - rect, - rounding: rounding.into(), - fill: fill_color.into(), - stroke: Default::default(), - blur_width: 0.0, - fill_texture_id: Default::default(), - uv: Rect::ZERO, - } + Self::new(rect, rounding, fill_color, Stroke::NONE) } /// The stroke extends _outside_ the [`Rect`]. #[inline] pub fn stroke(rect: Rect, rounding: impl Into, stroke: impl Into) -> Self { - Self { - rect, - rounding: rounding.into(), - fill: Default::default(), - stroke: stroke.into(), - blur_width: 0.0, - fill_texture_id: Default::default(), - uv: Rect::ZERO, - } + let fill = Color32::TRANSPARENT; + Self::new(rect, rounding, fill, stroke) } /// If larger than zero, the edges of the rectangle @@ -113,6 +96,15 @@ impl RectShape { self } + /// Set the texture to use when painting this rectangle, if any. + pub fn with_texture(mut self, fill_texture_id: TextureId, uv: Rect) -> Self { + self.brush = Some(Arc::new(Brush { + fill_texture_id, + uv, + })); + self + } + /// The visual bounding rectangle (includes stroke width) #[inline] pub fn visual_bounding_rect(&self) -> Rect { @@ -123,6 +115,15 @@ impl RectShape { self.rect.expand(width + self.blur_width / 2.0) } } + + /// The texture to use when painting this rectangle, if any. + /// + /// If no texture is set, this will return [`TextureId::default`]. + pub fn fill_texture_id(&self) -> TextureId { + self.brush + .as_ref() + .map_or_else(TextureId::default, |brush| brush.fill_texture_id) + } } impl From for Shape { diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 30654cc7418..93fff9e06d1 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -377,7 +377,7 @@ impl Shape { if let Self::Mesh(mesh) = self { mesh.texture_id } else if let Self::Rect(rect_shape) = self { - rect_shape.fill_texture_id + rect_shape.fill_texture_id() } else { crate::TextureId::default() } diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 5e8b131cf71..8e5eb47dd40 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1693,14 +1693,14 @@ impl Tessellator { /// * `rect`: the rectangle to tessellate. /// * `out`: triangles are appended to this. pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { + let brush = rect.brush.as_ref(); let RectShape { mut rect, mut rounding, fill, stroke, mut blur_width, - fill_texture_id, - uv, + .. } = *rect; if self.options.coarse_tessellation_culling @@ -1775,7 +1775,11 @@ impl Tessellator { path.add_line_loop(&self.scratchpad_points); let path_stroke = PathStroke::from(stroke).outside(); - if uv.is_positive() { + if let Some(brush) = brush { + let crate::Brush { + fill_texture_id, + uv, + } = **brush; // Textured let uv_from_pos = |p: Pos2| { pos2( From 741022b2b29617a4cf64914a177993dc8e900737 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Jan 2025 15:24:16 +0100 Subject: [PATCH 3/4] Put `Shape::Mesh` in an `Arc` to reduce the size of `Shape` --- crates/epaint/src/shape_transform.rs | 14 ++++++++------ crates/epaint/src/shapes/shape.rs | 22 ++++++++++++++++++---- crates/epaint/src/tessellator.rs | 4 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 5ff087d926a..25ff0d4699b 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -86,7 +86,7 @@ pub fn adjust_colors( } if !galley.is_empty() { - let galley = std::sync::Arc::make_mut(galley); + let galley = Arc::make_mut(galley); for row in &mut galley.rows { for vertex in &mut row.visuals.mesh.vertices { adjust_color(&mut vertex.color); @@ -95,11 +95,13 @@ pub fn adjust_colors( } } - Shape::Mesh(Mesh { - indices: _, - vertices, - texture_id: _, - }) => { + Shape::Mesh(mesh) => { + let Mesh { + indices: _, + vertices, + texture_id: _, + } = Arc::make_mut(mesh); + for v in vertices { adjust_color(&mut v.color); } diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 93fff9e06d1..62af1b58166 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -56,7 +56,9 @@ pub enum Shape { /// A general triangle mesh. /// /// Can be used to display images. - Mesh(Mesh), + /// + /// Wrapped in an [`Arc`] to minimize the size of [`Shape`]. + Mesh(Arc), /// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). QuadraticBezier(QuadraticBezierShape), @@ -71,9 +73,13 @@ pub enum Shape { #[test] fn shape_size() { assert_eq!( - std::mem::size_of::(), 72, + std::mem::size_of::(), 64, "Shape changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it." ); + assert!( + std::mem::size_of::() <= 64, + "Shape is getting way too big!" + ); } #[test] @@ -92,6 +98,13 @@ impl From> for Shape { impl From for Shape { #[inline(always)] fn from(mesh: Mesh) -> Self { + Self::Mesh(mesh.into()) + } +} + +impl From> for Shape { + #[inline(always)] + fn from(mesh: Arc) -> Self { Self::Mesh(mesh) } } @@ -322,7 +335,8 @@ impl Shape { } #[inline] - pub fn mesh(mesh: Mesh) -> Self { + pub fn mesh(mesh: impl Into>) -> Self { + let mesh = mesh.into(); debug_assert!(mesh.is_valid()); Self::Mesh(mesh) } @@ -454,7 +468,7 @@ impl Shape { galley.rect = transform.scaling * galley.rect; } Self::Mesh(mesh) => { - mesh.transform(transform); + Arc::make_mut(mesh).transform(transform); } Self::QuadraticBezier(bezier_shape) => { bezier_shape.points[0] = transform * bezier_shape.points[0]; diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 8e5eb47dd40..57d30b11be8 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1406,7 +1406,7 @@ impl Tessellator { return; } - out.append(mesh); + out.append_ref(&mesh); } Shape::LineSegment { points, stroke } => { self.tessellate_line_segment(points, stroke, out); @@ -2177,7 +2177,7 @@ impl Tessellator { profiling::scope!("distribute results", tessellated.len().to_string()); for (index, mesh) in tessellated { - shapes[index].shape = Shape::Mesh(mesh); + shapes[index].shape = Shape::Mesh(mesh.into()); } } From 7928725b66614f781fca09fe3724e224476f0d9e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Jan 2025 15:28:39 +0100 Subject: [PATCH 4/4] Mark builder method with `#[inline]` --- crates/epaint/src/shapes/rect_shape.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/epaint/src/shapes/rect_shape.rs b/crates/epaint/src/shapes/rect_shape.rs index 2c7f216a73e..b0750aa89ec 100644 --- a/crates/epaint/src/shapes/rect_shape.rs +++ b/crates/epaint/src/shapes/rect_shape.rs @@ -97,6 +97,7 @@ impl RectShape { } /// Set the texture to use when painting this rectangle, if any. + #[inline] pub fn with_texture(mut self, fill_texture_id: TextureId, uv: Rect) -> Self { self.brush = Some(Arc::new(Brush { fill_texture_id,