srs2dge_core/target/
mod.rs

1use self::{
2    belt::Belt,
3    catcher::Catcher,
4    surface::{ISurface, Surface},
5};
6use crate::{label, DeviceStorage, Frame};
7use colorful::Colorful;
8use std::sync::Arc;
9use wgpu::{
10    util::power_preference_from_env, Adapter, Device, DeviceDescriptor, Features, Instance, Limits,
11    PowerPreference, Queue, RequestAdapterOptionsBase, TextureFormat,
12};
13use winit::window::Window;
14
15//
16
17pub mod prelude;
18pub mod surface;
19
20//
21
22mod belt;
23mod catcher;
24
25//
26
27pub struct Target {
28    pub(crate) device: Arc<Device>,
29    pub(crate) queue: Arc<Queue>,
30
31    pub(crate) surface: Option<Surface>,
32    pub(crate) belt: Belt,
33    catcher: Catcher,
34
35    active: bool,
36    init: bool,
37}
38
39//
40
41impl Target {
42    pub async fn new(
43        instance: Arc<Instance>,
44        window: Arc<Window>,
45        device_storage: DeviceStorage,
46    ) -> Self {
47        // create a surface that is compatible with both the window and the instance
48        let surface = ISurface::new(window, instance.clone());
49
50        // create a device and a queue for it
51        let (adapter, device, queue) =
52            Self::new_with_opt(instance, Some(&surface), device_storage).await;
53
54        // complete the surface (ready for rendering)
55        let surface = Some(surface.complete(&adapter, device.clone()));
56
57        // create a belt for fast data uploading
58        let belt = Belt::new(device.clone());
59
60        // create a catcher to catch non fatal errors
61        // for example: shader compilation errors
62        let catcher = Catcher::new(&device);
63
64        Self {
65            device,
66            queue,
67
68            surface,
69            belt,
70            catcher,
71
72            active: false,
73            init: true,
74        }
75    }
76
77    pub async fn new_headless(instance: Arc<Instance>, device_storage: DeviceStorage) -> Self {
78        let (_, device, queue) = Self::new_with_opt(instance, None, device_storage).await;
79
80        // create a belt for fast data uploading
81        let belt = Belt::new(device.clone());
82
83        // create a catcher to catch non fatal errors
84        // for example: shader compilation errors
85        let catcher = Catcher::new(&device);
86
87        Self {
88            device,
89            queue,
90
91            surface: None,
92            belt,
93            catcher,
94
95            active: false,
96            init: true,
97        }
98    }
99
100    async fn new_with_opt(
101        instance: Arc<Instance>,
102        surface: Option<&wgpu::Surface>,
103        device_storage: DeviceStorage,
104    ) -> (Arc<Adapter>, Arc<Device>, Arc<Queue>) {
105        // 'borrow' a device and a queue if this surface is compatible with any previous ones
106        // or create new if there were none
107        if let Some(pre_existing) = Self::try_borrow_device(surface, device_storage.clone()) {
108            // borrow
109            pre_existing
110        } else {
111            // create
112            // get a GPU
113            let adapter = Self::make_adapter(surface, &instance).await;
114
115            // print out some info about the selected GPU
116            Self::debug_report(&adapter);
117
118            // create a logical device and a queue for it
119            let (device, queue) = Self::make_device(&adapter).await;
120
121            // push to the device storage
122            if let Ok(mut write) = device_storage.write() {
123                write.push((adapter.clone(), device.clone(), queue.clone()));
124            }
125
126            (adapter, device, queue)
127        }
128    }
129
130    /// check if objects created with `self` target
131    /// can be used with the `other` target
132    pub fn compatible_with(&self, other: &Target) -> bool {
133        Arc::ptr_eq(&self.device, &other.device) && Arc::ptr_eq(&self.queue, &other.queue)
134    }
135
136    #[must_use]
137    pub fn get_frame(&mut self) -> Frame {
138        if self.active {
139            panic!("Earlier frame was not finished before starting a new one");
140        }
141
142        if self.init {
143            self.init = false;
144            self.get_window().unwrap().set_visible(true);
145        }
146
147        Frame::new(
148            &self.device,
149            self.queue.clone(),
150            self.surface.as_mut().expect("TODO: Draw in headless mode"),
151            self.belt.get(),
152        )
153    }
154
155    pub fn finish_frame(&mut self, frame: Frame) {
156        self.belt.set(frame.finish())
157    }
158
159    pub fn set_vsync(&mut self, on: bool) {
160        if let Some(s) = self.surface.as_mut() {
161            s.set_vsync(on);
162        }
163    }
164
165    pub fn get_vsync(&self) -> Option<bool> {
166        self.surface.as_ref().map(|s| s.get_vsync())
167    }
168
169    pub fn get_window(&self) -> Option<Arc<Window>> {
170        self.surface.as_ref().map(|surface| surface.get_window())
171    }
172
173    pub fn get_format(&self) -> TextureFormat {
174        self.surface
175            .as_ref()
176            .map(|surface| surface.format())
177            .unwrap_or(TextureFormat::Rgba8Unorm)
178    }
179
180    pub fn get_device(&self) -> Arc<Device> {
181        self.device.clone()
182    }
183
184    pub fn catch_error<T, F: FnOnce(&Self) -> T>(&self, f: F) -> Result<T, String> {
185        Catcher::catch_error(self, f)
186    }
187
188    fn try_borrow_device(
189        compatible_surface: Option<&wgpu::Surface>,
190        device_storage: DeviceStorage,
191    ) -> Option<(Arc<Adapter>, Arc<Device>, Arc<Queue>)> {
192        device_storage
193            .read()
194            .ok()?
195            .iter()
196            .find(|(adapter, _, _)| {
197                if let Some(surface) = compatible_surface {
198                    adapter.is_surface_supported(surface)
199                } else {
200                    true
201                }
202            })
203            .cloned()
204    }
205
206    async fn make_adapter(
207        compatible_surface: Option<&wgpu::Surface>,
208        instance: &Instance,
209    ) -> Arc<Adapter> {
210        Arc::new(
211            instance
212                .request_adapter(&RequestAdapterOptionsBase {
213                    power_preference: power_preference_from_env()
214                        .unwrap_or(PowerPreference::HighPerformance),
215                    force_fallback_adapter: false,
216                    compatible_surface,
217                })
218                .await
219                .expect("No suitable GPUs"),
220        )
221    }
222
223    fn debug_report(adapter: &Adapter) {
224        if log::log_enabled!(log::Level::Debug) {
225            let gpu_info = adapter.get_info();
226            let api = format!("{:?}", gpu_info.backend).red();
227            let name = gpu_info.name.blue();
228            let ty = format!("{:?}", gpu_info.device_type).green();
229
230            log::debug!("GPU API: {api}");
231            log::debug!("GPU: {name} ({ty})");
232        }
233    }
234
235    async fn make_device(adapter: &Adapter) -> (Arc<Device>, Arc<Queue>) {
236        let (device, queue) = adapter
237            .request_device(
238                &DeviceDescriptor {
239                    label: label!(),
240                    features: Features::empty(),
241                    limits: Limits {
242                        // max_texture_dimension_2d: 16384,
243                        ..Limits::downlevel_webgl2_defaults()
244                    },
245                },
246                None,
247            )
248            .await
249            .unwrap();
250        (Arc::new(device), Arc::new(queue))
251    }
252}