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 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
68pub 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 #[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 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 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 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; let alignment = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize; 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}