limnus_wgpu_window/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/limnus
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use 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(), // Specify features as needed
77        required_limits: if cfg!(target_arch = "wasm32") {
78            Limits::downlevel_webgl2_defaults() // TODO: Not sure if this is needed?
79        } else {
80            Limits::default()
81        },
82        memory_hints: MemoryHints::default(), // Use default memory hints
83        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                // Surface is out of date: reconfigure and bail out of this frame.
210                // TODO: Get window size somehow
211//                self.surface.configure(&self.device, &self.config);
212                return Ok(()); // hopefully next frame will work
213            }
214            Err(wgpu::SurfaceError::Lost) => {
215                // Lost is treated the same as Outdated in wgpu 0.13+
216                return Ok(()); // hopefully next frame will work
217            }
218            Err(wgpu::SurfaceError::OutOfMemory) => {
219                // fatal – bail out of the app
220                eprintln!("GPU out of memory, exiting");
221                std::process::exit(1);
222            }
223            Err(e) => {
224                // other errors (Timeout, etc): log and skip this frame
225                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}