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