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