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