use std::time::Duration;
use web_time::Instant;
use crate::yuv::SourceWorkTicket;
use crate::{
GraphError, PendingSourceWork, RenderError, RenderGraph, RenderedTexture, Renderer,
Yuv420pReadback,
};
use alpha2_media::YuvEncoding;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct FrameSubmitTimings {
pub graph_build: Duration,
pub command_encode: Duration,
pub source_acquire: Duration,
pub queue_lock_wait: Duration,
pub queue_submit: Duration,
pub lease_retire: Duration,
pub complete: Duration,
}
impl FrameSubmitTimings {
#[must_use]
pub fn merged(self, other: Self) -> Self {
Self {
graph_build: self.graph_build.saturating_add(other.graph_build),
command_encode: self.command_encode.saturating_add(other.command_encode),
source_acquire: self.source_acquire.saturating_add(other.source_acquire),
queue_lock_wait: self.queue_lock_wait.saturating_add(other.queue_lock_wait),
queue_submit: self.queue_submit.saturating_add(other.queue_submit),
lease_retire: self.lease_retire.saturating_add(other.lease_retire),
complete: self.complete.saturating_add(other.complete),
}
}
const fn with_submission(self, later: Self) -> Self {
Self {
graph_build: self.graph_build,
..later
}
}
}
#[derive(Copy, Clone)]
struct StageClock(Option<Instant>);
impl StageClock {
fn start(enabled: bool) -> Self {
Self(enabled.then(Instant::now))
}
fn lap(&mut self) -> Duration {
let Some(mark) = self.0 else {
return Duration::ZERO;
};
let now = Instant::now();
self.0 = Some(now);
now.duration_since(mark)
}
}
impl Renderer {
pub fn render_graph(
&self,
graph: &RenderGraph<'_>,
sources: &[&PendingSourceWork],
) -> Result<RenderedTexture, RenderError> {
let frame = self.frame_lock.hold();
let mut command_encoder = self.command_encoder("alpha2 frame encoder");
let source_work = begin_source_work(&frame, sources);
for work in &source_work {
work.encode(&mut command_encoder);
}
let graph_work = self
.encode_graph(graph, &mut command_encoder)
.map_err(GraphError::into_render_error)?;
stage_source_submissions(self.gpu.queue(), &source_work)?;
graph_work.flush_uniforms(self.gpu.queue());
self.submit([command_encoder.finish()])?;
let combined_submission = !source_work.is_empty();
for work in source_work {
work.complete(self, false);
}
let texture = self.complete_graph_work(graph_work, !combined_submission);
if combined_submission {
crate::diagnostics::record_frame_submission(&self.diagnostics);
}
Ok(texture)
}
pub fn render_graph_yuv420p(
&self,
graph: &RenderGraph<'_>,
sources: &[&PendingSourceWork],
encoding: YuvEncoding,
) -> Result<Yuv420pReadback, RenderError> {
self.render_graph_yuv420p_measured(graph, sources, encoding, None)
}
pub fn render_graph_yuv420p_timed(
&self,
graph: &RenderGraph<'_>,
sources: &[&PendingSourceWork],
encoding: YuvEncoding,
timings: &mut FrameSubmitTimings,
) -> Result<Yuv420pReadback, RenderError> {
self.render_graph_yuv420p_measured(graph, sources, encoding, Some(timings))
}
#[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
pub fn render_graph_nv12_timed(
&self,
graph: &RenderGraph<'_>,
sources: &[&PendingSourceWork],
planes: crate::Nv12PlaneTargets<'_>,
encoding: YuvEncoding,
timings: &mut FrameSubmitTimings,
) -> Result<(), RenderError> {
let mut clock = StageClock::start(true);
let mut collected = FrameSubmitTimings::default();
let frame = self.frame_lock.hold();
let mut command_encoder = self.command_encoder("alpha2 render and NV12 pack encoder");
self.check_submitted_work()?;
let source_work = begin_source_work(&frame, sources);
for work in &source_work {
work.encode(&mut command_encoder);
}
let graph_work = self
.encode_graph(graph, &mut command_encoder)
.map_err(GraphError::into_render_error)?;
let pack =
self.encode_nv12_pack(graph_work.texture(), planes, encoding, &mut command_encoder)?;
let (canary_buffer, canary) = self.canaries.encode_own(&self.gpu, &mut command_encoder)?;
collected.command_encode = clock.lap();
stage_source_submissions(self.gpu.queue(), &source_work)?;
collected.source_acquire = clock.lap();
graph_work.flush_uniforms(self.gpu.queue());
let command_buffer = command_encoder.finish();
collected.command_encode = collected.command_encode.saturating_add(clock.lap());
let submitted = self.submit_measured([command_buffer], true)?;
self.submission_canaries
.track(&self.gpu, canary_buffer, canary);
drop(pack);
collected.queue_lock_wait = submitted.timings.queue_lock_wait;
collected.queue_submit = submitted.timings.queue_submit;
collected.lease_retire = submitted.lease_retire;
let _ = clock.lap();
for work in source_work {
work.complete(self, false);
}
drop(self.complete_graph_work(graph_work, false));
crate::diagnostics::record_frame_submission(&self.diagnostics);
collected.complete = clock.lap();
*timings = timings.with_submission(collected);
Ok(())
}
fn render_graph_yuv420p_measured(
&self,
graph: &RenderGraph<'_>,
sources: &[&PendingSourceWork],
encoding: YuvEncoding,
timings: Option<&mut FrameSubmitTimings>,
) -> Result<Yuv420pReadback, RenderError> {
let measure = timings.is_some();
let mut clock = StageClock::start(measure);
let mut collected = FrameSubmitTimings::default();
let frame = self.frame_lock.hold();
let mut command_encoder =
self.command_encoder("alpha2 render and YUV420P readback encoder");
let source_work = begin_source_work(&frame, sources);
for work in &source_work {
work.encode(&mut command_encoder);
}
let graph_work = self
.encode_graph(graph, &mut command_encoder)
.map_err(GraphError::into_render_error)?;
let readback_work =
self.encode_yuv420p_readback(graph_work.texture(), encoding, &mut command_encoder)?;
collected.command_encode = clock.lap();
stage_source_submissions(self.gpu.queue(), &source_work)?;
collected.source_acquire = clock.lap();
graph_work.flush_uniforms(self.gpu.queue());
readback_work.flush_uniforms(self.gpu.queue());
let command_buffer = command_encoder.finish();
collected.command_encode = collected.command_encode.saturating_add(clock.lap());
let submitted = self.submit_measured([command_buffer], measure)?;
collected.queue_lock_wait = submitted.timings.queue_lock_wait;
collected.queue_submit = submitted.timings.queue_submit;
collected.lease_retire = submitted.lease_retire;
let _ = clock.lap();
let readback = self.complete_yuv420p_readback(
readback_work,
#[cfg(not(target_arch = "wasm32"))]
submitted.index,
false,
);
for work in source_work {
work.complete(self, false);
}
drop(self.complete_graph_work(graph_work, false));
crate::diagnostics::record_frame_submission(&self.diagnostics);
collected.complete = clock.lap();
if let Some(timings) = timings {
*timings = timings.with_submission(collected);
}
Ok(readback)
}
}
pub(crate) fn stage_source_submissions(
queue: &wgpu::Queue,
source_work: &[SourceWorkTicket<'_>],
) -> Result<(), RenderError> {
debug_assert!(
source_work
.iter()
.all(SourceWorkTicket::has_no_staged_signals),
"a source lease carried a staged native signal into the next frame"
);
for work in source_work {
work.flush_uniforms(queue);
if !work.prepare_submission() {
return Err(RenderError::ExternalTextureSync(None));
}
}
for work in source_work {
work.flush_submission()?;
}
Ok(())
}
pub(crate) fn begin_source_work<'a>(
frame: &crate::renderer::FrameGuard,
sources: &[&'a PendingSourceWork],
) -> Vec<SourceWorkTicket<'a>> {
sources
.iter()
.filter_map(|pending| pending.begin(frame))
.collect()
}