iced_wgpu/
quad.rs

1mod gradient;
2mod solid;
3
4use gradient::Gradient;
5use solid::Solid;
6
7use crate::core::{Background, Rectangle, Transformation};
8use crate::graphics;
9use crate::graphics::color;
10
11use bytemuck::{Pod, Zeroable};
12
13use std::mem;
14
15const INITIAL_INSTANCES: usize = 2_000;
16
17/// The properties of a quad.
18#[derive(Clone, Copy, Debug, Pod, Zeroable)]
19#[repr(C)]
20pub struct Quad {
21    /// The position of the [`Quad`].
22    pub position: [f32; 2],
23
24    /// The size of the [`Quad`].
25    pub size: [f32; 2],
26
27    /// The border color of the [`Quad`], in __linear RGB__.
28    pub border_color: color::Packed,
29
30    /// The border radii of the [`Quad`].
31    pub border_radius: [f32; 4],
32
33    /// The border width of the [`Quad`].
34    pub border_width: f32,
35
36    /// The shadow color of the [`Quad`].
37    pub shadow_color: color::Packed,
38
39    /// The shadow offset of the [`Quad`].
40    pub shadow_offset: [f32; 2],
41
42    /// The shadow blur radius of the [`Quad`].
43    pub shadow_blur_radius: f32,
44}
45
46#[derive(Debug, Clone)]
47pub struct Pipeline {
48    solid: solid::Pipeline,
49    gradient: gradient::Pipeline,
50    constant_layout: wgpu::BindGroupLayout,
51}
52
53#[derive(Default)]
54pub struct State {
55    layers: Vec<Layer>,
56    prepare_layer: usize,
57}
58
59impl State {
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    pub fn prepare(
65        &mut self,
66        pipeline: &Pipeline,
67        device: &wgpu::Device,
68        belt: &mut wgpu::util::StagingBelt,
69        encoder: &mut wgpu::CommandEncoder,
70        quads: &Batch,
71        transformation: Transformation,
72        scale: f32,
73    ) {
74        if self.layers.len() <= self.prepare_layer {
75            self.layers
76                .push(Layer::new(device, &pipeline.constant_layout));
77        }
78
79        let layer = &mut self.layers[self.prepare_layer];
80        layer.prepare(device, encoder, belt, quads, transformation, scale);
81
82        self.prepare_layer += 1;
83    }
84
85    pub fn render<'a>(
86        &'a self,
87        pipeline: &'a Pipeline,
88        layer: usize,
89        bounds: Rectangle<u32>,
90        quads: &Batch,
91        render_pass: &mut wgpu::RenderPass<'a>,
92    ) {
93        if let Some(layer) = self.layers.get(layer) {
94            render_pass.set_scissor_rect(
95                bounds.x,
96                bounds.y,
97                bounds.width,
98                bounds.height,
99            );
100
101            let mut solid_offset = 0;
102            let mut gradient_offset = 0;
103
104            for (kind, count) in &quads.order {
105                match kind {
106                    Kind::Solid => {
107                        pipeline.solid.render(
108                            render_pass,
109                            &layer.constants,
110                            &layer.solid,
111                            solid_offset..(solid_offset + count),
112                        );
113
114                        solid_offset += count;
115                    }
116                    Kind::Gradient => {
117                        pipeline.gradient.render(
118                            render_pass,
119                            &layer.constants,
120                            &layer.gradient,
121                            gradient_offset..(gradient_offset + count),
122                        );
123
124                        gradient_offset += count;
125                    }
126                }
127            }
128        }
129    }
130
131    pub fn trim(&mut self) {
132        self.prepare_layer = 0;
133    }
134}
135
136impl Pipeline {
137    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline {
138        let constant_layout =
139            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
140                label: Some("iced_wgpu::quad uniforms layout"),
141                entries: &[wgpu::BindGroupLayoutEntry {
142                    binding: 0,
143                    visibility: wgpu::ShaderStages::VERTEX,
144                    ty: wgpu::BindingType::Buffer {
145                        ty: wgpu::BufferBindingType::Uniform,
146                        has_dynamic_offset: false,
147                        min_binding_size: wgpu::BufferSize::new(
148                            mem::size_of::<Uniforms>() as wgpu::BufferAddress,
149                        ),
150                    },
151                    count: None,
152                }],
153            });
154
155        Self {
156            solid: solid::Pipeline::new(device, format, &constant_layout),
157            gradient: gradient::Pipeline::new(device, format, &constant_layout),
158            constant_layout,
159        }
160    }
161}
162
163#[derive(Debug)]
164pub struct Layer {
165    constants: wgpu::BindGroup,
166    constants_buffer: wgpu::Buffer,
167    solid: solid::Layer,
168    gradient: gradient::Layer,
169}
170
171impl Layer {
172    pub fn new(
173        device: &wgpu::Device,
174        constant_layout: &wgpu::BindGroupLayout,
175    ) -> Self {
176        let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor {
177            label: Some("iced_wgpu::quad uniforms buffer"),
178            size: mem::size_of::<Uniforms>() as wgpu::BufferAddress,
179            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
180            mapped_at_creation: false,
181        });
182
183        let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
184            label: Some("iced_wgpu::quad uniforms bind group"),
185            layout: constant_layout,
186            entries: &[wgpu::BindGroupEntry {
187                binding: 0,
188                resource: constants_buffer.as_entire_binding(),
189            }],
190        });
191
192        Self {
193            constants,
194            constants_buffer,
195            solid: solid::Layer::new(device),
196            gradient: gradient::Layer::new(device),
197        }
198    }
199
200    pub fn prepare(
201        &mut self,
202        device: &wgpu::Device,
203        encoder: &mut wgpu::CommandEncoder,
204        belt: &mut wgpu::util::StagingBelt,
205        quads: &Batch,
206        transformation: Transformation,
207        scale: f32,
208    ) {
209        self.update(device, encoder, belt, transformation, scale);
210
211        if !quads.solids.is_empty() {
212            self.solid.prepare(device, encoder, belt, &quads.solids);
213        }
214
215        if !quads.gradients.is_empty() {
216            self.gradient
217                .prepare(device, encoder, belt, &quads.gradients);
218        }
219    }
220
221    pub fn update(
222        &mut self,
223        device: &wgpu::Device,
224        encoder: &mut wgpu::CommandEncoder,
225        belt: &mut wgpu::util::StagingBelt,
226        transformation: Transformation,
227        scale: f32,
228    ) {
229        let uniforms = Uniforms::new(transformation, scale);
230        let bytes = bytemuck::bytes_of(&uniforms);
231
232        belt.write_buffer(
233            encoder,
234            &self.constants_buffer,
235            0,
236            (bytes.len() as u64).try_into().expect("Sized uniforms"),
237            device,
238        )
239        .copy_from_slice(bytes);
240    }
241}
242
243/// A group of [`Quad`]s rendered together.
244#[derive(Default, Debug)]
245pub struct Batch {
246    /// The solid quads of the [`Layer`].
247    solids: Vec<Solid>,
248
249    /// The gradient quads of the [`Layer`].
250    gradients: Vec<Gradient>,
251
252    /// The quad order of the [`Layer`].
253    order: Order,
254}
255
256/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
257type Order = Vec<(Kind, usize)>;
258
259impl Batch {
260    /// Returns true if there are no quads of any type in [`Quads`].
261    pub fn is_empty(&self) -> bool {
262        self.solids.is_empty() && self.gradients.is_empty()
263    }
264
265    /// Adds a [`Quad`] with the provided `Background` type to the quad [`Layer`].
266    pub fn add(&mut self, quad: Quad, background: &Background) {
267        let kind = match background {
268            Background::Color(color) => {
269                self.solids.push(Solid {
270                    color: color::pack(*color),
271                    quad,
272                });
273
274                Kind::Solid
275            }
276            Background::Gradient(gradient) => {
277                self.gradients.push(Gradient {
278                    gradient: graphics::gradient::pack(
279                        gradient,
280                        Rectangle::new(quad.position.into(), quad.size.into()),
281                    ),
282                    quad,
283                });
284
285                Kind::Gradient
286            }
287        };
288
289        match self.order.last_mut() {
290            Some((last_kind, count)) if kind == *last_kind => {
291                *count += 1;
292            }
293            _ => {
294                self.order.push((kind, 1));
295            }
296        }
297    }
298
299    pub fn clear(&mut self) {
300        self.solids.clear();
301        self.gradients.clear();
302        self.order.clear();
303    }
304}
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307/// The kind of a quad.
308enum Kind {
309    /// A solid quad
310    Solid,
311    /// A gradient quad
312    Gradient,
313}
314
315fn color_target_state(
316    format: wgpu::TextureFormat,
317) -> [Option<wgpu::ColorTargetState>; 1] {
318    [Some(wgpu::ColorTargetState {
319        format,
320        blend: Some(wgpu::BlendState {
321            color: wgpu::BlendComponent {
322                src_factor: wgpu::BlendFactor::SrcAlpha,
323                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
324                operation: wgpu::BlendOperation::Add,
325            },
326            alpha: wgpu::BlendComponent {
327                src_factor: wgpu::BlendFactor::One,
328                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
329                operation: wgpu::BlendOperation::Add,
330            },
331        }),
332        write_mask: wgpu::ColorWrites::ALL,
333    })]
334}
335
336#[repr(C)]
337#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
338struct Uniforms {
339    transform: [f32; 16],
340    scale: f32,
341    // Uniforms must be aligned to their largest member,
342    // this uses a mat4x4<f32> which aligns to 16, so align to that
343    _padding: [f32; 3],
344}
345
346impl Uniforms {
347    fn new(transformation: Transformation, scale: f32) -> Uniforms {
348        Self {
349            transform: *transformation.as_ref(),
350            scale,
351            _padding: [0.0; 3],
352        }
353    }
354}
355
356impl Default for Uniforms {
357    fn default() -> Self {
358        Self {
359            transform: *Transformation::IDENTITY.as_ref(),
360            scale: 1.0,
361            _padding: [0.0; 3],
362        }
363    }
364}