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