Reintroduce support for custom primitives in iced_wgpu

This commit is contained in:
Héctor Ramón Jiménez 2024-04-08 15:04:35 +02:00
parent 6ea763c2a7
commit d922b47815
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
11 changed files with 220 additions and 173 deletions

View file

@ -147,13 +147,20 @@ impl Rectangle<f32> {
} }
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates. /// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
pub fn snap(self) -> Rectangle<u32> { pub fn snap(self) -> Option<Rectangle<u32>> {
Rectangle { 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, x: self.x as u32,
y: self.y as u32, y: self.y as u32,
width: self.width as u32, width,
height: self.height as u32, height,
} })
} }
/// Expands the [`Rectangle`] a given amount. /// Expands the [`Rectangle`] a given amount.

View file

@ -9,8 +9,8 @@ use pipeline::cube::{self, Cube};
use iced::mouse; use iced::mouse;
use iced::time::Duration; use iced::time::Duration;
use iced::widget::shader; use iced::widget::shader::{self, Viewport};
use iced::{Color, Rectangle, Size}; use iced::{Color, Rectangle};
use glam::Vec3; use glam::Vec3;
use rand::Rng; use rand::Rng;
@ -130,25 +130,29 @@ impl Primitive {
impl shader::Primitive for Primitive { impl shader::Primitive for Primitive {
fn prepare( fn prepare(
&self, &self,
format: wgpu::TextureFormat,
device: &wgpu::Device, device: &wgpu::Device,
queue: &wgpu::Queue, queue: &wgpu::Queue,
_bounds: Rectangle, format: wgpu::TextureFormat,
target_size: Size<u32>,
_scale_factor: f32,
storage: &mut shader::Storage, storage: &mut shader::Storage,
_bounds: &Rectangle,
viewport: &Viewport,
) { ) {
if !storage.has::<Pipeline>() { 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(); let pipeline = storage.get_mut::<Pipeline>().unwrap();
//upload data to GPU // Upload data to GPU
pipeline.update( pipeline.update(
device, device,
queue, queue,
target_size, viewport.physical_size(),
&self.uniforms, &self.uniforms,
self.cubes.len(), self.cubes.len(),
&self.cubes, &self.cubes,
@ -157,20 +161,19 @@ impl shader::Primitive for Primitive {
fn render( fn render(
&self, &self,
encoder: &mut wgpu::CommandEncoder,
storage: &shader::Storage, storage: &shader::Storage,
target: &wgpu::TextureView, target: &wgpu::TextureView,
_target_size: Size<u32>, clip_bounds: &Rectangle<u32>,
viewport: Rectangle<u32>,
encoder: &mut wgpu::CommandEncoder,
) { ) {
//at this point our pipeline should always be initialized // At this point our pipeline should always be initialized
let pipeline = storage.get::<Pipeline>().unwrap(); let pipeline = storage.get::<Pipeline>().unwrap();
//render primitive // Render primitive
pipeline.render( pipeline.render(
target, target,
encoder, encoder,
viewport, *clip_bounds,
self.cubes.len() as u32, self.cubes.len() as u32,
self.show_depth_buffer, self.show_depth_buffer,
); );

View file

@ -399,19 +399,19 @@ where
} }
#[cfg(feature = "wgpu")] #[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 where
A: iced_wgpu::primitive::pipeline::Renderer, A: iced_wgpu::primitive::Renderer,
B: core::Renderer, B: core::Renderer,
{ {
fn draw_pipeline_primitive( fn draw_primitive(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
primitive: impl iced_wgpu::primitive::pipeline::Primitive, primitive: impl iced_wgpu::Primitive,
) { ) {
match self { match self {
Self::Primary(renderer) => { Self::Primary(renderer) => {
renderer.draw_pipeline_primitive(bounds, primitive); renderer.draw_primitive(bounds, primitive);
} }
Self::Secondary(_) => { Self::Secondary(_) => {
log::warn!( log::warn!(

View file

@ -1,19 +1,21 @@
use crate::buffer; use crate::buffer;
use crate::graphics::Antialiasing; use crate::graphics::Antialiasing;
use crate::primitive::pipeline; use crate::primitive;
use crate::quad; use crate::quad;
use crate::text; use crate::text;
use crate::triangle; use crate::triangle;
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Engine { pub struct Engine {
pub(crate) staging_belt: wgpu::util::StagingBelt,
pub(crate) format: wgpu::TextureFormat,
pub(crate) quad_pipeline: quad::Pipeline, pub(crate) quad_pipeline: quad::Pipeline,
pub(crate) text_pipeline: text::Pipeline, pub(crate) text_pipeline: text::Pipeline,
pub(crate) triangle_pipeline: triangle::Pipeline, pub(crate) triangle_pipeline: triangle::Pipeline,
pub(crate) _pipeline_storage: pipeline::Storage,
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
pub(crate) image_pipeline: crate::image::Pipeline, pub(crate) image_pipeline: crate::image::Pipeline,
pub(crate) staging_belt: wgpu::util::StagingBelt, pub(crate) primitive_storage: primitive::Storage,
} }
impl Engine { impl Engine {
@ -43,13 +45,16 @@ impl Engine {
staging_belt: wgpu::util::StagingBelt::new( staging_belt: wgpu::util::StagingBelt::new(
buffer::MAX_WRITE_SIZE as u64, buffer::MAX_WRITE_SIZE as u64,
), ),
format,
quad_pipeline, quad_pipeline,
text_pipeline, text_pipeline,
triangle_pipeline, triangle_pipeline,
_pipeline_storage: pipeline::Storage::default(),
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline, image_pipeline,
primitive_storage: primitive::Storage::default(),
} }
} }

View file

@ -4,6 +4,7 @@ use crate::graphics::color;
use crate::graphics::text::{Editor, Paragraph}; use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Mesh; use crate::graphics::Mesh;
use crate::image::{self, Image}; use crate::image::{self, Image};
use crate::primitive::{self, Primitive};
use crate::quad::{self, Quad}; use crate::quad::{self, Quad};
use crate::text::{self, Text}; use crate::text::{self, Text};
use crate::triangle; use crate::triangle;
@ -13,6 +14,7 @@ pub struct Layer {
pub bounds: Rectangle, pub bounds: Rectangle,
pub quads: quad::Batch, pub quads: quad::Batch,
pub triangles: triangle::Batch, pub triangles: triangle::Batch,
pub primitives: primitive::Batch,
pub text: text::Batch, pub text: text::Batch,
pub images: image::Batch, pub images: image::Batch,
} }
@ -23,6 +25,7 @@ impl Default for Layer {
bounds: Rectangle::INFINITE, bounds: Rectangle::INFINITE,
quads: quad::Batch::default(), quads: quad::Batch::default(),
triangles: triangle::Batch::default(), triangles: triangle::Batch::default(),
primitives: primitive::Batch::default(),
text: text::Batch::default(), text: text::Batch::default(),
images: image::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) { pub fn push_clip(&mut self, bounds: Rectangle) {
self.previous.push(self.current); self.previous.push(self.current);
@ -282,6 +297,7 @@ impl Stack {
live.quads.clear(); live.quads.clear();
live.triangles.clear(); live.triangles.clear();
live.primitives.clear();
live.text.clear(); live.text.clear();
live.images.clear(); live.images.clear();
pending_meshes.clear(); pending_meshes.clear();

View file

@ -101,8 +101,6 @@ impl Renderer {
} }
} }
pub fn draw_primitive(&mut self, _primitive: Primitive) {}
pub fn present<T: AsRef<str>>( pub fn present<T: AsRef<str>>(
&mut self, &mut self,
engine: &mut Engine, 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() { if !layer.text.is_empty() {
engine.text_pipeline.prepare( engine.text_pipeline.prepare(
device, device,
@ -247,7 +258,9 @@ impl Renderer {
continue; continue;
}; };
let scissor_rect = physical_bounds.snap(); let Some(scissor_rect) = physical_bounds.snap() else {
continue;
};
if !layer.quads.is_empty() { if !layer.quads.is_empty() {
engine.quad_pipeline.render( 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() { if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render( text_layer += engine.text_pipeline.render(
&self.text_storage, &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 { impl graphics::compositor::Default for crate::Renderer {
type Compositor = window::Compositor; type Compositor = window::Compositor;
} }

View file

@ -1,20 +1,95 @@
//! Draw using different graphical primitives. //! Draw custom primitives.
pub mod pipeline; use crate::core::{self, Rectangle};
use crate::graphics::Viewport;
pub use pipeline::Pipeline;
use crate::graphics::Mesh;
use rustc_hash::FxHashMap;
use std::any::{Any, TypeId};
use std::fmt::Debug; use std::fmt::Debug;
/// The graphical primitives supported by `iced_wgpu`. /// A batch of primitives.
pub type Primitive = crate::graphics::Primitive<Custom>; pub type Batch = Vec<Instance>;
/// The custom primitives supported by `iced_wgpu`. /// A set of methods which allows a [`Primitive`] to be rendered.
#[derive(Debug, Clone, PartialEq)] pub trait Primitive: Debug + Send + Sync + 'static {
pub enum Custom { /// Processes the [`Primitive`], allowing for GPU buffer allocation.
/// A mesh primitive. fn prepare(
Mesh(Mesh), &self,
/// A custom pipeline primitive. device: &wgpu::Device,
Pipeline(Pipeline), 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));
}
/// 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.")
})
}
/// 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.")
})
}
} }

View file

@ -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.")
})
}
}

View file

@ -501,14 +501,13 @@ impl Layer {
let mut last_is_solid = None; let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() { for (index, mesh) in meshes.iter().enumerate() {
let Some(clip_bounds) = let Some(clip_bounds) = bounds
bounds.intersection(&(mesh.clip_bounds() * transformation)) .intersection(&(mesh.clip_bounds() * transformation))
.and_then(Rectangle::snap)
else { else {
continue; continue;
}; };
let clip_bounds = clip_bounds.snap();
render_pass.set_scissor_rect( render_pass.set_scissor_rect(
clip_bounds.x, clip_bounds.x,
clip_bounds.y, clip_bounds.y,

View file

@ -13,12 +13,13 @@ use crate::core::widget::tree::{self, Tree};
use crate::core::widget::{self, Widget}; use crate::core::widget::{self, Widget};
use crate::core::window; use crate::core::window;
use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size};
use crate::renderer::wgpu::primitive::pipeline; use crate::renderer::wgpu::primitive;
use std::marker::PhantomData; use std::marker::PhantomData;
pub use crate::graphics::Viewport;
pub use crate::renderer::wgpu::wgpu; 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. /// 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> for Shader<Message, P>
where where
P: Program<Message>, P: Program<Message>,
Renderer: pipeline::Renderer, Renderer: primitive::Renderer,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
struct Tag<T>(T); struct Tag<T>(T);
@ -160,7 +161,7 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
let state = tree.state.downcast_ref::<P::State>(); let state = tree.state.downcast_ref::<P::State>();
renderer.draw_pipeline_primitive( renderer.draw_primitive(
bounds, bounds,
self.program.draw(state, cursor_position, 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> for Element<'a, Message, Theme, Renderer>
where where
Message: 'a, Message: 'a,
Renderer: pipeline::Renderer, Renderer: primitive::Renderer,
P: Program<Message> + 'a, P: Program<Message> + 'a,
{ {
fn from( fn from(

View file

@ -1,7 +1,7 @@
use crate::core::event; use crate::core::event;
use crate::core::mouse; use crate::core::mouse;
use crate::core::{Rectangle, Shell}; use crate::core::{Rectangle, Shell};
use crate::renderer::wgpu::primitive::pipeline; use crate::renderer::wgpu::Primitive;
use crate::shader; use crate::shader;
/// The state and logic of a [`Shader`] widget. /// The state and logic of a [`Shader`] widget.
@ -15,7 +15,7 @@ pub trait Program<Message> {
type State: Default + 'static; type State: Default + 'static;
/// The type of primitive this [`Program`] can draw. /// 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 /// 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 /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a