wgpu_quick_start/
my_device.rs

1//! A module to create `wgpu::Surface` and manage the same.
2//!
3//!
4
5use super::MyDevice;
6
7use super::GPUStarterResult;
8
9/// An abstraction of the wgpu::Surface<'lifetime>
10/// to create from a given `winit` Window.
11///
12/// Following is an example to create the surface using a given `Window`
13///
14/// # Examples
15///
16/// ```rust
17/// use wgpu_quick_start::{MyDevice, create_new_device};
18/// use winit::{event::WindowEvent, window::WindowAttributes};
19/// use winit_app::{AppWindowEvent, Application};
20///
21///
22/// fn launch() -> Result<(), Box<dyn std::error::Error>> {
23///    let winit_app = Application::new();
24///    let mut opt_device: Option<Box<dyn MyDevice>> = None;
25///    winit_app.run(
26///        WindowAttributes::default().with_title("wgpu starter app"),
27///        move |app_window_event| match app_window_event {
28///            AppWindowEvent::NewWindow(window) => match create_new_device(window) {
29///                Ok(value) => {
30///                    opt_device = Some(value);
31///                }
32///                Err(err) => {
33///                    // warning - Error creating new surface from the window
34///                }
35///            },
36///            AppWindowEvent::OnWindowEvent(event, event_loop) => {
37///                if let Some(local_device) = opt_device.as_mut() {
38///                     // Handle those events
39///                }
40///            }
41///        },
42///    )?;
43///    Ok(())
44/// }
45/// ```
46///
47/// # Lifetimes
48///
49/// The lifetime of this struct refers to the underlying lifetime of the `wgpu::Surface<'lifetime>` inside the same.
50///
51pub struct MyDeviceImpl<'a> {
52    surface: Option<wgpu::Surface<'a>>,
53
54    device: wgpu::Device,
55
56    queue: wgpu::Queue,
57
58    adapter: wgpu::Adapter,
59
60    config: Option<wgpu::SurfaceConfiguration>,
61
62    texture_format: wgpu::TextureFormat,
63}
64
65impl<'a> MyDeviceImpl<'a> {
66    /// Create a new surface from the given window
67    /// for the given dimensions (width, height) tuple
68    ///
69    #[cfg(feature = "enable-sync-winit")]
70    pub async fn new(
71        window: impl Into<wgpu::SurfaceTarget<'a>>,
72        dimensions: (u32, u32),
73    ) -> GPUStarterResult<Self> {
74        use crate::GPUInstance;
75        let instance = GPUInstance::new();
76        let surface = instance.create_surface(window)?;
77        let adapter = instance
78            .instance
79            .request_adapter(&wgpu::RequestAdapterOptions {
80                power_preference: wgpu::PowerPreference::default(),
81                compatible_surface: Some(&surface),
82                force_fallback_adapter: false,
83            })
84            .await?;
85
86        let (device, queue) = adapter
87            .request_device(&wgpu::DeviceDescriptor {
88                label: None,
89                required_features: wgpu::Features::empty(),
90                experimental_features: wgpu::ExperimentalFeatures::disabled(),
91
92                // WebGL doesn't support all of wgpu's features, so if
93                // we're building for the web we'll have to disable some.
94                required_limits: if cfg!(target_arch = "wasm32") {
95                    wgpu::Limits::downlevel_webgl2_defaults()
96                } else {
97                    wgpu::Limits::default()
98                },
99                memory_hints: Default::default(),
100                trace: wgpu::Trace::Off,
101            })
102            .await?;
103
104        let surface_capabilities = surface.get_capabilities(&adapter);
105
106        // Shader code in this tutorial assumes an sRGB surface texture. Using a different
107        // one will result in all the colors coming out darker. If you want to support non
108        // sRGB surfaces, you'll need to account for that when drawing to the frame.
109        let surface_format = surface_capabilities
110            .formats
111            .iter()
112            .find(|f| f.is_srgb())
113            .copied()
114            .unwrap_or(surface_capabilities.formats[0]);
115        let (width, height) = dimensions;
116        let config = wgpu::SurfaceConfiguration {
117            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
118            format: surface_format,
119            width,
120            height,
121            present_mode: surface_capabilities.present_modes[0],
122            alpha_mode: surface_capabilities.alpha_modes[0],
123            view_formats: vec![],
124            desired_maximum_frame_latency: 2,
125        };
126
127        let surface_caps = surface.get_capabilities(&adapter);
128        let surface_format = surface_caps
129            .formats
130            .iter()
131            .copied()
132            .find(wgpu::TextureFormat::is_srgb)
133            .unwrap_or(surface_caps.formats[0]);
134
135        Ok(Self {
136            surface: Some(surface),
137            device,
138            queue,
139            adapter,
140            config: Some(config),
141            texture_format: surface_format,
142        })
143    }
144
145    /// Create a new device without a 'window' reference.
146    /// Useful for unit testing
147    #[cfg(feature = "enable-sync")]
148    pub async fn new_without_window() -> crate::GPUStarterResult<Self> {
149        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
150                    #[cfg(not(target_arch = "wasm32"))]
151                    backends: wgpu::Backends::VULKAN,
152                    #[cfg(target_arch = "wasm32")]
153                    backends: wgpu::Backends::GL,
154                    ..Default::default()
155                });        
156        let adapter = instance
157            .request_adapter(&wgpu::RequestAdapterOptions {
158                power_preference: wgpu::PowerPreference::default(),
159                compatible_surface: None,
160                force_fallback_adapter: false,
161            })
162            .await?;
163        let (device, queue) = adapter.request_device(&Default::default()).await?;
164        let texture_size = 256u32;
165
166        let texture_desc = wgpu::TextureDescriptor {
167            size: wgpu::Extent3d {
168                width: texture_size,
169                height: texture_size,
170                depth_or_array_layers: 1,
171            },
172            view_formats: &[],
173            mip_level_count: 1,
174            sample_count: 1,
175            dimension: wgpu::TextureDimension::D2,
176            format: wgpu::TextureFormat::Rgba8UnormSrgb,
177            usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
178            label: None,
179        };
180        let texture = device.create_texture(&texture_desc);
181        let _texture_view = texture.create_view(&Default::default());
182        Ok(Self {
183            surface: None,
184            device,
185            queue,
186            adapter,
187            config: None,
188            texture_format: texture.format(),
189        })
190    }
191}
192
193impl<'a> MyDevice for MyDeviceImpl<'a> {
194    fn get_device(&self) -> &wgpu::Device {
195        &self.device
196    }
197
198    fn get_queue(&self) -> &wgpu::Queue {
199        &self.queue
200    }
201
202    fn get_adapter(&self) -> &wgpu::Adapter {
203        &self.adapter
204    }
205
206    fn get_current_texture(&self) -> GPUStarterResult<wgpu::SurfaceTexture> {
207        if let Some(surface) = self.surface.as_ref() {
208            let output = surface.get_current_texture()?;
209            Ok(output)
210        } else {
211            Err(crate::GPUStarterError::UnsupportedError)
212        }
213    }
214
215    fn get_texture_format(&self) -> GPUStarterResult<wgpu::TextureFormat> {
216        Ok(self.texture_format)
217    }
218
219    fn resize(&mut self, dimensions: (u32, u32)) {
220        // Resize the same
221        let (width, height) = dimensions;
222        if let Some(surface) = self.surface.as_mut()
223            && let Some(config) = self.config.as_mut()
224            && width > 0
225            && height > 0
226        {
227            config.width = width;
228            config.height = height;
229            surface.configure(&self.device, config);
230        }
231    }
232}
233
234#[cfg(test)]
235mod tests {
236
237    #[test]
238    #[ignore = "bug with adapter Vulkan/GL support etc"]
239    #[cfg(feature = "enable-sync")]
240    fn test_windowless_device() -> super::super::GPUStarterResult<()> {
241        use super::*;
242        let my_device = pollster::block_on(MyDeviceImpl::new_without_window())?;
243        assert!(my_device.get_texture_format().is_ok());
244        Ok(())
245    }
246}