1use super::*;
4use crate::render_backend::capture::CaptureKey;
5
6pub(super) struct SourceCapture {
7 nodes: HashSet<u32>,
8 feather: f32,
9 parent: Option<u32>,
10}
11
12pub(super) struct SourceRecording {
13 pub owner: u32,
14 pub nodes: HashSet<u32>,
15 feather: f32,
16 transforms: Vec<Transform2D>,
17 clips: Vec<ClipReference>,
18 saves: Vec<SceneStateSave>,
19 consumer: PendingNode,
20}
21
22impl WgpuRenderer<'_> {
23 pub fn begin_alpha_source(&mut self, mapping: Transform2D, feather: f32) {
26 let consumer = self
27 .current_node
28 .take()
29 .expect("alpha source needs a mask owner");
30 let transform = mapping.then(&self.current_transform());
31 self.source_recordings.push(SourceRecording {
32 owner: consumer.id,
33 nodes: HashSet::new(),
34 feather: if feather.is_finite() {
35 feather.max(0.0)
36 } else {
37 0.0
38 },
39 transforms: std::mem::replace(&mut self.transform_stack, vec![transform]),
40 clips: std::mem::take(&mut self.clip_stack),
41 saves: std::mem::take(&mut self.saves),
42 consumer,
43 });
44 }
45
46 pub fn end_alpha_source(&mut self) {
48 let recording = self
49 .source_recordings
50 .pop()
51 .expect("balanced source capture");
52 assert!(
53 self.current_node.is_none(),
54 "source leaf must finish recording"
55 );
56 if let Some(previous) = self.source_captures.remove(&recording.owner) {
57 for id in previous.nodes.difference(&recording.nodes) {
58 self.remove_node(*id);
59 }
60 }
61 self.transform_stack = recording.transforms;
62 self.clip_stack = recording.clips;
63 self.saves = recording.saves;
64 self.current_node = Some(recording.consumer);
65 self.source_captures.insert(
66 recording.owner,
67 SourceCapture {
68 nodes: recording.nodes,
69 feather: recording.feather,
70 parent: alpha_for_clip_stack(&self.clip_stack),
71 },
72 );
73 self.clip_stack.push(ClipReference::Alpha {
74 owner: recording.owner,
75 });
76 self.scene_dirty = true;
77 }
78
79 fn source_dependencies(&self, owner: u32) -> Vec<u32> {
80 let Some(source) = self.source_captures.get(&owner) else {
81 return Vec::new();
82 };
83 source
84 .parent
85 .into_iter()
86 .chain(
87 source
88 .nodes
89 .iter()
90 .filter_map(|id| self.scene.get(id))
91 .flat_map(|node| node.clip_stack())
92 .filter_map(|clip| match clip {
93 ClipReference::Alpha { owner } => Some(*owner),
94 _ => None,
95 }),
96 )
97 .collect()
98 }
99
100 pub(super) fn retain_reachable_sources(&mut self) -> HashSet<u32> {
101 let mut pending: Vec<_> = self
102 .scene
103 .iter()
104 .filter(|(id, _)| !self.source_nodes.contains_key(id))
105 .flat_map(|(_, node)| node.clip_stack())
106 .filter_map(|clip| match clip {
107 ClipReference::Alpha { owner } => Some(*owner),
108 _ => None,
109 })
110 .collect();
111 let mut active = HashSet::new();
112 while let Some(owner) = pending.pop() {
113 if active.insert(owner) {
114 pending.extend(self.source_dependencies(owner));
115 }
116 }
117 let removed: Vec<_> = self
118 .source_captures
119 .keys()
120 .filter(|id| !active.contains(id))
121 .copied()
122 .collect();
123 for owner in removed {
124 if let Some(source) = self.source_captures.remove(&owner) {
125 for id in source.nodes {
126 self.remove_node(id);
127 }
128 }
129 }
130 active
131 }
132
133 pub(super) fn render_alpha_sources(
134 &mut self,
135 active_masks: &HashSet<u32>,
136 ) -> HashSet<CaptureKey> {
137 let mut active = HashSet::new();
138 if self.source_captures.is_empty() {
139 return active;
140 }
141 fn margin(renderer: &WgpuRenderer<'_>, owner: u32, visiting: &mut HashSet<u32>) -> f32 {
144 if !visiting.insert(owner) {
145 return 0.0;
146 }
147 let own = renderer
148 .source_captures
149 .get(&owner)
150 .map_or(0.0, |s| 3.0 * s.feather);
151 let child = renderer
152 .source_dependencies(owner)
153 .into_iter()
154 .map(|id| margin(renderer, id, visiting))
155 .fold(0.0, f32::max);
156 visiting.remove(&owner);
157 own + child
158 }
159 let mut visited = HashSet::new();
160 let mut gutter = 0.0f32;
161 for owner in active_masks {
162 if !self.source_captures.contains_key(owner) {
163 continue;
164 }
165 let required = margin(self, *owner, &mut HashSet::new());
166 #[cfg(feature = "shaders")]
167 let required = self.effect_margin() + required;
168 if self.render_backend.source_domain_fits(required) {
169 gutter = gutter.max(required);
170 } else {
171 log::error!("alpha source {owner} exceeds GPU texture limits; hiding its content");
172 self.render_backend.render_alpha_mask(
173 *owner,
174 0,
175 &[],
176 0.0,
177 None,
178 &[],
179 &mut self.resource_churn_stats,
180 );
181 visited.insert(*owner);
182 }
183 }
184 if visited.len() == self.source_captures.len() {
185 return active;
186 }
187 assert!(self.render_backend.begin_source_domain(gutter));
188 self.ensure_vector_resources_for_immediate_scene();
189 let mut visiting = HashSet::new();
190 for owner in active_masks {
191 self.render_alpha_source(*owner, &mut visited, &mut visiting, &mut active);
192 }
193 self.render_backend.end_source_domain();
194 active
195 }
196
197 fn render_alpha_source(
198 &mut self,
199 owner: u32,
200 visited: &mut HashSet<u32>,
201 visiting: &mut HashSet<u32>,
202 active: &mut HashSet<CaptureKey>,
203 ) {
204 if visited.contains(&owner) || !self.source_captures.contains_key(&owner) {
205 return;
206 }
207 assert!(visiting.insert(owner), "alpha-source dependency cycle");
208 for dependency in self.source_dependencies(owner) {
209 self.render_alpha_source(dependency, visited, visiting, active);
210 }
211 visiting.remove(&owner);
212 let source = &self.source_captures[&owner];
213 let feather = source.feather;
214 let parent = source.parent;
215 let mut nodes: Vec<_> = source
216 .nodes
217 .iter()
218 .filter_map(|id| self.scene.get(id).map(|n| (n.z_index(), *id)))
219 .collect();
220 nodes.sort_unstable();
221 let nodes: Vec<_> = nodes.into_iter().map(|(_, id)| id).collect();
222 let plan = self.opacity_plan_at_depth(&nodes, 0);
223 collect_opacity_group_keys(&plan, active);
224 let mut hash = DefaultHasher::new();
225 bytemuck::bytes_of(&self.render_backend.globals).hash(&mut hash);
226 for id in &nodes {
227 self.hash_opacity_content(*id, &mut hash);
228 for scope in self.opacity_scopes.get(id).into_iter().flatten() {
229 scope.node_id.hash(&mut hash);
230 scope.opacity.to_bits().hash(&mut hash);
231 }
232 }
233 let content_signature = hash.finish();
234 let key = CaptureKey::Alpha(owner);
235 active.insert(key);
236 if !self
237 .render_backend
238 .capture_is_cached(key, content_signature)
239 {
240 self.render_backend.begin_capture();
241 self.draw_opacity_plan(&plan);
242 let domain = self.viewport_bounds();
243 let bounds = nodes
244 .iter()
245 .map(|id| self.scene[id].bounds())
246 .filter(|bounds| boxes_intersect(bounds, &domain))
247 .reduce(|a, b| a.union(&b));
248 let bounds = bounds.map_or(
249 ScissorRect {
250 min_x: domain.min.x,
251 min_y: domain.min.y,
252 max_x: domain.min.x + 1.0 / self.render_backend.globals.dpr[0],
253 max_y: domain.min.y + 1.0 / self.render_backend.globals.dpr[1],
254 },
255 |bounds| ScissorRect {
256 min_x: (bounds.min.x - 1.0).max(domain.min.x),
257 min_y: (bounds.min.y - 1.0).max(domain.min.y),
258 max_x: (bounds.max.x + 1.0).min(domain.max.x),
259 max_y: (bounds.max.y + 1.0).min(domain.max.y),
260 },
261 );
262 self.render_backend
263 .end_capture(key, content_signature, bounds);
264 self.resource_churn_stats.alpha_source_renders += 1;
265 } else {
266 self.resource_churn_stats.alpha_source_cache_hits += 1;
267 }
268 feather.to_bits().hash(&mut hash);
269 self.render_backend
270 .alpha_masks
271 .signature(parent)
272 .hash(&mut hash);
273 self.render_backend
274 .render_captured_alpha(owner, hash.finish(), key, feather, parent);
275 visited.insert(owner);
276 }
277}
278
279#[cfg(test)]
280impl WgpuRenderer<'_> {
281 pub(crate) fn alpha_source_resource_counts(&self) -> (usize, usize, usize) {
282 (
283 self.source_captures.len(),
284 self.source_nodes.len(),
285 self.render_backend.alpha_capture_count(),
286 )
287 }
288}