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