use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone, Default)]
pub struct Renderer {
#[cfg(not(target_arch = "wasm32"))]
state: Option<eframe::egui_wgpu::RenderState>,
ok: Arc<AtomicBool>,
reason: Option<String>,
}
impl std::fmt::Debug for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Renderer").field("name", &self.name()).field("adapter", &self.adapter()).field("reason", &self.reason).finish()
}
}
impl Renderer {
pub fn none() -> Self {
Self::default()
}
pub fn wgpu() -> Self {
#[cfg(not(target_arch = "wasm32"))]
{
let made = std::panic::catch_unwind(gpu::create).unwrap_or_else(|_| Err("creating the wgpu device panicked".into()));
match made {
Ok(state) => Renderer { state: Some(state), ..Self::default() },
Err(e) => {
log::info!("headless renderer: no GPU ({e}); screenshots are grey");
Renderer { reason: Some(e), ..Self::default() }
}
}
}
#[cfg(target_arch = "wasm32")]
Renderer { reason: Some("no headless GPU renderer on the web".into()), ..Self::default() }
}
pub fn is_gpu(&self) -> bool {
#[cfg(not(target_arch = "wasm32"))]
return self.state.is_some();
#[cfg(target_arch = "wasm32")]
false
}
pub fn name(&self) -> &'static str {
if self.is_gpu() { "wgpu" } else { "none" }
}
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
pub fn adapter(&self) -> Option<String> {
#[cfg(not(target_arch = "wasm32"))]
return self.state.as_ref().map(|s| {
let i = s.adapter.get_info();
format!("{} ({:?})", i.name, i.backend)
});
#[cfg(target_arch = "wasm32")]
None
}
pub(crate) fn adapter_parts(&self) -> Option<(String, String)> {
#[cfg(not(target_arch = "wasm32"))]
return self.state.as_ref().map(|s| {
let i = s.adapter.get_info();
(i.name, format!("{:?}", i.backend))
});
#[cfg(target_arch = "wasm32")]
None
}
pub(crate) fn last_used(&self) -> &'static str {
if self.is_gpu() && self.ok.load(Ordering::Relaxed) { "wgpu" } else { "none" }
}
pub(crate) fn offscreen(&self) -> Option<ashlar_ui::devtools::Offscreen> {
#[cfg(not(target_arch = "wasm32"))]
if let Some(state) = &self.state {
let inner = crate::offscreen::renderer(state.clone());
let ok = Arc::clone(&self.ok);
return Some(Arc::new(move |f: &ashlar_ui::devtools::OffscreenFrame<'_>| {
let r = inner(f);
ok.store(r.is_ok(), Ordering::Relaxed);
r
}));
}
None
}
pub(crate) fn textures(&self, delta: &egui::TexturesDelta) {
#[cfg(not(target_arch = "wasm32"))]
if let Some(state) = &self.state {
let applied = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| crate::offscreen::apply_textures(state, delta)));
if applied.is_err() {
log::warn!("headless renderer: uploading textures panicked");
self.ok.store(false, Ordering::Relaxed);
}
}
#[cfg(target_arch = "wasm32")]
let _ = delta;
}
}
#[cfg(not(target_arch = "wasm32"))]
mod gpu {
use std::future::Future;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use eframe::egui_wgpu::{RenderState, RendererOptions, WgpuConfiguration, WgpuSetup, WgpuSetupCreateNew, wgpu};
pub(super) fn create() -> Result<RenderState, String> {
let mut setup = WgpuSetupCreateNew::without_display_handle();
setup.instance_descriptor.backends.remove(wgpu::Backends::BROWSER_WEBGPU);
setup.native_adapter_selector = Some(Arc::new(|adapters, _surface| {
let mut adapters: Vec<&wgpu::Adapter> = adapters.iter().filter(|a| a.get_info().backend != wgpu::Backend::Noop).collect();
adapters.sort_by_key(|a| match a.get_info().device_type {
wgpu::DeviceType::Cpu => 0,
wgpu::DeviceType::DiscreteGpu => 1,
_ => 2,
});
adapters.first().map(|a| (*a).clone()).ok_or_else(|| "no wgpu adapter found".to_owned())
}));
let setup = WgpuSetup::CreateNew(setup);
let instance = block_on(setup.new_instance());
let config = WgpuConfiguration { wgpu_setup: setup, ..Default::default() };
block_on(RenderState::create(&config, &instance, None, RendererOptions::PREDICTABLE)).map_err(|e| e.to_string())
}
struct Unpark(std::thread::Thread);
impl Wake for Unpark {
fn wake(self: Arc<Self>) {
self.0.unpark();
}
}
fn block_on<F: Future>(f: F) -> F::Output {
let waker = Waker::from(Arc::new(Unpark(std::thread::current())));
let mut cx = Context::from_waker(&waker);
let mut f = std::pin::pin!(f);
loop {
if let Poll::Ready(v) = f.as_mut().poll(&mut cx) {
return v;
}
std::thread::park();
}
}
}