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/// 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 {}