1use std::time::Duration;
2use web_time::Instant;
3
4use crate::yuv::SourceWorkTicket;
5use crate::{
6 GraphError, PendingSourceWork, RenderError, RenderGraph, RenderedTexture, Renderer,
7 Yuv420pReadback,
8};
9use alpha2_media::YuvEncoding;
10
11#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
17pub struct FrameSubmitTimings {
18 pub graph_build: Duration,
20 pub command_encode: Duration,
22 pub source_acquire: Duration,
24 pub queue_lock_wait: Duration,
26 pub queue_submit: Duration,
28 pub lease_retire: Duration,
30 pub complete: Duration,
32}
33
34impl FrameSubmitTimings {
35 #[must_use]
37 pub fn merged(self, other: Self) -> Self {
38 Self {
39 graph_build: self.graph_build.saturating_add(other.graph_build),
40 command_encode: self.command_encode.saturating_add(other.command_encode),
41 source_acquire: self.source_acquire.saturating_add(other.source_acquire),
42 queue_lock_wait: self.queue_lock_wait.saturating_add(other.queue_lock_wait),
43 queue_submit: self.queue_submit.saturating_add(other.queue_submit),
44 lease_retire: self.lease_retire.saturating_add(other.lease_retire),
45 complete: self.complete.saturating_add(other.complete),
46 }
47 }
48
49 const fn with_submission(self, later: Self) -> Self {
51 Self {
52 graph_build: self.graph_build,
53 ..later
54 }
55 }
56}
57
58#[derive(Copy, Clone)]
60struct StageClock(Option<Instant>);
61
62impl StageClock {
63 fn start(enabled: bool) -> Self {
64 Self(enabled.then(Instant::now))
65 }
66
67 fn lap(&mut self) -> Duration {
69 let Some(mark) = self.0 else {
70 return Duration::ZERO;
71 };
72 let now = Instant::now();
73 self.0 = Some(now);
74 now.duration_since(mark)
75 }
76}
77
78impl Renderer {
79 pub fn render_graph(
86 &self,
87 graph: &RenderGraph<'_>,
88 sources: &[&PendingSourceWork],
89 ) -> Result<RenderedTexture, RenderError> {
90 let frame = self.frame_lock.hold();
92 let mut command_encoder = self.command_encoder("alpha2 frame encoder");
93 let source_work = begin_source_work(&frame, sources);
94 for work in &source_work {
95 work.encode(&mut command_encoder);
96 }
97 let graph_work = self
98 .encode_graph(graph, &mut command_encoder)
99 .map_err(GraphError::into_render_error)?;
100 stage_source_submissions(self.gpu.queue(), &source_work)?;
101 graph_work.flush_uniforms(self.gpu.queue());
102 self.submit([command_encoder.finish()])?;
104 let combined_submission = !source_work.is_empty();
105 for work in source_work {
106 work.complete(self, false);
107 }
108 let texture = self.complete_graph_work(graph_work, !combined_submission);
109 if combined_submission {
110 crate::diagnostics::record_frame_submission(&self.diagnostics);
111 }
112 Ok(texture)
113 }
114
115 pub fn render_graph_yuv420p(
121 &self,
122 graph: &RenderGraph<'_>,
123 sources: &[&PendingSourceWork],
124 encoding: YuvEncoding,
125 ) -> Result<Yuv420pReadback, RenderError> {
126 self.render_graph_yuv420p_measured(graph, sources, encoding, None)
127 }
128
129 pub fn render_graph_yuv420p_timed(
135 &self,
136 graph: &RenderGraph<'_>,
137 sources: &[&PendingSourceWork],
138 encoding: YuvEncoding,
139 timings: &mut FrameSubmitTimings,
140 ) -> Result<Yuv420pReadback, RenderError> {
141 self.render_graph_yuv420p_measured(graph, sources, encoding, Some(timings))
142 }
143
144 #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
150 pub fn render_graph_nv12_timed(
151 &self,
152 graph: &RenderGraph<'_>,
153 sources: &[&PendingSourceWork],
154 planes: crate::Nv12PlaneTargets<'_>,
155 encoding: YuvEncoding,
156 timings: &mut FrameSubmitTimings,
157 ) -> Result<(), RenderError> {
158 let mut clock = StageClock::start(true);
159 let mut collected = FrameSubmitTimings::default();
160 let frame = self.frame_lock.hold();
161 let mut command_encoder = self.command_encoder("alpha2 render and NV12 pack encoder");
162 self.check_submitted_work()?;
163 let source_work = begin_source_work(&frame, sources);
164 for work in &source_work {
165 work.encode(&mut command_encoder);
166 }
167 let sample = self
168 .frame_gpu_timer()
169 .and_then(crate::frame_gpu_time::FrameGpuTimer::begin);
170 if let Some(sample) = &sample {
171 sample.graph_start(&mut command_encoder);
172 }
173 let graph_work = self
174 .encode_graph(graph, &mut command_encoder)
175 .map_err(GraphError::into_render_error)?;
176 if let Some(sample) = &sample {
177 sample.graph_end(&mut command_encoder);
178 }
179 let pack =
180 self.encode_nv12_pack(graph_work.texture(), planes, encoding, &mut command_encoder)?;
181 if let Some(sample) = &sample {
182 sample.pack_end(&mut command_encoder);
183 }
184 let (canary_buffer, canary) = self.canaries.encode_own(&self.gpu, &mut command_encoder)?;
186 collected.command_encode = clock.lap();
187 stage_source_submissions(self.gpu.queue(), &source_work)?;
188 collected.source_acquire = clock.lap();
189 graph_work.flush_uniforms(self.gpu.queue());
190 let command_buffer = command_encoder.finish();
191 collected.command_encode = collected.command_encode.saturating_add(clock.lap());
192 let submitted = self.submit_measured([command_buffer], true)?;
194 if let Some(sample) = sample {
195 sample.submitted(&self.gpu);
196 }
197 self.submission_canaries
198 .track(&self.gpu, canary_buffer, canary);
199 drop(pack);
200 collected.queue_lock_wait = submitted.timings.queue_lock_wait;
201 collected.queue_submit = submitted.timings.queue_submit;
202 collected.lease_retire = submitted.lease_retire;
203 let _ = clock.lap();
204 for work in source_work {
205 work.complete(self, false);
206 }
207 drop(self.complete_graph_work(graph_work, false));
208 crate::diagnostics::record_frame_submission(&self.diagnostics);
209 collected.complete = clock.lap();
210 *timings = timings.with_submission(collected);
211 Ok(())
212 }
213
214 fn render_graph_yuv420p_measured(
215 &self,
216 graph: &RenderGraph<'_>,
217 sources: &[&PendingSourceWork],
218 encoding: YuvEncoding,
219 timings: Option<&mut FrameSubmitTimings>,
220 ) -> Result<Yuv420pReadback, RenderError> {
221 let measure = timings.is_some();
222 let mut clock = StageClock::start(measure);
223 let mut collected = FrameSubmitTimings::default();
224 let frame = self.frame_lock.hold();
225 let mut command_encoder =
226 self.command_encoder("alpha2 render and YUV420P readback encoder");
227 let source_work = begin_source_work(&frame, sources);
228 for work in &source_work {
229 work.encode(&mut command_encoder);
230 }
231 let sample = measure
233 .then(|| self.frame_gpu_timer())
234 .flatten()
235 .and_then(crate::frame_gpu_time::FrameGpuTimer::begin);
236 if let Some(sample) = &sample {
237 sample.graph_start(&mut command_encoder);
238 }
239 let graph_work = self
240 .encode_graph(graph, &mut command_encoder)
241 .map_err(GraphError::into_render_error)?;
242 if let Some(sample) = &sample {
243 sample.graph_end(&mut command_encoder);
244 }
245 let readback_work =
246 self.encode_yuv420p_readback(graph_work.texture(), encoding, &mut command_encoder)?;
247 if let Some(sample) = &sample {
248 sample.pack_end(&mut command_encoder);
249 }
250 collected.command_encode = clock.lap();
251 stage_source_submissions(self.gpu.queue(), &source_work)?;
252 collected.source_acquire = clock.lap();
253 graph_work.flush_uniforms(self.gpu.queue());
254 readback_work.flush_uniforms(self.gpu.queue());
255 let command_buffer = command_encoder.finish();
256 collected.command_encode = collected.command_encode.saturating_add(clock.lap());
258 let submitted = self.submit_measured([command_buffer], measure)?;
259 if let Some(sample) = sample {
260 sample.submitted(&self.gpu);
261 }
262 collected.queue_lock_wait = submitted.timings.queue_lock_wait;
263 collected.queue_submit = submitted.timings.queue_submit;
264 collected.lease_retire = submitted.lease_retire;
265 let _ = clock.lap();
267 let readback = self.complete_yuv420p_readback(
268 readback_work,
269 #[cfg(not(target_arch = "wasm32"))]
270 submitted.index,
271 false,
272 );
273 for work in source_work {
274 work.complete(self, false);
275 }
276 drop(self.complete_graph_work(graph_work, false));
277 crate::diagnostics::record_frame_submission(&self.diagnostics);
278 collected.complete = clock.lap();
279 if let Some(timings) = timings {
280 *timings = timings.with_submission(collected);
281 }
282 Ok(readback)
283 }
284}
285
286pub(crate) fn stage_source_submissions(
292 queue: &wgpu::Queue,
293 source_work: &[SourceWorkTicket<'_>],
294) -> Result<(), RenderError> {
295 debug_assert!(
296 source_work
297 .iter()
298 .all(SourceWorkTicket::has_no_staged_signals),
299 "a source lease carried a staged native signal into the next frame"
300 );
301 for work in source_work {
302 work.flush_uniforms(queue);
303 if !work.prepare_submission() {
304 return Err(RenderError::ExternalTextureSync(None));
305 }
306 }
307 for work in source_work {
309 work.flush_submission()?;
310 }
311 Ok(())
312}
313
314pub(crate) fn begin_source_work<'a>(
319 frame: &crate::renderer::FrameGuard,
320 sources: &[&'a PendingSourceWork],
321) -> Vec<SourceWorkTicket<'a>> {
322 sources
323 .iter()
324 .filter_map(|pending| pending.begin(frame))
325 .collect()
326}