Skip to main content

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