iced_wgpu/
lib.rs

1//! A [`wgpu`] renderer for [Iced].
2//!
3//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
4//!
5//! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and
6//! DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the
7//! incoming [WebGPU API].
8//!
9//! Currently, `iced_wgpu` supports the following primitives:
10//! - Text, which is rendered using [`glyphon`].
11//! - Quads or rectangles, with rounded borders and a solid background color.
12//! - Clip areas, useful to implement scrollables or hide overflowing content.
13//! - Images and SVG, loaded from memory or the file system.
14//! - Meshes of triangles, useful to draw geometry freely.
15//!
16//! [Iced]: https://github.com/iced-rs/iced
17//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
18//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
19//! [`glyphon`]: https://github.com/grovesNL/glyphon
20#![doc(
21    html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
22)]
23#![cfg_attr(docsrs, feature(doc_auto_cfg))]
24#![allow(missing_docs)]
25pub mod layer;
26pub mod primitive;
27pub mod settings;
28pub mod window;
29
30#[cfg(feature = "geometry")]
31pub mod geometry;
32
33mod buffer;
34mod color;
35mod engine;
36mod quad;
37mod text;
38mod triangle;
39
40#[cfg(any(feature = "image", feature = "svg"))]
41#[path = "image/mod.rs"]
42mod image;
43
44#[cfg(not(any(feature = "image", feature = "svg")))]
45#[path = "image/null.rs"]
46mod image;
47
48use buffer::Buffer;
49
50pub use iced_graphics as graphics;
51pub use iced_graphics::core;
52
53pub use wgpu;
54
55pub use engine::Engine;
56pub use layer::Layer;
57pub use primitive::Primitive;
58pub use settings::Settings;
59
60#[cfg(feature = "geometry")]
61pub use geometry::Geometry;
62
63use crate::core::{
64    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
65    Vector,
66};
67use crate::graphics::Viewport;
68use crate::graphics::text::{Editor, Paragraph};
69
70/// A [`wgpu`] graphics renderer for [`iced`].
71///
72/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
73/// [`iced`]: https://github.com/iced-rs/iced
74#[allow(missing_debug_implementations)]
75pub struct Renderer {
76    default_font: Font,
77    default_text_size: Pixels,
78    layers: layer::Stack,
79
80    triangle_storage: triangle::Storage,
81    text_storage: text::Storage,
82    text_viewport: text::Viewport,
83
84    // TODO: Centralize all the image feature handling
85    #[cfg(any(feature = "svg", feature = "image"))]
86    image_cache: std::cell::RefCell<image::Cache>,
87}
88
89impl Renderer {
90    pub fn new(
91        device: &wgpu::Device,
92        engine: &Engine,
93        default_font: Font,
94        default_text_size: Pixels,
95    ) -> Self {
96        Self {
97            default_font,
98            default_text_size,
99            layers: layer::Stack::new(),
100
101            triangle_storage: triangle::Storage::new(),
102            text_storage: text::Storage::new(),
103            text_viewport: engine.text_pipeline.create_viewport(device),
104
105            #[cfg(any(feature = "svg", feature = "image"))]
106            image_cache: std::cell::RefCell::new(
107                engine.create_image_cache(device),
108            ),
109        }
110    }
111
112    pub fn present<T: AsRef<str>>(
113        &mut self,
114        engine: &mut Engine,
115        device: &wgpu::Device,
116        queue: &wgpu::Queue,
117        encoder: &mut wgpu::CommandEncoder,
118        clear_color: Option<Color>,
119        format: wgpu::TextureFormat,
120        frame: &wgpu::TextureView,
121        viewport: &Viewport,
122        overlay: &[T],
123    ) {
124        self.draw_overlay(overlay, viewport);
125        self.prepare(engine, device, queue, format, encoder, viewport);
126        self.render(engine, encoder, frame, clear_color, viewport);
127
128        self.triangle_storage.trim();
129        self.text_storage.trim();
130
131        #[cfg(any(feature = "svg", feature = "image"))]
132        self.image_cache.borrow_mut().trim();
133    }
134
135    fn prepare(
136        &mut self,
137        engine: &mut Engine,
138        device: &wgpu::Device,
139        queue: &wgpu::Queue,
140        _format: wgpu::TextureFormat,
141        encoder: &mut wgpu::CommandEncoder,
142        viewport: &Viewport,
143    ) {
144        let scale_factor = viewport.scale_factor() as f32;
145
146        self.text_viewport.update(queue, viewport.physical_size());
147
148        let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
149            viewport.physical_size(),
150        ));
151
152        for layer in self.layers.iter_mut() {
153            if physical_bounds
154                .intersection(&(layer.bounds * scale_factor))
155                .and_then(Rectangle::snap)
156                .is_none()
157            {
158                continue;
159            }
160
161            if !layer.quads.is_empty() {
162                engine.quad_pipeline.prepare(
163                    device,
164                    encoder,
165                    &mut engine.staging_belt,
166                    &layer.quads,
167                    viewport.projection(),
168                    scale_factor,
169                );
170            }
171
172            if !layer.triangles.is_empty() {
173                engine.triangle_pipeline.prepare(
174                    device,
175                    encoder,
176                    &mut engine.staging_belt,
177                    &mut self.triangle_storage,
178                    &layer.triangles,
179                    Transformation::scale(scale_factor),
180                    viewport.physical_size(),
181                );
182            }
183
184            if !layer.primitives.is_empty() {
185                for instance in &layer.primitives {
186                    instance.primitive.prepare(
187                        device,
188                        queue,
189                        engine.format,
190                        &mut engine.primitive_storage,
191                        &instance.bounds,
192                        viewport,
193                    );
194                }
195            }
196
197            #[cfg(any(feature = "svg", feature = "image"))]
198            if !layer.images.is_empty() {
199                engine.image_pipeline.prepare(
200                    device,
201                    encoder,
202                    &mut engine.staging_belt,
203                    &mut self.image_cache.borrow_mut(),
204                    &layer.images,
205                    viewport.projection(),
206                    scale_factor,
207                );
208            }
209
210            if !layer.text.is_empty() {
211                engine.text_pipeline.prepare(
212                    device,
213                    queue,
214                    &self.text_viewport,
215                    encoder,
216                    &mut self.text_storage,
217                    &layer.text,
218                    layer.bounds,
219                    Transformation::scale(scale_factor),
220                );
221            }
222        }
223    }
224
225    fn render(
226        &mut self,
227        engine: &mut Engine,
228        encoder: &mut wgpu::CommandEncoder,
229        frame: &wgpu::TextureView,
230        clear_color: Option<Color>,
231        viewport: &Viewport,
232    ) {
233        use std::mem::ManuallyDrop;
234
235        let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
236            &wgpu::RenderPassDescriptor {
237                label: Some("iced_wgpu render pass"),
238                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
239                    view: frame,
240                    resolve_target: None,
241                    ops: wgpu::Operations {
242                        load: match clear_color {
243                            Some(background_color) => wgpu::LoadOp::Clear({
244                                let [r, g, b, a] =
245                                    graphics::color::pack(background_color)
246                                        .components();
247
248                                wgpu::Color {
249                                    r: f64::from(r),
250                                    g: f64::from(g),
251                                    b: f64::from(b),
252                                    a: f64::from(a),
253                                }
254                            }),
255                            None => wgpu::LoadOp::Load,
256                        },
257                        store: wgpu::StoreOp::Store,
258                    },
259                })],
260                depth_stencil_attachment: None,
261                timestamp_writes: None,
262                occlusion_query_set: None,
263            },
264        ));
265
266        let mut quad_layer = 0;
267        let mut mesh_layer = 0;
268        let mut text_layer = 0;
269
270        #[cfg(any(feature = "svg", feature = "image"))]
271        let mut image_layer = 0;
272        #[cfg(any(feature = "svg", feature = "image"))]
273        let image_cache = self.image_cache.borrow();
274
275        let scale_factor = viewport.scale_factor() as f32;
276        let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
277            viewport.physical_size(),
278        ));
279
280        let scale = Transformation::scale(scale_factor);
281
282        for layer in self.layers.iter() {
283            let Some(physical_bounds) =
284                physical_bounds.intersection(&(layer.bounds * scale_factor))
285            else {
286                continue;
287            };
288
289            let Some(scissor_rect) = physical_bounds.snap() else {
290                continue;
291            };
292
293            if !layer.quads.is_empty() {
294                engine.quad_pipeline.render(
295                    quad_layer,
296                    scissor_rect,
297                    &layer.quads,
298                    &mut render_pass,
299                );
300
301                quad_layer += 1;
302            }
303
304            if !layer.triangles.is_empty() {
305                let _ = ManuallyDrop::into_inner(render_pass);
306
307                mesh_layer += engine.triangle_pipeline.render(
308                    encoder,
309                    frame,
310                    &self.triangle_storage,
311                    mesh_layer,
312                    &layer.triangles,
313                    physical_bounds,
314                    scale,
315                );
316
317                render_pass = ManuallyDrop::new(encoder.begin_render_pass(
318                    &wgpu::RenderPassDescriptor {
319                        label: Some("iced_wgpu render pass"),
320                        color_attachments: &[Some(
321                            wgpu::RenderPassColorAttachment {
322                                view: frame,
323                                resolve_target: None,
324                                ops: wgpu::Operations {
325                                    load: wgpu::LoadOp::Load,
326                                    store: wgpu::StoreOp::Store,
327                                },
328                            },
329                        )],
330                        depth_stencil_attachment: None,
331                        timestamp_writes: None,
332                        occlusion_query_set: None,
333                    },
334                ));
335            }
336
337            if !layer.primitives.is_empty() {
338                let _ = ManuallyDrop::into_inner(render_pass);
339
340                for instance in &layer.primitives {
341                    if let Some(clip_bounds) = (instance.bounds * scale)
342                        .intersection(&physical_bounds)
343                        .and_then(Rectangle::snap)
344                    {
345                        instance.primitive.render(
346                            encoder,
347                            &engine.primitive_storage,
348                            frame,
349                            &clip_bounds,
350                        );
351                    }
352                }
353
354                render_pass = ManuallyDrop::new(encoder.begin_render_pass(
355                    &wgpu::RenderPassDescriptor {
356                        label: Some("iced_wgpu render pass"),
357                        color_attachments: &[Some(
358                            wgpu::RenderPassColorAttachment {
359                                view: frame,
360                                resolve_target: None,
361                                ops: wgpu::Operations {
362                                    load: wgpu::LoadOp::Load,
363                                    store: wgpu::StoreOp::Store,
364                                },
365                            },
366                        )],
367                        depth_stencil_attachment: None,
368                        timestamp_writes: None,
369                        occlusion_query_set: None,
370                    },
371                ));
372            }
373
374            #[cfg(any(feature = "svg", feature = "image"))]
375            if !layer.images.is_empty() {
376                engine.image_pipeline.render(
377                    &image_cache,
378                    image_layer,
379                    scissor_rect,
380                    &mut render_pass,
381                );
382
383                image_layer += 1;
384            }
385
386            if !layer.text.is_empty() {
387                text_layer += engine.text_pipeline.render(
388                    &self.text_viewport,
389                    &self.text_storage,
390                    text_layer,
391                    &layer.text,
392                    scissor_rect,
393                    &mut render_pass,
394                );
395            }
396        }
397
398        let _ = ManuallyDrop::into_inner(render_pass);
399    }
400
401    fn draw_overlay(
402        &mut self,
403        overlay: &[impl AsRef<str>],
404        viewport: &Viewport,
405    ) {
406        use crate::core::Renderer as _;
407        use crate::core::alignment;
408        use crate::core::text::Renderer as _;
409
410        self.with_layer(
411            Rectangle::with_size(viewport.logical_size()),
412            |renderer| {
413                for (i, line) in overlay.iter().enumerate() {
414                    let text = crate::core::Text {
415                        content: line.as_ref().to_owned(),
416                        bounds: viewport.logical_size(),
417                        size: Pixels(20.0),
418                        line_height: core::text::LineHeight::default(),
419                        font: Font::MONOSPACE,
420                        align_x: core::text::Alignment::Default,
421                        align_y: alignment::Vertical::Top,
422                        shaping: core::text::Shaping::Basic,
423                        wrapping: core::text::Wrapping::Word,
424                    };
425
426                    renderer.fill_text(
427                        text.clone(),
428                        Point::new(11.0, 11.0 + 25.0 * i as f32),
429                        Color::from_rgba(0.9, 0.9, 0.9, 1.0),
430                        Rectangle::with_size(Size::INFINITY),
431                    );
432
433                    renderer.fill_text(
434                        text,
435                        Point::new(11.0, 11.0 + 25.0 * i as f32)
436                            + Vector::new(-1.0, -1.0),
437                        Color::BLACK,
438                        Rectangle::with_size(Size::INFINITY),
439                    );
440                }
441            },
442        );
443    }
444}
445
446impl core::Renderer for Renderer {
447    fn start_layer(&mut self, bounds: Rectangle) {
448        self.layers.push_clip(bounds);
449    }
450
451    fn end_layer(&mut self) {
452        self.layers.pop_clip();
453    }
454
455    fn start_transformation(&mut self, transformation: Transformation) {
456        self.layers.push_transformation(transformation);
457    }
458
459    fn end_transformation(&mut self) {
460        self.layers.pop_transformation();
461    }
462
463    fn fill_quad(
464        &mut self,
465        quad: core::renderer::Quad,
466        background: impl Into<Background>,
467    ) {
468        let (layer, transformation) = self.layers.current_mut();
469        layer.draw_quad(quad, background.into(), transformation);
470    }
471
472    fn clear(&mut self) {
473        self.layers.clear();
474    }
475}
476
477impl core::text::Renderer for Renderer {
478    type Font = Font;
479    type Paragraph = Paragraph;
480    type Editor = Editor;
481
482    const ICON_FONT: Font = Font::with_name("Iced-Icons");
483    const CHECKMARK_ICON: char = '\u{f00c}';
484    const ARROW_DOWN_ICON: char = '\u{e800}';
485
486    fn default_font(&self) -> Self::Font {
487        self.default_font
488    }
489
490    fn default_size(&self) -> Pixels {
491        self.default_text_size
492    }
493
494    fn fill_paragraph(
495        &mut self,
496        text: &Self::Paragraph,
497        position: Point,
498        color: Color,
499        clip_bounds: Rectangle,
500    ) {
501        let (layer, transformation) = self.layers.current_mut();
502
503        layer.draw_paragraph(
504            text,
505            position,
506            color,
507            clip_bounds,
508            transformation,
509        );
510    }
511
512    fn fill_editor(
513        &mut self,
514        editor: &Self::Editor,
515        position: Point,
516        color: Color,
517        clip_bounds: Rectangle,
518    ) {
519        let (layer, transformation) = self.layers.current_mut();
520        layer.draw_editor(editor, position, color, clip_bounds, transformation);
521    }
522
523    fn fill_text(
524        &mut self,
525        text: core::Text,
526        position: Point,
527        color: Color,
528        clip_bounds: Rectangle,
529    ) {
530        let (layer, transformation) = self.layers.current_mut();
531        layer.draw_text(text, position, color, clip_bounds, transformation);
532    }
533}
534
535#[cfg(feature = "image")]
536impl core::image::Renderer for Renderer {
537    type Handle = core::image::Handle;
538
539    fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
540        self.image_cache.borrow_mut().measure_image(handle)
541    }
542
543    fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
544        let (layer, transformation) = self.layers.current_mut();
545        layer.draw_raster(image, bounds, transformation);
546    }
547}
548
549#[cfg(feature = "svg")]
550impl core::svg::Renderer for Renderer {
551    fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
552        self.image_cache.borrow_mut().measure_svg(handle)
553    }
554
555    fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
556        let (layer, transformation) = self.layers.current_mut();
557        layer.draw_svg(svg, bounds, transformation);
558    }
559}
560
561impl graphics::mesh::Renderer for Renderer {
562    fn draw_mesh(&mut self, mesh: graphics::Mesh) {
563        debug_assert!(
564            !mesh.indices().is_empty(),
565            "Mesh must not have empty indices"
566        );
567
568        debug_assert!(
569            mesh.indices().len() % 3 == 0,
570            "Mesh indices length must be a multiple of 3"
571        );
572
573        let (layer, transformation) = self.layers.current_mut();
574        layer.draw_mesh(mesh, transformation);
575    }
576}
577
578#[cfg(feature = "geometry")]
579impl graphics::geometry::Renderer for Renderer {
580    type Geometry = Geometry;
581    type Frame = geometry::Frame;
582
583    fn new_frame(&self, size: Size) -> Self::Frame {
584        geometry::Frame::new(size)
585    }
586
587    fn draw_geometry(&mut self, geometry: Self::Geometry) {
588        let (layer, transformation) = self.layers.current_mut();
589
590        match geometry {
591            Geometry::Live {
592                meshes,
593                images,
594                text,
595            } => {
596                layer.draw_mesh_group(meshes, transformation);
597
598                for image in images {
599                    layer.draw_image(image, transformation);
600                }
601
602                layer.draw_text_group(text, transformation);
603            }
604            Geometry::Cached(cache) => {
605                if let Some(meshes) = cache.meshes {
606                    layer.draw_mesh_cache(meshes, transformation);
607                }
608
609                if let Some(images) = cache.images {
610                    for image in images.iter().cloned() {
611                        layer.draw_image(image, transformation);
612                    }
613                }
614
615                if let Some(text) = cache.text {
616                    layer.draw_text_cache(text, transformation);
617                }
618            }
619        }
620    }
621}
622
623impl primitive::Renderer for Renderer {
624    fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
625        let (layer, transformation) = self.layers.current_mut();
626        layer.draw_primitive(bounds, Box::new(primitive), transformation);
627    }
628}
629
630impl graphics::compositor::Default for crate::Renderer {
631    type Compositor = window::Compositor;
632}