iced_wgpu/image/
mod.rs

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                        // Center
120                        0 => Float32x2,
121                        // Clip bounds
122                        1 => Float32x4,
123                        // Border radius
124                        2 => Float32x4,
125                        // Tile
126                        3 => Float32x4,
127                        // Rotation
128                        4 => Float32,
129                        // Opacity
130                        5 => Float32,
131                        // Atlas position
132                        6 => Float32x2,
133                        // Atlas scale
134                        7 => Float32x2,
135                        // Layer
136                        8 => Sint32,
137                        // Snap
138                        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    // Uniforms must be aligned to their largest member,
587    // this uses a mat4x4<f32> which aligns to 16, so align to that
588    _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}