tgf/ui/
batching.rs

1use std::rc::Rc;
2
3use crate::{
4    renderer::sdf_sprite::AlphaSdfParams, Aabb, BindableTexture, Color, GraphicsContext,
5    GrowableBuffer, VertexT,
6};
7use wgpu::BufferUsages;
8
9use crate::ui::{
10    element::{ComputedBounds, DivComputed, SdfTextureRegion, Section, TextureRegion},
11    layout::GlyphBoundsAndUv,
12    Corners, Div, DivTexture, ElementWithComputed, SdfFont, TextSection,
13};
14
15use crate::utils::rc_addr_as_u64;
16
17#[repr(C)]
18#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
19pub struct RectRaw {
20    pub bounds: Aabb,
21    pub color: Color,
22    pub border_radius: Corners<f32>,
23    pub border_color: Color,
24    // these are bundled together into another 16 byte chunk.
25    border_width: f32,
26    border_softness: f32,
27    shadow_width: f32,
28    shadow_curve: f32,
29    shadow_color: Color,
30}
31
32impl VertexT for RectRaw {
33    const ATTRIBUTES: &'static [wgpu::VertexFormat] = &[
34        wgpu::VertexFormat::Float32x4, // "pos"
35        wgpu::VertexFormat::Float32x4, // "color"
36        wgpu::VertexFormat::Float32x4, // "border_radius"
37        wgpu::VertexFormat::Float32x4, // "border_color"
38        wgpu::VertexFormat::Float32x4, // "border_width", "border_softness", "shadow_width", "shadow_curve"
39        wgpu::VertexFormat::Float32x4, // "shadow_color",
40    ];
41}
42
43impl RectRaw {
44    fn new(div: &Div, computed: &DivComputed) -> Self {
45        RectRaw {
46            bounds: bounds_from_computed(&computed.bounds),
47            color: div.color,
48            border_radius: div.border.radius,
49            border_color: div.border.color,
50            border_width: div.border.width,
51            border_softness: div.border.softness,
52            shadow_width: div.shadow.width,
53            shadow_curve: div.shadow.curve_param,
54            shadow_color: div.shadow.color,
55        }
56    }
57}
58
59#[inline(always)]
60fn bounds_from_computed(computed: &ComputedBounds) -> Aabb {
61    let pos = computed.pos.as_vec2();
62    let size = computed.size.as_vec2();
63    Aabb::new(pos, pos + size)
64}
65
66#[repr(C)]
67#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
68pub struct TexturedRectRaw {
69    pub rect: RectRaw,
70    pub uv: Aabb,
71}
72
73impl VertexT for TexturedRectRaw {
74    const ATTRIBUTES: &'static [wgpu::VertexFormat] = &[
75        wgpu::VertexFormat::Float32x4, // "pos"
76        wgpu::VertexFormat::Float32x4, // "color"
77        wgpu::VertexFormat::Float32x4, // "border_radius"
78        wgpu::VertexFormat::Float32x4, // "border_color"
79        wgpu::VertexFormat::Float32x4, // "border_width", "border_softness", "shadow_width", "shadow_curve"
80        wgpu::VertexFormat::Float32x4, // "shadow_color",
81        wgpu::VertexFormat::Float32x4, // "uv"
82    ];
83}
84
85#[repr(C)]
86#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
87pub struct AlphaSdfRectRaw {
88    pub bounds: Aabb,
89    pub color: Color,
90    pub params: AlphaSdfParams,
91    pub uv: Aabb,
92}
93
94impl VertexT for AlphaSdfRectRaw {
95    const ATTRIBUTES: &'static [wgpu::VertexFormat] = &[
96        wgpu::VertexFormat::Float32x4, // "bounds"
97        wgpu::VertexFormat::Float32x4, // "color"
98        wgpu::VertexFormat::Float32x4, // "border_color"
99        wgpu::VertexFormat::Float32x4, // "in_to_border_cutoff", "in_to_border_smooth", "border_to_out_cutoff", "border_to_out_smooth"
100        wgpu::VertexFormat::Float32x4, // "uv"
101    ];
102}
103
104#[repr(C)]
105#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
106pub struct GlyphRaw {
107    pub bounds: Aabb,
108    pub color: Color,
109    pub uv: Aabb,
110    pub shadow_intensity: f32,
111}
112
113impl VertexT for GlyphRaw {
114    const ATTRIBUTES: &'static [wgpu::VertexFormat] = &[
115        wgpu::VertexFormat::Float32x4, // "pos"
116        wgpu::VertexFormat::Float32x4, // "color"
117        wgpu::VertexFormat::Float32x4, // "uv"
118        wgpu::VertexFormat::Float32,   // "shadow_intensity"
119    ];
120}
121
122#[derive(Debug)]
123pub struct Batch {
124    /// Note: the key is not unique, it just describes what elements the batch is compatible with.
125    /// There could be two batches with the same key in one `ElementBatches` object (but not directly next to each other)
126    pub key: u64,
127    pub range: std::ops::Range<usize>,
128    pub kind: BatchKind,
129}
130
131#[derive(Debug)]
132pub enum BatchKind {
133    Rect,
134    TexturedRect(Rc<BindableTexture>),
135    AlphaSdfRect(Rc<BindableTexture>),
136    Glyph(Rc<SdfFont>),
137}
138
139#[derive(Debug, Default)]
140pub struct ElementBatches {
141    pub rects: Vec<RectRaw>,
142    pub textured_rects: Vec<TexturedRectRaw>,
143    pub alpha_sdf_rects: Vec<AlphaSdfRectRaw>,
144    pub glyphs: Vec<GlyphRaw>,
145    pub batches: Vec<Batch>,
146}
147
148pub enum PrimElement<'a> {
149    Rect(&'a (Div, DivComputed)),
150    TexturedRect(&'a (Div, DivComputed), &'a TextureRegion),
151    AlphaSdfRect(&'a (Div, DivComputed), &'a SdfTextureRegion),
152    Text(&'a TextSection, &'a [GlyphBoundsAndUv]),
153}
154
155impl<'a> PrimElement<'a> {
156    fn batch_key(&self) -> u64 {
157        match self {
158            PrimElement::Rect(_) => 0,
159            PrimElement::TexturedRect(_, texture) => rc_addr_as_u64(&texture.texture),
160            PrimElement::Text(text, _) => rc_addr_as_u64(&text.font),
161            PrimElement::AlphaSdfRect(_, sdf_texture) => {
162                rc_addr_as_u64(&sdf_texture.region.texture) ^ 21891209983212317
163                // this is such that we do not confuse a key for a AlphaSdfRect with a key for a TexturedRect
164            }
165        }
166    }
167}
168
169/// In the stacking order, this is the priority order:
170/// - high z-index in front of low z-index
171/// - text in front of rects, if z-index is the same
172/// - children in front of parents, if both are rects with the same z-index
173/// , followed by the fact if it is text or not, then if it is a chi
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub struct StackingLevel {
176    z_index: i16,
177    /// - 0 for divs
178    /// - 1 for text
179    /// - 1 for inline divs in text
180    /// - 2 for text in inline divs
181    text_level: u16,
182    nesting_level: u16,
183}
184
185impl StackingLevel {
186    pub fn new(z_index: i16, text_level: u16, nesting_level: u16) -> Self {
187        StackingLevel {
188            nesting_level,
189            text_level,
190            z_index,
191        }
192    }
193    pub const ZERO: StackingLevel = StackingLevel {
194        z_index: 0,
195        text_level: 0,
196        nesting_level: 0,
197    };
198}
199
200impl PartialOrd for StackingLevel {
201    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
202        match self.z_index.partial_cmp(&other.z_index) {
203            Some(core::cmp::Ordering::Equal) => {}
204            ord => return ord,
205        }
206        match self.text_level.partial_cmp(&other.text_level) {
207            Some(core::cmp::Ordering::Equal) => {}
208            ord => return ord,
209        }
210        self.nesting_level.partial_cmp(&other.nesting_level)
211    }
212}
213
214impl Ord for StackingLevel {
215    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
216        match self.z_index.cmp(&other.z_index) {
217            core::cmp::Ordering::Equal => {}
218            ord => return ord,
219        }
220        match self.text_level.cmp(&other.text_level) {
221            core::cmp::Ordering::Equal => {}
222            ord => return ord,
223        }
224        self.nesting_level.cmp(&other.nesting_level)
225    }
226}
227
228impl ElementWithComputed {
229    pub fn get_batches(&self) -> ElementBatches {
230        get_batches(&[&self])
231    }
232
233    /// The `level` passed in is the level of the parent
234    fn collect_prim_elements<'a>(
235        &'a self,
236        mut level: StackingLevel,
237        prim_elements: &mut Vec<(StackingLevel, PrimElement<'a>)>,
238    ) {
239        level.nesting_level += 1;
240
241        match self {
242            ElementWithComputed::Div(div) => {
243                level.z_index += div.0.z_index;
244
245                // Note: elements with color = 0,0,0,0 will be discarded even if they have a colored border or shadow!!!
246                if div.0.color != Color::TRANSPARENT {
247                    let prim = match &div.0.texture {
248                        DivTexture::None => PrimElement::Rect(div),
249                        DivTexture::Texture(texture) => PrimElement::TexturedRect(div, texture),
250                        DivTexture::AlphaSdfTexture(sdf_texture) => {
251                            PrimElement::AlphaSdfRect(div, sdf_texture)
252                        }
253                    };
254
255                    prim_elements.push((level, prim));
256                }
257
258                for ch in div.0.children.iter() {
259                    ch.element().collect_prim_elements(level, prim_elements);
260                }
261            }
262            ElementWithComputed::Text(text) => {
263                level.text_level += 1;
264
265                let mut i: usize = 0;
266                for section in text.0.sections.iter() {
267                    match section {
268                        Section::Text(text_section) => {
269                            let glyph_range = text.1.text_section_glyphs[i].clone();
270                            i += 1;
271                            let glyphs = &text.1.glyphs[glyph_range];
272                            let prim = PrimElement::Text(text_section, glyphs);
273                            prim_elements.push((level, prim));
274                        }
275                        Section::Element { element, .. } => {
276                            element
277                                .element()
278                                .collect_prim_elements(level, prim_elements);
279                        }
280                    }
281                }
282            }
283        }
284    }
285}
286
287pub fn get_batches(elements: &[&ElementWithComputed]) -> ElementBatches {
288    // step 1: create an array with pointers to all elements and their z-order:
289    let mut prim_elements: Vec<(StackingLevel, PrimElement)> = vec![];
290    for element in elements {
291        element.collect_prim_elements(StackingLevel::ZERO, &mut prim_elements);
292    }
293
294    // step 2: sort the array by the stacking level, from back to forth, to render them in correct order:
295    prim_elements.sort_by(|a, b| a.0.cmp(&b.0));
296
297    // step 3: create actual badges by merging prim elements of the same type together into one batch:
298    let mut rects: Vec<RectRaw> = vec![];
299    let mut textured_rects: Vec<TexturedRectRaw> = vec![];
300    let mut alpha_sdf_rects: Vec<AlphaSdfRectRaw> = vec![];
301    let mut glyphs: Vec<GlyphRaw> = vec![];
302    let mut batches: Vec<Batch> = vec![];
303
304    for (_level, element) in prim_elements {
305        let key = element.batch_key();
306
307        let add_new_batch = match batches.last_mut() {
308            Some(batch) => {
309                if batch.key != key {
310                    // incompatible, finish the last batch:
311                    let batch_end = match batch.kind {
312                        BatchKind::Rect => rects.len(),
313                        BatchKind::TexturedRect(_) => textured_rects.len(),
314                        BatchKind::Glyph(_) => glyphs.len(),
315                        BatchKind::AlphaSdfRect(_) => alpha_sdf_rects.len(),
316                    };
317                    batch.range.end = batch_end;
318                    true
319                } else {
320                    // compatible, no action needed
321                    false
322                }
323            }
324            None => true,
325        };
326
327        // add a new batch, if last batch in
328        if add_new_batch {
329            let batch = match &element {
330                PrimElement::Rect(_) => Batch {
331                    key,
332                    range: rects.len()..rects.len(),
333                    kind: BatchKind::Rect,
334                },
335                PrimElement::TexturedRect(_, texture) => Batch {
336                    key,
337                    range: textured_rects.len()..textured_rects.len(),
338                    kind: BatchKind::TexturedRect(texture.texture.clone()),
339                },
340                PrimElement::AlphaSdfRect(_, sdf_texture) => Batch {
341                    key,
342                    range: alpha_sdf_rects.len()..alpha_sdf_rects.len(),
343                    kind: BatchKind::AlphaSdfRect(sdf_texture.region.texture.clone()),
344                },
345                PrimElement::Text(section, _) => Batch {
346                    key,
347                    range: glyphs.len()..glyphs.len(),
348                    kind: BatchKind::Glyph(section.font.clone()),
349                },
350            };
351            batches.push(batch);
352        }
353
354        // add primitives to the respective arrays:
355        match element {
356            PrimElement::Rect((div, computed)) => {
357                let rect = RectRaw::new(div, computed);
358                rects.push(rect);
359            }
360            PrimElement::TexturedRect((div, computed), texture) => {
361                let rect = RectRaw::new(div, computed);
362                let textured_rect = TexturedRectRaw {
363                    rect,
364                    uv: texture.uv,
365                };
366                textured_rects.push(textured_rect);
367            }
368            PrimElement::AlphaSdfRect((div, computed), sdf_texture) => {
369                let alpha_sdf_rect = AlphaSdfRectRaw {
370                    bounds: bounds_from_computed(&computed.bounds),
371                    color: div.color,
372                    params: sdf_texture.params,
373                    uv: sdf_texture.region.uv,
374                };
375                alpha_sdf_rects.push(alpha_sdf_rect);
376            }
377            PrimElement::Text(section, text_glyphs) => {
378                for g in text_glyphs {
379                    let glyph_raw = GlyphRaw {
380                        bounds: g.bounds.into(),
381                        color: section.color,
382                        uv: g.uv,
383                        shadow_intensity: section.shadow_intensity,
384                    };
385                    glyphs.push(glyph_raw);
386                }
387            }
388        }
389    }
390
391    // finish the last batch:
392    if let Some(batch) = batches.last_mut() {
393        let batch_end = match batch.kind {
394            BatchKind::Rect => rects.len(),
395            BatchKind::TexturedRect(_) => textured_rects.len(),
396            BatchKind::AlphaSdfRect(_) => alpha_sdf_rects.len(),
397            BatchKind::Glyph(_) => glyphs.len(),
398        };
399        batch.range.end = batch_end;
400    }
401
402    ElementBatches {
403        rects,
404        textured_rects,
405        glyphs,
406        batches,
407        alpha_sdf_rects,
408    }
409}
410
411#[derive(Debug)]
412pub struct ElementBatchesGR {
413    pub rects: GrowableBuffer<RectRaw>,
414    pub textured_rects: GrowableBuffer<TexturedRectRaw>,
415    pub alpha_sdf_rects: GrowableBuffer<AlphaSdfRectRaw>,
416    pub glyphs: GrowableBuffer<GlyphRaw>,
417}
418
419impl ElementBatchesGR {
420    pub fn new(batches: &ElementBatches, device: &wgpu::Device) -> ElementBatchesGR {
421        let rects: GrowableBuffer<RectRaw> =
422            GrowableBuffer::new_from_data(device, BufferUsages::VERTEX, &batches.rects);
423        let textured_rects =
424            GrowableBuffer::new_from_data(device, BufferUsages::VERTEX, &batches.textured_rects);
425        let alpha_sdf_rects =
426            GrowableBuffer::new_from_data(device, BufferUsages::VERTEX, &batches.alpha_sdf_rects);
427        let glyphs = GrowableBuffer::new_from_data(device, BufferUsages::VERTEX, &batches.glyphs);
428
429        ElementBatchesGR {
430            rects,
431            textured_rects,
432            glyphs,
433            alpha_sdf_rects,
434        }
435    }
436
437    pub fn prepare(
438        &mut self,
439        batches: &ElementBatches,
440        device: &wgpu::Device,
441        queue: &wgpu::Queue,
442    ) {
443        self.rects.prepare(&batches.rects, device, queue);
444        self.textured_rects
445            .prepare(&batches.textured_rects, device, queue);
446        self.glyphs.prepare(&batches.glyphs, device, queue);
447    }
448}