Skip to main content

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/// Which adapter class to prefer when several are present.
54#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
55pub enum PowerPreference {
56    /// The fastest available adapter; the default for a rendering engine.
57    #[default]
58    HighPerformance,
59    /// The most efficient adapter.
60    LowPower,
61}
62
63/// Options for opening a device.
64#[derive(Clone, Copy, Debug, Default)]
65pub struct DeviceDesc {
66    /// Adapter preference.
67    pub power: PowerPreference,
68    /// Optional hard ceiling for live buffer and texture bytes owned by the device.
69    pub resource_memory_limit_bytes: Option<u64>,
70}
71
72/// Live physical resources allocated through a device.
73#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
74pub struct ResourceMemory {
75    /// Live buffer bytes.
76    pub buffer_bytes: u64,
77    /// Live texture bytes, including all layers.
78    pub texture_bytes: u64,
79    /// Highest live total observed since device creation.
80    pub peak_bytes: u64,
81}
82
83impl ResourceMemory {
84    /// Current accounted device bytes.
85    #[must_use]
86    pub const fn total_bytes(self) -> u64 {
87        self.buffer_bytes.saturating_add(self.texture_bytes)
88    }
89}
90
91/// The result of opening a device: the device, its queue, and a surface
92/// when a window was supplied.
93#[derive(Debug)]
94pub struct Opened<D: Device> {
95    /// The device.
96    pub device: D,
97    /// Its submission queue.
98    pub queue: D::Queue,
99    /// The presentation surface, when opened against a window.
100    pub surface: Option<D::Surface>,
101}
102
103/// A GPU device: resource creation and capability report.
104///
105/// Everything created here is created at load and reused; the trait offers
106/// no per-frame conveniences by design.
107pub trait Device: Sized + 'static {
108    /// GPU buffer.
109    type Buffer: std::fmt::Debug;
110    /// GPU texture.
111    type Texture: std::fmt::Debug;
112    /// View over a texture, bindable or attachable.
113    type TextureView: std::fmt::Debug;
114    /// Texture sampler.
115    type Sampler: std::fmt::Debug;
116    /// Compiled shader module.
117    type ShaderModule: std::fmt::Debug;
118    /// Bind-group layout.
119    type BindGroupLayout: std::fmt::Debug;
120    /// Bind group.
121    type BindGroup: std::fmt::Debug;
122    /// Render or compute pipeline.
123    type Pipeline: std::fmt::Debug;
124    /// Timestamp or occlusion query storage.
125    type QuerySet: std::fmt::Debug;
126    /// Bottom-level acceleration structure. Portable devices may use a
127    /// zero-sized placeholder and return a capability error from every
128    /// ray-query operation.
129    type Blas: std::fmt::Debug;
130    /// Top-level acceleration structure. Portable devices may use a
131    /// zero-sized placeholder and return a capability error from every
132    /// ray-query operation.
133    type Tlas: std::fmt::Debug;
134    /// Command encoder.
135    type CommandEncoder: CommandEncoder<Self>;
136    /// Submission queue.
137    type Queue: Queue<Self>;
138    /// A detached readback over one of this device's buffers.
139    type Readback: Readback;
140    /// Presentation surface.
141    type Surface: Surface<Self>;
142
143    /// Selects an adapter and asynchronously opens a device, with a surface
144    /// when a window is supplied.
145    ///
146    /// # Errors
147    ///
148    /// No compatible adapter, or device creation failed.
149    fn open_async(
150        desc: &DeviceDesc,
151        window: Option<WindowTarget>,
152    ) -> impl Future<Output = Result<Opened<Self>, GpuError>>;
153
154    /// Native convenience for callers that do not already run an async
155    /// executor. Browser builds expose only asynchronous device opening.
156    ///
157    /// # Errors
158    ///
159    /// No compatible adapter, or device creation failed.
160    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
161    fn open_blocking(
162        desc: &DeviceDesc,
163        window: Option<WindowTarget>,
164    ) -> Result<Opened<Self>, GpuError>;
165
166    /// Creates a buffer.
167    ///
168    /// # Errors
169    ///
170    /// The buffer exceeded device limits.
171    fn create_buffer(&self, desc: &BufferDesc) -> Result<Self::Buffer, GpuError>;
172
173    /// Creates a 2-D texture.
174    ///
175    /// # Errors
176    ///
177    /// The texture exceeded device limits.
178    fn create_texture(&self, desc: &TextureDesc) -> Result<Self::Texture, GpuError>;
179
180    /// Creates a view over a texture.
181    fn create_texture_view(
182        &self,
183        texture: &Self::Texture,
184        desc: &TextureViewDesc,
185    ) -> Self::TextureView;
186
187    /// Creates a sampler.
188    fn create_sampler(&self, desc: &SamplerDesc) -> Self::Sampler;
189
190    /// Compiles a WGSL shader module.
191    ///
192    /// # Errors
193    ///
194    /// Compilation failed; the error carries the backend's detail.
195    fn create_shader_module(
196        &self,
197        desc: &ShaderModuleDesc<'_>,
198    ) -> Result<Self::ShaderModule, GpuError>;
199
200    /// Creates a bind-group layout.
201    fn create_bind_group_layout(&self, desc: &BindGroupLayoutDesc<'_>) -> Self::BindGroupLayout;
202
203    /// Creates a bind group over a layout.
204    fn create_bind_group(&self, desc: &BindGroupDesc<'_, Self>) -> Self::BindGroup;
205
206    /// Creates a render pipeline. Created at load, cached by the caller.
207    ///
208    /// # Errors
209    ///
210    /// Pipeline creation failed (most often shader/interface mismatch).
211    fn create_render_pipeline(
212        &self,
213        desc: &RenderPipelineDesc<'_, Self>,
214    ) -> Result<Self::Pipeline, GpuError>;
215
216    /// Creates a compute pipeline.
217    ///
218    /// # Errors
219    ///
220    /// Pipeline creation failed.
221    fn create_compute_pipeline(
222        &self,
223        desc: &ComputePipelineDesc<'_, Self>,
224    ) -> Result<Self::Pipeline, GpuError>;
225
226    /// Creates a command encoder for one frame or task.
227    fn create_command_encoder(&self) -> Self::CommandEncoder;
228
229    /// Creates a timestamp query set when the capability is available.
230    ///
231    /// # Errors
232    ///
233    /// Returns a capability error when timestamp queries are unavailable.
234    fn create_timestamp_query_set(&self, count: u32) -> Result<Self::QuerySet, GpuError>;
235
236    /// The opened device's capability report.
237    fn capabilities(&self) -> &Capabilities;
238
239    /// The recorded cause once the backend has reported the device lost, and
240    /// `None` while it is healthy or when the backend cannot tell.
241    fn device_loss(&self) -> Option<crate::DeviceLoss> {
242        None
243    }
244
245    /// Reports asynchronous backend failures observed since the last check.
246    ///
247    /// # Errors
248    ///
249    /// Returns the first pending runtime diagnostic or a sticky device loss.
250    fn check_errors(&self) -> Result<(), GpuError> {
251        Ok(())
252    }
253
254    /// Returns live physical resource accounting when the backend supports it.
255    fn resource_memory(&self) -> ResourceMemory {
256        ResourceMemory::default()
257    }
258
259    /// Returns negotiated acceleration-structure ceilings.
260    ///
261    /// # Errors
262    ///
263    /// Returns a capability error when ray queries were not negotiated.
264    fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
265        Err(GpuError::Capability { name: "ray query" })
266    }
267
268    /// Allocates a BLAS with fixed geometry ceilings.
269    ///
270    /// # Errors
271    ///
272    /// Returns capability, size or backend allocation failures.
273    fn create_blas(&self, _desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
274        Err(GpuError::Capability { name: "ray query" })
275    }
276
277    /// Allocates a TLAS with a fixed instance ceiling.
278    ///
279    /// # Errors
280    ///
281    /// Returns capability, size or backend allocation failures.
282    fn create_tlas(&self, _desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
283        Err(GpuError::Capability { name: "ray query" })
284    }
285
286    /// Replaces or clears one TLAS instance.
287    ///
288    /// # Errors
289    ///
290    /// Returns a capability error or an out-of-range instance failure.
291    fn set_tlas_instance(
292        &self,
293        _tlas: &mut Self::Tlas,
294        _index: u32,
295        _instance: Option<TlasInstance<'_, Self>>,
296    ) -> Result<(), GpuError> {
297        Err(GpuError::Capability { name: "ray query" })
298    }
299
300    /// Creates a layout containing acceleration-structure slots.
301    ///
302    /// # Errors
303    ///
304    /// Returns a capability or backend layout failure.
305    fn create_ray_query_bind_group_layout(
306        &self,
307        _desc: &RayQueryBindGroupLayoutDesc<'_>,
308    ) -> Result<Self::BindGroupLayout, GpuError> {
309        Err(GpuError::Capability { name: "ray query" })
310    }
311
312    /// Creates a bind group containing TLAS resources.
313    ///
314    /// # Errors
315    ///
316    /// Returns a capability or backend binding failure.
317    fn create_ray_query_bind_group(
318        &self,
319        _desc: &RayQueryBindGroupDesc<'_, Self>,
320    ) -> Result<Self::BindGroup, GpuError> {
321        Err(GpuError::Capability { name: "ray query" })
322    }
323}
324
325/// Device extension for hardware acceleration structures and WGSL ray queries.
326///
327/// Implementations must return [`GpuError::Capability`] when the opened device
328/// did not negotiate the ray-query feature. Keeping this separate from
329/// [`Device`] lets portable mocks and browser-only backends remain minimal.
330pub trait RayQueryDevice: Device {
331    /// Returns negotiated acceleration-structure ceilings.
332    ///
333    /// # Errors
334    ///
335    /// Returns a capability error when ray queries were not negotiated.
336    fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
337        Device::ray_query_limits(self)
338    }
339
340    /// Allocates a BLAS with fixed geometry ceilings.
341    ///
342    /// # Errors
343    ///
344    /// Returns capability, size or backend allocation failures.
345    fn create_blas(&self, desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
346        Device::create_blas(self, desc)
347    }
348
349    /// Allocates a TLAS with a fixed instance ceiling.
350    ///
351    /// # Errors
352    ///
353    /// Returns capability, size or backend allocation failures.
354    fn create_tlas(&self, desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
355        Device::create_tlas(self, desc)
356    }
357
358    /// Replaces or clears one TLAS instance without exposing backend types.
359    ///
360    /// # Errors
361    ///
362    /// Returns a capability error or an out-of-range instance failure.
363    fn set_tlas_instance(
364        &self,
365        tlas: &mut Self::Tlas,
366        index: u32,
367        instance: Option<TlasInstance<'_, Self>>,
368    ) -> Result<(), GpuError> {
369        Device::set_tlas_instance(self, tlas, index, instance)
370    }
371
372    /// Creates a layout containing acceleration-structure slots.
373    ///
374    /// # Errors
375    ///
376    /// Returns a capability or backend layout failure.
377    fn create_ray_query_bind_group_layout(
378        &self,
379        desc: &RayQueryBindGroupLayoutDesc<'_>,
380    ) -> Result<Self::BindGroupLayout, GpuError> {
381        Device::create_ray_query_bind_group_layout(self, desc)
382    }
383
384    /// Creates a bind group containing TLAS resources.
385    ///
386    /// # Errors
387    ///
388    /// Returns a capability or backend binding failure.
389    fn create_ray_query_bind_group(
390        &self,
391        desc: &RayQueryBindGroupDesc<'_, Self>,
392    ) -> Result<Self::BindGroup, GpuError> {
393        Device::create_ray_query_bind_group(self, desc)
394    }
395}
396
397impl<D: Device> RayQueryDevice for D {}