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