use renderer_core::RenderBackend;
use super::FRAME_BUDGET;
pub(super) struct FrameMsg {
pub(super) width: u32,
pub(super) height: u32,
pub(super) scale_factor: f32,
pub(super) generation: u64,
pub(super) commands: Vec<renderer_core::DrawCommand>,
pub(super) clear: Option<renderer_core::Color>,
pub(super) timestamp: web_time::Instant,
}
pub(super) fn spawn_render_thread<R>(
renderer: R,
) -> (
std::sync::mpsc::SyncSender<FrameMsg>,
std::sync::mpsc::Receiver<Vec<renderer_core::DrawCommand>>,
std::thread::JoinHandle<R>,
)
where
R: RenderBackend + Send + 'static,
{
let (tx, rx) = std::sync::mpsc::sync_channel::<FrameMsg>(1);
let (ret_tx, ret_rx) = std::sync::mpsc::channel::<Vec<renderer_core::DrawCommand>>();
let join = std::thread::Builder::new()
.name("telar-render".to_string())
.spawn(move || {
let mut renderer = renderer;
renderer.bind_to_render_thread();
let mut current_width = 0u32;
let mut current_height = 0u32;
let mut scale_scratch = renderer_core::ScaleScratch::new();
let scales_itself = renderer.applies_scale_factor();
#[cfg(all(feature = "android-bare", target_os = "android"))]
let hint_session = platform_android::AdpfSession::new(16_666_667, None);
let idle_sweep_after = renderer.idle_sweep_after();
loop {
let msg = match idle_sweep_after {
Some(after) => match rx.recv_timeout(after) {
Ok(msg) => msg,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
renderer.sweep_idle_caches();
match rx.recv() {
Ok(msg) => msg,
Err(_) => break,
}
}
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
},
None => match rx.recv() {
Ok(msg) => msg,
Err(_) => break,
},
};
let size_changed = msg.width != current_width || msg.height != current_height;
if !size_changed && msg.timestamp.elapsed() > FRAME_BUDGET {
let _ = ret_tx.send(msg.commands);
continue;
}
#[cfg(all(feature = "android-bare", target_os = "android"))]
let frame_start = web_time::Instant::now();
let began = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
renderer.begin_frame(msg.width, msg.height, msg.scale_factor, msg.generation)
}));
if !matches!(began, Ok(Ok(()))) {
let _ = ret_tx.send(msg.commands);
continue;
}
current_width = msg.width;
current_height = msg.height;
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let commands: &[renderer_core::DrawCommand] =
if scales_itself || msg.scale_factor == 1.0 {
&msg.commands
} else {
scale_scratch.scale_into(&msg.commands, msg.scale_factor)
};
renderer.render_frame(commands, msg.clear)
}));
#[cfg(all(feature = "android-bare", target_os = "android"))]
if let Some(session) = &hint_session {
let duration_ns = frame_start.elapsed().as_nanos() as i64;
session.report(duration_ns);
}
let _ = ret_tx.send(msg.commands);
}
renderer
})
.expect("failed to spawn render thread");
(tx, ret_rx, join)
}
#[cfg(test)]
#[path = "frame_thread_test.rs"]
mod tests;