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