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