From aeea70d9e768488605fb866dd14bbf0e91a75d61 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 Jan 2025 15:34:28 +0100 Subject: [PATCH] Add `epaint::Brush` for controlling `RectShape` texturing (#5565) Also wraps `Shape::Mesh` in an `Arc`. No new features, but decreases size of `Shape` from 72 bytes to 64. --- 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 | 17 +++--- crates/epaint/src/shapes/rect_shape.rs | 72 +++++++++++++++----------- crates/epaint/src/shapes/shape.rs | 30 +++++++++-- crates/epaint/src/tessellator.rs | 14 +++-- 7 files changed, 111 insertions(+), 58 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 3abcf5fd..d1976a12 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 00000000..a414194a --- /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 5aeb9676..226821c1 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 263f9cf0..25ff0d46 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); @@ -87,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); @@ -96,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/rect_shape.rs b/crates/epaint/src/shapes/rect_shape.rs index a36ae08c..b0750aa8 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,18 +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? + /// Controls texturing, if any. /// - /// The texture is multiplied with [`Self::fill`]. - pub fill_texture_id: TextureId, + /// Since most rectangles do not have a texture, this is optional and in an `Arc`, + /// so that [`RectShape`] is kept small.. + pub brush: Option>, +} - /// 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, +#[test] +fn rect_shape_size() { + assert_eq!( + 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 { @@ -57,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(), } } @@ -68,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 @@ -105,6 +96,16 @@ impl RectShape { self } + /// 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, + uv, + })); + self + } + /// The visual bounding rectangle (includes stroke width) #[inline] pub fn visual_bounding_rect(&self) -> Rect { @@ -115,6 +116,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 945e5563..62af1b58 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), @@ -68,6 +70,18 @@ pub enum Shape { Callback(PaintCallback), } +#[test] +fn shape_size() { + assert_eq!( + 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] fn shape_impl_send_sync() { fn assert_send_sync() {} @@ -84,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) } } @@ -314,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) } @@ -369,7 +391,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() } @@ -446,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 5e8b131c..57d30b11 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); @@ -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( @@ -2173,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()); } }