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_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
50use iced_debug as debug;
51pub use iced_graphics as graphics;
52pub use iced_graphics::core;
53
54pub use wgpu;
55
56pub use engine::Engine;
57pub use layer::Layer;
58pub use primitive::Primitive;
59pub use settings::Settings;
60
61#[cfg(feature = "geometry")]
62pub use geometry::Geometry;
63
64use crate::core::renderer;
65use crate::core::{
66    Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
67};
68use crate::graphics::mesh;
69use crate::graphics::text::{Editor, Paragraph};
70use crate::graphics::{Shell, Viewport};
71
72/// A [`wgpu`] graphics renderer for [`iced`].
73///
74/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
75/// [`iced`]: https://github.com/iced-rs/iced
76pub struct Renderer {
77    engine: Engine,
78
79    default_font: Font,
80    default_text_size: Pixels,
81    layers: layer::Stack,
82
83    quad: quad::State,
84    triangle: triangle::State,
85    text: text::State,
86    text_viewport: text::Viewport,
87
88    #[cfg(any(feature = "svg", feature = "image"))]
89    image: image::State,
90
91    // TODO: Centralize all the image feature handling
92    #[cfg(any(feature = "svg", feature = "image"))]
93    image_cache: std::cell::RefCell<image::Cache>,
94
95    staging_belt: wgpu::util::StagingBelt,
96}
97
98impl Renderer {
99    pub fn new(
100        engine: Engine,
101        default_font: Font,
102        default_text_size: Pixels,
103    ) -> Self {
104        Self {
105            default_font,
106            default_text_size,
107            layers: layer::Stack::new(),
108
109            quad: quad::State::new(),
110            triangle: triangle::State::new(
111                &engine.device,
112                &engine.triangle_pipeline,
113            ),
114            text: text::State::new(),
115            text_viewport: engine.text_pipeline.create_viewport(&engine.device),
116
117            #[cfg(any(feature = "svg", feature = "image"))]
118            image: image::State::new(),
119
120            #[cfg(any(feature = "svg", feature = "image"))]
121            image_cache: std::cell::RefCell::new(engine.create_image_cache()),
122
123            // TODO: Resize belt smartly (?)
124            // It would be great if the `StagingBelt` API exposed methods
125            // for introspection to detect when a resize may be worth it.
126            staging_belt: wgpu::util::StagingBelt::new(
127                buffer::MAX_WRITE_SIZE as u64,
128            ),
129
130            engine,
131        }
132    }
133
134    fn draw(
135        &mut self,
136        clear_color: Option<Color>,
137        target: &wgpu::TextureView,
138        viewport: &Viewport,
139    ) -> wgpu::CommandEncoder {
140        let mut encoder = self.engine.device.create_command_encoder(
141            &wgpu::CommandEncoderDescriptor {
142                label: Some("iced_wgpu encoder"),
143            },
144        );
145
146        self.prepare(&mut encoder, viewport);
147        self.render(&mut encoder, target, clear_color, viewport);
148
149        self.quad.trim();
150        self.triangle.trim();
151        self.text.trim();
152
153        // TODO: Provide window id (?)
154        self.engine.trim();
155
156        #[cfg(any(feature = "svg", feature = "image"))]
157        {
158            self.image.trim();
159            self.image_cache.borrow_mut().trim();
160        }
161
162        encoder
163    }
164
165    pub fn present(
166        &mut self,
167        clear_color: Option<Color>,
168        _format: wgpu::TextureFormat,
169        frame: &wgpu::TextureView,
170        viewport: &Viewport,
171    ) -> wgpu::SubmissionIndex {
172        let encoder = self.draw(clear_color, frame, viewport);
173
174        self.staging_belt.finish();
175        let submission = self.engine.queue.submit([encoder.finish()]);
176        self.staging_belt.recall();
177        submission
178    }
179
180    /// Renders the current surface to an offscreen buffer.
181    ///
182    /// Returns RGBA bytes of the texture data.
183    pub fn screenshot(
184        &mut self,
185        viewport: &Viewport,
186        background_color: Color,
187    ) -> Vec<u8> {
188        #[derive(Clone, Copy, Debug)]
189        struct BufferDimensions {
190            width: u32,
191            height: u32,
192            unpadded_bytes_per_row: usize,
193            padded_bytes_per_row: usize,
194        }
195
196        impl BufferDimensions {
197            fn new(size: Size<u32>) -> Self {
198                let unpadded_bytes_per_row = size.width as usize * 4; //slice of buffer per row; always RGBA
199                let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; //256
200                let padded_bytes_per_row_padding = (alignment
201                    - unpadded_bytes_per_row % alignment)
202                    % alignment;
203                let padded_bytes_per_row =
204                    unpadded_bytes_per_row + padded_bytes_per_row_padding;
205
206                Self {
207                    width: size.width,
208                    height: size.height,
209                    unpadded_bytes_per_row,
210                    padded_bytes_per_row,
211                }
212            }
213        }
214
215        let dimensions = BufferDimensions::new(viewport.physical_size());
216
217        let texture_extent = wgpu::Extent3d {
218            width: dimensions.width,
219            height: dimensions.height,
220            depth_or_array_layers: 1,
221        };
222
223        let texture =
224            self.engine.device.create_texture(&wgpu::TextureDescriptor {
225                label: Some("iced_wgpu.offscreen.source_texture"),
226                size: texture_extent,
227                mip_level_count: 1,
228                sample_count: 1,
229                dimension: wgpu::TextureDimension::D2,
230                format: self.engine.format,
231                usage: wgpu::TextureUsages::RENDER_ATTACHMENT
232                    | wgpu::TextureUsages::COPY_SRC
233                    | wgpu::TextureUsages::TEXTURE_BINDING,
234                view_formats: &[],
235            });
236
237        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
238
239        let mut encoder = self.draw(Some(background_color), &view, viewport);
240
241        let texture = crate::color::convert(
242            &self.engine.device,
243            &mut encoder,
244            texture,
245            if graphics::color::GAMMA_CORRECTION {
246                wgpu::TextureFormat::Rgba8UnormSrgb
247            } else {
248                wgpu::TextureFormat::Rgba8Unorm
249            },
250        );
251
252        let output_buffer =
253            self.engine.device.create_buffer(&wgpu::BufferDescriptor {
254                label: Some("iced_wgpu.offscreen.output_texture_buffer"),
255                size: (dimensions.padded_bytes_per_row
256                    * dimensions.height as usize) as u64,
257                usage: wgpu::BufferUsages::MAP_READ
258                    | wgpu::BufferUsages::COPY_DST,
259                mapped_at_creation: false,
260            });
261
262        encoder.copy_texture_to_buffer(
263            texture.as_image_copy(),
264            wgpu::TexelCopyBufferInfo {
265                buffer: &output_buffer,
266                layout: wgpu::TexelCopyBufferLayout {
267                    offset: 0,
268                    bytes_per_row: Some(dimensions.padded_bytes_per_row as u32),
269                    rows_per_image: None,
270                },
271            },
272            texture_extent,
273        );
274
275        self.staging_belt.finish();
276        let index = self.engine.queue.submit([encoder.finish()]);
277        self.staging_belt.recall();
278
279        let slice = output_buffer.slice(..);
280        slice.map_async(wgpu::MapMode::Read, |_| {});
281
282        let _ = self.engine.device.poll(wgpu::PollType::Wait {
283            submission_index: Some(index),
284            timeout: None,
285        });
286
287        let mapped_buffer = slice.get_mapped_range();
288
289        mapped_buffer.chunks(dimensions.padded_bytes_per_row).fold(
290            vec![],
291            |mut acc, row| {
292                acc.extend(&row[..dimensions.unpadded_bytes_per_row]);
293                acc
294            },
295        )
296    }
297
298    fn prepare(
299        &mut self,
300        encoder: &mut wgpu::CommandEncoder,
301        viewport: &Viewport,
302    ) {
303        let scale_factor = viewport.scale_factor();
304
305        self.text_viewport
306            .update(&self.engine.queue, viewport.physical_size());
307
308        let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
309            viewport.physical_size(),
310        ));
311
312        self.layers.merge();
313
314        for layer in self.layers.iter() {
315            let clip_bounds = layer.bounds * scale_factor;
316
317            if physical_bounds
318                .intersection(&clip_bounds)
319                .and_then(Rectangle::snap)
320                .is_none()
321            {
322                continue;
323            }
324
325            if !layer.quads.is_empty() {
326                let prepare_span = debug::prepare(debug::Primitive::Quad);
327
328                self.quad.prepare(
329                    &self.engine.quad_pipeline,
330                    &self.engine.device,
331                    &mut self.staging_belt,
332                    encoder,
333                    &layer.quads,
334                    viewport.projection(),
335                    scale_factor,
336                );
337
338                prepare_span.finish();
339            }
340
341            if !layer.triangles.is_empty() {
342                let prepare_span = debug::prepare(debug::Primitive::Triangle);
343
344                self.triangle.prepare(
345                    &self.engine.triangle_pipeline,
346                    &self.engine.device,
347                    &mut self.staging_belt,
348                    encoder,
349                    &layer.triangles,
350                    Transformation::scale(scale_factor),
351                    viewport.physical_size(),
352                );
353
354                prepare_span.finish();
355            }
356
357            if !layer.primitives.is_empty() {
358                let prepare_span = debug::prepare(debug::Primitive::Shader);
359
360                let mut primitive_storage = self
361                    .engine
362                    .primitive_storage
363                    .write()
364                    .expect("Write primitive storage");
365
366                for instance in &layer.primitives {
367                    instance.primitive.prepare(
368                        &mut primitive_storage,
369                        &self.engine.device,
370                        &self.engine.queue,
371                        self.engine.format,
372                        &instance.bounds,
373                        viewport,
374                    );
375                }
376
377                prepare_span.finish();
378            }
379
380            #[cfg(any(feature = "svg", feature = "image"))]
381            if !layer.images.is_empty() {
382                let prepare_span = debug::prepare(debug::Primitive::Image);
383
384                self.image.prepare(
385                    &self.engine.image_pipeline,
386                    &self.engine.device,
387                    &mut self.staging_belt,
388                    encoder,
389                    &mut self.image_cache.borrow_mut(),
390                    &layer.images,
391                    viewport.projection(),
392                    scale_factor,
393                );
394
395                prepare_span.finish();
396            }
397
398            if !layer.text.is_empty() {
399                let prepare_span = debug::prepare(debug::Primitive::Text);
400
401                self.text.prepare(
402                    &self.engine.text_pipeline,
403                    &self.engine.device,
404                    &self.engine.queue,
405                    &self.text_viewport,
406                    encoder,
407                    &layer.text,
408                    layer.bounds,
409                    Transformation::scale(scale_factor),
410                );
411
412                prepare_span.finish();
413            }
414        }
415    }
416
417    fn render(
418        &mut self,
419        encoder: &mut wgpu::CommandEncoder,
420        frame: &wgpu::TextureView,
421        clear_color: Option<Color>,
422        viewport: &Viewport,
423    ) {
424        use std::mem::ManuallyDrop;
425
426        let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
427            &wgpu::RenderPassDescriptor {
428                label: Some("iced_wgpu render pass"),
429                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
430                    view: frame,
431                    depth_slice: None,
432                    resolve_target: None,
433                    ops: wgpu::Operations {
434                        load: match clear_color {
435                            Some(background_color) => wgpu::LoadOp::Clear({
436                                let [r, g, b, a] =
437                                    graphics::color::pack(background_color)
438                                        .components();
439
440                                wgpu::Color {
441                                    r: f64::from(r),
442                                    g: f64::from(g),
443                                    b: f64::from(b),
444                                    a: f64::from(a),
445                                }
446                            }),
447                            None => wgpu::LoadOp::Load,
448                        },
449                        store: wgpu::StoreOp::Store,
450                    },
451                })],
452                depth_stencil_attachment: None,
453                timestamp_writes: None,
454                occlusion_query_set: None,
455            },
456        ));
457
458        let mut quad_layer = 0;
459        let mut mesh_layer = 0;
460        let mut text_layer = 0;
461
462        #[cfg(any(feature = "svg", feature = "image"))]
463        let mut image_layer = 0;
464
465        let scale_factor = viewport.scale_factor();
466        let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
467            viewport.physical_size(),
468        ));
469
470        let scale = Transformation::scale(scale_factor);
471
472        for layer in self.layers.iter() {
473            let Some(physical_bounds) =
474                physical_bounds.intersection(&(layer.bounds * scale_factor))
475            else {
476                continue;
477            };
478
479            let Some(scissor_rect) = physical_bounds.snap() else {
480                continue;
481            };
482
483            if !layer.quads.is_empty() {
484                let render_span = debug::render(debug::Primitive::Quad);
485                self.quad.render(
486                    &self.engine.quad_pipeline,
487                    quad_layer,
488                    scissor_rect,
489                    &layer.quads,
490                    &mut render_pass,
491                );
492                render_span.finish();
493
494                quad_layer += 1;
495            }
496
497            if !layer.triangles.is_empty() {
498                let _ = ManuallyDrop::into_inner(render_pass);
499
500                let render_span = debug::render(debug::Primitive::Triangle);
501                mesh_layer += self.triangle.render(
502                    &self.engine.triangle_pipeline,
503                    encoder,
504                    frame,
505                    mesh_layer,
506                    &layer.triangles,
507                    physical_bounds,
508                    scale,
509                );
510                render_span.finish();
511
512                render_pass = ManuallyDrop::new(encoder.begin_render_pass(
513                    &wgpu::RenderPassDescriptor {
514                        label: Some("iced_wgpu render pass"),
515                        color_attachments: &[Some(
516                            wgpu::RenderPassColorAttachment {
517                                view: frame,
518                                depth_slice: None,
519                                resolve_target: None,
520                                ops: wgpu::Operations {
521                                    load: wgpu::LoadOp::Load,
522                                    store: wgpu::StoreOp::Store,
523                                },
524                            },
525                        )],
526                        depth_stencil_attachment: None,
527                        timestamp_writes: None,
528                        occlusion_query_set: None,
529                    },
530                ));
531            }
532
533            if !layer.primitives.is_empty() {
534                let render_span = debug::render(debug::Primitive::Shader);
535
536                let primitive_storage = self
537                    .engine
538                    .primitive_storage
539                    .read()
540                    .expect("Read primitive storage");
541
542                let mut need_render = Vec::new();
543
544                for instance in &layer.primitives {
545                    let bounds = instance.bounds * scale;
546
547                    if let Some(clip_bounds) = (instance.bounds * scale)
548                        .intersection(&physical_bounds)
549                        .and_then(Rectangle::snap)
550                    {
551                        render_pass.set_viewport(
552                            bounds.x,
553                            bounds.y,
554                            bounds.width,
555                            bounds.height,
556                            0.0,
557                            1.0,
558                        );
559
560                        render_pass.set_scissor_rect(
561                            clip_bounds.x,
562                            clip_bounds.y,
563                            clip_bounds.width,
564                            clip_bounds.height,
565                        );
566
567                        let drawn = instance
568                            .primitive
569                            .draw(&primitive_storage, &mut render_pass);
570
571                        if !drawn {
572                            need_render.push((instance, clip_bounds));
573                        }
574                    }
575                }
576
577                render_pass.set_viewport(
578                    0.0,
579                    0.0,
580                    viewport.physical_width() as f32,
581                    viewport.physical_height() as f32,
582                    0.0,
583                    1.0,
584                );
585
586                render_pass.set_scissor_rect(
587                    0,
588                    0,
589                    viewport.physical_width(),
590                    viewport.physical_height(),
591                );
592
593                if !need_render.is_empty() {
594                    let _ = ManuallyDrop::into_inner(render_pass);
595
596                    for (instance, clip_bounds) in need_render {
597                        instance.primitive.render(
598                            &primitive_storage,
599                            encoder,
600                            frame,
601                            &clip_bounds,
602                        );
603                    }
604
605                    render_pass = ManuallyDrop::new(encoder.begin_render_pass(
606                        &wgpu::RenderPassDescriptor {
607                            label: Some("iced_wgpu render pass"),
608                            color_attachments: &[Some(
609                                wgpu::RenderPassColorAttachment {
610                                    view: frame,
611                                    depth_slice: None,
612                                    resolve_target: None,
613                                    ops: wgpu::Operations {
614                                        load: wgpu::LoadOp::Load,
615                                        store: wgpu::StoreOp::Store,
616                                    },
617                                },
618                            )],
619                            depth_stencil_attachment: None,
620                            timestamp_writes: None,
621                            occlusion_query_set: None,
622                        },
623                    ));
624                }
625
626                render_span.finish();
627            }
628
629            #[cfg(any(feature = "svg", feature = "image"))]
630            if !layer.images.is_empty() {
631                let render_span = debug::render(debug::Primitive::Image);
632                self.image.render(
633                    &self.engine.image_pipeline,
634                    image_layer,
635                    scissor_rect,
636                    &mut render_pass,
637                );
638                render_span.finish();
639
640                image_layer += 1;
641            }
642
643            if !layer.text.is_empty() {
644                let render_span = debug::render(debug::Primitive::Text);
645                text_layer += self.text.render(
646                    &self.engine.text_pipeline,
647                    &self.text_viewport,
648                    text_layer,
649                    &layer.text,
650                    scissor_rect,
651                    &mut render_pass,
652                );
653                render_span.finish();
654            }
655        }
656
657        let _ = ManuallyDrop::into_inner(render_pass);
658
659        debug::layers_rendered(|| {
660            self.layers
661                .iter()
662                .filter(|layer| {
663                    !layer.is_empty()
664                        && physical_bounds
665                            .intersection(&(layer.bounds * scale_factor))
666                            .is_some_and(|viewport| viewport.snap().is_some())
667                })
668                .count()
669        });
670    }
671}
672
673impl core::Renderer for Renderer {
674    fn start_layer(&mut self, bounds: Rectangle) {
675        self.layers.push_clip(bounds);
676    }
677
678    fn end_layer(&mut self) {
679        self.layers.pop_clip();
680    }
681
682    fn start_transformation(&mut self, transformation: Transformation) {
683        self.layers.push_transformation(transformation);
684    }
685
686    fn end_transformation(&mut self) {
687        self.layers.pop_transformation();
688    }
689
690    fn fill_quad(
691        &mut self,
692        quad: core::renderer::Quad,
693        background: impl Into<Background>,
694    ) {
695        let (layer, transformation) = self.layers.current_mut();
696        layer.draw_quad(quad, background.into(), transformation);
697    }
698
699    fn reset(&mut self, new_bounds: Rectangle) {
700        self.layers.reset(new_bounds);
701    }
702
703    fn allocate_image(
704        &mut self,
705        _handle: &core::image::Handle,
706        _callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>)
707        + Send
708        + 'static,
709    ) {
710        #[cfg(feature = "image")]
711        self.image_cache
712            .get_mut()
713            .allocate_image(_handle, _callback);
714    }
715}
716
717impl core::text::Renderer for Renderer {
718    type Font = Font;
719    type Paragraph = Paragraph;
720    type Editor = Editor;
721
722    const ICON_FONT: Font = Font::with_name("Iced-Icons");
723    const CHECKMARK_ICON: char = '\u{f00c}';
724    const ARROW_DOWN_ICON: char = '\u{e800}';
725    const ICED_LOGO: char = '\u{e801}';
726    const SCROLL_UP_ICON: char = '\u{e802}';
727    const SCROLL_DOWN_ICON: char = '\u{e803}';
728    const SCROLL_LEFT_ICON: char = '\u{e804}';
729    const SCROLL_RIGHT_ICON: char = '\u{e805}';
730
731    fn default_font(&self) -> Self::Font {
732        self.default_font
733    }
734
735    fn default_size(&self) -> Pixels {
736        self.default_text_size
737    }
738
739    fn fill_paragraph(
740        &mut self,
741        text: &Self::Paragraph,
742        position: Point,
743        color: Color,
744        clip_bounds: Rectangle,
745    ) {
746        let (layer, transformation) = self.layers.current_mut();
747
748        layer.draw_paragraph(
749            text,
750            position,
751            color,
752            clip_bounds,
753            transformation,
754        );
755    }
756
757    fn fill_editor(
758        &mut self,
759        editor: &Self::Editor,
760        position: Point,
761        color: Color,
762        clip_bounds: Rectangle,
763    ) {
764        let (layer, transformation) = self.layers.current_mut();
765        layer.draw_editor(editor, position, color, clip_bounds, transformation);
766    }
767
768    fn fill_text(
769        &mut self,
770        text: core::Text,
771        position: Point,
772        color: Color,
773        clip_bounds: Rectangle,
774    ) {
775        let (layer, transformation) = self.layers.current_mut();
776        layer.draw_text(text, position, color, clip_bounds, transformation);
777    }
778}
779
780impl graphics::text::Renderer for Renderer {
781    fn fill_raw(&mut self, raw: graphics::text::Raw) {
782        let (layer, transformation) = self.layers.current_mut();
783        layer.draw_text_raw(raw, transformation);
784    }
785}
786
787#[cfg(feature = "image")]
788impl core::image::Renderer for Renderer {
789    type Handle = core::image::Handle;
790
791    fn load_image(
792        &self,
793        handle: &Self::Handle,
794    ) -> Result<core::image::Allocation, core::image::Error> {
795        self.image_cache.borrow_mut().load_image(
796            &self.engine.device,
797            &self.engine.queue,
798            handle,
799        )
800    }
801
802    fn measure_image(&self, handle: &Self::Handle) -> Option<core::Size<u32>> {
803        self.image_cache.borrow_mut().measure_image(handle)
804    }
805
806    fn draw_image(
807        &mut self,
808        image: core::Image,
809        bounds: Rectangle,
810        clip_bounds: Rectangle,
811    ) {
812        let (layer, transformation) = self.layers.current_mut();
813        layer.draw_raster(image, bounds, clip_bounds, transformation);
814    }
815}
816
817#[cfg(feature = "svg")]
818impl core::svg::Renderer for Renderer {
819    fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size<u32> {
820        self.image_cache.borrow_mut().measure_svg(handle)
821    }
822
823    fn draw_svg(
824        &mut self,
825        svg: core::Svg,
826        bounds: Rectangle,
827        clip_bounds: Rectangle,
828    ) {
829        let (layer, transformation) = self.layers.current_mut();
830        layer.draw_svg(svg, bounds, clip_bounds, transformation);
831    }
832}
833
834impl graphics::mesh::Renderer for Renderer {
835    fn draw_mesh(&mut self, mesh: graphics::Mesh) {
836        debug_assert!(
837            !mesh.indices().is_empty(),
838            "Mesh must not have empty indices"
839        );
840
841        debug_assert!(
842            mesh.indices().len().is_multiple_of(3),
843            "Mesh indices length must be a multiple of 3"
844        );
845
846        let (layer, transformation) = self.layers.current_mut();
847        layer.draw_mesh(mesh, transformation);
848    }
849
850    fn draw_mesh_cache(&mut self, cache: mesh::Cache) {
851        let (layer, transformation) = self.layers.current_mut();
852        layer.draw_mesh_cache(cache, transformation);
853    }
854}
855
856#[cfg(feature = "geometry")]
857impl graphics::geometry::Renderer for Renderer {
858    type Geometry = Geometry;
859    type Frame = geometry::Frame;
860
861    fn new_frame(&self, bounds: Rectangle) -> Self::Frame {
862        geometry::Frame::new(bounds)
863    }
864
865    fn draw_geometry(&mut self, geometry: Self::Geometry) {
866        let (layer, transformation) = self.layers.current_mut();
867
868        match geometry {
869            Geometry::Live {
870                meshes,
871                images,
872                text,
873            } => {
874                layer.draw_mesh_group(meshes, transformation);
875
876                for image in images {
877                    layer.draw_image(image, transformation);
878                }
879
880                layer.draw_text_group(text, transformation);
881            }
882            Geometry::Cached(cache) => {
883                if let Some(meshes) = cache.meshes {
884                    layer.draw_mesh_cache(meshes, transformation);
885                }
886
887                if let Some(images) = cache.images {
888                    for image in images.iter().cloned() {
889                        layer.draw_image(image, transformation);
890                    }
891                }
892
893                if let Some(text) = cache.text {
894                    layer.draw_text_cache(text, transformation);
895                }
896            }
897        }
898    }
899}
900
901impl primitive::Renderer for Renderer {
902    fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
903        let (layer, transformation) = self.layers.current_mut();
904        layer.draw_primitive(bounds, primitive, transformation);
905    }
906}
907
908impl graphics::compositor::Default for crate::Renderer {
909    type Compositor = window::Compositor;
910}
911
912impl renderer::Headless for Renderer {
913    async fn new(
914        default_font: Font,
915        default_text_size: Pixels,
916        backend: Option<&str>,
917    ) -> Option<Self> {
918        if backend.is_some_and(|backend| backend != "wgpu") {
919            return None;
920        }
921
922        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
923            backends: wgpu::Backends::from_env()
924                .unwrap_or(wgpu::Backends::PRIMARY),
925            flags: wgpu::InstanceFlags::empty(),
926            ..wgpu::InstanceDescriptor::default()
927        });
928
929        let adapter = instance
930            .request_adapter(&wgpu::RequestAdapterOptions {
931                power_preference: wgpu::PowerPreference::HighPerformance,
932                force_fallback_adapter: false,
933                compatible_surface: None,
934            })
935            .await
936            .ok()?;
937
938        let (device, queue) = adapter
939            .request_device(&wgpu::DeviceDescriptor {
940                label: Some("iced_wgpu [headless]"),
941                required_features: wgpu::Features::empty(),
942                required_limits: wgpu::Limits {
943                    max_bind_groups: 2,
944                    ..wgpu::Limits::default()
945                },
946                memory_hints: wgpu::MemoryHints::MemoryUsage,
947                trace: wgpu::Trace::Off,
948                experimental_features: wgpu::ExperimentalFeatures::disabled(),
949            })
950            .await
951            .ok()?;
952
953        let engine = Engine::new(
954            &adapter,
955            device,
956            queue,
957            if graphics::color::GAMMA_CORRECTION {
958                wgpu::TextureFormat::Rgba8UnormSrgb
959            } else {
960                wgpu::TextureFormat::Rgba8Unorm
961            },
962            Some(graphics::Antialiasing::MSAAx4),
963            Shell::headless(),
964        );
965
966        Some(Self::new(engine, default_font, default_text_size))
967    }
968
969    fn name(&self) -> String {
970        "wgpu".to_owned()
971    }
972
973    fn screenshot(
974        &mut self,
975        size: Size<u32>,
976        scale_factor: f32,
977        background_color: Color,
978    ) -> Vec<u8> {
979        self.screenshot(
980            &Viewport::with_physical_size(size, scale_factor),
981            background_color,
982        )
983    }
984}