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::Maintain::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 resolve_target: None,
430 ops: wgpu::Operations {
431 load: match clear_color {
432 Some(background_color) => wgpu::LoadOp::Clear({
433 let [r, g, b, a] =
434 graphics::color::pack(background_color)
435 .components();
436
437 wgpu::Color {
438 r: f64::from(r),
439 g: f64::from(g),
440 b: f64::from(b),
441 a: f64::from(a),
442 }
443 }),
444 None => wgpu::LoadOp::Load,
445 },
446 store: wgpu::StoreOp::Store,
447 },
448 })],
449 depth_stencil_attachment: None,
450 timestamp_writes: None,
451 occlusion_query_set: None,
452 },
453 ));
454
455 let mut quad_layer = 0;
456 let mut mesh_layer = 0;
457 let mut text_layer = 0;
458
459 #[cfg(any(feature = "svg", feature = "image"))]
460 let mut image_layer = 0;
461 #[cfg(any(feature = "svg", feature = "image"))]
462 let image_cache = self.image_cache.borrow();
463
464 let scale_factor = viewport.scale_factor() as f32;
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 resolve_target: None,
518 ops: wgpu::Operations {
519 load: wgpu::LoadOp::Load,
520 store: wgpu::StoreOp::Store,
521 },
522 },
523 )],
524 depth_stencil_attachment: None,
525 timestamp_writes: None,
526 occlusion_query_set: None,
527 },
528 ));
529 }
530
531 if !layer.primitives.is_empty() {
532 let render_span = debug::render(debug::Primitive::Shader);
533 let _ = ManuallyDrop::into_inner(render_pass);
534
535 let primitive_storage = self
536 .engine
537 .primitive_storage
538 .read()
539 .expect("Read primitive storage");
540
541 for instance in &layer.primitives {
542 if let Some(clip_bounds) = (instance.bounds * scale)
543 .intersection(&physical_bounds)
544 .and_then(Rectangle::snap)
545 {
546 instance.primitive.render(
547 encoder,
548 &primitive_storage,
549 frame,
550 &clip_bounds,
551 );
552 }
553 }
554
555 render_span.finish();
556
557 render_pass = ManuallyDrop::new(encoder.begin_render_pass(
558 &wgpu::RenderPassDescriptor {
559 label: Some("iced_wgpu render pass"),
560 color_attachments: &[Some(
561 wgpu::RenderPassColorAttachment {
562 view: frame,
563 resolve_target: None,
564 ops: wgpu::Operations {
565 load: wgpu::LoadOp::Load,
566 store: wgpu::StoreOp::Store,
567 },
568 },
569 )],
570 depth_stencil_attachment: None,
571 timestamp_writes: None,
572 occlusion_query_set: None,
573 },
574 ));
575 }
576
577 #[cfg(any(feature = "svg", feature = "image"))]
578 if !layer.images.is_empty() {
579 let render_span = debug::render(debug::Primitive::Image);
580 self.image.render(
581 &self.engine.image_pipeline,
582 &image_cache,
583 image_layer,
584 scissor_rect,
585 &mut render_pass,
586 );
587 render_span.finish();
588
589 image_layer += 1;
590 }
591
592 if !layer.text.is_empty() {
593 let render_span = debug::render(debug::Primitive::Text);
594 text_layer += self.text.render(
595 &self.engine.text_pipeline,
596 &self.text_viewport,
597 text_layer,
598 &layer.text,
599 scissor_rect,
600 &mut render_pass,
601 );
602 render_span.finish();
603 }
604 }
605
606 let _ = ManuallyDrop::into_inner(render_pass);
607 }
608}
609
610impl core::Renderer for Renderer {
611 fn start_layer(&mut self, bounds: Rectangle) {
612 self.layers.push_clip(bounds);
613 }
614
615 fn end_layer(&mut self) {
616 self.layers.pop_clip();
617 }
618
619 fn start_transformation(&mut self, transformation: Transformation) {
620 self.layers.push_transformation(transformation);
621 }
622
623 fn end_transformation(&mut self) {
624 self.layers.pop_transformation();
625 }
626
627 fn fill_quad(
628 &mut self,
629 quad: core::renderer::Quad,
630 background: impl Into<Background>,
631 ) {
632 let (layer, transformation) = self.layers.current_mut();
633 layer.draw_quad(quad, background.into(), transformation);
634 }
635
636 fn clear(&mut self) {
637 self.layers.clear();
638 }
639}
640
641impl core::text::Renderer for Renderer {
642 type Font = Font;
643 type Paragraph = Paragraph;
644 type Editor = Editor;
645
646 const ICON_FONT: Font = Font::with_name("Iced-Icons");
647 const CHECKMARK_ICON: char = '\u{f00c}';
648 const ARROW_DOWN_ICON: char = '\u{e800}';
649
650 fn default_font(&self) -> Self::Font {
651 self.default_font
652 }
653
654 fn default_size(&self) -> Pixels {
655 self.default_text_size
656 }
657
658 fn fill_paragraph(
659 &mut self,
660 text: &Self::Paragraph,
661 position: Point,
662 color: Color,
663 clip_bounds: Rectangle,
664 ) {
665 let (layer, transformation) = self.layers.current_mut();
666
667 layer.draw_paragraph(
668 text,
669 position,
670 color,
671 clip_bounds,
672 transformation,
673 );
674 }
675
676 fn fill_editor(
677 &mut self,
678 editor: &Self::Editor,
679 position: Point,
680 color: Color,
681 clip_bounds: Rectangle,
682 ) {
683 let (layer, transformation) = self.layers.current_mut();
684 layer.draw_editor(editor, position, color, clip_bounds, transformation);
685 }
686
687 fn fill_text(
688 &mut self,
689 text: core::Text,
690 position: Point,
691 color: Color,
692 clip_bounds: Rectangle,
693 ) {
694 let (layer, transformation) = self.layers.current_mut();
695 layer.draw_text(text, position, color, clip_bounds, transformation);
696 }
697}
698
699#[cfg(feature = "image")]
700impl core::image::Renderer for Renderer {
701 type Handle = core::image::Handle;
702
703 fn measure_image(&self, handle: &Self::Handle) -> core::Size<u32> {
704 self.image_cache.borrow_mut().measure_image(handle)
705 }
706
707 fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
708 let (layer, transformation) = self.layers.current_mut();
709 layer.draw_raster(image, bounds, transformation);
710 }
711}
712
713#[cfg(feature = "svg")]
714impl core::svg::Renderer for Renderer {
715 fn measure_svg(&self, handle: &core::svg::Handle) -> core::Size<u32> {
716 self.image_cache.borrow_mut().measure_svg(handle)
717 }
718
719 fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
720 let (layer, transformation) = self.layers.current_mut();
721 layer.draw_svg(svg, bounds, transformation);
722 }
723}
724
725impl graphics::mesh::Renderer for Renderer {
726 fn draw_mesh(&mut self, mesh: graphics::Mesh) {
727 debug_assert!(
728 !mesh.indices().is_empty(),
729 "Mesh must not have empty indices"
730 );
731
732 debug_assert!(
733 mesh.indices().len() % 3 == 0,
734 "Mesh indices length must be a multiple of 3"
735 );
736
737 let (layer, transformation) = self.layers.current_mut();
738 layer.draw_mesh(mesh, transformation);
739 }
740}
741
742#[cfg(feature = "geometry")]
743impl graphics::geometry::Renderer for Renderer {
744 type Geometry = Geometry;
745 type Frame = geometry::Frame;
746
747 fn new_frame(&self, size: core::Size) -> Self::Frame {
748 geometry::Frame::new(size)
749 }
750
751 fn draw_geometry(&mut self, geometry: Self::Geometry) {
752 let (layer, transformation) = self.layers.current_mut();
753
754 match geometry {
755 Geometry::Live {
756 meshes,
757 images,
758 text,
759 } => {
760 layer.draw_mesh_group(meshes, transformation);
761
762 for image in images {
763 layer.draw_image(image, transformation);
764 }
765
766 layer.draw_text_group(text, transformation);
767 }
768 Geometry::Cached(cache) => {
769 if let Some(meshes) = cache.meshes {
770 layer.draw_mesh_cache(meshes, transformation);
771 }
772
773 if let Some(images) = cache.images {
774 for image in images.iter().cloned() {
775 layer.draw_image(image, transformation);
776 }
777 }
778
779 if let Some(text) = cache.text {
780 layer.draw_text_cache(text, transformation);
781 }
782 }
783 }
784 }
785}
786
787impl primitive::Renderer for Renderer {
788 fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
789 let (layer, transformation) = self.layers.current_mut();
790 layer.draw_primitive(bounds, Box::new(primitive), transformation);
791 }
792}
793
794impl graphics::compositor::Default for crate::Renderer {
795 type Compositor = window::Compositor;
796}
797
798impl renderer::Headless for Renderer {
799 async fn new(
800 default_font: Font,
801 default_text_size: Pixels,
802 backend: Option<&str>,
803 ) -> Option<Self> {
804 if backend.is_some_and(|backend| backend != "wgpu") {
805 return None;
806 }
807
808 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
809 backends: wgpu::Backends::from_env()
810 .unwrap_or(wgpu::Backends::PRIMARY),
811 flags: wgpu::InstanceFlags::empty(),
812 ..wgpu::InstanceDescriptor::default()
813 });
814
815 let adapter = instance
816 .request_adapter(&wgpu::RequestAdapterOptions {
817 power_preference: wgpu::PowerPreference::HighPerformance,
818 force_fallback_adapter: false,
819 compatible_surface: None,
820 })
821 .await?;
822
823 let (device, queue) = adapter
824 .request_device(
825 &wgpu::DeviceDescriptor {
826 label: Some("iced_wgpu [headless]"),
827 required_features: wgpu::Features::empty(),
828 required_limits: wgpu::Limits {
829 max_bind_groups: 2,
830 ..wgpu::Limits::default()
831 },
832 memory_hints: wgpu::MemoryHints::MemoryUsage,
833 },
834 None,
835 )
836 .await
837 .ok()?;
838
839 let engine = Engine::new(
840 &adapter,
841 device,
842 queue,
843 if graphics::color::GAMMA_CORRECTION {
844 wgpu::TextureFormat::Rgba8UnormSrgb
845 } else {
846 wgpu::TextureFormat::Rgba8Unorm
847 },
848 Some(graphics::Antialiasing::MSAAx4),
849 );
850
851 Some(Self::new(engine, default_font, default_text_size))
852 }
853
854 fn name(&self) -> String {
855 "wgpu".to_owned()
856 }
857
858 fn screenshot(
859 &mut self,
860 size: Size<u32>,
861 scale_factor: f32,
862 background_color: Color,
863 ) -> Vec<u8> {
864 self.screenshot(
865 &Viewport::with_physical_size(size, f64::from(scale_factor)),
866 background_color,
867 )
868 }
869}