Skip to main content

gpui/scene/
plan.rs

1use super::{AtlasTextureId, BatchIterator, FilterBoundary, Scene};
2use smallvec::SmallVec;
3use std::ops::Range;
4
5/// Inline capacity of renderer isolation stacks. Deeper groups allocate additional targets.
6pub const MAX_FILTER_GROUP_DEPTH: usize = 2;
7
8/// The index of an offscreen target in the renderer's content-filter target pool.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct FilterTargetIndex(usize);
11
12impl FilterTargetIndex {
13    /// Returns the index as a `usize`.
14    pub fn as_usize(self) -> usize {
15        self.0
16    }
17}
18
19/// Where the contents of a filter group are rendered.
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum FilterRenderTarget {
22    /// Render an unmatched group directly into the current target.
23    Inline,
24    /// Render into a dedicated offscreen target and composite it into the parent.
25    Isolated(FilterTargetIndex),
26}
27
28impl FilterRenderTarget {
29    fn is_isolated(self) -> bool {
30        matches!(self, Self::Isolated(_))
31    }
32}
33
34/// Allocation and draw totals collected while compiling one [`ScenePlan`].
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36pub struct ScenePlanRequirements {
37    /// The number of entries in [`ScenePlan::commands`].
38    pub command_count: usize,
39    /// The number of instanced draw calls. A path batch that rasterizes counts as two.
40    pub instance_batch_count: usize,
41    /// The number of path vertices to rasterize into the path target.
42    pub path_rasterization_vertex_count: usize,
43    /// The number of sprites that composite the rasterized paths.
44    pub path_sprite_count: usize,
45    /// The number of [`crate::PaintSurface`]s to draw.
46    pub surface_count: usize,
47    /// The number of [`crate::BackdropFilter`]s to draw.
48    pub backdrop_filter_count: usize,
49    /// The number of filter or grouped-opacity groups that draw into an isolated target.
50    pub isolated_filter_count: usize,
51    /// The number of isolated targets needed at the deepest level of nesting.
52    pub isolated_target_count: usize,
53    /// Whether any path is rasterized into an offscreen target.
54    pub uses_path_target: bool,
55    /// Whether a backdrop filter or an isolated filter or opacity group needs an offscreen target.
56    pub uses_offscreen_target: bool,
57    /// Whether a foreground or backdrop filter needs a temporary blur target.
58    pub uses_blur_target: bool,
59}
60
61/// A compiled scene command stream, built by [`Scene::finish`] and shared by every renderer.
62#[derive(Debug, Default)]
63pub struct ScenePlan {
64    // Retain this allocation across `Scene::clear`/`Scene::finish`; scenes are rebuilt every frame.
65    pub(super) commands: Vec<RenderCommand>,
66    requirements: ScenePlanRequirements,
67    scene_lengths: SceneLengths,
68}
69
70impl ScenePlan {
71    pub(super) fn clear(&mut self) {
72        self.commands.clear();
73        self.requirements = ScenePlanRequirements::default();
74        self.scene_lengths = SceneLengths::default();
75    }
76
77    pub(super) fn build(scene: &Scene, mut commands: Vec<RenderCommand>) -> Self {
78        commands.clear();
79        commands.reserve(scene.len());
80        let mut matched_starts =
81            SmallVec::<[bool; 8]>::from_elem(false, scene.filter_boundaries.len());
82        let mut pending_starts = SmallVec::<[usize; 4]>::new();
83        for (index, boundary) in scene.filter_boundaries.iter().enumerate() {
84            if boundary.is_start {
85                pending_starts.push(index);
86            } else if let Some(start_index) = pending_starts.pop() {
87                matched_starts[start_index] = true;
88            }
89        }
90
91        let mut filter_stack = SmallVec::<[(usize, FilterRenderTarget); 4]>::new();
92        let mut isolated_depth = 0;
93        let mut requirements = ScenePlanRequirements::default();
94
95        for batch in BatchIterator::new(scene) {
96            match batch {
97                PrimitiveBatch::FilterBoundary(boundary_index) => {
98                    let boundary = &scene.filter_boundaries[boundary_index];
99                    if boundary.is_start {
100                        let target = if matched_starts[boundary_index] {
101                            FilterRenderTarget::Isolated(FilterTargetIndex(isolated_depth))
102                        } else {
103                            FilterRenderTarget::Inline
104                        };
105                        if target.is_isolated() {
106                            isolated_depth += 1;
107                            requirements.uses_offscreen_target = true;
108                            requirements.uses_blur_target |= boundary.max_blur_radius() > 0.0;
109                            requirements.isolated_target_count =
110                                requirements.isolated_target_count.max(isolated_depth);
111                        }
112                        filter_stack.push((boundary_index, target));
113                        commands.push(RenderCommand::BeginFilter {
114                            boundary_index,
115                            target,
116                        });
117                    } else if let Some((start_index, target)) = filter_stack.pop() {
118                        if target.is_isolated() {
119                            isolated_depth -= 1;
120                            requirements.isolated_filter_count += 1;
121                        }
122                        commands.push(RenderCommand::EndFilter {
123                            boundary_index: start_index,
124                            closing_boundary_index: boundary_index,
125                            target,
126                        });
127                    } else {
128                        debug_assert!(false, "content-filter end boundary has no matching start");
129                        commands.push(RenderCommand::EndFilter {
130                            boundary_index,
131                            closing_boundary_index: boundary_index,
132                            target: FilterRenderTarget::Inline,
133                        });
134                    }
135                }
136                batch => {
137                    if let PrimitiveBatch::BackdropFilters(range) = &batch {
138                        requirements.uses_blur_target |= scene.backdrop_filters[range.clone()]
139                            .iter()
140                            .any(|filter| filter.max_blur_radius() > 0.0);
141                    }
142
143                    requirements.include_batch(&batch);
144                    commands.push(RenderCommand::Batch(batch));
145                }
146            }
147        }
148
149        requirements.command_count = commands.len();
150        Self {
151            commands,
152            requirements,
153            scene_lengths: SceneLengths::for_scene(scene),
154        }
155    }
156
157    /// Returns the commands in the order they must be drawn.
158    pub fn commands(&self) -> &[RenderCommand] {
159        &self.commands
160    }
161
162    /// Returns the totals collected while this plan was compiled.
163    pub fn requirements(&self) -> &ScenePlanRequirements {
164        &self.requirements
165    }
166
167    pub(super) fn assert_matches(&self, scene: &Scene) {
168        debug_assert_eq!(
169            self.scene_lengths,
170            SceneLengths::for_scene(scene),
171            "scene primitive storage changed after Scene::finish"
172        );
173    }
174}
175
176impl ScenePlanRequirements {
177    fn include_batch(&mut self, batch: &PrimitiveBatch) {
178        match batch {
179            PrimitiveBatch::Shadows { range, .. }
180            | PrimitiveBatch::Quads { range, .. }
181            | PrimitiveBatch::Underlines(range) => {
182                self.instance_batch_count += usize::from(!range.is_empty());
183            }
184            PrimitiveBatch::Paths {
185                rasterization_vertex_count,
186                sprite_count,
187                ..
188            } => {
189                self.path_rasterization_vertex_count += rasterization_vertex_count;
190                if *rasterization_vertex_count > 0 {
191                    self.path_sprite_count += sprite_count;
192                    self.uses_path_target = true;
193                    self.instance_batch_count += 2;
194                }
195            }
196            PrimitiveBatch::MonochromeSprites { range, .. }
197            | PrimitiveBatch::SubpixelSprites { range, .. }
198            | PrimitiveBatch::PolychromeSprites { range, .. } => {
199                self.instance_batch_count += usize::from(!range.is_empty());
200            }
201            PrimitiveBatch::Surfaces(range) => self.surface_count += range.len(),
202            PrimitiveBatch::BackdropFilters(range) => {
203                self.backdrop_filter_count += range.len();
204                self.uses_offscreen_target |= !range.is_empty();
205            }
206            PrimitiveBatch::FilterBoundary(_) => {
207                unreachable!("filter boundaries are compiled before requirements are collected")
208            }
209        }
210    }
211}
212
213#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
214struct SceneLengths {
215    shadows: usize,
216    quads: usize,
217    paths: usize,
218    underlines: usize,
219    monochrome_sprites: usize,
220    subpixel_sprites: usize,
221    polychrome_sprites: usize,
222    surfaces: usize,
223    backdrop_filters: usize,
224    filter_boundaries: usize,
225}
226
227impl SceneLengths {
228    fn for_scene(scene: &Scene) -> Self {
229        Self {
230            shadows: scene.shadows.len(),
231            quads: scene.quads.len(),
232            paths: scene.paths.len(),
233            underlines: scene.underlines.len(),
234            monochrome_sprites: scene.monochrome_sprites.len(),
235            subpixel_sprites: scene.subpixel_sprites.len(),
236            polychrome_sprites: scene.polychrome_sprites.len(),
237            surfaces: scene.surfaces.len(),
238            backdrop_filters: scene.backdrop_filters.len(),
239            filter_boundaries: scene.filter_boundaries.len(),
240        }
241    }
242}
243
244/// A contiguous run of one primitive type that the renderer draws in one call.
245///
246/// The ranges index the matching vector in [`Scene`]. A batch never mixes primitive types.
247#[derive(Clone, Debug, Eq, PartialEq)]
248pub enum PrimitiveBatch {
249    /// Shadows, drawn with or without corner smoothing as a unit.
250    Shadows {
251        /// The indices into [`Scene::shadows`].
252        range: Range<usize>,
253        /// Whether the shadows use corner smoothing.
254        smoothed: bool,
255    },
256    /// Quads, drawn with or without corner smoothing as a unit.
257    Quads {
258        /// The indices into [`Scene::quads`].
259        range: Range<usize>,
260        /// Whether the quads use corner smoothing.
261        smoothed: bool,
262    },
263    /// Paths. The renderer rasterizes their vertices, then composites the results as sprites.
264    Paths {
265        /// The indices into [`Scene::paths`].
266        range: Range<usize>,
267        /// The number of path vertices to rasterize in this batch.
268        rasterization_vertex_count: usize,
269        /// The number of sprites that composite the rasterized paths.
270        sprite_count: usize,
271    },
272    /// Underlines, as indices into [`Scene::underlines`].
273    Underlines(Range<usize>),
274    /// Single-color sprites that all read from one atlas texture.
275    MonochromeSprites {
276        /// The atlas texture that the sprites read from.
277        texture_id: AtlasTextureId,
278        /// The indices into [`Scene::monochrome_sprites`].
279        range: Range<usize>,
280    },
281    /// Subpixel-antialiased sprites that all read from one atlas texture.
282    SubpixelSprites {
283        /// The atlas texture that the sprites read from.
284        texture_id: AtlasTextureId,
285        /// The indices into [`Scene::subpixel_sprites`].
286        range: Range<usize>,
287    },
288    /// Full-color sprites that all read from one atlas texture.
289    PolychromeSprites {
290        /// The atlas texture that the sprites read from.
291        texture_id: AtlasTextureId,
292        /// The indices into [`Scene::polychrome_sprites`].
293        range: Range<usize>,
294        /// Whether the sprites use corner smoothing.
295        smoothed: bool,
296    },
297    /// Surfaces, as indices into [`Scene::surfaces`].
298    Surfaces(Range<usize>),
299    /// Backdrop filters, as indices into [`Scene::backdrop_filters`].
300    BackdropFilters(Range<usize>),
301    /// A filter or grouped-opacity boundary, as an index into [`Scene::filter_boundaries`].
302    FilterBoundary(usize),
303}
304
305/// Backend-neutral rendering work derived from a [`Scene`].
306#[derive(Clone, Debug, Eq, PartialEq)]
307pub enum RenderCommand {
308    /// A normal primitive batch.
309    Batch(PrimitiveBatch),
310    /// Begin rendering a filter or grouped-opacity group.
311    BeginFilter {
312        /// Index of the opening boundary in [`Scene::filter_boundaries`].
313        boundary_index: usize,
314        /// Whether this group renders inline or into an isolated target.
315        target: FilterRenderTarget,
316    },
317    /// Finish and composite a filter or grouped-opacity group.
318    EndFilter {
319        /// Index of the matched opening boundary in [`Scene::filter_boundaries`].
320        boundary_index: usize,
321        /// Index of the closing marker in [`Scene::filter_boundaries`].
322        closing_boundary_index: usize,
323        /// The same target selected by the matching begin command.
324        target: FilterRenderTarget,
325    },
326}
327
328impl RenderCommand {
329    /// Returns the opening [`FilterBoundary`] of a filter command, or `None` for a batch.
330    pub fn boundary<'a>(&self, scene: &'a Scene) -> Option<&'a FilterBoundary> {
331        match self {
332            Self::BeginFilter { boundary_index, .. } | Self::EndFilter { boundary_index, .. } => {
333                Some(&scene.filter_boundaries[*boundary_index])
334            }
335            Self::Batch(_) => None,
336        }
337    }
338
339    /// Returns a short description of this command, for GPU debugger labels.
340    pub fn label(&self) -> String {
341        match self {
342            Self::Batch(batch) => batch.label(),
343            Self::BeginFilter { target, .. } => match target {
344                FilterRenderTarget::Isolated(index) => {
345                    format!("begin filter group ({})", index.as_usize())
346                }
347                FilterRenderTarget::Inline => "begin inline filter group".into(),
348            },
349            Self::EndFilter { target, .. } => match target {
350                FilterRenderTarget::Isolated(index) => {
351                    format!("end filter group ({})", index.as_usize())
352                }
353                FilterRenderTarget::Inline => "end inline filter group".into(),
354            },
355        }
356    }
357}
358
359impl PrimitiveBatch {
360    /// Returns a short description of this batch, for GPU debugger labels.
361    pub fn label(&self) -> String {
362        match self {
363            Self::Shadows { range, smoothed } => format!(
364                "{}shadows ({})",
365                if *smoothed { "smoothed " } else { "" },
366                range.len()
367            ),
368            Self::Quads { range, smoothed } => format!(
369                "{}quads ({})",
370                if *smoothed { "smoothed " } else { "" },
371                range.len()
372            ),
373            Self::Paths { range, .. } => format!("paths ({})", range.len()),
374            Self::Underlines(range) => format!("underlines ({})", range.len()),
375            Self::MonochromeSprites { texture_id, range } => format!(
376                "monochrome sprites ({}) on atlas {}",
377                range.len(),
378                texture_id.index
379            ),
380            Self::SubpixelSprites { texture_id, range } => format!(
381                "subpixel sprites ({}) on atlas {}",
382                range.len(),
383                texture_id.index
384            ),
385            Self::PolychromeSprites {
386                texture_id,
387                range,
388                smoothed,
389            } => format!(
390                "{}polychrome sprites ({}) on atlas {}",
391                if *smoothed { "smoothed " } else { "" },
392                range.len(),
393                texture_id.index
394            ),
395            Self::Surfaces(range) => format!("surfaces ({})", range.len()),
396            Self::BackdropFilters(range) => format!("backdrop filters ({})", range.len()),
397            Self::FilterBoundary(index) => format!("filter boundary ({index})"),
398        }
399    }
400}