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