diff options
-rw-r--r-- | core/src/rectangle.rs | 17 | ||||
-rw-r--r-- | examples/custom_shader/src/scene.rs | 33 | ||||
-rw-r--r-- | renderer/src/fallback.rs | 10 | ||||
-rw-r--r-- | wgpu/src/engine.rs | 13 | ||||
-rw-r--r-- | wgpu/src/layer.rs | 16 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 62 | ||||
-rw-r--r-- | wgpu/src/primitive.rs | 103 | ||||
-rw-r--r-- | wgpu/src/primitive/pipeline.rs | 115 | ||||
-rw-r--r-- | wgpu/src/triangle.rs | 7 | ||||
-rw-r--r-- | widget/src/shader.rs | 11 | ||||
-rw-r--r-- | widget/src/shader/program.rs | 4 |
11 files changed, 219 insertions, 172 deletions
diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 446d3769..2ab50137 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -147,13 +147,20 @@ impl Rectangle<f32> { } /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates. - pub fn snap(self) -> Rectangle<u32> { - Rectangle { + pub fn snap(self) -> Option<Rectangle<u32>> { + let width = self.width as u32; + let height = self.height as u32; + + if width < 1 || height < 1 { + return None; + } + + Some(Rectangle { x: self.x as u32, y: self.y as u32, - width: self.width as u32, - height: self.height as u32, - } + width, + height, + }) } /// Expands the [`Rectangle`] a given amount. diff --git a/examples/custom_shader/src/scene.rs b/examples/custom_shader/src/scene.rs index a35efdd9..5fa42188 100644 --- a/examples/custom_shader/src/scene.rs +++ b/examples/custom_shader/src/scene.rs @@ -9,8 +9,8 @@ use pipeline::cube::{self, Cube}; use iced::mouse; use iced::time::Duration; -use iced::widget::shader; -use iced::{Color, Rectangle, Size}; +use iced::widget::shader::{self, Viewport}; +use iced::{Color, Rectangle}; use glam::Vec3; use rand::Rng; @@ -130,25 +130,29 @@ impl Primitive { impl shader::Primitive for Primitive { fn prepare( &self, - format: wgpu::TextureFormat, device: &wgpu::Device, queue: &wgpu::Queue, - _bounds: Rectangle, - target_size: Size<u32>, - _scale_factor: f32, + format: wgpu::TextureFormat, storage: &mut shader::Storage, + _bounds: &Rectangle, + viewport: &Viewport, ) { if !storage.has::<Pipeline>() { - storage.store(Pipeline::new(device, queue, format, target_size)); + storage.store(Pipeline::new( + device, + queue, + format, + viewport.physical_size(), + )); } let pipeline = storage.get_mut::<Pipeline>().unwrap(); - //upload data to GPU + // Upload data to GPU pipeline.update( device, queue, - target_size, + viewport.physical_size(), &self.uniforms, self.cubes.len(), &self.cubes, @@ -157,20 +161,19 @@ impl shader::Primitive for Primitive { fn render( &self, + encoder: &mut wgpu::CommandEncoder, storage: &shader::Storage, target: &wgpu::TextureView, - _target_size: Size<u32>, - viewport: Rectangle<u32>, - encoder: &mut wgpu::CommandEncoder, + clip_bounds: &Rectangle<u32>, ) { - //at this point our pipeline should always be initialized + // At this point our pipeline should always be initialized let pipeline = storage.get::<Pipeline>().unwrap(); - //render primitive + // Render primitive pipeline.render( target, encoder, - viewport, + *clip_bounds, self.cubes.len() as u32, self.show_depth_buffer, ); diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 2676ba87..975f4866 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -399,19 +399,19 @@ where } #[cfg(feature = "wgpu")] -impl<A, B> iced_wgpu::primitive::pipeline::Renderer for Renderer<A, B> +impl<A, B> iced_wgpu::primitive::Renderer for Renderer<A, B> where - A: iced_wgpu::primitive::pipeline::Renderer, + A: iced_wgpu::primitive::Renderer, B: core::Renderer, { - fn draw_pipeline_primitive( + fn draw_primitive( &mut self, bounds: Rectangle, - primitive: impl iced_wgpu::primitive::pipeline::Primitive, + primitive: impl iced_wgpu::Primitive, ) { match self { Self::Primary(renderer) => { - renderer.draw_pipeline_primitive(bounds, primitive); + renderer.draw_primitive(bounds, primitive); } Self::Secondary(_) => { log::warn!( diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs index e45b62b2..96cd6db8 100644 --- a/wgpu/src/engine.rs +++ b/wgpu/src/engine.rs @@ -1,19 +1,21 @@ use crate::buffer; use crate::graphics::Antialiasing; -use crate::primitive::pipeline; +use crate::primitive; use crate::quad; use crate::text; use crate::triangle; #[allow(missing_debug_implementations)] pub struct Engine { + pub(crate) staging_belt: wgpu::util::StagingBelt, + pub(crate) format: wgpu::TextureFormat, + pub(crate) quad_pipeline: quad::Pipeline, pub(crate) text_pipeline: text::Pipeline, pub(crate) triangle_pipeline: triangle::Pipeline, - pub(crate) _pipeline_storage: pipeline::Storage, #[cfg(any(feature = "image", feature = "svg"))] pub(crate) image_pipeline: crate::image::Pipeline, - pub(crate) staging_belt: wgpu::util::StagingBelt, + pub(crate) primitive_storage: primitive::Storage, } impl Engine { @@ -43,13 +45,16 @@ impl Engine { staging_belt: wgpu::util::StagingBelt::new( buffer::MAX_WRITE_SIZE as u64, ), + format, + quad_pipeline, text_pipeline, triangle_pipeline, - _pipeline_storage: pipeline::Storage::default(), #[cfg(any(feature = "image", feature = "svg"))] image_pipeline, + + primitive_storage: primitive::Storage::default(), } } diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index c8c27c61..7a18e322 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -4,6 +4,7 @@ use crate::graphics::color; use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::Mesh; use crate::image::{self, Image}; +use crate::primitive::{self, Primitive}; use crate::quad::{self, Quad}; use crate::text::{self, Text}; use crate::triangle; @@ -13,6 +14,7 @@ pub struct Layer { pub bounds: Rectangle, pub quads: quad::Batch, pub triangles: triangle::Batch, + pub primitives: primitive::Batch, pub text: text::Batch, pub images: image::Batch, } @@ -23,6 +25,7 @@ impl Default for Layer { bounds: Rectangle::INFINITE, quads: quad::Batch::default(), triangles: triangle::Batch::default(), + primitives: primitive::Batch::default(), text: text::Batch::default(), images: image::Batch::default(), } @@ -222,6 +225,18 @@ impl Stack { }); } + pub fn draw_primitive( + &mut self, + bounds: Rectangle, + primitive: Box<dyn Primitive>, + ) { + let bounds = bounds * self.transformation(); + + self.layers[self.current] + .primitives + .push(primitive::Instance { bounds, primitive }); + } + pub fn push_clip(&mut self, bounds: Rectangle) { self.previous.push(self.current); @@ -282,6 +297,7 @@ impl Stack { live.quads.clear(); live.triangles.clear(); + live.primitives.clear(); live.text.clear(); live.images.clear(); pending_meshes.clear(); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 030bcade..0580399d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -101,8 +101,6 @@ impl Renderer { } } - pub fn draw_primitive(&mut self, _primitive: Primitive) {} - pub fn present<T: AsRef<str>>( &mut self, engine: &mut Engine, @@ -158,6 +156,19 @@ impl Renderer { ); } + if !layer.primitives.is_empty() { + for instance in &layer.primitives { + instance.primitive.prepare( + device, + queue, + engine.format, + &mut engine.primitive_storage, + &instance.bounds, + viewport, + ); + } + } + if !layer.text.is_empty() { engine.text_pipeline.prepare( device, @@ -247,7 +258,9 @@ impl Renderer { continue; }; - let scissor_rect = physical_bounds.snap(); + let Some(scissor_rect) = physical_bounds.snap() else { + continue; + }; if !layer.quads.is_empty() { engine.quad_pipeline.render( @@ -293,6 +306,43 @@ impl Renderer { )); } + if !layer.primitives.is_empty() { + let _ = ManuallyDrop::into_inner(render_pass); + + for instance in &layer.primitives { + if let Some(clip_bounds) = (instance.bounds * scale) + .intersection(&physical_bounds) + .and_then(Rectangle::snap) + { + instance.primitive.render( + encoder, + &engine.primitive_storage, + frame, + &clip_bounds, + ); + } + } + + render_pass = ManuallyDrop::new(encoder.begin_render_pass( + &wgpu::RenderPassDescriptor { + label: Some("iced_wgpu render pass"), + color_attachments: &[Some( + wgpu::RenderPassColorAttachment { + view: frame, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + }, + )], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }, + )); + } + if !layer.text.is_empty() { text_layer += engine.text_pipeline.render( &self.text_storage, @@ -520,6 +570,12 @@ impl graphics::geometry::Renderer for Renderer { } } +impl primitive::Renderer for Renderer { + fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) { + self.layers.draw_primitive(bounds, Box::new(primitive)); + } +} + impl graphics::compositor::Default for crate::Renderer { type Compositor = window::Compositor; } diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 8e311d2b..4ba1ed9a 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,20 +1,95 @@ -//! Draw using different graphical primitives. -pub mod pipeline; +//! Draw custom primitives. +use crate::core::{self, Rectangle}; +use crate::graphics::Viewport; -pub use pipeline::Pipeline; +use rustc_hash::FxHashMap; +use std::any::{Any, TypeId}; +use std::fmt::Debug; -use crate::graphics::Mesh; +/// A batch of primitives. +pub type Batch = Vec<Instance>; -use std::fmt::Debug; +/// A set of methods which allows a [`Primitive`] to be rendered. +pub trait Primitive: Debug + Send + Sync + 'static { + /// Processes the [`Primitive`], allowing for GPU buffer allocation. + fn prepare( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + format: wgpu::TextureFormat, + storage: &mut Storage, + bounds: &Rectangle, + viewport: &Viewport, + ); + + /// Renders the [`Primitive`]. + fn render( + &self, + encoder: &mut wgpu::CommandEncoder, + storage: &Storage, + target: &wgpu::TextureView, + clip_bounds: &Rectangle<u32>, + ); +} + +#[derive(Debug)] +/// An instance of a specific [`Primitive`]. +pub struct Instance { + /// The bounds of the [`Instance`]. + pub bounds: Rectangle, + + /// The [`Primitive`] to render. + pub primitive: Box<dyn Primitive>, +} + +impl Instance { + /// Creates a new [`Pipeline`] with the given [`Primitive`]. + pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { + Instance { + bounds, + primitive: Box::new(primitive), + } + } +} + +/// A renderer than can draw custom primitives. +pub trait Renderer: core::Renderer { + /// Draws a custom primitive. + fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive); +} + +/// Stores custom, user-provided types. +#[derive(Default, Debug)] +pub struct Storage { + pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>, +} + +impl Storage { + /// Returns `true` if `Storage` contains a type `T`. + pub fn has<T: 'static>(&self) -> bool { + self.pipelines.get(&TypeId::of::<T>()).is_some() + } + + /// Inserts the data `T` in to [`Storage`]. + pub fn store<T: 'static + Send>(&mut self, data: T) { + let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data)); + } -/// The graphical primitives supported by `iced_wgpu`. -pub type Primitive = crate::graphics::Primitive<Custom>; + /// Returns a reference to the data with type `T` if it exists in [`Storage`]. + pub fn get<T: 'static>(&self) -> Option<&T> { + self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| { + pipeline + .downcast_ref::<T>() + .expect("Pipeline with this type does not exist in Storage.") + }) + } -/// The custom primitives supported by `iced_wgpu`. -#[derive(Debug, Clone, PartialEq)] -pub enum Custom { - /// A mesh primitive. - Mesh(Mesh), - /// A custom pipeline primitive. - Pipeline(Pipeline), + /// Returns a mutable reference to the data with type `T` if it exists in [`Storage`]. + pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { + self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| { + pipeline + .downcast_mut::<T>() + .expect("Pipeline with this type does not exist in Storage.") + }) + } } diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 59c54db9..8b137891 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -1,116 +1 @@ -//! Draw primitives using custom pipelines. -use crate::core::{self, Rectangle, Size}; -use rustc_hash::FxHashMap; -use std::any::{Any, TypeId}; -use std::fmt::Debug; -use std::sync::Arc; - -#[derive(Clone, Debug)] -/// A custom primitive which can be used to render primitives associated with a custom pipeline. -pub struct Pipeline { - /// The bounds of the [`Pipeline`]. - pub bounds: Rectangle, - - /// The [`Primitive`] to render. - pub primitive: Arc<dyn Primitive>, -} - -impl Pipeline { - /// Creates a new [`Pipeline`] with the given [`Primitive`]. - pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self { - Pipeline { - bounds, - primitive: Arc::new(primitive), - } - } -} - -impl PartialEq for Pipeline { - fn eq(&self, other: &Self) -> bool { - self.primitive.type_id() == other.primitive.type_id() - } -} - -/// A set of methods which allows a [`Primitive`] to be rendered. -pub trait Primitive: Debug + Send + Sync + 'static { - /// Processes the [`Primitive`], allowing for GPU buffer allocation. - fn prepare( - &self, - format: wgpu::TextureFormat, - device: &wgpu::Device, - queue: &wgpu::Queue, - bounds: Rectangle, - target_size: Size<u32>, - scale_factor: f32, - storage: &mut Storage, - ); - - /// Renders the [`Primitive`]. - fn render( - &self, - storage: &Storage, - target: &wgpu::TextureView, - target_size: Size<u32>, - viewport: Rectangle<u32>, - encoder: &mut wgpu::CommandEncoder, - ); -} - -/// A renderer than can draw custom pipeline primitives. -pub trait Renderer: core::Renderer { - /// Draws a custom pipeline primitive. - fn draw_pipeline_primitive( - &mut self, - bounds: Rectangle, - primitive: impl Primitive, - ); -} - -impl Renderer for crate::Renderer { - fn draw_pipeline_primitive( - &mut self, - bounds: Rectangle, - primitive: impl Primitive, - ) { - self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline( - Pipeline::new(bounds, primitive), - ))); - } -} - -/// Stores custom, user-provided pipelines. -#[derive(Default, Debug)] -pub struct Storage { - pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>, -} - -impl Storage { - /// Returns `true` if `Storage` contains a pipeline with type `T`. - pub fn has<T: 'static>(&self) -> bool { - self.pipelines.get(&TypeId::of::<T>()).is_some() - } - - /// Inserts the pipeline `T` in to [`Storage`]. - pub fn store<T: 'static + Send>(&mut self, pipeline: T) { - let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline)); - } - - /// Returns a reference to pipeline with type `T` if it exists in [`Storage`]. - pub fn get<T: 'static>(&self) -> Option<&T> { - self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| { - pipeline - .downcast_ref::<T>() - .expect("Pipeline with this type does not exist in Storage.") - }) - } - - /// Returns a mutable reference to pipeline `T` if it exists in [`Storage`]. - pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> { - self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| { - pipeline - .downcast_mut::<T>() - .expect("Pipeline with this type does not exist in Storage.") - }) - } -} diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 98ba41b3..8470ea39 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -501,14 +501,13 @@ impl Layer { let mut last_is_solid = None; for (index, mesh) in meshes.iter().enumerate() { - let Some(clip_bounds) = - bounds.intersection(&(mesh.clip_bounds() * transformation)) + let Some(clip_bounds) = bounds + .intersection(&(mesh.clip_bounds() * transformation)) + .and_then(Rectangle::snap) else { continue; }; - let clip_bounds = clip_bounds.snap(); - render_pass.set_scissor_rect( clip_bounds.x, clip_bounds.y, diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 68112f83..fad2f4eb 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -13,12 +13,13 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::widget::{self, Widget}; use crate::core::window; use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; -use crate::renderer::wgpu::primitive::pipeline; +use crate::renderer::wgpu::primitive; use std::marker::PhantomData; +pub use crate::graphics::Viewport; pub use crate::renderer::wgpu::wgpu; -pub use pipeline::{Primitive, Storage}; +pub use primitive::{Primitive, Storage}; /// A widget which can render custom shaders with Iced's `wgpu` backend. /// @@ -60,7 +61,7 @@ impl<P, Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Shader<Message, P> where P: Program<Message>, - Renderer: pipeline::Renderer, + Renderer: primitive::Renderer, { fn tag(&self) -> tree::Tag { struct Tag<T>(T); @@ -160,7 +161,7 @@ where let bounds = layout.bounds(); let state = tree.state.downcast_ref::<P::State>(); - renderer.draw_pipeline_primitive( + renderer.draw_primitive( bounds, self.program.draw(state, cursor_position, bounds), ); @@ -171,7 +172,7 @@ impl<'a, Message, Theme, Renderer, P> From<Shader<Message, P>> for Element<'a, Message, Theme, Renderer> where Message: 'a, - Renderer: pipeline::Renderer, + Renderer: primitive::Renderer, P: Program<Message> + 'a, { fn from( diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 6dd50404..902c7c3b 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -1,7 +1,7 @@ use crate::core::event; use crate::core::mouse; use crate::core::{Rectangle, Shell}; -use crate::renderer::wgpu::primitive::pipeline; +use crate::renderer::wgpu::Primitive; use crate::shader; /// The state and logic of a [`Shader`] widget. @@ -15,7 +15,7 @@ pub trait Program<Message> { type State: Default + 'static; /// The type of primitive this [`Program`] can draw. - type Primitive: pipeline::Primitive + 'static; + type Primitive: Primitive + 'static; /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a |