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