use crate::app::state::App;
use crate::ecs::{PipelinedFrames, StepResult};
use crate::gfx::backend::RenderBackend;
use crate::gfx::feedback::FrameFeedback;
use crate::gfx::graphics_system::frame_policy::FramePolicy;
use crate::gfx::graphics_system::submit::submit;
use crate::gfx::input::InputPacket;
use crate::gfx::snapshot::RenderSnapshot;
use crate::shutdown::ShutdownToken;
use std::sync::mpsc::{Receiver, Sender};
pub(crate) fn run_pipelined(mut app: App, screenshot: Option<&str>) {
let Some(mut backend) = crate::ecs::take_render_backend(app.world_mut()) else {
crate::app::runloop::run_loop(&mut app, false, |_| {});
return;
};
let shutdown = app.shutdown_token();
let (snapshot_tx, snapshot_rx) = std::sync::mpsc::sync_channel::<RenderSnapshot>(0);
let (feedback_tx, feedback_rx) = std::sync::mpsc::channel::<FrameFeedback>();
app.world_mut()
.insert_resource(PipelinedFrames(Some(crate::ecs::PipelineChannels {
snapshot_tx,
feedback_rx,
})));
let sim_shutdown = shutdown.clone();
let sim = std::thread::Builder::new()
.name("sim".to_string())
.spawn(move || {
loop {
if sim_shutdown.is_cancelled() {
return;
}
match app.world_step() {
StepResult::Continue => {}
StepResult::Stop | StepResult::Done => return,
}
}
})
.expect("failed to spawn the sim thread");
let rendered = render_half(backend.as_mut(), snapshot_rx, &feedback_tx, &shutdown);
tracing::info!("pipeline: {} frame(s) submitted", rendered.submitted);
shutdown.cancel();
drop(feedback_tx);
if let Err(payload) = sim.join() {
std::panic::resume_unwind(payload);
}
if !rendered.device_lost {
backend.wait_idle();
if let Some(path) = screenshot {
match backend.screenshot(path) {
Ok(saved) => tracing::info!("screenshot saved: {}", saved),
Err(e) => tracing::warn!("screenshot failed: {}", e),
}
}
}
}
fn render_half(
backend: &mut dyn RenderBackend,
snapshot_rx: Receiver<RenderSnapshot>,
feedback_tx: &Sender<FrameFeedback>,
shutdown: &ShutdownToken,
) -> RenderHalfOutcome {
let mut policy = FramePolicy::default();
let mut submitted = 0u64;
loop {
if shutdown.is_cancelled() {
return RenderHalfOutcome::stopped(submitted);
}
let mut snapshot = match wait_for_snapshot(&snapshot_rx) {
Ok(snapshot) => snapshot,
#[cfg(target_os = "macos")]
Err(SnapshotWaitEnd::Empty) => continue,
Err(SnapshotWaitEnd::Closed) => return RenderHalfOutcome::stopped(submitted),
};
let mut outcome = submit(&mut policy, &mut snapshot, backend);
submitted += 1;
outcome.replay.memory_pressure |= outcome.memory_pressure;
let stop = outcome.result != StepResult::Continue;
let feedback = FrameFeedback {
input: InputPacket::sample(backend),
render_stats: outcome.render_stats.unwrap_or_default(),
replay: outcome.replay,
recycled: snapshot,
stop,
};
let _ = feedback_tx.send(feedback);
if stop {
return RenderHalfOutcome {
device_lost: outcome.device_lost,
submitted,
};
}
}
}
struct RenderHalfOutcome {
device_lost: bool,
submitted: u64,
}
impl RenderHalfOutcome {
fn stopped(submitted: u64) -> Self {
Self {
device_lost: false,
submitted,
}
}
}
enum SnapshotWaitEnd {
#[cfg(target_os = "macos")]
Empty,
Closed,
}
#[cfg(target_os = "macos")]
fn wait_for_snapshot(rx: &Receiver<RenderSnapshot>) -> Result<RenderSnapshot, SnapshotWaitEnd> {
use std::sync::mpsc::RecvTimeoutError;
crate::app::runloop::drain_cocoa_events();
match rx.recv_timeout(std::time::Duration::from_millis(2)) {
Ok(snapshot) => Ok(snapshot),
Err(RecvTimeoutError::Timeout) => Err(SnapshotWaitEnd::Empty),
Err(RecvTimeoutError::Disconnected) => Err(SnapshotWaitEnd::Closed),
}
}
#[cfg(not(target_os = "macos"))]
fn wait_for_snapshot(rx: &Receiver<RenderSnapshot>) -> Result<RenderSnapshot, SnapshotWaitEnd> {
match rx.recv() {
Ok(snapshot) => Ok(snapshot),
Err(_) => Err(SnapshotWaitEnd::Closed),
}
}