1pub(crate) mod cache;
2pub(crate) use cache::Cache;
3
4mod atlas;
5
6#[cfg(feature = "image")]
7mod raster;
8
9#[cfg(feature = "svg")]
10mod vector;
11
12use crate::Buffer;
13use crate::core::border;
14use crate::core::{Rectangle, Size, Transformation};
15use crate::graphics::Shell;
16
17use bytemuck::{Pod, Zeroable};
18
19use std::mem;
20use std::sync::Arc;
21
22pub use crate::graphics::Image;
23
24pub type Batch = Vec<Image>;
25
26#[derive(Debug, Clone)]
27pub struct Pipeline {
28 raw: wgpu::RenderPipeline,
29 backend: wgpu::Backend,
30 nearest_sampler: wgpu::Sampler,
31 linear_sampler: wgpu::Sampler,
32 texture_layout: wgpu::BindGroupLayout,
33 constant_layout: wgpu::BindGroupLayout,
34}
35
36impl Pipeline {
37 pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat, backend: wgpu::Backend) -> Self {
38 let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
39 address_mode_u: wgpu::AddressMode::ClampToEdge,
40 address_mode_v: wgpu::AddressMode::ClampToEdge,
41 address_mode_w: wgpu::AddressMode::ClampToEdge,
42 min_filter: wgpu::FilterMode::Nearest,
43 mag_filter: wgpu::FilterMode::Nearest,
44 mipmap_filter: wgpu::FilterMode::Nearest,
45 ..Default::default()
46 });
47
48 let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
49 address_mode_u: wgpu::AddressMode::ClampToEdge,
50 address_mode_v: wgpu::AddressMode::ClampToEdge,
51 address_mode_w: wgpu::AddressMode::ClampToEdge,
52 min_filter: wgpu::FilterMode::Linear,
53 mag_filter: wgpu::FilterMode::Linear,
54 mipmap_filter: wgpu::FilterMode::Linear,
55 ..Default::default()
56 });
57
58 let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
59 label: Some("iced_wgpu::image constants layout"),
60 entries: &[
61 wgpu::BindGroupLayoutEntry {
62 binding: 0,
63 visibility: wgpu::ShaderStages::VERTEX,
64 ty: wgpu::BindingType::Buffer {
65 ty: wgpu::BufferBindingType::Uniform,
66 has_dynamic_offset: false,
67 min_binding_size: wgpu::BufferSize::new(mem::size_of::<Uniforms>() as u64),
68 },
69 count: None,
70 },
71 wgpu::BindGroupLayoutEntry {
72 binding: 1,
73 visibility: wgpu::ShaderStages::FRAGMENT,
74 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
75 count: None,
76 },
77 ],
78 });
79
80 let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
81 label: Some("iced_wgpu::image texture atlas layout"),
82 entries: &[wgpu::BindGroupLayoutEntry {
83 binding: 0,
84 visibility: wgpu::ShaderStages::FRAGMENT,
85 ty: wgpu::BindingType::Texture {
86 sample_type: wgpu::TextureSampleType::Float { filterable: true },
87 view_dimension: wgpu::TextureViewDimension::D2Array,
88 multisampled: false,
89 },
90 count: None,
91 }],
92 });
93
94 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
95 label: Some("iced_wgpu::image pipeline layout"),
96 push_constant_ranges: &[],
97 bind_group_layouts: &[&constant_layout, &texture_layout],
98 });
99
100 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
101 label: Some("iced_wgpu image shader"),
102 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(concat!(
103 include_str!("../shader/vertex.wgsl"),
104 "\n",
105 include_str!("../shader/image.wgsl"),
106 ))),
107 });
108
109 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
110 label: Some("iced_wgpu::image pipeline"),
111 layout: Some(&layout),
112 vertex: wgpu::VertexState {
113 module: &shader,
114 entry_point: Some("vs_main"),
115 buffers: &[wgpu::VertexBufferLayout {
116 array_stride: mem::size_of::<Instance>() as u64,
117 step_mode: wgpu::VertexStepMode::Instance,
118 attributes: &wgpu::vertex_attr_array!(
119 0 => Float32x2,
121 1 => Float32x4,
123 2 => Float32x4,
125 3 => Float32x4,
127 4 => Float32,
129 5 => Float32,
131 6 => Float32x2,
133 7 => Float32x2,
135 8 => Sint32,
137 9 => Uint32,
139 ),
140 }],
141 compilation_options: wgpu::PipelineCompilationOptions::default(),
142 },
143 fragment: Some(wgpu::FragmentState {
144 module: &shader,
145 entry_point: Some("fs_main"),
146 targets: &[Some(wgpu::ColorTargetState {
147 format,
148 blend: Some(wgpu::BlendState {
149 color: wgpu::BlendComponent {
150 src_factor: wgpu::BlendFactor::SrcAlpha,
151 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
152 operation: wgpu::BlendOperation::Add,
153 },
154 alpha: wgpu::BlendComponent {
155 src_factor: wgpu::BlendFactor::One,
156 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
157 operation: wgpu::BlendOperation::Add,
158 },
159 }),
160 write_mask: wgpu::ColorWrites::ALL,
161 })],
162 compilation_options: wgpu::PipelineCompilationOptions::default(),
163 }),
164 primitive: wgpu::PrimitiveState {
165 topology: wgpu::PrimitiveTopology::TriangleList,
166 front_face: wgpu::FrontFace::Cw,
167 ..Default::default()
168 },
169 depth_stencil: None,
170 multisample: wgpu::MultisampleState {
171 count: 1,
172 mask: !0,
173 alpha_to_coverage_enabled: false,
174 },
175 multiview: None,
176 cache: None,
177 });
178
179 Pipeline {
180 raw: pipeline,
181 backend,
182 nearest_sampler,
183 linear_sampler,
184 texture_layout,
185 constant_layout,
186 }
187 }
188
189 pub fn create_cache(&self, device: &wgpu::Device, queue: &wgpu::Queue, shell: &Shell) -> Cache {
190 Cache::new(
191 device,
192 queue,
193 self.backend,
194 self.texture_layout.clone(),
195 shell,
196 )
197 }
198}
199
200#[derive(Default)]
201pub struct State {
202 layers: Vec<Layer>,
203 prepare_layer: usize,
204 nearest_instances: Vec<Instance>,
205 linear_instances: Vec<Instance>,
206}
207
208impl State {
209 pub fn new() -> Self {
210 Self::default()
211 }
212
213 pub fn prepare(
214 &mut self,
215 pipeline: &Pipeline,
216 device: &wgpu::Device,
217 belt: &mut wgpu::util::StagingBelt,
218 encoder: &mut wgpu::CommandEncoder,
219 cache: &mut Cache,
220 images: &Batch,
221 transformation: Transformation,
222 scale: f32,
223 ) {
224 if self.layers.len() <= self.prepare_layer {
225 self.layers.push(Layer::new(
226 device,
227 &pipeline.constant_layout,
228 &pipeline.nearest_sampler,
229 &pipeline.linear_sampler,
230 ));
231 }
232
233 let layer = &mut self.layers[self.prepare_layer];
234
235 let mut atlas: Option<Arc<wgpu::BindGroup>> = None;
236
237 for image in images {
238 match &image {
239 #[cfg(feature = "image")]
240 Image::Raster {
241 image,
242 bounds,
243 clip_bounds,
244 } => {
245 if let Some((atlas_entry, bind_group)) =
246 cache.upload_raster(device, encoder, belt, &image.handle)
247 {
248 match atlas.as_mut() {
249 None => {
250 atlas = Some(bind_group.clone());
251 }
252 Some(atlas) if atlas != bind_group => {
253 layer.push(atlas, &self.nearest_instances, &self.linear_instances);
254
255 *atlas = Arc::clone(bind_group);
256 }
257 _ => {}
258 }
259
260 add_instances(
261 *bounds,
262 *clip_bounds,
263 image.border_radius,
264 f32::from(image.rotation),
265 image.opacity,
266 image.snap,
267 atlas_entry,
268 match image.filter_method {
269 crate::core::image::FilterMethod::Nearest => {
270 &mut self.nearest_instances
271 }
272 crate::core::image::FilterMethod::Linear => {
273 &mut self.linear_instances
274 }
275 },
276 );
277 }
278 }
279 #[cfg(not(feature = "image"))]
280 Image::Raster { .. } => continue,
281
282 #[cfg(feature = "svg")]
283 Image::Vector {
284 svg,
285 bounds,
286 clip_bounds,
287 } => {
288 if let Some((atlas_entry, bind_group)) = cache.upload_vector(
289 device,
290 encoder,
291 belt,
292 &svg.handle,
293 svg.color,
294 bounds.size(),
295 scale,
296 ) {
297 match atlas.as_mut() {
298 None => {
299 atlas = Some(bind_group.clone());
300 }
301 Some(atlas) if atlas != bind_group => {
302 layer.push(atlas, &self.nearest_instances, &self.linear_instances);
303
304 *atlas = bind_group.clone();
305 }
306 _ => {}
307 }
308
309 add_instances(
310 *bounds,
311 *clip_bounds,
312 border::radius(0),
313 f32::from(svg.rotation),
314 svg.opacity,
315 true,
316 atlas_entry,
317 &mut self.nearest_instances,
318 );
319 }
320 }
321 #[cfg(not(feature = "svg"))]
322 Image::Vector { .. } => continue,
323 }
324 }
325
326 if let Some(atlas) = &atlas {
327 layer.push(atlas, &self.nearest_instances, &self.linear_instances);
328 }
329
330 layer.prepare(
331 device,
332 encoder,
333 belt,
334 transformation,
335 scale,
336 &self.nearest_instances,
337 &self.linear_instances,
338 );
339
340 self.prepare_layer += 1;
341 self.nearest_instances.clear();
342 self.linear_instances.clear();
343 }
344
345 pub fn render<'a>(
346 &'a self,
347 pipeline: &'a Pipeline,
348 layer: usize,
349 bounds: Rectangle<u32>,
350 render_pass: &mut wgpu::RenderPass<'a>,
351 ) {
352 if let Some(layer) = self.layers.get(layer) {
353 render_pass.set_pipeline(&pipeline.raw);
354
355 render_pass.set_scissor_rect(bounds.x, bounds.y, bounds.width, bounds.height);
356
357 layer.render(render_pass);
358 }
359 }
360
361 pub fn trim(&mut self) {
362 for layer in &mut self.layers[..self.prepare_layer] {
363 layer.clear();
364 }
365
366 self.prepare_layer = 0;
367 }
368}
369
370#[derive(Debug)]
371struct Layer {
372 uniforms: wgpu::Buffer,
373 instances: Buffer<Instance>,
374 nearest: Vec<Group>,
375 nearest_layout: wgpu::BindGroup,
376 nearest_total: usize,
377 linear: Vec<Group>,
378 linear_layout: wgpu::BindGroup,
379 linear_total: usize,
380}
381
382#[derive(Debug)]
383struct Group {
384 atlas: Arc<wgpu::BindGroup>,
385 instance_count: usize,
386}
387
388impl Layer {
389 fn new(
390 device: &wgpu::Device,
391 constant_layout: &wgpu::BindGroupLayout,
392 nearest_sampler: &wgpu::Sampler,
393 linear_sampler: &wgpu::Sampler,
394 ) -> Self {
395 let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
396 label: Some("iced_wgpu::image uniforms buffer"),
397 size: mem::size_of::<Uniforms>() as u64,
398 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
399 mapped_at_creation: false,
400 });
401
402 let instances = Buffer::new(
403 device,
404 "iced_wgpu::image instance buffer",
405 Instance::INITIAL,
406 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
407 );
408
409 let nearest_layout = device.create_bind_group(&wgpu::BindGroupDescriptor {
410 label: Some("iced_wgpu::image constants bind group"),
411 layout: constant_layout,
412 entries: &[
413 wgpu::BindGroupEntry {
414 binding: 0,
415 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
416 buffer: &uniforms,
417 offset: 0,
418 size: None,
419 }),
420 },
421 wgpu::BindGroupEntry {
422 binding: 1,
423 resource: wgpu::BindingResource::Sampler(nearest_sampler),
424 },
425 ],
426 });
427
428 let linear_layout = device.create_bind_group(&wgpu::BindGroupDescriptor {
429 label: Some("iced_wgpu::image constants bind group"),
430 layout: constant_layout,
431 entries: &[
432 wgpu::BindGroupEntry {
433 binding: 0,
434 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
435 buffer: &uniforms,
436 offset: 0,
437 size: None,
438 }),
439 },
440 wgpu::BindGroupEntry {
441 binding: 1,
442 resource: wgpu::BindingResource::Sampler(linear_sampler),
443 },
444 ],
445 });
446
447 Self {
448 uniforms,
449 instances,
450 nearest: Vec::new(),
451 nearest_layout,
452 nearest_total: 0,
453 linear: Vec::new(),
454 linear_layout,
455 linear_total: 0,
456 }
457 }
458
459 fn prepare(
460 &mut self,
461 device: &wgpu::Device,
462 encoder: &mut wgpu::CommandEncoder,
463 belt: &mut wgpu::util::StagingBelt,
464 transformation: Transformation,
465 scale_factor: f32,
466 nearest: &[Instance],
467 linear: &[Instance],
468 ) {
469 let uniforms = Uniforms {
470 transform: transformation.into(),
471 scale_factor,
472 _padding: [0.0; 3],
473 };
474
475 let bytes = bytemuck::bytes_of(&uniforms);
476
477 belt.write_buffer(
478 encoder,
479 &self.uniforms,
480 0,
481 (bytes.len() as u64).try_into().expect("Sized uniforms"),
482 device,
483 )
484 .copy_from_slice(bytes);
485
486 let _ = self
487 .instances
488 .resize(device, self.nearest_total + self.linear_total);
489
490 let mut offset = 0;
491
492 if !nearest.is_empty() {
493 offset += self.instances.write(device, encoder, belt, 0, nearest);
494 }
495
496 if !linear.is_empty() {
497 let _ = self.instances.write(device, encoder, belt, offset, linear);
498 }
499 }
500
501 fn push(&mut self, atlas: &Arc<wgpu::BindGroup>, nearest: &[Instance], linear: &[Instance]) {
502 let new_nearest = nearest.len() - self.nearest_total;
503
504 if new_nearest > 0 {
505 self.nearest.push(Group {
506 atlas: atlas.clone(),
507 instance_count: new_nearest,
508 });
509
510 self.nearest_total = nearest.len();
511 }
512
513 let new_linear = linear.len() - self.linear_total;
514
515 if new_linear > 0 {
516 self.linear.push(Group {
517 atlas: atlas.clone(),
518 instance_count: new_linear,
519 });
520
521 self.linear_total = linear.len();
522 }
523 }
524
525 fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
526 render_pass.set_vertex_buffer(0, self.instances.slice(..));
527
528 let mut offset = 0;
529
530 if !self.nearest.is_empty() {
531 render_pass.set_bind_group(0, &self.nearest_layout, &[]);
532
533 for group in &self.nearest {
534 render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
535 render_pass.draw(0..6, offset..offset + group.instance_count as u32);
536
537 offset += group.instance_count as u32;
538 }
539 }
540
541 if !self.linear.is_empty() {
542 render_pass.set_bind_group(0, &self.linear_layout, &[]);
543
544 for group in &self.linear {
545 render_pass.set_bind_group(1, group.atlas.as_ref(), &[]);
546 render_pass.draw(0..6, offset..offset + group.instance_count as u32);
547
548 offset += group.instance_count as u32;
549 }
550 }
551 }
552
553 fn clear(&mut self) {
554 self.nearest.clear();
555 self.nearest_total = 0;
556
557 self.linear.clear();
558 self.linear_total = 0;
559 }
560}
561
562#[repr(C)]
563#[derive(Debug, Clone, Copy, Zeroable, Pod)]
564struct Instance {
565 _center: [f32; 2],
566 _clip_bounds: [f32; 4],
567 _border_radius: [f32; 4],
568 _tile: [f32; 4],
569 _rotation: f32,
570 _opacity: f32,
571 _position_in_atlas: [f32; 2],
572 _size_in_atlas: [f32; 2],
573 _layer: u32,
574 _snap: u32,
575}
576
577impl Instance {
578 pub const INITIAL: usize = 20;
579}
580
581#[repr(C)]
582#[derive(Debug, Clone, Copy, Zeroable, Pod)]
583struct Uniforms {
584 transform: [f32; 16],
585 scale_factor: f32,
586 _padding: [f32; 3],
589}
590
591fn add_instances(
592 bounds: Rectangle,
593 clip_bounds: Rectangle,
594 border_radius: border::Radius,
595 rotation: f32,
596 opacity: f32,
597 snap: bool,
598 entry: &atlas::Entry,
599 instances: &mut Vec<Instance>,
600) {
601 let center = [
602 bounds.x + bounds.width / 2.0,
603 bounds.y + bounds.height / 2.0,
604 ];
605
606 let clip_bounds = [
607 clip_bounds.x,
608 clip_bounds.y,
609 clip_bounds.width,
610 clip_bounds.height,
611 ];
612
613 let border_radius = border_radius.into();
614
615 match entry {
616 atlas::Entry::Contiguous(allocation) => {
617 add_instance(
618 center,
619 clip_bounds,
620 border_radius,
621 [bounds.x, bounds.y, bounds.width, bounds.height],
622 rotation,
623 opacity,
624 snap,
625 allocation,
626 instances,
627 );
628 }
629 atlas::Entry::Fragmented { fragments, size } => {
630 let scaling_x = bounds.width / size.width as f32;
631 let scaling_y = bounds.height / size.height as f32;
632
633 for fragment in fragments {
634 let allocation = &fragment.allocation;
635 let (fragment_x, fragment_y) = fragment.position;
636
637 let Size {
638 width: fragment_width,
639 height: fragment_height,
640 } = allocation.size();
641
642 let tile = [
643 bounds.x + fragment_x as f32 * scaling_x,
644 bounds.y + fragment_y as f32 * scaling_y,
645 fragment_width as f32 * scaling_x,
646 fragment_height as f32 * scaling_y,
647 ];
648
649 add_instance(
650 center,
651 clip_bounds,
652 border_radius,
653 tile,
654 rotation,
655 opacity,
656 snap,
657 allocation,
658 instances,
659 );
660 }
661 }
662 }
663}
664
665#[inline]
666fn add_instance(
667 center: [f32; 2],
668 clip_bounds: [f32; 4],
669 border_radius: [f32; 4],
670 tile: [f32; 4],
671 rotation: f32,
672 opacity: f32,
673 snap: bool,
674 allocation: &atlas::Allocation,
675 instances: &mut Vec<Instance>,
676) {
677 let (x, y) = allocation.position();
678 let Size { width, height } = allocation.size();
679 let layer = allocation.layer();
680 let atlas_size = allocation.atlas_size();
681
682 let instance = Instance {
683 _center: center,
684 _clip_bounds: clip_bounds,
685 _border_radius: border_radius,
686 _tile: tile,
687 _rotation: rotation,
688 _opacity: opacity,
689 _position_in_atlas: [x as f32 / atlas_size as f32, y as f32 / atlas_size as f32],
690 _size_in_atlas: [
691 width as f32 / atlas_size as f32,
692 height as f32 / atlas_size as f32,
693 ],
694 _layer: layer as u32,
695 _snap: snap as u32,
696 };
697
698 instances.push(instance);
699}