Skip to main content

wisp/
application.rs

1//! `Application` — root context for wisp, owns the wgpu device and queue.
2//!
3//! Headless only in M0.4; surface support lands in M0.5.
4
5use wgpu::{Adapter, Device, Instance, Queue};
6
7use crate::Error;
8use crate::scene::Stage;
9
10/// Runtime configuration passed to [`Application::new`].
11#[derive(Debug, Clone)]
12pub struct AppConfig {
13    /// Logical width of the stage in pixels at DPR 1×.
14    pub width: u32,
15    /// Logical height of the stage in pixels at DPR 1×.
16    pub height: u32,
17    /// GPU power preference. Defaults to `HighPerformance`.
18    pub power_preference: wgpu::PowerPreference,
19}
20
21impl Default for AppConfig {
22    fn default() -> Self {
23        Self {
24            width: 1280,
25            height: 720,
26            power_preference: wgpu::PowerPreference::HighPerformance,
27        }
28    }
29}
30
31/// Root wgpu context.
32///
33/// Owns the `Instance`, `Adapter`, `Device`, and `Queue`. Construct with
34/// [`Application::new`] (async, await with a runtime such as `pollster`).
35///
36/// Headless only in M0.4 — surface attachment lands in M0.5.
37pub struct Application {
38    config: AppConfig,
39    instance: Instance,
40    adapter: Adapter,
41    device: Device,
42    queue: Queue,
43    stage: Stage,
44}
45
46impl Application {
47    /// Adopt a pre-existing wgpu context (e.g. from `eframe`).
48    ///
49    /// All four wgpu resources are cloned into the `Application`; `wgpu` types
50    /// are `Arc`-backed so this is cheap. Use this instead of [`Application::new`]
51    /// when integrating with an embedding host that owns the device.
52    #[must_use]
53    pub fn from_wgpu(
54        instance: Instance,
55        adapter: Adapter,
56        device: Device,
57        queue: Queue,
58        config: AppConfig,
59    ) -> Self {
60        Self {
61            config,
62            instance,
63            adapter,
64            device,
65            queue,
66            stage: Stage::new(),
67        }
68    }
69
70    /// Construct a new `Application`.
71    ///
72    /// # Errors
73    ///
74    /// - [`Error::AdapterUnavailable`] if no compatible GPU adapter is found.
75    /// - [`Error::DeviceRequest`] if device creation fails.
76    pub async fn new(config: AppConfig) -> Result<Self, Error> {
77        let instance = Instance::new(&wgpu::InstanceDescriptor::default());
78
79        let adapter = instance
80            .request_adapter(&wgpu::RequestAdapterOptions {
81                power_preference: config.power_preference,
82                compatible_surface: None,
83                force_fallback_adapter: false,
84            })
85            .await
86            .ok_or_else(|| Error::AdapterUnavailable("no compatible adapter found".to_owned()))?;
87
88        let (device, queue) = adapter
89            .request_device(
90                &wgpu::DeviceDescriptor {
91                    label: Some("wisp::Application device"),
92                    required_features: wgpu::Features::empty(),
93                    required_limits: wgpu::Limits::default(),
94                    memory_hints: wgpu::MemoryHints::Performance,
95                },
96                None,
97            )
98            .await
99            .map_err(|e| Error::DeviceRequest(e.to_string()))?;
100
101        let info = adapter.get_info();
102        tracing::info!(
103            adapter = %info.name,
104            backend = ?info.backend,
105            device_type = ?info.device_type,
106            "wgpu adapter selected"
107        );
108
109        Ok(Self {
110            config,
111            instance,
112            adapter,
113            device,
114            queue,
115            stage: Stage::new(),
116        })
117    }
118
119    /// Borrow the scene graph stage.
120    #[must_use]
121    pub fn stage(&self) -> &Stage {
122        &self.stage
123    }
124
125    /// Mutably borrow the scene graph stage.
126    pub fn stage_mut(&mut self) -> &mut Stage {
127        &mut self.stage
128    }
129
130    /// Adapter info (name, backend, device type, driver).
131    #[must_use]
132    pub fn adapter_info(&self) -> wgpu::AdapterInfo {
133        self.adapter.get_info()
134    }
135
136    /// Borrow the wgpu instance.
137    #[must_use]
138    pub fn instance(&self) -> &Instance {
139        &self.instance
140    }
141
142    /// Borrow the wgpu adapter.
143    #[must_use]
144    pub fn adapter(&self) -> &Adapter {
145        &self.adapter
146    }
147
148    /// Borrow the wgpu device.
149    #[must_use]
150    pub fn device(&self) -> &Device {
151        &self.device
152    }
153
154    /// Borrow the wgpu queue.
155    #[must_use]
156    pub fn queue(&self) -> &Queue {
157        &self.queue
158    }
159
160    /// Logical stage width.
161    #[must_use]
162    pub fn width(&self) -> u32 {
163        self.config.width
164    }
165
166    /// Logical stage height.
167    #[must_use]
168    pub fn height(&self) -> u32 {
169        self.config.height
170    }
171}