1use limnus_app::prelude::{App, Plugin};
6use limnus_default_stages::RenderFirst;
7use limnus_local_resource::prelude::LocalResource;
8use limnus_screen::WindowMessage;
9use limnus_system_params::{LoReM, Msg};
10use std::default::Default;
11use std::sync::Arc;
12use tracing::{debug, info, trace, warn};
13use wgpu::{
14 Adapter, BackendOptions, Backends, Device, DeviceDescriptor, Features, Instance,
15 InstanceDescriptor, InstanceFlags, Limits, MemoryBudgetThresholds, MemoryHints,
16 PowerPreference, Queue, RequestAdapterOptions, Surface, SurfaceConfiguration, SurfaceError,
17 Trace,
18};
19use winit::dpi::PhysicalSize;
20use winit::window::Window;
21
22#[derive(Debug, LocalResource)]
23pub struct WgpuWindow {
24 surface: Arc<Surface<'static>>,
25 device: Arc<Device>,
26 queue: Arc<Queue>,
27
28 config: SurfaceConfiguration,
29}
30
31impl WgpuWindow {
32 #[must_use]
33 pub const fn queue(&self) -> &Arc<Queue> {
34 &self.queue
35 }
36}
37
38pub struct ReceiveAnnoyingAsync {
39 pub device_info: Option<BasicDeviceInfo>,
40}
41
42#[derive(Debug, LocalResource)]
43pub struct BasicDeviceInfo {
44 pub adapter: Adapter,
45 pub device: Arc<Device>,
46 pub queue: Arc<Queue>,
47 pub surface: Arc<Surface<'static>>,
48 pub physical_surface_size: PhysicalSize<u32>,
49}
50
51async fn try_pref(
52 instance: &Instance,
53 surface: Option<&Surface<'_>>,
54 pref: PowerPreference,
55) -> Option<wgpu::Adapter> {
56 debug!(?pref, "trying to find adapter with power preference");
57 let res = instance
58 .request_adapter(&RequestAdapterOptions {
59 power_preference: pref,
60 compatible_surface: surface,
61 force_fallback_adapter: false,
62 })
63 .await;
64
65 match res {
66 Ok(adapter) => Some(adapter),
67 Err(err) => {
68 warn!("request_adapter({pref:?}) failed: {err}");
69 None
70 }
71 }
72}
73
74pub async fn pick_best_adapter(
77 instance: &Instance,
78 surface: Option<&Surface<'_>>,
79) -> Result<Adapter, InitError> {
80 for &pref in &[
81 PowerPreference::HighPerformance,
82 PowerPreference::LowPower,
83 PowerPreference::None,
84 ] {
85 if let Some(a) = try_pref(instance, surface, pref).await {
86 return Ok(a);
87 }
88 }
89 Err(InitError::NoAdapter)
90}
91
92#[derive(Debug)]
93pub enum WgpuInitError {
94 NoAdapter,
95 NoDevice,
96}
97
98#[derive(Debug, Clone, Copy)]
99pub enum LimitsTier {
100 ModernDefault,
101 Downlevel,
102 WebGL2,
103}
104
105#[derive(Debug)]
106pub struct DeviceSelection {
107 pub device: Device,
108 pub queue: Queue,
109 pub tier: LimitsTier,
110 pub applied_limits: Limits,
111}
112
113async fn try_with(adapter: &Adapter, base: Limits, tier: LimitsTier) -> Option<DeviceSelection> {
114 let applied = base.using_resolution(adapter.limits());
118
119 info!(?tier, "trying to find device with tier");
120
121 adapter
122 .request_device(&DeviceDescriptor {
123 label: Some("wgpu device"),
124 required_features: Features::empty(), required_limits: applied.clone(),
126 experimental_features: Default::default(),
127 memory_hints: MemoryHints::default(),
128 trace: Trace::default(),
129 })
130 .await
131 .ok()
132 .map(|(device, queue)| DeviceSelection {
133 device,
134 queue,
135 tier,
136 applied_limits: applied,
137 })
138}
139
140pub async fn request_device_smart(adapter: &Adapter) -> Result<DeviceSelection, InitError> {
166 if let Some(sel) = try_with(adapter, Limits::default(), LimitsTier::ModernDefault).await {
167 return Ok(sel);
168 }
169 if let Some(sel) = try_with(adapter, Limits::downlevel_defaults(), LimitsTier::Downlevel).await
170 {
171 return Ok(sel);
172 }
173 if let Some(sel) = try_with(
174 adapter,
175 Limits::downlevel_webgl2_defaults(),
176 LimitsTier::WebGL2,
177 )
178 .await
179 {
180 return Ok(sel);
181 }
182
183 Err(InitError::NoDevice)
184}
185
186#[derive(Debug)]
187pub enum InitError {
188 CreateSurface(wgpu::CreateSurfaceError),
189 NoAdapter,
190 NoDevice,
191 RequestDevice(wgpu::RequestDeviceError),
192}
193
194pub async fn annoying_async_device_creation(
199 window: Arc<Window>,
200) -> Result<BasicDeviceInfo, InitError> {
201 let instance = Instance::new(&InstanceDescriptor {
202 backends: Backends::default(),
203 flags: InstanceFlags::default(),
204 memory_budget_thresholds: MemoryBudgetThresholds::default(),
205 backend_options: BackendOptions::default(),
206 });
207 debug!(?instance, "found instance");
208
209 let surface = instance
210 .create_surface(Arc::clone(&window))
211 .map_err(InitError::CreateSurface)?;
212 debug!(?surface, "found surface");
213
214 let adapter = pick_best_adapter(&instance, Some(&surface)).await?;
215 debug!(?adapter, "found adapter");
216
217 let device_selection = request_device_smart(&adapter).await?;
218 debug!(?device_selection, "found device selection");
219
220 let inner_size = window.inner_size();
221 debug!(?inner_size, "window size detected");
222
223 let device_info = BasicDeviceInfo {
224 adapter,
225 device: device_selection.device.into(),
226 queue: device_selection.queue.into(),
227 surface: surface.into(),
228 physical_surface_size: inner_size,
229 };
230
231 debug!(?device_info, "final device info selection");
232
233 Ok(device_info)
234}
235
236fn tick(mut wgpu_window: LoReM<WgpuWindow>, window_messages: Msg<WindowMessage>) {
237 for msg in window_messages.iter_previous() {
238 if let WindowMessage::Resized(size) = msg {
239 debug!("resized to {:?}", size);
240 wgpu_window.resize((size.x, size.y));
241 }
242 }
243}
244
245pub struct WgpuWindowPlugin;
246impl Plugin for WgpuWindowPlugin {
247 fn build(&self, _app: &mut App) {}
248
249 fn post_initialization(&self, app: &mut App) {
250 app.insert_local_resource(WgpuWindow::new(
251 app.local_resources().fetch::<BasicDeviceInfo>(),
252 ));
253 app.add_system(RenderFirst, tick);
254 info!("wgpu window plugin is done");
255 }
256}
257
258impl WgpuWindow {
259 #[must_use]
260 pub fn new(info: &BasicDeviceInfo) -> Self {
261 let config = Self::configure_render_surface(info);
262
263 Self {
264 device: Arc::clone(&info.device),
265 config,
266 queue: Arc::clone(&info.queue),
267 surface: Arc::clone(&info.surface),
268 }
269 }
270
271 #[must_use]
272 pub const fn device(&self) -> &Arc<Device> {
273 &self.device
274 }
275
276 fn configure_render_surface(info: &BasicDeviceInfo) -> SurfaceConfiguration {
277 let surface_caps = info.surface.get_capabilities(&info.adapter);
278 let surface_format = surface_caps
279 .formats
280 .iter()
281 .copied()
282 .find(wgpu::TextureFormat::is_srgb)
283 .unwrap_or(surface_caps.formats[0]);
284
285 let present_mode = wgpu::PresentMode::Fifo; let config = wgpu::SurfaceConfiguration {
287 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
288 format: surface_format,
289 width: info.physical_surface_size.width,
290 height: info.physical_surface_size.height,
291 present_mode,
292 alpha_mode: surface_caps.alpha_modes[0],
293 desired_maximum_frame_latency: 2,
294 view_formats: vec![],
295 };
296
297 info.surface.configure(&info.device, &config);
298
299 let alpha_mode = surface_caps.alpha_modes[0];
300 trace!(
301 "found surface format {:?} {:?} {:?}",
302 surface_format, present_mode, alpha_mode
303 );
304
305 config
306 }
307
308 #[must_use]
309 pub const fn texture_format(&self) -> wgpu::TextureFormat {
310 self.config.format
311 }
312
313 pub fn configure_surface(&mut self) {
314 debug!("configured surface! {:?}", self.config);
315 self.surface.configure(&self.device, &self.config);
316 }
317
318 pub fn resize(&mut self, new_size: (u16, u16)) {
319 let width = new_size.0 as usize;
320 let height = new_size.1 as usize;
321
322 if width == 0 || height == 0 {
323 return;
324 }
325
326 self.config.width = width as u32;
327 self.config.height = height as u32;
328 self.configure_surface();
329 }
330
331 pub fn render(
332 &self,
333 mut render_fn: impl FnMut(&mut wgpu::CommandEncoder, &wgpu::TextureView),
334 ) -> Result<(), SurfaceError> {
335 let surface_texture = match self.surface.get_current_texture() {
336 Ok(frame) => frame,
337 Err(wgpu::SurfaceError::Outdated) => {
338 return Ok(()); }
343 Err(wgpu::SurfaceError::Lost) => {
344 return Ok(()); }
347 Err(wgpu::SurfaceError::OutOfMemory) => {
348 eprintln!("GPU out of memory, exiting");
350 std::process::exit(1);
351 }
352 Err(e) => {
353 eprintln!("wgpu error getting next swap chain texture: {e:?}");
355 return Err(SurfaceError::Other);
356 }
357 };
358 let texture_view = surface_texture
359 .texture
360 .create_view(&wgpu::TextureViewDescriptor::default());
361
362 let mut encoder = self
363 .device
364 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
365 label: Some("Render Encoder"),
366 });
367
368 render_fn(&mut encoder, &texture_view);
369
370 self.queue.submit(std::iter::once(encoder.finish()));
371
372 surface_texture.present();
373
374 Ok(())
375 }
376}