wgpu_core/device/
mod.rs

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;
24pub mod global;
25mod life;
26pub mod queue;
27pub mod ray_tracing;
28pub mod resource;
29mod surface_config;
30#[cfg(any(feature = "trace", feature = "replay"))]
31pub mod trace;
32pub use {life::WaitIdleError, resource::Device};
33
34pub const SHADER_STAGE_COUNT: usize = hal::MAX_CONCURRENT_SHADER_STAGES;
35// Should be large enough for the largest possible texture row. This
36// value is enough for a 16k texture with float4 format.
37pub(crate) const ZERO_BUFFER_SIZE: BufferAddress = 512 << 10;
38
39pub(crate) const ENTRYPOINT_FAILURE_ERROR: &str = "The given EntryPoint is Invalid";
40
41pub type DeviceDescriptor<'a> = wgt::DeviceDescriptor<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    // Assumes the renderpass only contains one subpass
126    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#[derive(Default)]
177pub struct UserClosures {
178    pub mappings: Vec<BufferMapPendingClosure>,
179    pub blas_compact_ready: Vec<BlasCompactReadyPendingClosure>,
180    pub submissions: SmallVec<[queue::SubmittedWorkDoneClosure; 1]>,
181    pub device_lost_invocations: SmallVec<[DeviceLostInvocation; 1]>,
182}
183
184impl UserClosures {
185    fn extend(&mut self, other: Self) {
186        self.mappings.extend(other.mappings);
187        self.blas_compact_ready.extend(other.blas_compact_ready);
188        self.submissions.extend(other.submissions);
189        self.device_lost_invocations
190            .extend(other.device_lost_invocations);
191    }
192
193    fn fire(self) {
194        // Note: this logic is specifically moved out of `handle_mapping()` in order to
195        // have nothing locked by the time we execute users callback code.
196
197        // Mappings _must_ be fired before submissions, as the spec requires all mapping callbacks that are registered before
198        // a on_submitted_work_done callback to be fired before the on_submitted_work_done callback.
199        for (mut operation, status) in self.mappings {
200            if let Some(callback) = operation.callback.take() {
201                callback(status);
202            }
203        }
204        for (mut operation, status) in self.blas_compact_ready {
205            if let Some(callback) = operation.take() {
206                callback(status);
207            }
208        }
209        for closure in self.submissions {
210            closure();
211        }
212        for invocation in self.device_lost_invocations {
213            (invocation.closure)(invocation.reason, invocation.message);
214        }
215    }
216}
217
218#[cfg(send_sync)]
219pub type DeviceLostClosure = Box<dyn FnOnce(DeviceLostReason, String) + Send + 'static>;
220#[cfg(not(send_sync))]
221pub type DeviceLostClosure = Box<dyn FnOnce(DeviceLostReason, String) + 'static>;
222
223pub struct DeviceLostInvocation {
224    closure: DeviceLostClosure,
225    reason: DeviceLostReason,
226    message: String,
227}
228
229pub(crate) fn map_buffer(
230    buffer: &Buffer,
231    offset: BufferAddress,
232    size: BufferAddress,
233    kind: HostMap,
234    snatch_guard: &SnatchGuard,
235) -> Result<hal::BufferMapping, BufferAccessError> {
236    let raw_device = buffer.device.raw();
237    let raw_buffer = buffer.try_raw(snatch_guard)?;
238    let mapping = unsafe {
239        raw_device
240            .map_buffer(raw_buffer, offset..offset + size)
241            .map_err(|e| buffer.device.handle_hal_error(e))?
242    };
243
244    if !mapping.is_coherent && kind == HostMap::Read {
245        #[allow(clippy::single_range_in_vec_init)]
246        unsafe {
247            raw_device.invalidate_mapped_ranges(raw_buffer, &[offset..offset + size]);
248        }
249    }
250
251    assert_eq!(offset % wgt::COPY_BUFFER_ALIGNMENT, 0);
252    assert_eq!(size % wgt::COPY_BUFFER_ALIGNMENT, 0);
253    // Zero out uninitialized parts of the mapping. (Spec dictates all resources
254    // behave as if they were initialized with zero)
255    //
256    // If this is a read mapping, ideally we would use a `clear_buffer` command
257    // before reading the data from GPU (i.e. `invalidate_range`). However, this
258    // would require us to kick off and wait for a command buffer or piggy back
259    // on an existing one (the later is likely the only worthwhile option). As
260    // reading uninitialized memory isn't a particular important path to
261    // support, we instead just initialize the memory here and make sure it is
262    // GPU visible, so this happens at max only once for every buffer region.
263    //
264    // If this is a write mapping zeroing out the memory here is the only
265    // reasonable way as all data is pushed to GPU anyways.
266
267    let mapped = unsafe { core::slice::from_raw_parts_mut(mapping.ptr.as_ptr(), size as usize) };
268
269    // We can't call flush_mapped_ranges in this case, so we can't drain the uninitialized ranges either
270    if !mapping.is_coherent
271        && kind == HostMap::Read
272        && !buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
273    {
274        for uninitialized in buffer
275            .initialization_status
276            .write()
277            .uninitialized(offset..(size + offset))
278        {
279            // The mapping's pointer is already offset, however we track the
280            // uninitialized range relative to the buffer's start.
281            let fill_range =
282                (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
283            mapped[fill_range].fill(0);
284        }
285    } else {
286        for uninitialized in buffer
287            .initialization_status
288            .write()
289            .drain(offset..(size + offset))
290        {
291            // The mapping's pointer is already offset, however we track the
292            // uninitialized range relative to the buffer's start.
293            let fill_range =
294                (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
295            mapped[fill_range].fill(0);
296
297            // NOTE: This is only possible when MAPPABLE_PRIMARY_BUFFERS is enabled.
298            if !mapping.is_coherent
299                && kind == HostMap::Read
300                && buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
301            {
302                unsafe { raw_device.flush_mapped_ranges(raw_buffer, &[uninitialized]) };
303            }
304        }
305    }
306
307    Ok(mapping)
308}
309
310#[derive(Clone, Debug)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312pub struct DeviceMismatch {
313    pub(super) res: ResourceErrorIdent,
314    pub(super) res_device: ResourceErrorIdent,
315    pub(super) target: Option<ResourceErrorIdent>,
316    pub(super) target_device: ResourceErrorIdent,
317}
318
319impl fmt::Display for DeviceMismatch {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
321        write!(
322            f,
323            "{} of {} doesn't match {}",
324            self.res_device, self.res, self.target_device
325        )?;
326        if let Some(target) = self.target.as_ref() {
327            write!(f, " of {target}")?;
328        }
329        Ok(())
330    }
331}
332
333impl core::error::Error for DeviceMismatch {}
334
335impl WebGpuError for DeviceMismatch {
336    fn webgpu_error_type(&self) -> ErrorType {
337        ErrorType::Validation
338    }
339}
340
341#[derive(Clone, Debug, Error)]
342#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
343#[non_exhaustive]
344pub enum DeviceError {
345    #[error("Parent device is lost")]
346    Lost,
347    #[error("Not enough memory left.")]
348    OutOfMemory,
349    #[error(transparent)]
350    DeviceMismatch(#[from] Box<DeviceMismatch>),
351}
352
353impl WebGpuError for DeviceError {
354    fn webgpu_error_type(&self) -> ErrorType {
355        match self {
356            Self::DeviceMismatch(e) => e.webgpu_error_type(),
357            Self::Lost => ErrorType::DeviceLost,
358            Self::OutOfMemory => ErrorType::OutOfMemory,
359        }
360    }
361}
362
363impl DeviceError {
364    /// Only use this function in contexts where there is no `Device`.
365    ///
366    /// Use [`Device::handle_hal_error`] otherwise.
367    pub fn from_hal(error: hal::DeviceError) -> Self {
368        match error {
369            hal::DeviceError::Lost => Self::Lost,
370            hal::DeviceError::OutOfMemory => Self::OutOfMemory,
371            hal::DeviceError::Unexpected => Self::Lost,
372        }
373    }
374}
375
376#[derive(Clone, Debug, Error)]
377#[error("Features {0:?} are required but not enabled on the device")]
378pub struct MissingFeatures(pub wgt::Features);
379
380impl WebGpuError for MissingFeatures {
381    fn webgpu_error_type(&self) -> ErrorType {
382        ErrorType::Validation
383    }
384}
385
386#[derive(Clone, Debug, Error)]
387#[error(
388    "Downlevel flags {0:?} are required but not supported on the device.\n{DOWNLEVEL_ERROR_MESSAGE}",
389)]
390pub struct MissingDownlevelFlags(pub wgt::DownlevelFlags);
391
392impl WebGpuError for MissingDownlevelFlags {
393    fn webgpu_error_type(&self) -> ErrorType {
394        ErrorType::Validation
395    }
396}
397
398pub use wgpu_naga_bridge::create_validator;
399pub use wgpu_naga_bridge::features_to_naga_capabilities;