use renderer_core::{RenderBackend, RendererError};
use crate::config::RendererBackend;
use super::{RenderChannels, RendererHost, RendererRequest, RendererStart, SurfaceWindow};
#[cfg(any(feature = "hardware", feature = "software"))]
use crate::runner::font_config::SystemFonts;
#[cfg(any(feature = "hardware", feature = "software"))]
use crate::runner::frame_thread::spawn_render_thread;
#[cfg(not(feature = "hardware"))]
const NO_HARDWARE: &str = "telar was built without its `hardware` feature, so there is no wgpu renderer to \
build: enable the feature, ask for the software backend, or install a renderer of \
your own with `run_with_platform_and_renderer`";
#[cfg(not(feature = "software"))]
const NO_SOFTWARE: &str = "telar was built without its `software` feature, so there is no rasteriser to \
build: enable the feature, ask for the hardware backend, or install a renderer of \
your own with `run_with_platform_and_renderer`";
pub(crate) struct BuiltinHost<W: SurfaceWindow> {
channels: Option<RenderChannels>,
#[cfg(feature = "hardware")]
hw_join: Option<std::thread::JoinHandle<renderer_hardware::HardwareRenderer<W>>>,
#[cfg(feature = "software")]
sw_join: Option<std::thread::JoinHandle<renderer_software::SoftwareRenderer<W, W>>>,
#[cfg(feature = "hardware")]
pending: Option<
std::thread::JoinHandle<Result<renderer_hardware::HardwareRenderer<W>, RendererError>>,
>,
#[cfg(feature = "hardware")]
warm: Option<renderer_hardware::HardwareRenderer<W>>,
_window: std::marker::PhantomData<W>,
}
impl<W: SurfaceWindow> BuiltinHost<W> {
pub(crate) fn new() -> Self {
Self {
channels: None,
#[cfg(feature = "hardware")]
hw_join: None,
#[cfg(feature = "software")]
sw_join: None,
#[cfg(feature = "hardware")]
pending: None,
#[cfg(feature = "hardware")]
warm: None,
_window: std::marker::PhantomData,
}
}
fn join_thread(&mut self, keep_warm: bool) {
self.channels = None;
#[cfg(feature = "hardware")]
if let Some(join) = self.hw_join.take() {
match join.join() {
Ok(hardware) if keep_warm => self.warm = Some(hardware),
Ok(_) => {}
Err(_) => tracing::warn!("the render thread panicked, so its renderer is lost"),
}
}
#[cfg(feature = "software")]
if let Some(join) = self.sw_join.take() {
let _ = join.join();
}
#[cfg(target_os = "linux")]
if !keep_warm {
unsafe {
libc::malloc_trim(0);
}
}
}
#[cfg(feature = "hardware")]
fn start_hardware(&mut self, window: &W, req: &RendererRequest<'_>) -> RendererStart {
if let Some(mut warm) = self.warm.take() {
match warm.rebind_surface(std::sync::Arc::new(window.clone())) {
Ok(()) => return self.run_hardware(warm),
Err(e) => tracing::warn!("rebinding the suspended renderer failed ({e})"),
}
}
self.spawn_hardware_build(window, req);
RendererStart::Building
}
#[cfg(not(feature = "hardware"))]
fn start_hardware(&mut self, window: &W, req: &RendererRequest<'_>) -> RendererStart {
if matches!(req.backend, RendererBackend::Auto) {
return self.start_software(window, req);
}
RendererStart::Failed(RendererError::Backend(NO_HARDWARE.to_string()))
}
#[cfg(feature = "hardware")]
fn run_hardware(&mut self, renderer: renderer_hardware::HardwareRenderer<W>) -> RendererStart {
let (tx, ret_rx, join) = spawn_render_thread(renderer);
self.channels = Some(RenderChannels { tx, ret_rx });
self.hw_join = Some(join);
RendererStart::Started {
keepalive: true,
label: "hardware (wgpu)",
}
}
#[cfg(feature = "hardware")]
fn spawn_hardware_build(&mut self, window: &W, req: &RendererRequest<'_>) {
let wake = window.clone();
let window = window.clone();
let cache_path = crate::runner::font_config::hardware_cache_path(req.app_name, req.paths);
let font_paths = req.font_paths.to_vec();
let font_data = req.font_data.to_vec();
let system_fonts = SystemFonts::from_provider(req.paths);
let android = cfg!(target_os = "android");
let transparent = req.transparent;
self.pending = Some(std::thread::spawn(move || {
let font_config = crate::runner::font_config::build_hardware_font_config(
font_paths,
font_data,
&system_fonts,
);
let built = renderer_hardware::HardwareRenderer::new(
window,
cache_path.as_deref(),
android,
font_config,
renderer_hardware::HardwareRendererConfig { transparent },
);
wake.request_redraw();
built
}));
}
#[cfg(feature = "hardware")]
fn poll_hardware_build(&mut self) -> Option<RendererStart> {
let handle = self.pending.take()?;
if !handle.is_finished() {
self.pending = Some(handle);
return None;
}
let built = handle.join().unwrap_or_else(|_| {
Err(RendererError::Backend(
"renderer build thread panicked".to_string(),
))
});
self.join_thread(false);
Some(match built {
Ok(renderer) => self.run_hardware(renderer),
Err(e) => RendererStart::Failed(e),
})
}
#[cfg(not(feature = "hardware"))]
fn poll_hardware_build(&mut self) -> Option<RendererStart> {
None
}
#[cfg(feature = "software")]
fn start_software(&mut self, window: &W, req: &RendererRequest<'_>) -> RendererStart {
let config = crate::runner::font_config::build_software_renderer_config(
req.font_paths.to_vec(),
req.font_data.to_vec(),
&SystemFonts::from_provider(req.paths),
req.transparent,
);
match renderer_software::SoftwareRenderer::new(window.clone(), window.clone(), config) {
Ok(renderer) => {
let (tx, ret_rx, join) = spawn_render_thread(renderer);
self.channels = Some(RenderChannels { tx, ret_rx });
self.sw_join = Some(join);
RendererStart::Started {
keepalive: false,
label: "software",
}
}
Err(e) => RendererStart::Failed(e),
}
}
#[cfg(not(feature = "software"))]
fn start_software(&mut self, _window: &W, _req: &RendererRequest<'_>) -> RendererStart {
RendererStart::Failed(RendererError::Backend(NO_SOFTWARE.to_string()))
}
#[cfg(feature = "software")]
fn build_headless(
&mut self,
window: &W,
req: &RendererRequest<'_>,
) -> Option<Box<dyn RenderBackend>> {
let config = crate::runner::font_config::build_software_renderer_config(
req.font_paths.to_vec(),
req.font_data.to_vec(),
&SystemFonts::from_provider(req.paths),
req.transparent,
);
Some(Box::new(
renderer_software::SoftwareRenderer::<W, W>::new_headless(
window.width(),
window.height(),
config,
),
))
}
#[cfg(not(feature = "software"))]
fn build_headless(
&mut self,
_window: &W,
_req: &RendererRequest<'_>,
) -> Option<Box<dyn RenderBackend>> {
tracing::error!("{NO_SOFTWARE}");
None
}
}
impl<W: SurfaceWindow> RendererHost<W> for BuiltinHost<W> {
fn start(&mut self, window: &W, req: &RendererRequest<'_>) -> RendererStart {
match req.backend {
RendererBackend::Software => self.start_software(window, req),
RendererBackend::Hardware | RendererBackend::Auto => self.start_hardware(window, req),
}
}
fn poll(&mut self) -> Option<RendererStart> {
self.poll_hardware_build()
}
fn is_building(&self) -> bool {
#[cfg(feature = "hardware")]
return self.pending.is_some();
#[cfg(not(feature = "hardware"))]
false
}
fn channels(&self) -> Option<&RenderChannels> {
self.channels.as_ref()
}
fn suspend(&mut self) {
self.join_thread(true);
}
fn retire(&mut self) {
self.join_thread(false);
}
fn build_offscreen(
&mut self,
window: &W,
req: &RendererRequest<'_>,
) -> Option<Box<dyn RenderBackend>> {
self.build_headless(window, req)
}
}