catgirl_engine_client/window/
window_state.rs

1use std::sync::Arc;
2
3use winit::{dpi::PhysicalSize, window::Window};
4
5use wgpu::{
6    Adapter, Device, DeviceDescriptor, Instance, Queue, RequestAdapterOptionsBase, Surface,
7    SurfaceConfiguration,
8};
9
10/// Struct used for storing the state of a window
11#[derive(Debug)]
12pub(crate) struct WindowState<'a> {
13    /// Handle to the window which holds the drawable surface
14    pub(crate) window: Arc<Window>,
15
16    /// Context for WGPU objects
17    instance: Option<Instance>,
18
19    /// Handle to the graphics device (e.g. the gpu)
20    pub(crate) adapter: Option<Adapter>,
21
22    /// The surface on which to draw graphics on
23    pub(crate) surface: Option<Surface<'a>>,
24
25    /// The configuration used to setup the surface
26    surface_config: Option<SurfaceConfiguration>,
27
28    /// Connection to the graphics device provided by the adapter
29    pub(crate) device: Option<Device>,
30
31    /// Queue in which to send commands to the graphics device
32    pub(crate) queue: Option<Queue>,
33}
34
35impl WindowState<'_> {
36    /// Used to initialize a new window and setup the graphics
37    ///
38    /// # Panics
39    ///
40    /// This may fail to create a WGPU surface
41    #[must_use]
42    pub(crate) fn new(window: Window) -> Self {
43        let window_arc: Arc<Window> = Arc::new(window);
44
45        Self {
46            window: window_arc,
47            instance: None,
48            surface: None,
49            surface_config: None,
50            adapter: None,
51            device: None,
52            queue: None,
53        }
54    }
55
56    /// Used to retrieve the best limits for the target
57    fn get_limits(&self) -> wgpu::Limits {
58        if cfg!(target_family = "wasm") {
59            wgpu::Limits {
60                max_color_attachments: 4,
61                ..wgpu::Limits::downlevel_webgl2_defaults()
62            }
63        } else if cfg!(any(target_os = "android", target_os = "ios")) {
64            wgpu::Limits::downlevel_defaults()
65        } else {
66            wgpu::Limits::default()
67        }
68    }
69
70    /// Initalize the async graphics portion of the window state
71    ///
72    /// # Panics
73    ///
74    /// This may fail to grab a connection to the graphics devices (e.g. gpu)
75    pub(crate) async fn initialize_graphics(&mut self) {
76        // Context for all WGPU objects
77        // https://docs.rs/wgpu/latest/wgpu/struct.Instance.html
78        debug!("Creating wgpu instance...");
79
80        self.instance = Some(if cfg!(target_family = "wasm") {
81            wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor::default())
82                .await
83        } else {
84            wgpu::Instance::new(wgpu::InstanceDescriptor::default())
85        });
86
87        debug!("Creating wgpu surface...");
88        let instance: &Instance = self.instance.as_ref().unwrap();
89        self.surface = Some(
90            instance
91                .create_surface(self.window.clone())
92                .expect("Could not create surface!"),
93        );
94
95        // Describe's a device
96        // For use with adapter's request device
97        // https://docs.rs/wgpu/latest/wgpu/type.DeviceDescriptor.html
98        debug!("Describing wgpu device...");
99        let mut device_descriptor: DeviceDescriptor = wgpu::DeviceDescriptor::default();
100
101        // Set limits to make this run on more devices
102        // TODO: Research how to dynamically set limits for the running device
103        debug!("Setting WGPU limits...");
104
105        let limits: wgpu::Limits = self.get_limits();
106        device_descriptor.required_limits = limits;
107
108        // Create Adapter Options (Reference to Surface Required for WASM)
109        let request_adapter_options: RequestAdapterOptionsBase<&Surface<'_>> =
110            RequestAdapterOptionsBase {
111                compatible_surface: self.surface.as_ref(),
112                ..Default::default()
113            };
114
115        // Handle to graphics device (e.g. GPU)
116        // https://docs.rs/wgpu/latest/wgpu/struct.Adapter.html
117        // https://crates.io/crates/futures
118        debug!("Grabbing wgpu adapter...");
119        let adapter_future = instance.request_adapter(&request_adapter_options);
120        self.adapter = Some(adapter_future.await.expect("Could not grab WGPU adapter!"));
121
122        // Opens a connection to the graphics device (e.g. GPU)
123        debug!("Opening connection with graphics device (e.g. GPU)...");
124        let device_future = self
125            .adapter
126            .as_ref()
127            .unwrap()
128            .request_device(&device_descriptor, None);
129
130        let (device, queue) = device_future
131            .await
132            .expect("Could not open a connection with the graphics device!");
133        self.device = Some(device);
134        self.queue = Some(queue);
135
136        let size: PhysicalSize<u32> = if cfg!(target_family = "wasm") {
137            PhysicalSize::new(400, 100)
138        } else {
139            self.window.inner_size()
140        };
141
142        trace!(
143            "Window inner size (Initialize Graphics): ({}, {})",
144            size.width,
145            size.height
146        );
147        let surface: &Surface<'_> = self.surface.as_ref().unwrap();
148        self.surface_config =
149            surface.get_default_config(self.adapter.as_ref().unwrap(), size.width, size.height);
150
151        let surface_config = self
152            .surface_config
153            .as_ref()
154            .expect("Could not get surface config!");
155        // surface_config.format = TextureFormat::Rgba8UnormSrgb;
156
157        // https://github-wiki-see.page/m/gfx-rs/wgpu/wiki/Texture-Color-Formats-and-Srgb-conversions
158        // https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/
159        let texture_format: wgpu::TextureFormat = surface_config.format;
160
161        trace!(
162            "Texture Format: {:?}, SRGB: {}, Depth Aspect: {}, Color Aspect: {}, Stencil Aspect: {}",
163            texture_format,
164            texture_format.is_srgb(),
165            texture_format.has_depth_aspect(),
166            texture_format.has_color_aspect(),
167            texture_format.has_stencil_aspect()
168        );
169
170        trace!("Surface Present Mode: {:?}", surface_config.present_mode);
171        trace!("Texture Usages: {:?}", surface_config.usage);
172        trace!("Alpha Mode: {:?}", surface_config.alpha_mode);
173
174        surface.configure(self.device.as_ref().unwrap(), surface_config);
175    }
176
177    /// Recreate the surface after it has been destroyed (e.g. used on Android)
178    ///
179    /// # Panics
180    ///
181    /// This may fail to recreate the surface
182    pub(crate) fn recreate_surface(&mut self) {
183        if self.device.is_none() {
184            warn!("Device is not setup... Have graphics been initialized?");
185            return;
186        }
187
188        if self.adapter.is_none() {
189            warn!("Adapter is not setup... Have graphics been initialized?");
190            return;
191        }
192
193        // Handle to the surface on which to draw on (e.g. a window)
194        // https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
195        debug!("Creating wgpu surface...");
196        let surface: Surface<'_> = self
197            .instance
198            .as_ref()
199            .unwrap()
200            .create_surface(self.window.clone())
201            .expect("Could not create surface!");
202
203        let size: PhysicalSize<u32> = self.window.inner_size();
204        surface.configure(
205            self.device.as_ref().unwrap(),
206            &surface
207                .get_default_config(self.adapter.as_ref().unwrap(), size.width, size.height)
208                .expect("Could not get surface default config!"),
209        );
210
211        self.surface = Some(surface);
212    }
213}