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};
13use wgpu::{
14 Adapter, Device, DeviceDescriptor, Features, Instance, InstanceDescriptor, Limits, MemoryHints, Queue, RequestAdapterOptions, RequestDeviceError, Surface,
15 SurfaceConfiguration, SurfaceError,
16};
17use winit::dpi::PhysicalSize;
18use winit::window::Window;
19
20#[derive(Debug, LocalResource)]
21pub struct WgpuWindow {
22 surface: Arc<Surface<'static>>,
23 device: Arc<Device>,
24 queue: Arc<Queue>,
25
26 config: SurfaceConfiguration,
27}
28
29impl WgpuWindow {
30 #[must_use]
31 pub const fn queue(&self) -> &Arc<Queue> {
32 &self.queue
33 }
34}
35
36pub struct ReceiveAnnoyingAsync {
37 pub device_info: Option<BasicDeviceInfo>,
38}
39
40#[derive(Debug, LocalResource)]
41pub struct BasicDeviceInfo {
42 pub adapter: Adapter,
43 pub device: Arc<Device>,
44 pub queue: Arc<Queue>,
45 pub surface: Arc<Surface<'static>>,
46 pub physical_surface_size: PhysicalSize<u32>,
47}
48
49pub async fn annoying_async_device_creation(
50 window: Arc<Window>,
51) -> Result<BasicDeviceInfo, RequestDeviceError> {
52 let instance = Instance::new(&InstanceDescriptor {
53 backends: Default::default(),
54 flags: Default::default(),
55 memory_budget_thresholds: Default::default(),
56 backend_options: Default::default(),
57 });
58 trace!(?instance, "found instance");
59
60 let surface = instance.create_surface(Arc::clone(&window)).unwrap();
61 trace!(?surface, "surface");
62
63 let adapter = instance
64 .request_adapter(&RequestAdapterOptions {
65 power_preference: Default::default(),
66 compatible_surface: Some(&surface),
67 force_fallback_adapter: false,
68 })
69 .await
70 .unwrap();
71
72 trace!(?adapter, "found adapter");
73
74 let device_descriptor = DeviceDescriptor {
75 label: None,
76 required_features: Features::empty(), required_limits: if cfg!(target_arch = "wasm32") {
78 Limits::downlevel_webgl2_defaults() } else {
80 Limits::default()
81 },
82 memory_hints: MemoryHints::default(), trace: Default::default(),
84 };
85
86 debug!(?device_descriptor, "device descriptor");
87
88 let (device, queue) = adapter
89 .request_device(&device_descriptor)
90 .await
91 .expect("Failed to request device");
92 debug!(?device, "device");
93
94 let inner_size = window.inner_size();
95
96 debug!(?inner_size, "inner size");
97
98 Ok(BasicDeviceInfo {
99 adapter,
100 device: device.into(),
101 queue: queue.into(),
102 surface: surface.into(),
103 physical_surface_size: inner_size,
104 })
105}
106
107fn tick(mut wgpu_window: LoReM<WgpuWindow>, window_messages: Msg<WindowMessage>) {
108 for msg in window_messages.iter_previous() {
109 if let WindowMessage::Resized(size) = msg {
110 debug!("resized to {:?}", size);
111 wgpu_window.resize((size.x, size.y));
112 }
113 }
114}
115
116pub struct WgpuWindowPlugin;
117impl Plugin for WgpuWindowPlugin {
118 fn build(&self, _app: &mut App) {}
119
120 fn post_initialization(&self, app: &mut App) {
121 app.insert_local_resource(WgpuWindow::new(
122 app.local_resources().fetch::<BasicDeviceInfo>(),
123 ));
124 app.add_system(RenderFirst, tick);
125 info!("wgpu window plugin is done");
126 }
127}
128
129impl WgpuWindow {
130 #[must_use]
131 pub fn new(info: &BasicDeviceInfo) -> Self {
132 let config = Self::configure_render_surface(info);
133
134 Self {
135 device: Arc::clone(&info.device),
136 config,
137 queue: Arc::clone(&info.queue),
138 surface: Arc::clone(&info.surface),
139 }
140 }
141
142 #[must_use]
143 pub const fn device(&self) -> &Arc<Device> {
144 &self.device
145 }
146
147 fn configure_render_surface(info: &BasicDeviceInfo) -> SurfaceConfiguration {
148 let surface_caps = info.surface.get_capabilities(&info.adapter);
149 let surface_format = surface_caps
150 .formats
151 .iter()
152 .copied()
153 .find(wgpu::TextureFormat::is_srgb)
154 .unwrap_or(surface_caps.formats[0]);
155
156 let config = wgpu::SurfaceConfiguration {
157 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
158 format: surface_format,
159 width: info.physical_surface_size.width,
160 height: info.physical_surface_size.height,
161 present_mode: surface_caps.present_modes[0],
162 alpha_mode: surface_caps.alpha_modes[0],
163 desired_maximum_frame_latency: 2,
164 view_formats: vec![],
165 };
166
167 info.surface.configure(&info.device, &config);
168
169 let present_mode = surface_caps.present_modes[0];
170 let alpha_mode = surface_caps.alpha_modes[0];
171 trace!(
172 "found surface format {:?} {:?} {:?}",
173 surface_format, present_mode, alpha_mode
174 );
175
176 config
177 }
178
179 #[must_use]
180 pub const fn texture_format(&self) -> wgpu::TextureFormat {
181 self.config.format
182 }
183
184 pub fn configure_surface(&mut self) {
185 debug!("configured surface! {:?}", self.config);
186 self.surface.configure(&self.device, &self.config);
187 }
188
189 pub fn resize(&mut self, new_size: (u16, u16)) {
190 let width = new_size.0 as usize;
191 let height = new_size.1 as usize;
192
193 if width == 0 || height == 0 {
194 return;
195 }
196
197 self.config.width = width as u32;
198 self.config.height = height as u32;
199 self.configure_surface();
200 }
201
202 pub fn render(
203 &self,
204 mut render_fn: impl FnMut(&mut wgpu::CommandEncoder, &wgpu::TextureView),
205 ) -> Result<(), SurfaceError> {
206 let surface_texture = match self.surface.get_current_texture() {
207 Ok(frame) => frame,
208 Err(wgpu::SurfaceError::Outdated) => {
209 return Ok(()); }
214 Err(wgpu::SurfaceError::Lost) => {
215 return Ok(()); }
218 Err(wgpu::SurfaceError::OutOfMemory) => {
219 eprintln!("GPU out of memory, exiting");
221 std::process::exit(1);
222 }
223 Err(e) => {
224 eprintln!("wgpu error getting next swap chain texture: {e:?}");
226 return Err(SurfaceError::Other);
227 }
228 };
229 let texture_view = surface_texture
230 .texture
231 .create_view(&wgpu::TextureViewDescriptor::default());
232
233 let mut encoder = self
234 .device
235 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
236 label: Some("Render Encoder"),
237 });
238
239 render_fn(&mut encoder, &texture_view);
240
241 self.queue.submit(std::iter::once(encoder.finish()));
242
243 surface_texture.present();
244
245 Ok(())
246 }
247}