1use super::{AtlasTextureId, BatchIterator, FilterBoundary, Scene};
2use smallvec::SmallVec;
3use std::ops::Range;
4
5pub const MAX_FILTER_GROUP_DEPTH: usize = 2;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct FilterTargetIndex(usize);
11
12impl FilterTargetIndex {
13 pub fn as_usize(self) -> usize {
15 self.0
16 }
17}
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum FilterRenderTarget {
22 Inline,
24 Isolated(FilterTargetIndex),
26}
27
28impl FilterRenderTarget {
29 fn is_isolated(self) -> bool {
30 matches!(self, Self::Isolated(_))
31 }
32}
33
34#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
36pub struct ScenePlanRequirements {
37 pub command_count: usize,
39 pub instance_batch_count: usize,
41 pub path_rasterization_vertex_count: usize,
43 pub path_sprite_count: usize,
45 pub surface_count: usize,
47 pub backdrop_filter_count: usize,
49 pub isolated_filter_count: usize,
51 pub isolated_target_count: usize,
53 pub uses_path_target: bool,
55 pub uses_offscreen_target: bool,
57 pub uses_blur_target: bool,
59}
60
61#[derive(Debug, Default)]
63pub struct ScenePlan {
64 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 pub fn commands(&self) -> &[RenderCommand] {
159 &self.commands
160 }
161
162 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#[derive(Clone, Debug, Eq, PartialEq)]
248pub enum PrimitiveBatch {
249 Shadows {
251 range: Range<usize>,
253 smoothed: bool,
255 },
256 Quads {
258 range: Range<usize>,
260 smoothed: bool,
262 },
263 Paths {
265 range: Range<usize>,
267 rasterization_vertex_count: usize,
269 sprite_count: usize,
271 },
272 Underlines(Range<usize>),
274 MonochromeSprites {
276 texture_id: AtlasTextureId,
278 range: Range<usize>,
280 },
281 SubpixelSprites {
283 texture_id: AtlasTextureId,
285 range: Range<usize>,
287 },
288 PolychromeSprites {
290 texture_id: AtlasTextureId,
292 range: Range<usize>,
294 smoothed: bool,
296 },
297 Surfaces(Range<usize>),
299 BackdropFilters(Range<usize>),
301 FilterBoundary(usize),
303}
304
305#[derive(Clone, Debug, Eq, PartialEq)]
307pub enum RenderCommand {
308 Batch(PrimitiveBatch),
310 BeginFilter {
312 boundary_index: usize,
314 target: FilterRenderTarget,
316 },
317 EndFilter {
319 boundary_index: usize,
321 closing_boundary_index: usize,
323 target: FilterRenderTarget,
325 },
326}
327
328impl RenderCommand {
329 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 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 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}