1use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9
10#[derive(Clone, Default)]
12pub struct Renderer {
13 #[cfg(not(target_arch = "wasm32"))]
14 state: Option<eframe::egui_wgpu::RenderState>,
15 ok: Arc<AtomicBool>,
17 reason: Option<String>,
19}
20
21impl std::fmt::Debug for Renderer {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("Renderer").field("name", &self.name()).field("adapter", &self.adapter()).field("reason", &self.reason).finish()
24 }
25}
26
27impl Renderer {
28 pub fn none() -> Self {
30 Self::default()
31 }
32
33 pub fn wgpu() -> Self {
36 #[cfg(not(target_arch = "wasm32"))]
37 {
38 let made = std::panic::catch_unwind(gpu::create).unwrap_or_else(|_| Err("creating the wgpu device panicked".into()));
39 match made {
40 Ok(state) => Renderer { state: Some(state), ..Self::default() },
41 Err(e) => {
42 log::info!("headless renderer: no GPU ({e}); screenshots are grey");
43 Renderer { reason: Some(e), ..Self::default() }
44 }
45 }
46 }
47 #[cfg(target_arch = "wasm32")]
48 Renderer { reason: Some("no headless GPU renderer on the web".into()), ..Self::default() }
49 }
50
51 pub fn is_gpu(&self) -> bool {
53 #[cfg(not(target_arch = "wasm32"))]
54 return self.state.is_some();
55 #[cfg(target_arch = "wasm32")]
56 false
57 }
58
59 pub fn name(&self) -> &'static str {
61 if self.is_gpu() { "wgpu" } else { "none" }
62 }
63
64 pub fn reason(&self) -> Option<&str> {
66 self.reason.as_deref()
67 }
68
69 pub fn adapter(&self) -> Option<String> {
71 #[cfg(not(target_arch = "wasm32"))]
72 return self.state.as_ref().map(|s| {
73 let i = s.adapter.get_info();
74 format!("{} ({:?})", i.name, i.backend)
75 });
76 #[cfg(target_arch = "wasm32")]
77 None
78 }
79
80 pub(crate) fn adapter_parts(&self) -> Option<(String, String)> {
82 #[cfg(not(target_arch = "wasm32"))]
83 return self.state.as_ref().map(|s| {
84 let i = s.adapter.get_info();
85 (i.name, format!("{:?}", i.backend))
86 });
87 #[cfg(target_arch = "wasm32")]
88 None
89 }
90
91 pub(crate) fn last_used(&self) -> &'static str {
94 if self.is_gpu() && self.ok.load(Ordering::Relaxed) { "wgpu" } else { "none" }
95 }
96
97 pub(crate) fn offscreen(&self) -> Option<ashlar_ui::devtools::Offscreen> {
99 #[cfg(not(target_arch = "wasm32"))]
100 if let Some(state) = &self.state {
101 let inner = crate::offscreen::renderer(state.clone());
102 let ok = Arc::clone(&self.ok);
103 return Some(Arc::new(move |f: &ashlar_ui::devtools::OffscreenFrame<'_>| {
104 let r = inner(f);
105 ok.store(r.is_ok(), Ordering::Relaxed);
106 r
107 }));
108 }
109 None
110 }
111
112 pub(crate) fn textures(&self, delta: &egui::TexturesDelta) {
114 #[cfg(not(target_arch = "wasm32"))]
115 if let Some(state) = &self.state {
116 let applied = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| crate::offscreen::apply_textures(state, delta)));
117 if applied.is_err() {
118 log::warn!("headless renderer: uploading textures panicked");
119 self.ok.store(false, Ordering::Relaxed);
120 }
121 }
122 #[cfg(target_arch = "wasm32")]
123 let _ = delta;
124 }
125}
126
127#[cfg(not(target_arch = "wasm32"))]
128mod gpu {
129 use std::future::Future;
130 use std::sync::Arc;
131 use std::task::{Context, Poll, Wake, Waker};
132
133 use eframe::egui_wgpu::{RenderState, RendererOptions, WgpuConfiguration, WgpuSetup, WgpuSetupCreateNew, wgpu};
134
135 pub(super) fn create() -> Result<RenderState, String> {
138 let mut setup = WgpuSetupCreateNew::without_display_handle();
139 setup.instance_descriptor.backends.remove(wgpu::Backends::BROWSER_WEBGPU);
140 setup.native_adapter_selector = Some(Arc::new(|adapters, _surface| {
141 let mut adapters: Vec<&wgpu::Adapter> = adapters.iter().filter(|a| a.get_info().backend != wgpu::Backend::Noop).collect();
142 adapters.sort_by_key(|a| match a.get_info().device_type {
143 wgpu::DeviceType::Cpu => 0,
144 wgpu::DeviceType::DiscreteGpu => 1,
145 _ => 2,
146 });
147 adapters.first().map(|a| (*a).clone()).ok_or_else(|| "no wgpu adapter found".to_owned())
148 }));
149 let setup = WgpuSetup::CreateNew(setup);
150 let instance = block_on(setup.new_instance());
151 let config = WgpuConfiguration { wgpu_setup: setup, ..Default::default() };
152 block_on(RenderState::create(&config, &instance, None, RendererOptions::PREDICTABLE)).map_err(|e| e.to_string())
153 }
154
155 struct Unpark(std::thread::Thread);
156
157 impl Wake for Unpark {
158 fn wake(self: Arc<Self>) {
159 self.0.unpark();
160 }
161 }
162
163 fn block_on<F: Future>(f: F) -> F::Output {
166 let waker = Waker::from(Arc::new(Unpark(std::thread::current())));
167 let mut cx = Context::from_waker(&waker);
168 let mut f = std::pin::pin!(f);
169 loop {
170 if let Poll::Ready(v) = f.as_mut().poll(&mut cx) {
171 return v;
172 }
173 std::thread::park();
174 }
175 }
176}