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, RenderPass, RequestAdapterOptions,
16    RequestDeviceError, Surface, 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,
177            present_mode,
178            alpha_mode
179        );
180
181        config
182    }
183
184    #[must_use]
185    pub const fn texture_format(&self) -> wgpu::TextureFormat {
186        self.config.format
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.surface.configure(&self.device, &self.config);
200    }
201
202    pub fn render(
203        &self,
204        clear_color: wgpu::Color,
205        mut render_fn: impl FnMut(&mut RenderPass),
206    ) -> Result<(), SurfaceError> {
207        // Gets a new texture from the swap chain
208        let surface_texture = self.surface.get_current_texture()?;
209        let texture_view = surface_texture
210            .texture
211            .create_view(&wgpu::TextureViewDescriptor::default());
212
213        let mut encoder = self
214            .device
215            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
216                label: Some("Render Encoder"),
217            });
218
219        // THIS SCOPE IS ABSOLUTELY NEEDED FOR THE RENDER PASS - DO NOT REMOVE
220        {
221            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
222                label: Some("Render Pass"),
223                color_attachments: &[
224                    // This is what @location(0) in the fragment shader targets
225                    Some(wgpu::RenderPassColorAttachment {
226                        view: &texture_view,
227                        resolve_target: None,
228                        ops: wgpu::Operations {
229                            load: wgpu::LoadOp::Clear(clear_color),
230                            store: wgpu::StoreOp::Store,
231                        },
232                    }),
233                ],
234                depth_stencil_attachment: None,
235                timestamp_writes: None,
236                occlusion_query_set: None,
237            });
238
239            render_fn(&mut render_pass);
240        }
241
242        self.queue.submit(std::iter::once(encoder.finish()));
243
244        surface_texture.present();
245
246        Ok(())
247    }
248}