1use alloc::{boxed::Box, string::String, vec::Vec};
2use core::{fmt, num::NonZeroU32};
3
4use crate::{
5 binding_model,
6 ray_tracing::BlasCompactReadyPendingClosure,
7 resource::{
8 Buffer, BufferAccessError, BufferAccessResult, BufferMapOperation, Labeled,
9 RawResourceAccess, ResourceErrorIdent,
10 },
11 snatch::SnatchGuard,
12 Label, DOWNLEVEL_ERROR_MESSAGE,
13};
14
15use arrayvec::ArrayVec;
16use smallvec::SmallVec;
17use thiserror::Error;
18use wgt::{
19 error::{ErrorType, WebGpuError},
20 BufferAddress, DeviceLostReason, TextureFormat,
21};
22
23pub(crate) mod bgl;
24mod life;
25pub mod queue;
26pub mod ray_tracing;
27pub mod resource;
28pub(crate) mod surface_config;
29#[cfg(any(feature = "trace", feature = "replay"))]
30pub mod trace;
31pub use {life::WaitIdleError, resource::Device};
32
33pub const SHADER_STAGE_COUNT: usize = hal::MAX_CONCURRENT_SHADER_STAGES;
34pub(crate) const ZERO_BUFFER_SIZE: BufferAddress = 512 << 10;
37
38pub(crate) const ENTRYPOINT_FAILURE_ERROR: &str = "The given EntryPoint is Invalid";
39
40pub type DeviceDescriptor<'a> = wgt::DeviceDescriptor<Label<'a>>;
41pub type QueueDescriptor<'a> = wgt::QueueDescriptor<Label<'a>>;
42
43#[repr(C)]
44#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub enum HostMap {
47 Read,
48 Write,
49}
50
51#[derive(Clone, Debug, Hash, PartialEq)]
52#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
53pub(crate) struct AttachmentData<T> {
54 pub colors: ArrayVec<Option<T>, { hal::MAX_COLOR_ATTACHMENTS }>,
55 pub resolves: ArrayVec<T, { hal::MAX_COLOR_ATTACHMENTS }>,
56 pub depth_stencil: Option<T>,
57}
58impl<T: PartialEq> Eq for AttachmentData<T> {}
59
60#[derive(Clone, Debug, Hash, PartialEq)]
61#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
62pub(crate) struct RenderPassContext {
63 pub attachments: AttachmentData<TextureFormat>,
64 pub sample_count: u32,
65 pub multiview_mask: Option<NonZeroU32>,
66}
67
68impl Default for RenderPassContext {
69 fn default() -> Self {
70 Self {
71 attachments: AttachmentData {
72 colors: ArrayVec::new(),
73 resolves: ArrayVec::new(),
74 depth_stencil: None,
75 },
76 sample_count: Default::default(),
77 multiview_mask: Default::default(),
78 }
79 }
80}
81
82#[derive(Clone, Debug, Error)]
83#[non_exhaustive]
84pub enum RenderPassCompatibilityError {
85 #[error(
86 "Incompatible color attachments at indices {indices:?}: the RenderPass uses textures with formats {expected:?} but the {res} uses attachments with formats {actual:?}",
87 )]
88 IncompatibleColorAttachment {
89 indices: Vec<usize>,
90 expected: Vec<Option<TextureFormat>>,
91 actual: Vec<Option<TextureFormat>>,
92 res: ResourceErrorIdent,
93 },
94 #[error(
95 "Incompatible depth-stencil attachment format: the RenderPass uses a texture with format {expected:?} but the {res} uses an attachment with format {actual:?}",
96 )]
97 IncompatibleDepthStencilAttachment {
98 expected: Option<TextureFormat>,
99 actual: Option<TextureFormat>,
100 res: ResourceErrorIdent,
101 },
102 #[error(
103 "Incompatible sample count: the RenderPass uses textures with sample count {expected:?} but the {res} uses attachments with format {actual:?}",
104 )]
105 IncompatibleSampleCount {
106 expected: u32,
107 actual: u32,
108 res: ResourceErrorIdent,
109 },
110 #[error("Incompatible multiview setting: the RenderPass uses setting {expected:?} but the {res} uses setting {actual:?}")]
111 IncompatibleMultiview {
112 expected: Option<NonZeroU32>,
113 actual: Option<NonZeroU32>,
114 res: ResourceErrorIdent,
115 },
116}
117
118impl WebGpuError for RenderPassCompatibilityError {
119 fn webgpu_error_type(&self) -> ErrorType {
120 ErrorType::Validation
121 }
122}
123
124impl RenderPassContext {
125 pub(crate) fn check_compatible<T: Labeled>(
127 &self,
128 other: &Self,
129 res: &T,
130 ) -> Result<(), RenderPassCompatibilityError> {
131 if self.attachments.colors != other.attachments.colors {
132 let indices = self
133 .attachments
134 .colors
135 .iter()
136 .zip(&other.attachments.colors)
137 .enumerate()
138 .filter_map(|(idx, (left, right))| (left != right).then_some(idx))
139 .collect();
140 return Err(RenderPassCompatibilityError::IncompatibleColorAttachment {
141 indices,
142 expected: self.attachments.colors.iter().cloned().collect(),
143 actual: other.attachments.colors.iter().cloned().collect(),
144 res: res.error_ident(),
145 });
146 }
147 if self.attachments.depth_stencil != other.attachments.depth_stencil {
148 return Err(
149 RenderPassCompatibilityError::IncompatibleDepthStencilAttachment {
150 expected: self.attachments.depth_stencil,
151 actual: other.attachments.depth_stencil,
152 res: res.error_ident(),
153 },
154 );
155 }
156 if self.sample_count != other.sample_count {
157 return Err(RenderPassCompatibilityError::IncompatibleSampleCount {
158 expected: self.sample_count,
159 actual: other.sample_count,
160 res: res.error_ident(),
161 });
162 }
163 if self.multiview_mask != other.multiview_mask {
164 return Err(RenderPassCompatibilityError::IncompatibleMultiview {
165 expected: self.multiview_mask,
166 actual: other.multiview_mask,
167 res: res.error_ident(),
168 });
169 }
170 Ok(())
171 }
172}
173
174pub type BufferMapPendingClosure = (BufferMapOperation, BufferAccessResult);
175
176#[must_use]
177#[derive(Default)]
178pub struct UserClosures {
179 pub mappings: Vec<BufferMapPendingClosure>,
180 pub blas_compact_ready: Vec<BlasCompactReadyPendingClosure>,
181 pub submissions: SmallVec<[queue::SubmittedWorkDoneClosure; 1]>,
182 pub device_lost_invocations: SmallVec<[DeviceLostInvocation; 1]>,
183}
184
185impl UserClosures {
186 pub(crate) fn extend(&mut self, other: Self) {
187 self.mappings.extend(other.mappings);
188 self.blas_compact_ready.extend(other.blas_compact_ready);
189 self.submissions.extend(other.submissions);
190 self.device_lost_invocations
191 .extend(other.device_lost_invocations);
192 }
193
194 pub(crate) fn fire(self) {
195 for (mut operation, status) in self.mappings {
201 if let Some(callback) = operation.callback.take() {
202 callback(status);
203 }
204 }
205 for (mut operation, status) in self.blas_compact_ready {
206 if let Some(callback) = operation.take() {
207 callback(status);
208 }
209 }
210 for closure in self.submissions {
211 closure();
212 }
213 for invocation in self.device_lost_invocations {
214 (invocation.closure)(invocation.reason, invocation.message);
215 }
216 }
217}
218
219#[cfg(send_sync)]
220pub type DeviceLostClosure = Box<dyn FnOnce(DeviceLostReason, String) + Send + 'static>;
221#[cfg(not(send_sync))]
222pub type DeviceLostClosure = Box<dyn FnOnce(DeviceLostReason, String) + 'static>;
223
224pub struct DeviceLostInvocation {
225 closure: DeviceLostClosure,
226 reason: DeviceLostReason,
227 message: String,
228}
229
230pub(crate) fn map_buffer(
231 buffer: &Buffer,
232 offset: BufferAddress,
233 size: BufferAddress,
234 kind: HostMap,
235 snatch_guard: &SnatchGuard,
236) -> Result<hal::BufferMapping, BufferAccessError> {
237 let raw_device = buffer.device.raw();
238 let raw_buffer = buffer.try_raw(snatch_guard)?;
239 let mapping = unsafe {
240 raw_device
241 .map_buffer(raw_buffer, offset..offset + size)
242 .map_err(|e| buffer.device.handle_hal_error(e))?
243 };
244
245 if !mapping.is_coherent && kind == HostMap::Read {
246 #[allow(clippy::single_range_in_vec_init)]
247 unsafe {
248 raw_device.invalidate_mapped_ranges(raw_buffer, &[offset..offset + size]);
249 }
250 }
251
252 assert_eq!(offset % wgt::COPY_BUFFER_ALIGNMENT, 0);
253 assert_eq!(size % wgt::COPY_BUFFER_ALIGNMENT, 0);
254 let mapped = unsafe { core::slice::from_raw_parts_mut(mapping.ptr.as_ptr(), size as usize) };
269
270 if !mapping.is_coherent
272 && kind == HostMap::Read
273 && !buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
274 {
275 for uninitialized in buffer
276 .initialization_status
277 .write()
278 .uninitialized(offset..(size + offset))
279 {
280 let fill_range =
283 (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
284 mapped[fill_range].fill(0);
285 }
286 } else {
287 for uninitialized in buffer
288 .initialization_status
289 .write()
290 .drain(offset..(size + offset))
291 {
292 let fill_range =
295 (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
296 mapped[fill_range].fill(0);
297
298 if !mapping.is_coherent
300 && kind == HostMap::Read
301 && buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
302 {
303 unsafe { raw_device.flush_mapped_ranges(raw_buffer, &[uninitialized]) };
304 }
305 }
306 }
307
308 Ok(mapping)
309}
310
311#[derive(Clone, Debug)]
312#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
313pub struct DeviceMismatch {
314 pub(super) res: ResourceErrorIdent,
315 pub(super) res_device: ResourceErrorIdent,
316 pub(super) target: Option<ResourceErrorIdent>,
317 pub(super) target_device: ResourceErrorIdent,
318}
319
320impl fmt::Display for DeviceMismatch {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
322 write!(
323 f,
324 "{} of {} doesn't match {}",
325 self.res_device, self.res, self.target_device
326 )?;
327 if let Some(target) = self.target.as_ref() {
328 write!(f, " of {target}")?;
329 }
330 Ok(())
331 }
332}
333
334impl core::error::Error for DeviceMismatch {}
335
336impl WebGpuError for DeviceMismatch {
337 fn webgpu_error_type(&self) -> ErrorType {
338 ErrorType::Validation
339 }
340}
341
342#[derive(Clone, Debug, Error)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
344#[non_exhaustive]
345pub enum DeviceError {
346 #[error("Parent device is lost")]
347 Lost,
348 #[error("Not enough memory left.")]
349 OutOfMemory,
350 #[error(transparent)]
351 DeviceMismatch(#[from] Box<DeviceMismatch>),
352}
353
354impl WebGpuError for DeviceError {
355 fn webgpu_error_type(&self) -> ErrorType {
356 match self {
357 Self::DeviceMismatch(e) => e.webgpu_error_type(),
358 Self::Lost => ErrorType::DeviceLost,
359 Self::OutOfMemory => ErrorType::OutOfMemory,
360 }
361 }
362}
363
364impl DeviceError {
365 pub fn from_hal(error: hal::DeviceError) -> Self {
369 match error {
370 hal::DeviceError::Lost => Self::Lost,
371 hal::DeviceError::OutOfMemory => Self::OutOfMemory,
372 hal::DeviceError::Unexpected => Self::Lost,
373 }
374 }
375}
376
377#[derive(Clone, Debug, Error)]
378#[error("Features {0:?} are required but not enabled on the device")]
379pub struct MissingFeatures(pub wgt::Features);
380
381impl WebGpuError for MissingFeatures {
382 fn webgpu_error_type(&self) -> ErrorType {
383 ErrorType::Validation
384 }
385}
386
387#[derive(Clone, Debug, Error)]
388#[error(
389 "Downlevel flags {0:?} are required but not supported on the device.\n{DOWNLEVEL_ERROR_MESSAGE}",
390)]
391pub struct MissingDownlevelFlags(pub wgt::DownlevelFlags);
392
393impl WebGpuError for MissingDownlevelFlags {
394 fn webgpu_error_type(&self) -> ErrorType {
395 ErrorType::Validation
396 }
397}
398
399pub use wgpu_naga_bridge::create_validator;
400pub use wgpu_naga_bridge::features_to_naga_capabilities;