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)]
20pub struct FrameSubmitTimings {
21 pub graph_build: Duration,
23 pub command_encode: Duration,
25 pub source_acquire: Duration,
27 pub queue_lock_wait: Duration,
29 pub queue_submit: Duration,
31 pub lease_retire: Duration,
33 pub complete: Duration,
35}
36
37impl FrameSubmitTimings {
38 #[must_use]
40 pub fn merged(self, other: Self) -> Self {
41 Self {
42 graph_build: self.graph_build.saturating_add(other.graph_build),
43 command_encode: self.command_encode.saturating_add(other.command_encode),
44 source_acquire: self.source_acquire.saturating_add(other.source_acquire),
45 queue_lock_wait: self.queue_lock_wait.saturating_add(other.queue_lock_wait),
46 queue_submit: self.queue_submit.saturating_add(other.queue_submit),
47 lease_retire: self.lease_retire.saturating_add(other.lease_retire),
48 complete: self.complete.saturating_add(other.complete),
49 }
50 }
51
52 const fn with_submission(self, later: Self) -> Self {
54 Self {
55 graph_build: self.graph_build,
56 ..later
57 }
58 }
59}
60
61#[derive(Copy, Clone)]
63struct StageClock(Option<Instant>);
64
65impl StageClock {
66 fn start(enabled: bool) -> Self {
67 Self(enabled.then(Instant::now))
68 }
69
70 fn lap(&mut self) -> Duration {
72 let Some(mark) = self.0 else {
73 return Duration::ZERO;
74 };
75 let now = Instant::now();
76 self.0 = Some(now);
77 now.duration_since(mark)
78 }
79}
80
81impl Renderer {
82 pub fn render_graph(
92 &self,
93 graph: &RenderGraph<'_>,
94 sources: &[&PendingSourceWork],
95 ) -> Result<RenderedTexture, RenderError> {
96 let frame = self.frame_lock.hold();
98 let mut command_encoder = self.command_encoder("alpha2 frame encoder");
99 let source_work = begin_source_work(&frame, sources);
100 for work in &source_work {
101 work.encode(&mut command_encoder);
102 }
103 let graph_work = self
104 .encode_graph(graph, &mut command_encoder)
105 .map_err(GraphError::into_render_error)?;
106 stage_source_submissions(self.gpu.queue(), &source_work)?;
107 graph_work.flush_uniforms(self.gpu.queue());
108 self.submit([command_encoder.finish()])?;
110 let combined_submission = !source_work.is_empty();
111 for work in source_work {
112 work.complete(self, false);
113 }
114 let texture = self.complete_graph_work(graph_work, !combined_submission);
115 if combined_submission {
116 crate::diagnostics::record_frame_submission(&self.diagnostics);
117 }
118 Ok(texture)
119 }
120
121 pub fn render_graph_yuv420p(
129 &self,
130 graph: &RenderGraph<'_>,
131 sources: &[&PendingSourceWork],
132 encoding: YuvEncoding,
133 ) -> Result<Yuv420pReadback, RenderError> {
134 self.render_graph_yuv420p_measured(graph, sources, encoding, None)
135 }
136
137 pub fn render_graph_yuv420p_timed(
146 &self,
147 graph: &RenderGraph<'_>,
148 sources: &[&PendingSourceWork],
149 encoding: YuvEncoding,
150 timings: &mut FrameSubmitTimings,
151 ) -> Result<Yuv420pReadback, RenderError> {
152 self.render_graph_yuv420p_measured(graph, sources, encoding, Some(timings))
153 }
154
155 #[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
166 pub fn render_graph_nv12_timed(
167 &self,
168 graph: &RenderGraph<'_>,
169 sources: &[&PendingSourceWork],
170 planes: crate::Nv12PlaneTargets<'_>,
171 encoding: YuvEncoding,
172 timings: &mut FrameSubmitTimings,
173 ) -> Result<(), RenderError> {
174 let mut clock = StageClock::start(true);
175 let mut collected = FrameSubmitTimings::default();
176 let frame = self.frame_lock.hold();
177 let mut command_encoder = self.command_encoder("alpha2 render and NV12 pack encoder");
178 self.check_submitted_work()?;
179 let source_work = begin_source_work(&frame, sources);
180 for work in &source_work {
181 work.encode(&mut command_encoder);
182 }
183 let graph_work = self
184 .encode_graph(graph, &mut command_encoder)
185 .map_err(GraphError::into_render_error)?;
186 let pack =
187 self.encode_nv12_pack(graph_work.texture(), planes, encoding, &mut command_encoder)?;
188 let (canary_buffer, canary) = self.canaries.encode_own(&self.gpu, &mut command_encoder)?;
190 collected.command_encode = clock.lap();
191 stage_source_submissions(self.gpu.queue(), &source_work)?;
192 collected.source_acquire = clock.lap();
193 graph_work.flush_uniforms(self.gpu.queue());
194 let command_buffer = command_encoder.finish();
195 collected.command_encode = collected.command_encode.saturating_add(clock.lap());
196 let submitted = self.submit_measured([command_buffer], true)?;
198 self.submission_canaries
199 .track(&self.gpu, canary_buffer, canary);
200 drop(pack);
201 collected.queue_lock_wait = submitted.timings.queue_lock_wait;
202 collected.queue_submit = submitted.timings.queue_submit;
203 collected.lease_retire = submitted.lease_retire;
204 let _ = clock.lap();
205 for work in source_work {
206 work.complete(self, false);
207 }
208 drop(self.complete_graph_work(graph_work, false));
209 crate::diagnostics::record_frame_submission(&self.diagnostics);
210 collected.complete = clock.lap();
211 *timings = timings.with_submission(collected);
212 Ok(())
213 }
214
215 fn render_graph_yuv420p_measured(
216 &self,
217 graph: &RenderGraph<'_>,
218 sources: &[&PendingSourceWork],
219 encoding: YuvEncoding,
220 timings: Option<&mut FrameSubmitTimings>,
221 ) -> Result<Yuv420pReadback, RenderError> {
222 let measure = timings.is_some();
223 let mut clock = StageClock::start(measure);
224 let mut collected = FrameSubmitTimings::default();
225 let frame = self.frame_lock.hold();
226 let mut command_encoder =
227 self.command_encoder("alpha2 render and YUV420P readback encoder");
228 let source_work = begin_source_work(&frame, sources);
229 for work in &source_work {
230 work.encode(&mut command_encoder);
231 }
232 let graph_work = self
233 .encode_graph(graph, &mut command_encoder)
234 .map_err(GraphError::into_render_error)?;
235 let readback_work =
236 self.encode_yuv420p_readback(graph_work.texture(), encoding, &mut command_encoder)?;
237 collected.command_encode = clock.lap();
238 stage_source_submissions(self.gpu.queue(), &source_work)?;
239 collected.source_acquire = clock.lap();
240 graph_work.flush_uniforms(self.gpu.queue());
241 readback_work.flush_uniforms(self.gpu.queue());
242 let command_buffer = command_encoder.finish();
243 collected.command_encode = collected.command_encode.saturating_add(clock.lap());
245 let submitted = self.submit_measured([command_buffer], measure)?;
246 collected.queue_lock_wait = submitted.timings.queue_lock_wait;
247 collected.queue_submit = submitted.timings.queue_submit;
248 collected.lease_retire = submitted.lease_retire;
249 let _ = clock.lap();
251 let readback = self.complete_yuv420p_readback(
252 readback_work,
253 #[cfg(not(target_arch = "wasm32"))]
254 submitted.index,
255 false,
256 );
257 for work in source_work {
258 work.complete(self, false);
259 }
260 drop(self.complete_graph_work(graph_work, false));
261 crate::diagnostics::record_frame_submission(&self.diagnostics);
262 collected.complete = clock.lap();
263 if let Some(timings) = timings {
264 *timings = timings.with_submission(collected);
265 }
266 Ok(readback)
267 }
268}
269
270pub(crate) fn stage_source_submissions(
277 queue: &wgpu::Queue,
278 source_work: &[SourceWorkTicket<'_>],
279) -> Result<(), RenderError> {
280 debug_assert!(
281 source_work
282 .iter()
283 .all(SourceWorkTicket::has_no_staged_signals),
284 "a source lease carried a staged native signal into the next frame"
285 );
286 for work in source_work {
287 work.flush_uniforms(queue);
288 if !work.prepare_submission() {
289 return Err(RenderError::ExternalTextureSync(None));
290 }
291 }
292 for work in source_work {
294 work.flush_submission()?;
295 }
296 Ok(())
297}
298
299pub(crate) fn begin_source_work<'a>(
306 frame: &crate::renderer::FrameGuard,
307 sources: &[&'a PendingSourceWork],
308) -> Vec<SourceWorkTicket<'a>> {
309 sources
310 .iter()
311 .filter_map(|pending| pending.begin(frame))
312 .collect()
313}