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