From 42bc4af972fb0d9ecba6834f88d48a32c339516a Mon Sep 17 00:00:00 2001 From: edwloef Date: Sat, 4 Oct 2025 23:47:55 +0200 Subject: [PATCH 1/2] make `Mesh`es cheaply cloneable by using `Arc<[T]>` in `iced_graphics::mesh::Indexed` --- examples/geometry/src/main.rs | 10 ++++++---- graphics/src/mesh.rs | 6 ++++-- wgpu/src/geometry.rs | 8 ++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index feffd2df72..0c352f107a 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -100,7 +100,7 @@ mod rainbow { let mesh = Mesh::Solid { buffers: mesh::Indexed { - vertices: vec![ + vertices: [ SolidVertex2D { position: posn_center, color: color::pack([1.0, 1.0, 1.0, 1.0]), @@ -137,8 +137,9 @@ mod rainbow { position: posn_l, color: color::pack(color_v), }, - ], - indices: vec![ + ] + .into(), + indices: [ 0, 1, 2, // TL 0, 2, 3, // T 0, 3, 4, // TR @@ -147,7 +148,8 @@ mod rainbow { 0, 6, 7, // B 0, 7, 8, // BL 0, 8, 1, // L - ], + ] + .into(), }, transformation: Transformation::IDENTITY, clip_bounds: Rectangle::INFINITE, diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index 7660231930..bc8efe1c6c 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -5,6 +5,8 @@ use crate::gradient; use bytemuck::{Pod, Zeroable}; +use std::sync::Arc; + /// A low-level primitive to render a mesh of triangles. #[derive(Debug, Clone, PartialEq)] pub enum Mesh { @@ -70,12 +72,12 @@ impl Mesh { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Indexed { /// The vertices of the mesh - pub vertices: Vec, + pub vertices: Arc<[T]>, /// The list of vertex indices that defines the triangles of the mesh. /// /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Vec, + pub indices: Arc<[u32]>, } /// A two-dimensional vertex with a color. diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index f82adedb36..3842813a58 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -547,8 +547,8 @@ impl BufferStack { Buffer::Solid(buffer) if !buffer.indices.is_empty() => { Some(Mesh::Solid { buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, + vertices: buffer.vertices.into(), + indices: buffer.indices.into(), }, clip_bounds, transformation: Transformation::IDENTITY, @@ -557,8 +557,8 @@ impl BufferStack { Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { Some(Mesh::Gradient { buffers: mesh::Indexed { - vertices: buffer.vertices, - indices: buffer.indices, + vertices: buffer.vertices.into(), + indices: buffer.indices.into(), }, clip_bounds, transformation: Transformation::IDENTITY, From afb3b8fce62d4be2cd019147c85b3d5f9e7a3721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 29 Nov 2025 17:33:01 +0100 Subject: [PATCH 2/2] Introduce `draw_mesh_cache` in `mesh::Renderer` --- examples/geometry/src/main.rs | 10 ++--- graphics/src/mesh.rs | 66 +++++++++++++++++++++++++++++++-- renderer/src/fallback.rs | 4 ++ tiny_skia/src/lib.rs | 4 ++ wgpu/src/geometry.rs | 25 +++++++++---- wgpu/src/layer.rs | 3 +- wgpu/src/lib.rs | 6 +++ wgpu/src/triangle.rs | 70 ++++++++++------------------------- 8 files changed, 119 insertions(+), 69 deletions(-) diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 0c352f107a..feffd2df72 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -100,7 +100,7 @@ mod rainbow { let mesh = Mesh::Solid { buffers: mesh::Indexed { - vertices: [ + vertices: vec![ SolidVertex2D { position: posn_center, color: color::pack([1.0, 1.0, 1.0, 1.0]), @@ -137,9 +137,8 @@ mod rainbow { position: posn_l, color: color::pack(color_v), }, - ] - .into(), - indices: [ + ], + indices: vec![ 0, 1, 2, // TL 0, 2, 3, // T 0, 3, 4, // TR @@ -148,8 +147,7 @@ mod rainbow { 0, 6, 7, // B 0, 7, 8, // BL 0, 8, 1, // L - ] - .into(), + ], }, transformation: Transformation::IDENTITY, clip_bounds: Rectangle::INFINITE, diff --git a/graphics/src/mesh.rs b/graphics/src/mesh.rs index bc8efe1c6c..9e11635461 100644 --- a/graphics/src/mesh.rs +++ b/graphics/src/mesh.rs @@ -5,7 +5,8 @@ use crate::gradient; use bytemuck::{Pod, Zeroable}; -use std::sync::Arc; +use std::sync::atomic::{self, AtomicU64}; +use std::sync::{Arc, Weak}; /// A low-level primitive to render a mesh of triangles. #[derive(Debug, Clone, PartialEq)] @@ -72,12 +73,12 @@ impl Mesh { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Indexed { /// The vertices of the mesh - pub vertices: Arc<[T]>, + pub vertices: Vec, /// The list of vertex indices that defines the triangles of the mesh. /// /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Arc<[u32]>, + pub indices: Vec, } /// A two-dimensional vertex with a color. @@ -143,8 +144,67 @@ pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount { }) } +/// A cache of multiple meshes. +#[derive(Debug, Clone)] +pub struct Cache { + id: Id, + batch: Arc<[Mesh]>, + version: usize, +} + +/// The unique id of a [`Cache`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(u64); + +impl Cache { + /// Creates a new [`Cache`] for the given meshes. + pub fn new(meshes: Arc<[Mesh]>) -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + + Self { + id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), + batch: meshes, + version: 0, + } + } + + /// Returns the [`Id`] of the [`Cache`]. + pub fn id(&self) -> Id { + self.id + } + + /// Returns the current version of the [`Cache`]. + pub fn version(&self) -> usize { + self.version + } + + /// Returns the batch of meshes in the [`Cache`]. + pub fn batch(&self) -> &[Mesh] { + &self.batch + } + + /// Returns a [`Weak`] reference to the contents of the [`Cache`]. + pub fn downgrade(&self) -> Weak<[Mesh]> { + Arc::downgrade(&self.batch) + } + + /// Returns true if the [`Cache`] is empty. + pub fn is_empty(&self) -> bool { + self.batch.is_empty() + } + + /// Updates the [`Cache`] with the given meshes. + pub fn update(&mut self, meshes: Arc<[Mesh]>) { + self.batch = meshes; + self.version += 1; + } +} + /// A renderer capable of drawing a [`Mesh`]. pub trait Renderer { /// Draws the given [`Mesh`]. fn draw_mesh(&mut self, mesh: Mesh); + + /// Draws the given [`Cache`]. + fn draw_mesh_cache(&mut self, cache: Cache); } diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 0825480527..d9b9e38cc0 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -224,6 +224,10 @@ where fn draw_mesh(&mut self, mesh: graphics::Mesh) { delegate!(self, renderer, renderer.draw_mesh(mesh)); } + + fn draw_mesh_cache(&mut self, cache: mesh::Cache) { + delegate!(self, renderer, renderer.draw_mesh_cache(cache)); + } } /// A compositor `A` with a fallback strategy `B`. diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 9226e1a8b1..55fad06ea4 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -373,6 +373,10 @@ impl graphics::mesh::Renderer for Renderer { fn draw_mesh(&mut self, _mesh: graphics::Mesh) { log::warn!("iced_tiny_skia does not support drawing meshes"); } + + fn draw_mesh_cache(&mut self, _cache: iced_graphics::mesh::Cache) { + log::warn!("iced_tiny_skia does not support drawing meshes"); + } } #[cfg(feature = "image")] diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index 3842813a58..c1433c4541 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -13,7 +13,6 @@ use crate::graphics::gradient::{self, Gradient}; use crate::graphics::mesh::{self, Mesh}; use crate::graphics::{Image, Text}; use crate::text; -use crate::triangle; use lyon::geom::euclid; use lyon::tessellation; @@ -33,7 +32,7 @@ pub enum Geometry { #[derive(Debug, Clone, Default)] pub struct Cache { - pub meshes: Option, + pub meshes: Option, pub images: Option>, pub text: Option, } @@ -62,11 +61,17 @@ impl Cached for Geometry { Some(Arc::from(images)) }; + let meshes = Arc::from(meshes); + if let Some(mut previous) = previous { if let Some(cache) = &mut previous.meshes { cache.update(meshes); } else { - previous.meshes = triangle::Cache::new(meshes); + previous.meshes = if meshes.is_empty() { + None + } else { + Some(mesh::Cache::new(meshes)) + }; } if let Some(cache) = &mut previous.text { @@ -80,7 +85,11 @@ impl Cached for Geometry { previous } else { Cache { - meshes: triangle::Cache::new(meshes), + meshes: if meshes.is_empty() { + None + } else { + Some(mesh::Cache::new(meshes)) + }, images, text: text::Cache::new(group, text), } @@ -547,8 +556,8 @@ impl BufferStack { Buffer::Solid(buffer) if !buffer.indices.is_empty() => { Some(Mesh::Solid { buffers: mesh::Indexed { - vertices: buffer.vertices.into(), - indices: buffer.indices.into(), + vertices: buffer.vertices, + indices: buffer.indices, }, clip_bounds, transformation: Transformation::IDENTITY, @@ -557,8 +566,8 @@ impl BufferStack { Buffer::Gradient(buffer) if !buffer.indices.is_empty() => { Some(Mesh::Gradient { buffers: mesh::Indexed { - vertices: buffer.vertices.into(), - indices: buffer.indices.into(), + vertices: buffer.vertices, + indices: buffer.indices, }, clip_bounds, transformation: Transformation::IDENTITY, diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index bb557edd47..c61730b669 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -5,6 +5,7 @@ use crate::graphics; use crate::graphics::Mesh; use crate::graphics::color; use crate::graphics::layer; +use crate::graphics::mesh; use crate::graphics::text::{Editor, Paragraph}; use crate::image::{self, Image}; use crate::primitive::{self, Primitive}; @@ -230,7 +231,7 @@ impl Layer { pub fn draw_mesh_cache( &mut self, - cache: triangle::Cache, + cache: mesh::Cache, transformation: Transformation, ) { self.flush_meshes(); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 616c09ae9c..ec103cf358 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -65,6 +65,7 @@ use crate::core::renderer; use crate::core::{ Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation, }; +use crate::graphics::mesh; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::{Shell, Viewport}; @@ -845,6 +846,11 @@ impl graphics::mesh::Renderer for Renderer { let (layer, transformation) = self.layers.current_mut(); layer.draw_mesh(mesh, transformation); } + + fn draw_mesh_cache(&mut self, cache: mesh::Cache) { + let (layer, transformation) = self.layers.current_mut(); + layer.draw_mesh_cache(cache, transformation); + } } #[cfg(feature = "geometry")] diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 149879aa0f..fa5ecb9c3b 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -8,8 +8,7 @@ use crate::graphics::mesh::{self, Mesh}; use rustc_hash::FxHashMap; use std::collections::hash_map; -use std::sync::atomic::{self, AtomicU64}; -use std::sync::{self, Arc}; +use std::sync::Weak; const INITIAL_INDEX_COUNT: usize = 1_000; const INITIAL_VERTEX_COUNT: usize = 1_000; @@ -24,52 +23,21 @@ pub enum Item { }, Cached { transformation: Transformation, - cache: Cache, + cache: mesh::Cache, }, } -#[derive(Debug, Clone)] -pub struct Cache { - id: Id, - batch: Arc<[Mesh]>, - version: usize, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id(u64); - -impl Cache { - pub fn new(meshes: Vec) -> Option { - static NEXT_ID: AtomicU64 = AtomicU64::new(0); - - if meshes.is_empty() { - return None; - } - - Some(Self { - id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)), - batch: Arc::from(meshes), - version: 0, - }) - } - - pub fn update(&mut self, meshes: Vec) { - self.batch = Arc::from(meshes); - self.version += 1; - } -} - #[derive(Debug)] struct Upload { layer: Layer, transformation: Transformation, version: usize, - batch: sync::Weak<[Mesh]>, + batch: Weak<[Mesh]>, } #[derive(Debug, Default)] pub struct Storage { - uploads: FxHashMap, + uploads: FxHashMap, } impl Storage { @@ -77,12 +45,12 @@ impl Storage { Self::default() } - fn get(&self, cache: &Cache) -> Option<&Upload> { - if cache.batch.is_empty() { + fn get(&self, cache: &mesh::Cache) -> Option<&Upload> { + if cache.is_empty() { return None; } - self.uploads.get(&cache.id) + self.uploads.get(&cache.id()) } fn prepare( @@ -92,15 +60,15 @@ impl Storage { belt: &mut wgpu::util::StagingBelt, solid: &solid::Pipeline, gradient: &gradient::Pipeline, - cache: &Cache, + cache: &mesh::Cache, new_transformation: Transformation, ) { - match self.uploads.entry(cache.id) { + match self.uploads.entry(cache.id()) { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if !cache.batch.is_empty() - && (upload.version != cache.version + if !cache.is_empty() + && (upload.version != cache.version() || upload.transformation != new_transformation) { upload.layer.prepare( @@ -109,12 +77,12 @@ impl Storage { belt, solid, gradient, - &cache.batch, + cache.batch(), new_transformation, ); - upload.batch = Arc::downgrade(&cache.batch); - upload.version = cache.version; + upload.batch = cache.downgrade(); + upload.version = cache.version(); upload.transformation = new_transformation; } } @@ -127,7 +95,7 @@ impl Storage { belt, solid, gradient, - &cache.batch, + cache.batch(), new_transformation, ); @@ -135,12 +103,12 @@ impl Storage { layer, transformation: new_transformation, version: 0, - batch: Arc::downgrade(&cache.batch), + batch: cache.downgrade(), }); log::debug!( - "New mesh upload: {} (total: {})", - cache.id.0, + "New mesh upload: {:?} (total: {})", + cache.id(), self.uploads.len() ); } @@ -278,7 +246,7 @@ impl State { Some(( &upload.layer, - &cache.batch, + cache.batch(), screen_transformation * *transformation, )) }