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 /// Compiles a WGSL shader module.
200 ///
201 /// # Errors
202 ///
203 /// Compilation failed; the error carries the backend's detail.
204 fn create_shader_module(
205 &self,
206 desc: &ShaderModuleDesc<'_>,
207 ) -> Result<Self::ShaderModule, GpuError>;
208
209 /// Creates a bind-group layout.
210 fn create_bind_group_layout(&self, desc: &BindGroupLayoutDesc<'_>) -> Self::BindGroupLayout;
211
212 /// Creates a bind group over a layout.
213 fn create_bind_group(&self, desc: &BindGroupDesc<'_, Self>) -> Self::BindGroup;
214
215 /// Creates a render pipeline. Created at load, cached by the caller.
216 ///
217 /// # Errors
218 ///
219 /// Pipeline creation failed (most often shader/interface mismatch).
220 fn create_render_pipeline(
221 &self,
222 desc: &RenderPipelineDesc<'_, Self>,
223 ) -> Result<Self::Pipeline, GpuError>;
224
225 /// Creates a compute pipeline.
226 ///
227 /// # Errors
228 ///
229 /// Pipeline creation failed.
230 fn create_compute_pipeline(
231 &self,
232 desc: &ComputePipelineDesc<'_, Self>,
233 ) -> Result<Self::Pipeline, GpuError>;
234
235 /// Creates a command encoder for one frame or task.
236 fn create_command_encoder(&self) -> Self::CommandEncoder;
237
238 /// Creates a timestamp query set when the capability is available.
239 ///
240 /// # Errors
241 ///
242 /// Returns a capability error when timestamp queries are unavailable.
243 fn create_timestamp_query_set(&self, count: u32) -> Result<Self::QuerySet, GpuError>;
244
245 /// The opened device's capability report.
246 fn capabilities(&self) -> &Capabilities;
247
248 /// The recorded cause once the backend has reported the device lost, and
249 /// `None` while it is healthy or when the backend cannot tell.
250 fn device_loss(&self) -> Option<crate::DeviceLoss> {
251 None
252 }
253
254 /// Reports asynchronous backend failures observed since the last check.
255 ///
256 /// # Errors
257 ///
258 /// Returns the first pending runtime diagnostic or a sticky device loss.
259 fn check_errors(&self) -> Result<(), GpuError> {
260 Ok(())
261 }
262
263 /// Advances completed GPU work without waiting for it.
264 ///
265 /// Fence waits and mapped readbacks resolve only as the embedder calls
266 /// this: no future blocks the thread to make progress on its own. Backends
267 /// that deliver completions from their own event loop have nothing to
268 /// drive and report [`PollOutcome::Idle`].
269 ///
270 /// # Errors
271 ///
272 /// Returns a pending runtime diagnostic or [`GpuError::DeviceLost`].
273 fn poll(&self) -> Result<PollOutcome, GpuError> {
274 self.check_errors()?;
275 Ok(PollOutcome::Idle)
276 }
277
278 /// Returns live physical resource accounting when the backend supports it.
279 fn resource_memory(&self) -> ResourceMemory {
280 ResourceMemory::default()
281 }
282
283 /// Returns negotiated acceleration-structure ceilings.
284 ///
285 /// # Errors
286 ///
287 /// Returns a capability error when ray queries were not negotiated.
288 fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
289 Err(GpuError::Capability { name: "ray query" })
290 }
291
292 /// Allocates a BLAS with fixed geometry ceilings.
293 ///
294 /// # Errors
295 ///
296 /// Returns capability, size or backend allocation failures.
297 fn create_blas(&self, _desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
298 Err(GpuError::Capability { name: "ray query" })
299 }
300
301 /// Allocates a TLAS with a fixed instance ceiling.
302 ///
303 /// # Errors
304 ///
305 /// Returns capability, size or backend allocation failures.
306 fn create_tlas(&self, _desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
307 Err(GpuError::Capability { name: "ray query" })
308 }
309
310 /// Replaces or clears one TLAS instance.
311 ///
312 /// # Errors
313 ///
314 /// Returns a capability error or an out-of-range instance failure.
315 fn set_tlas_instance(
316 &self,
317 _tlas: &mut Self::Tlas,
318 _index: u32,
319 _instance: Option<TlasInstance<'_, Self>>,
320 ) -> Result<(), GpuError> {
321 Err(GpuError::Capability { name: "ray query" })
322 }
323
324 /// Creates a layout containing acceleration-structure slots.
325 ///
326 /// # Errors
327 ///
328 /// Returns a capability or backend layout failure.
329 fn create_ray_query_bind_group_layout(
330 &self,
331 _desc: &RayQueryBindGroupLayoutDesc<'_>,
332 ) -> Result<Self::BindGroupLayout, GpuError> {
333 Err(GpuError::Capability { name: "ray query" })
334 }
335
336 /// Creates a bind group containing TLAS resources.
337 ///
338 /// # Errors
339 ///
340 /// Returns a capability or backend binding failure.
341 fn create_ray_query_bind_group(
342 &self,
343 _desc: &RayQueryBindGroupDesc<'_, Self>,
344 ) -> Result<Self::BindGroup, GpuError> {
345 Err(GpuError::Capability { name: "ray query" })
346 }
347}
348
349/// Device extension for hardware acceleration structures and WGSL ray queries.
350///
351/// Implementations must return [`GpuError::Capability`] when the opened device
352/// did not negotiate the ray-query feature. Keeping this separate from
353/// [`Device`] lets portable mocks and browser-only backends remain minimal.
354pub trait RayQueryDevice: Device {
355 /// Returns negotiated acceleration-structure ceilings.
356 ///
357 /// # Errors
358 ///
359 /// Returns a capability error when ray queries were not negotiated.
360 fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
361 Device::ray_query_limits(self)
362 }
363
364 /// Allocates a BLAS with fixed geometry ceilings.
365 ///
366 /// # Errors
367 ///
368 /// Returns capability, size or backend allocation failures.
369 fn create_blas(&self, desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
370 Device::create_blas(self, desc)
371 }
372
373 /// Allocates a TLAS with a fixed instance ceiling.
374 ///
375 /// # Errors
376 ///
377 /// Returns capability, size or backend allocation failures.
378 fn create_tlas(&self, desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
379 Device::create_tlas(self, desc)
380 }
381
382 /// Replaces or clears one TLAS instance without exposing backend types.
383 ///
384 /// # Errors
385 ///
386 /// Returns a capability error or an out-of-range instance failure.
387 fn set_tlas_instance(
388 &self,
389 tlas: &mut Self::Tlas,
390 index: u32,
391 instance: Option<TlasInstance<'_, Self>>,
392 ) -> Result<(), GpuError> {
393 Device::set_tlas_instance(self, tlas, index, instance)
394 }
395
396 /// Creates a layout containing acceleration-structure slots.
397 ///
398 /// # Errors
399 ///
400 /// Returns a capability or backend layout failure.
401 fn create_ray_query_bind_group_layout(
402 &self,
403 desc: &RayQueryBindGroupLayoutDesc<'_>,
404 ) -> Result<Self::BindGroupLayout, GpuError> {
405 Device::create_ray_query_bind_group_layout(self, desc)
406 }
407
408 /// Creates a bind group containing TLAS resources.
409 ///
410 /// # Errors
411 ///
412 /// Returns a capability or backend binding failure.
413 fn create_ray_query_bind_group(
414 &self,
415 desc: &RayQueryBindGroupDesc<'_, Self>,
416 ) -> Result<Self::BindGroup, GpuError> {
417 Device::create_ray_query_bind_group(self, desc)
418 }
419}
420
421impl<D: Device> RayQueryDevice for D {}