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 /// Reports asynchronous backend failures observed since the last check.
240 ///
241 /// # Errors
242 ///
243 /// Returns the first pending runtime diagnostic or a sticky device loss.
244 fn check_errors(&self) -> Result<(), GpuError> {
245 Ok(())
246 }
247
248 /// Returns live physical resource accounting when the backend supports it.
249 fn resource_memory(&self) -> ResourceMemory {
250 ResourceMemory::default()
251 }
252
253 /// Returns negotiated acceleration-structure ceilings.
254 ///
255 /// # Errors
256 ///
257 /// Returns a capability error when ray queries were not negotiated.
258 fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
259 Err(GpuError::Capability { name: "ray query" })
260 }
261
262 /// Allocates a BLAS with fixed geometry ceilings.
263 ///
264 /// # Errors
265 ///
266 /// Returns capability, size or backend allocation failures.
267 fn create_blas(&self, _desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
268 Err(GpuError::Capability { name: "ray query" })
269 }
270
271 /// Allocates a TLAS with a fixed instance ceiling.
272 ///
273 /// # Errors
274 ///
275 /// Returns capability, size or backend allocation failures.
276 fn create_tlas(&self, _desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
277 Err(GpuError::Capability { name: "ray query" })
278 }
279
280 /// Replaces or clears one TLAS instance.
281 ///
282 /// # Errors
283 ///
284 /// Returns a capability error or an out-of-range instance failure.
285 fn set_tlas_instance(
286 &self,
287 _tlas: &mut Self::Tlas,
288 _index: u32,
289 _instance: Option<TlasInstance<'_, Self>>,
290 ) -> Result<(), GpuError> {
291 Err(GpuError::Capability { name: "ray query" })
292 }
293
294 /// Creates a layout containing acceleration-structure slots.
295 ///
296 /// # Errors
297 ///
298 /// Returns a capability or backend layout failure.
299 fn create_ray_query_bind_group_layout(
300 &self,
301 _desc: &RayQueryBindGroupLayoutDesc<'_>,
302 ) -> Result<Self::BindGroupLayout, GpuError> {
303 Err(GpuError::Capability { name: "ray query" })
304 }
305
306 /// Creates a bind group containing TLAS resources.
307 ///
308 /// # Errors
309 ///
310 /// Returns a capability or backend binding failure.
311 fn create_ray_query_bind_group(
312 &self,
313 _desc: &RayQueryBindGroupDesc<'_, Self>,
314 ) -> Result<Self::BindGroup, GpuError> {
315 Err(GpuError::Capability { name: "ray query" })
316 }
317}
318
319/// Device extension for hardware acceleration structures and WGSL ray queries.
320///
321/// Implementations must return [`GpuError::Capability`] when the opened device
322/// did not negotiate the ray-query feature. Keeping this separate from
323/// [`Device`] lets portable mocks and browser-only backends remain minimal.
324pub trait RayQueryDevice: Device {
325 /// Returns negotiated acceleration-structure ceilings.
326 ///
327 /// # Errors
328 ///
329 /// Returns a capability error when ray queries were not negotiated.
330 fn ray_query_limits(&self) -> Result<RayQueryLimits, GpuError> {
331 Device::ray_query_limits(self)
332 }
333
334 /// Allocates a BLAS with fixed geometry ceilings.
335 ///
336 /// # Errors
337 ///
338 /// Returns capability, size or backend allocation failures.
339 fn create_blas(&self, desc: &BlasDesc<'_>) -> Result<Self::Blas, GpuError> {
340 Device::create_blas(self, desc)
341 }
342
343 /// Allocates a TLAS with a fixed instance ceiling.
344 ///
345 /// # Errors
346 ///
347 /// Returns capability, size or backend allocation failures.
348 fn create_tlas(&self, desc: &TlasDesc) -> Result<Self::Tlas, GpuError> {
349 Device::create_tlas(self, desc)
350 }
351
352 /// Replaces or clears one TLAS instance without exposing backend types.
353 ///
354 /// # Errors
355 ///
356 /// Returns a capability error or an out-of-range instance failure.
357 fn set_tlas_instance(
358 &self,
359 tlas: &mut Self::Tlas,
360 index: u32,
361 instance: Option<TlasInstance<'_, Self>>,
362 ) -> Result<(), GpuError> {
363 Device::set_tlas_instance(self, tlas, index, instance)
364 }
365
366 /// Creates a layout containing acceleration-structure slots.
367 ///
368 /// # Errors
369 ///
370 /// Returns a capability or backend layout failure.
371 fn create_ray_query_bind_group_layout(
372 &self,
373 desc: &RayQueryBindGroupLayoutDesc<'_>,
374 ) -> Result<Self::BindGroupLayout, GpuError> {
375 Device::create_ray_query_bind_group_layout(self, desc)
376 }
377
378 /// Creates a bind group containing TLAS resources.
379 ///
380 /// # Errors
381 ///
382 /// Returns a capability or backend binding failure.
383 fn create_ray_query_bind_group(
384 &self,
385 desc: &RayQueryBindGroupDesc<'_, Self>,
386 ) -> Result<Self::BindGroup, GpuError> {
387 Device::create_ray_query_bind_group(self, desc)
388 }
389}
390
391impl<D: Device> RayQueryDevice for D {}