molgfx_gpu/device.rs
1//! The device trait: the root of the hardware abstraction.
2//!
3//! `Device` carries every backend resource as an associated type, so the
4//! renderer is generic over it and pays no dynamic dispatch on the hot path.
5//! Behavior traits (`Queue`, `CommandEncoder`, `Surface`) take the device as
6//! a type parameter to name those resource types in their signatures.
7
8use crate::capabilities::Capabilities;
9use crate::descriptors::{
10 BindGroupDesc, BindGroupLayoutDesc, BufferDesc, ComputePipelineDesc, RenderPipelineDesc,
11 SamplerDesc, ShaderModuleDesc, TextureDesc, TextureViewDesc,
12};
13use crate::encoder::CommandEncoder;
14use crate::error::GpuError;
15use crate::queue::{Queue, Readback};
16use crate::surface::Surface;
17use crate::{
18 BlasDesc, RayQueryBindGroupDesc, RayQueryBindGroupLayoutDesc, RayQueryLimits, TlasDesc,
19 TlasInstance,
20};
21use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
22use std::future::Future;
23#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
24use std::sync::Arc;
25
26/// Anything a presentation surface can be created from. Embedders hand the
27/// engine their window behind this trait; the engine never names a
28/// windowing toolkit.
29///
30/// Native window handles may cross worker threads. Browser canvas handles are
31/// bound to the JavaScript main thread, so requiring `Send + Sync` there would
32/// reject the platform's real WebGPU resources.
33#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
34pub trait WindowSource: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync {}
35#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
36impl<T: HasWindowHandle + HasDisplayHandle + std::fmt::Debug + Send + Sync> WindowSource for T {}
37
38/// Browser presentation source, intentionally confined to the JavaScript
39/// thread that owns its canvas.
40#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
41pub trait WindowSource: HasWindowHandle + HasDisplayHandle + std::fmt::Debug {}
42#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
43impl<T: HasWindowHandle + HasDisplayHandle + std::fmt::Debug> WindowSource for T {}
44
45/// A shared native window handle, alive for as long as its surface.
46#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
47pub type WindowTarget = Arc<dyn WindowSource>;
48
49/// A browser canvas supplied and owned by the host page.
50#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
51pub type WindowTarget = web_sys::HtmlCanvasElement;
52
53/// What one [`Device::poll`] left behind.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum PollOutcome {
56 /// No submitted work was outstanding when the poll began.
57 Idle,
58 /// Submitted work is still executing; poll again later.
59 Busy,
60}
61
62/// Which adapter class to prefer when several are present.
63#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
64pub enum PowerPreference {
65 /// The fastest available adapter; the default for a rendering engine.
66 #[default]
67 HighPerformance,
68 /// The most efficient adapter.
69 LowPower,
70}
71
72/// Options for opening a device.
73#[derive(Clone, Copy, Debug, Default)]
74pub struct DeviceDesc {
75 /// Adapter preference.
76 pub power: PowerPreference,
77 /// Optional hard ceiling for live buffer and texture bytes owned by the device.
78 pub resource_memory_limit_bytes: Option<u64>,
79}
80
81/// Live physical resources allocated through a device.
82#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
83pub struct ResourceMemory {
84 /// Live buffer bytes.
85 pub buffer_bytes: u64,
86 /// Live texture bytes, including all layers.
87 pub texture_bytes: u64,
88 /// Highest live total observed since device creation.
89 pub peak_bytes: u64,
90}
91
92impl ResourceMemory {
93 /// Current accounted device bytes.
94 #[must_use]
95 pub const fn total_bytes(self) -> u64 {
96 self.buffer_bytes.saturating_add(self.texture_bytes)
97 }
98}
99
100/// The result of opening a device: the device, its queue, and a surface
101/// when a window was supplied.
102#[derive(Debug)]
103pub struct Opened<D: Device> {
104 /// The device.
105 pub device: D,
106 /// Its submission queue.
107 pub queue: D::Queue,
108 /// The presentation surface, when opened against a window.
109 pub surface: Option<D::Surface>,
110}
111
112/// A GPU device: resource creation and capability report.
113///
114/// Everything created here is created at load and reused; the trait offers
115/// no per-frame conveniences by design.
116pub trait Device: Sized + 'static {
117 /// GPU buffer.
118 type Buffer: std::fmt::Debug;
119 /// GPU texture.
120 type Texture: std::fmt::Debug;
121 /// View over a texture, bindable or attachable.
122 type TextureView: std::fmt::Debug;
123 /// Texture sampler.
124 type Sampler: std::fmt::Debug;
125 /// Compiled shader module.
126 type ShaderModule: std::fmt::Debug;
127 /// Bind-group layout.
128 type BindGroupLayout: std::fmt::Debug;
129 /// Bind group.
130 type BindGroup: std::fmt::Debug;
131 /// Render or compute pipeline.
132 type Pipeline: std::fmt::Debug;
133 /// Timestamp or occlusion query storage.
134 type QuerySet: std::fmt::Debug;
135 /// Bottom-level acceleration structure. Portable devices may use a
136 /// zero-sized placeholder and return a capability error from every
137 /// ray-query operation.
138 type Blas: std::fmt::Debug;
139 /// Top-level acceleration structure. Portable devices may use a
140 /// zero-sized placeholder and return a capability error from every
141 /// ray-query operation.
142 type Tlas: std::fmt::Debug;
143 /// Command encoder.
144 type CommandEncoder: CommandEncoder<Self>;
145 /// Submission queue.
146 type Queue: Queue<Self>;
147 /// A detached readback over one of this device's buffers.
148 type Readback: Readback;
149 /// Presentation surface.
150 type Surface: Surface<Self>;
151
152 /// Selects an adapter and asynchronously opens a device, with a surface
153 /// when a window is supplied.
154 ///
155 /// # Errors
156 ///
157 /// No compatible adapter, or device creation failed.
158 fn open_async(
159 desc: &DeviceDesc,
160 window: Option<WindowTarget>,
161 ) -> impl Future<Output = Result<Opened<Self>, GpuError>>;
162
163 /// Native convenience for callers that do not already run an async
164 /// executor. Browser builds expose only asynchronous device opening.
165 ///
166 /// # Errors
167 ///
168 /// No compatible adapter, or device creation failed.
169 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
170 fn open_blocking(
171 desc: &DeviceDesc,
172 window: Option<WindowTarget>,
173 ) -> Result<Opened<Self>, GpuError>;
174
175 /// Creates a buffer.
176 ///
177 /// # Errors
178 ///
179 /// The buffer exceeded device limits.
180 fn create_buffer(&self, desc: &BufferDesc) -> Result<Self::Buffer, GpuError>;
181
182 /// Creates a 2-D texture.
183 ///
184 /// # Errors
185 ///
186 /// The texture exceeded device limits.
187 fn create_texture(&self, desc: &TextureDesc) -> Result<Self::Texture, GpuError>;
188
189 /// Creates a view over a texture.
190 fn create_texture_view(
191 &self,
192 texture: &Self::Texture,
193 desc: &TextureViewDesc,
194 ) -> Self::TextureView;
195
196 /// Creates a sampler.
197 fn create_sampler(&self, desc: &SamplerDesc) -> Self::Sampler;
198
199 /// Starts compiling `sources` in the background, in order, so a later
200 /// [`Device::create_shader_module`] for the same text finds its module
201 /// ready. A backend with nothing to gain ignores it.
202 fn prewarm_shaders(&self, _sources: &[&'static str]) {}
203
204 /// Compiles a WGSL shader module.
205 ///
206 /// # Errors
207 ///
208 /// Compilation failed; the error carries the backend's detail.
209 fn create_shader_module(
210 &self,
211 desc: &ShaderModuleDesc<'_>,
212 ) -> Result<Self::ShaderModule, GpuError>;
213
214 /// Creates a bind-group layout.
215 fn create_bind_group_layout(&self, desc: &BindGroupLayoutDesc<'_>) -> Self::BindGroupLayout;
216
217 /// Creates a bind group over a layout.
218 fn create_bind_group(&self, desc: &BindGroupDesc<'_, Self>) -> Self::BindGroup;
219
220 /// Creates a render pipeline. Created at load, cached by the caller.
221 ///
222 /// # Errors
223 ///
224 /// Pipeline creation failed (most often shader/interface mismatch).
225 fn create_render_pipeline(
226 &self,
227 desc: &RenderPipelineDesc<'_, Self>,
228 ) -> Result<Self::Pipeline, GpuError>;
229
230 /// Creates a compute pipeline.
231 ///
232 /// # Errors
233 ///
234 /// Pipeline creation failed.
235 fn create_compute_pipeline(
236 &self,
237 desc: &ComputePipelineDesc<'_, Self>,
238 ) -> Result<Self::Pipeline, GpuError>;
239
240 /// Creates a command encoder for one frame or task.
241 fn create_command_encoder(&self) -> Self::CommandEncoder;
242
243 /// Creates a timestamp query set when the capability is available.
244 ///
245 /// # Errors
246 ///
247 /// Returns a capability error when timestamp queries are unavailable.
248 fn create_timestamp_query_set(&self, count: u32) -> Result<Self::QuerySet, GpuError>;
249
250 /// The opened device's capability report.
251 fn capabilities(&self) -> &Capabilities;
252
253 /// The recorded cause once the backend has reported the device lost, and
254 /// `None` while it is healthy or when the backend cannot tell.
255 fn device_loss(&self) -> Option<crate::DeviceLoss> {
256 None
257 }
258
259 /// Reports asynchronous backend failures observed since the last check.
260 ///
261 /// # Errors
262 ///
263 /// Returns the first pending runtime diagnostic or a sticky device loss.
264 fn check_errors(&self) -> Result<(), GpuError> {
265 Ok(())
266 }
267
268 /// Advances completed GPU work without waiting for it.
269 ///
270 /// Fence waits and mapped readbacks resolve only as the embedder calls
271 /// this: no future blocks the thread to make progress on its own. Backends
272 /// that deliver completions from their own event loop have nothing to
273 /// drive and report [`PollOutcome::Idle`].
274 ///
275 /// # Errors
276 ///
277 /// Returns a pending runtime diagnostic or [`GpuError::DeviceLost`].
278 fn poll(&self) -> Result<PollOutcome, GpuError> {
279 self.check_errors()?;
280 Ok(PollOutcome::Idle)
281 }
282
283 /// Returns live physical resource accounting when the backend supports it.
284 fn resource_memory(&self) -> ResourceMemory {
285 ResourceMemory::default()
286 }
287
288 /// Returns negotiated acceleration-structure ceilings.
289 ///
290 /// # Errors
291 ///
292 /// Returns a capability error when ray queries were not negotiated.
293 fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
294 Err(GpuError::Capability { name: "ray query" })
295 }
296
297 /// Allocates a BLAS with fixed geometry ceilings.
298 ///
299 /// # Errors
300 ///
301 /// Returns capability, size or backend allocation failures.
302 fn create_blas(&self, _desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
303 Err(GpuError::Capability { name: "ray query" })
304 }
305
306 /// Allocates a TLAS with a fixed instance ceiling.
307 ///
308 /// # Errors
309 ///
310 /// Returns capability, size or backend allocation failures.
311 fn create_tlas(&self, _desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
312 Err(GpuError::Capability { name: "ray query" })
313 }
314
315 /// Replaces or clears one TLAS instance.
316 ///
317 /// # Errors
318 ///
319 /// Returns a capability error or an out-of-range instance failure.
320 fn set_tlas_instance(
321 &self,
322 _tlas: &mut Self::Tlas,
323 _index: u32,
324 _instance: Option<TlasInstance<'_, Self>>,
325 ) -> Result<(), GpuError> {
326 Err(GpuError::Capability { name: "ray query" })
327 }
328
329 /// Creates a layout containing acceleration-structure slots.
330 ///
331 /// # Errors
332 ///
333 /// Returns a capability or backend layout failure.
334 fn create_ray_query_bind_group_layout(
335 &self,
336 _desc: &RayQueryBindGroupLayoutDesc<'_>,
337 ) -> Result<Self::BindGroupLayout, GpuError> {
338 Err(GpuError::Capability { name: "ray query" })
339 }
340
341 /// Creates a bind group containing TLAS resources.
342 ///
343 /// # Errors
344 ///
345 /// Returns a capability or backend binding failure.
346 fn create_ray_query_bind_group(
347 &self,
348 _desc: &RayQueryBindGroupDesc<'_, Self>,
349 ) -> Result<Self::BindGroup, GpuError> {
350 Err(GpuError::Capability { name: "ray query" })
351 }
352}
353
354/// Device extension for hardware acceleration structures and WGSL ray queries.
355///
356/// Implementations must return [`GpuError::Capability`] when the opened device
357/// did not negotiate the ray-query feature. Keeping this separate from
358/// [`Device`] lets portable mocks and browser-only backends remain minimal.
359pub trait RayQueryDevice: Device {
360 /// Returns negotiated acceleration-structure ceilings.
361 ///
362 /// # Errors
363 ///
364 /// Returns a capability error when ray queries were not negotiated.
365 fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
366 Device::ray_query_limits(self)
367 }
368
369 /// Allocates a BLAS with fixed geometry ceilings.
370 ///
371 /// # Errors
372 ///
373 /// Returns capability, size or backend allocation failures.
374 fn create_blas(&self, desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
375 Device::create_blas(self, desc)
376 }
377
378 /// Allocates a TLAS with a fixed instance ceiling.
379 ///
380 /// # Errors
381 ///
382 /// Returns capability, size or backend allocation failures.
383 fn create_tlas(&self, desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
384 Device::create_tlas(self, desc)
385 }
386
387 /// Replaces or clears one TLAS instance without exposing backend types.
388 ///
389 /// # Errors
390 ///
391 /// Returns a capability error or an out-of-range instance failure.
392 fn set_tlas_instance(
393 &self,
394 tlas: &mut Self::Tlas,
395 index: u32,
396 instance: Option<TlasInstance<'_, Self>>,
397 ) -> Result<(), GpuError> {
398 Device::set_tlas_instance(self, tlas, index, instance)
399 }
400
401 /// Creates a layout containing acceleration-structure slots.
402 ///
403 /// # Errors
404 ///
405 /// Returns a capability or backend layout failure.
406 fn create_ray_query_bind_group_layout(
407 &self,
408 desc: &RayQueryBindGroupLayoutDesc<'_>,
409 ) -> Result<Self::BindGroupLayout, GpuError> {
410 Device::create_ray_query_bind_group_layout(self, desc)
411 }
412
413 /// Creates a bind group containing TLAS resources.
414 ///
415 /// # Errors
416 ///
417 /// Returns a capability or backend binding failure.
418 fn create_ray_query_bind_group(
419 &self,
420 desc: &RayQueryBindGroupDesc<'_, Self>,
421 ) -> Result<Self::BindGroup, GpuError> {
422 Device::create_ray_query_bind_group(self, desc)
423 }
424}
425
426impl<D: Device> RayQueryDevice for D {}