wgpu_core/device/
mod.rs

1use crate::{
2    binding_model,
3    hub::Hub,
4    id::{BindGroupLayoutId, PipelineLayoutId},
5    resource::{
6        Buffer, BufferAccessError, BufferAccessResult, BufferMapOperation, Labeled,
7        ResourceErrorIdent,
8    },
9    snatch::SnatchGuard,
10    Label, DOWNLEVEL_ERROR_MESSAGE,
11};
12
13use arrayvec::ArrayVec;
14use smallvec::SmallVec;
15use thiserror::Error;
16use wgt::{BufferAddress, DeviceLostReason, TextureFormat};
17
18use std::num::NonZeroU32;
19
20pub(crate) mod bgl;
21pub mod global;
22mod life;
23pub mod queue;
24pub mod ray_tracing;
25pub mod resource;
26#[cfg(any(feature = "trace", feature = "replay"))]
27pub mod trace;
28pub use {life::WaitIdleError, resource::Device};
29
30pub const SHADER_STAGE_COUNT: usize = hal::MAX_CONCURRENT_SHADER_STAGES;
31// Should be large enough for the largest possible texture row. This
32// value is enough for a 16k texture with float4 format.
33pub(crate) const ZERO_BUFFER_SIZE: BufferAddress = 512 << 10;
34
35// If a submission is not completed within this time, we go off into UB land.
36// See https://github.com/gfx-rs/wgpu/issues/4589. 60s to reduce the chances of this.
37const CLEANUP_WAIT_MS: u32 = 60000;
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: Option<NonZeroU32>,
66}
67#[derive(Clone, Debug, Error)]
68#[non_exhaustive]
69pub enum RenderPassCompatibilityError {
70    #[error(
71        "Incompatible color attachments at indices {indices:?}: the RenderPass uses textures with formats {expected:?} but the {res} uses attachments with formats {actual:?}",
72    )]
73    IncompatibleColorAttachment {
74        indices: Vec<usize>,
75        expected: Vec<Option<TextureFormat>>,
76        actual: Vec<Option<TextureFormat>>,
77        res: ResourceErrorIdent,
78    },
79    #[error(
80        "Incompatible depth-stencil attachment format: the RenderPass uses a texture with format {expected:?} but the {res} uses an attachment with format {actual:?}",
81    )]
82    IncompatibleDepthStencilAttachment {
83        expected: Option<TextureFormat>,
84        actual: Option<TextureFormat>,
85        res: ResourceErrorIdent,
86    },
87    #[error(
88        "Incompatible sample count: the RenderPass uses textures with sample count {expected:?} but the {res} uses attachments with format {actual:?}",
89    )]
90    IncompatibleSampleCount {
91        expected: u32,
92        actual: u32,
93        res: ResourceErrorIdent,
94    },
95    #[error("Incompatible multiview setting: the RenderPass uses setting {expected:?} but the {res} uses setting {actual:?}")]
96    IncompatibleMultiview {
97        expected: Option<NonZeroU32>,
98        actual: Option<NonZeroU32>,
99        res: ResourceErrorIdent,
100    },
101}
102
103impl RenderPassContext {
104    // Assumes the renderpass only contains one subpass
105    pub(crate) fn check_compatible<T: Labeled>(
106        &self,
107        other: &Self,
108        res: &T,
109    ) -> Result<(), RenderPassCompatibilityError> {
110        if self.attachments.colors != other.attachments.colors {
111            let indices = self
112                .attachments
113                .colors
114                .iter()
115                .zip(&other.attachments.colors)
116                .enumerate()
117                .filter_map(|(idx, (left, right))| (left != right).then_some(idx))
118                .collect();
119            return Err(RenderPassCompatibilityError::IncompatibleColorAttachment {
120                indices,
121                expected: self.attachments.colors.iter().cloned().collect(),
122                actual: other.attachments.colors.iter().cloned().collect(),
123                res: res.error_ident(),
124            });
125        }
126        if self.attachments.depth_stencil != other.attachments.depth_stencil {
127            return Err(
128                RenderPassCompatibilityError::IncompatibleDepthStencilAttachment {
129                    expected: self.attachments.depth_stencil,
130                    actual: other.attachments.depth_stencil,
131                    res: res.error_ident(),
132                },
133            );
134        }
135        if self.sample_count != other.sample_count {
136            return Err(RenderPassCompatibilityError::IncompatibleSampleCount {
137                expected: self.sample_count,
138                actual: other.sample_count,
139                res: res.error_ident(),
140            });
141        }
142        if self.multiview != other.multiview {
143            return Err(RenderPassCompatibilityError::IncompatibleMultiview {
144                expected: self.multiview,
145                actual: other.multiview,
146                res: res.error_ident(),
147            });
148        }
149        Ok(())
150    }
151}
152
153pub type BufferMapPendingClosure = (BufferMapOperation, BufferAccessResult);
154
155#[derive(Default)]
156pub struct UserClosures {
157    pub mappings: Vec<BufferMapPendingClosure>,
158    pub submissions: SmallVec<[queue::SubmittedWorkDoneClosure; 1]>,
159    pub device_lost_invocations: SmallVec<[DeviceLostInvocation; 1]>,
160}
161
162impl UserClosures {
163    fn extend(&mut self, other: Self) {
164        self.mappings.extend(other.mappings);
165        self.submissions.extend(other.submissions);
166        self.device_lost_invocations
167            .extend(other.device_lost_invocations);
168    }
169
170    fn fire(self) {
171        // Note: this logic is specifically moved out of `handle_mapping()` in order to
172        // have nothing locked by the time we execute users callback code.
173
174        // Mappings _must_ be fired before submissions, as the spec requires all mapping callbacks that are registered before
175        // a on_submitted_work_done callback to be fired before the on_submitted_work_done callback.
176        for (mut operation, status) in self.mappings {
177            if let Some(callback) = operation.callback.take() {
178                callback(status);
179            }
180        }
181        for closure in self.submissions {
182            closure();
183        }
184        for invocation in self.device_lost_invocations {
185            (invocation.closure)(invocation.reason, invocation.message);
186        }
187    }
188}
189
190#[cfg(send_sync)]
191pub type DeviceLostClosure = Box<dyn FnOnce(DeviceLostReason, String) + Send + 'static>;
192#[cfg(not(send_sync))]
193pub type DeviceLostClosure = Box<dyn FnOnce(DeviceLostReason, String) + 'static>;
194
195pub struct DeviceLostInvocation {
196    closure: DeviceLostClosure,
197    reason: DeviceLostReason,
198    message: String,
199}
200
201pub(crate) fn map_buffer(
202    buffer: &Buffer,
203    offset: BufferAddress,
204    size: BufferAddress,
205    kind: HostMap,
206    snatch_guard: &SnatchGuard,
207) -> Result<hal::BufferMapping, BufferAccessError> {
208    let raw_device = buffer.device.raw();
209    let raw_buffer = buffer.try_raw(snatch_guard)?;
210    let mapping = unsafe {
211        raw_device
212            .map_buffer(raw_buffer, offset..offset + size)
213            .map_err(|e| buffer.device.handle_hal_error(e))?
214    };
215
216    if !mapping.is_coherent && kind == HostMap::Read {
217        #[allow(clippy::single_range_in_vec_init)]
218        unsafe {
219            raw_device.invalidate_mapped_ranges(raw_buffer, &[offset..offset + size]);
220        }
221    }
222
223    assert_eq!(offset % wgt::COPY_BUFFER_ALIGNMENT, 0);
224    assert_eq!(size % wgt::COPY_BUFFER_ALIGNMENT, 0);
225    // Zero out uninitialized parts of the mapping. (Spec dictates all resources
226    // behave as if they were initialized with zero)
227    //
228    // If this is a read mapping, ideally we would use a `clear_buffer` command
229    // before reading the data from GPU (i.e. `invalidate_range`). However, this
230    // would require us to kick off and wait for a command buffer or piggy back
231    // on an existing one (the later is likely the only worthwhile option). As
232    // reading uninitialized memory isn't a particular important path to
233    // support, we instead just initialize the memory here and make sure it is
234    // GPU visible, so this happens at max only once for every buffer region.
235    //
236    // If this is a write mapping zeroing out the memory here is the only
237    // reasonable way as all data is pushed to GPU anyways.
238
239    let mapped = unsafe { std::slice::from_raw_parts_mut(mapping.ptr.as_ptr(), size as usize) };
240
241    // We can't call flush_mapped_ranges in this case, so we can't drain the uninitialized ranges either
242    if !mapping.is_coherent
243        && kind == HostMap::Read
244        && !buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
245    {
246        for uninitialized in buffer
247            .initialization_status
248            .write()
249            .uninitialized(offset..(size + offset))
250        {
251            // The mapping's pointer is already offset, however we track the
252            // uninitialized range relative to the buffer's start.
253            let fill_range =
254                (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
255            mapped[fill_range].fill(0);
256        }
257    } else {
258        for uninitialized in buffer
259            .initialization_status
260            .write()
261            .drain(offset..(size + offset))
262        {
263            // The mapping's pointer is already offset, however we track the
264            // uninitialized range relative to the buffer's start.
265            let fill_range =
266                (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize;
267            mapped[fill_range].fill(0);
268
269            // NOTE: This is only possible when MAPPABLE_PRIMARY_BUFFERS is enabled.
270            if !mapping.is_coherent
271                && kind == HostMap::Read
272                && buffer.usage.contains(wgt::BufferUsages::MAP_WRITE)
273            {
274                unsafe { raw_device.flush_mapped_ranges(raw_buffer, &[uninitialized]) };
275            }
276        }
277    }
278
279    Ok(mapping)
280}
281
282#[derive(Clone, Debug)]
283#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
284pub struct DeviceMismatch {
285    pub(super) res: ResourceErrorIdent,
286    pub(super) res_device: ResourceErrorIdent,
287    pub(super) target: Option<ResourceErrorIdent>,
288    pub(super) target_device: ResourceErrorIdent,
289}
290
291impl std::fmt::Display for DeviceMismatch {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
293        write!(
294            f,
295            "{} of {} doesn't match {}",
296            self.res_device, self.res, self.target_device
297        )?;
298        if let Some(target) = self.target.as_ref() {
299            write!(f, " of {target}")?;
300        }
301        Ok(())
302    }
303}
304
305impl std::error::Error for DeviceMismatch {}
306
307#[derive(Clone, Debug, Error)]
308#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
309#[non_exhaustive]
310pub enum DeviceError {
311    #[error("{0} is invalid.")]
312    Invalid(ResourceErrorIdent),
313    #[error("Parent device is lost")]
314    Lost,
315    #[error("Not enough memory left.")]
316    OutOfMemory,
317    #[error("Creation of a resource failed for a reason other than running out of memory.")]
318    ResourceCreationFailed,
319    #[error(transparent)]
320    DeviceMismatch(#[from] Box<DeviceMismatch>),
321}
322
323impl DeviceError {
324    /// Only use this function in contexts where there is no `Device`.
325    ///
326    /// Use [`Device::handle_hal_error`] otherwise.
327    pub fn from_hal(error: hal::DeviceError) -> Self {
328        match error {
329            hal::DeviceError::Lost => Self::Lost,
330            hal::DeviceError::OutOfMemory => Self::OutOfMemory,
331            hal::DeviceError::ResourceCreationFailed => Self::ResourceCreationFailed,
332            hal::DeviceError::Unexpected => Self::Lost,
333        }
334    }
335}
336
337#[derive(Clone, Debug, Error)]
338#[error("Features {0:?} are required but not enabled on the device")]
339pub struct MissingFeatures(pub wgt::Features);
340
341#[derive(Clone, Debug, Error)]
342#[error(
343    "Downlevel flags {0:?} are required but not supported on the device.\n{DOWNLEVEL_ERROR_MESSAGE}",
344)]
345pub struct MissingDownlevelFlags(pub wgt::DownlevelFlags);
346
347#[derive(Clone, Debug)]
348#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
349pub struct ImplicitPipelineContext {
350    pub root_id: PipelineLayoutId,
351    pub group_ids: ArrayVec<BindGroupLayoutId, { hal::MAX_BIND_GROUPS }>,
352}
353
354pub struct ImplicitPipelineIds<'a> {
355    pub root_id: PipelineLayoutId,
356    pub group_ids: &'a [BindGroupLayoutId],
357}
358
359impl ImplicitPipelineIds<'_> {
360    fn prepare(self, hub: &Hub) -> ImplicitPipelineContext {
361        ImplicitPipelineContext {
362            root_id: hub.pipeline_layouts.prepare(Some(self.root_id)).id(),
363            group_ids: self
364                .group_ids
365                .iter()
366                .map(|id_in| hub.bind_group_layouts.prepare(Some(*id_in)).id())
367                .collect(),
368        }
369    }
370}
371
372/// Create a validator with the given validation flags.
373pub fn create_validator(
374    features: wgt::Features,
375    downlevel: wgt::DownlevelFlags,
376    flags: naga::valid::ValidationFlags,
377) -> naga::valid::Validator {
378    use naga::valid::Capabilities as Caps;
379    let mut caps = Caps::empty();
380    caps.set(
381        Caps::PUSH_CONSTANT,
382        features.contains(wgt::Features::PUSH_CONSTANTS),
383    );
384    caps.set(Caps::FLOAT64, features.contains(wgt::Features::SHADER_F64));
385    caps.set(
386        Caps::PRIMITIVE_INDEX,
387        features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX),
388    );
389    caps.set(
390        Caps::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
391        features
392            .contains(wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING),
393    );
394    caps.set(
395        Caps::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
396        features
397            .contains(wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING),
398    );
399    // TODO: This needs a proper wgpu feature
400    caps.set(
401        Caps::SAMPLER_NON_UNIFORM_INDEXING,
402        features
403            .contains(wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING),
404    );
405    caps.set(
406        Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS,
407        features.contains(wgt::Features::TEXTURE_FORMAT_16BIT_NORM),
408    );
409    caps.set(Caps::MULTIVIEW, features.contains(wgt::Features::MULTIVIEW));
410    caps.set(
411        Caps::EARLY_DEPTH_TEST,
412        features.contains(wgt::Features::SHADER_EARLY_DEPTH_TEST),
413    );
414    caps.set(
415        Caps::SHADER_INT64,
416        features.contains(wgt::Features::SHADER_INT64),
417    );
418    caps.set(
419        Caps::SHADER_INT64_ATOMIC_MIN_MAX,
420        features.intersects(
421            wgt::Features::SHADER_INT64_ATOMIC_MIN_MAX | wgt::Features::SHADER_INT64_ATOMIC_ALL_OPS,
422        ),
423    );
424    caps.set(
425        Caps::SHADER_INT64_ATOMIC_ALL_OPS,
426        features.contains(wgt::Features::SHADER_INT64_ATOMIC_ALL_OPS),
427    );
428    caps.set(
429        Caps::TEXTURE_ATOMIC,
430        features.contains(wgt::Features::TEXTURE_ATOMIC),
431    );
432    caps.set(
433        Caps::TEXTURE_INT64_ATOMIC,
434        features.contains(wgt::Features::TEXTURE_INT64_ATOMIC),
435    );
436    caps.set(
437        Caps::SHADER_FLOAT32_ATOMIC,
438        features.contains(wgt::Features::SHADER_FLOAT32_ATOMIC),
439    );
440    caps.set(
441        Caps::MULTISAMPLED_SHADING,
442        downlevel.contains(wgt::DownlevelFlags::MULTISAMPLED_SHADING),
443    );
444    caps.set(
445        Caps::DUAL_SOURCE_BLENDING,
446        features.contains(wgt::Features::DUAL_SOURCE_BLENDING),
447    );
448    caps.set(
449        Caps::CUBE_ARRAY_TEXTURES,
450        downlevel.contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES),
451    );
452    caps.set(
453        Caps::SUBGROUP,
454        features.intersects(wgt::Features::SUBGROUP | wgt::Features::SUBGROUP_VERTEX),
455    );
456    caps.set(
457        Caps::SUBGROUP_BARRIER,
458        features.intersects(wgt::Features::SUBGROUP_BARRIER),
459    );
460    caps.set(
461        Caps::RAY_QUERY,
462        features.intersects(wgt::Features::EXPERIMENTAL_RAY_QUERY),
463    );
464    caps.set(
465        Caps::SUBGROUP_VERTEX_STAGE,
466        features.contains(wgt::Features::SUBGROUP_VERTEX),
467    );
468
469    naga::valid::Validator::new(flags, caps)
470}