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