wgpu/util/
mod.rs

1//! Utility structures and functions that are built on top of the main `wgpu` API.
2//!
3//! Nothing in this module is a part of the WebGPU API specification;
4//! they are unique to the `wgpu` library.
5
6// TODO: For [`belt::StagingBelt`] to be available in `no_std` its usage of [`std::sync::mpsc`]
7// must be replaced with an appropriate alternative.
8#[cfg(std)]
9mod belt;
10mod device;
11mod encoder;
12mod init;
13mod mutex;
14mod texture_blitter;
15
16use alloc::{borrow::Cow, format, string::String, vec};
17use core::{mem, ptr::copy_nonoverlapping};
18
19#[cfg(std)]
20pub use belt::StagingBelt;
21pub use device::{BufferInitDescriptor, DeviceExt};
22pub use encoder::RenderEncoder;
23pub use init::*;
24#[cfg(feature = "wgsl")]
25pub use texture_blitter::{TextureBlitter, TextureBlitterBuilder};
26pub use wgt::{
27    math::*, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs, TextureDataOrder,
28};
29
30pub(crate) use mutex::Mutex;
31
32use crate::dispatch;
33
34/// Treat the given byte slice as a SPIR-V module.
35///
36/// # Panic
37///
38/// This function panics if:
39///
40/// - Input length isn't multiple of 4
41/// - Input is longer than [`usize::MAX`]
42/// - Input is empty
43/// - SPIR-V magic number is missing from beginning of stream
44#[cfg(feature = "spirv")]
45pub fn make_spirv(data: &[u8]) -> super::ShaderSource<'_> {
46    super::ShaderSource::SpirV(make_spirv_raw(data))
47}
48
49const SPIRV_MAGIC_NUMBER: u32 = 0x0723_0203;
50
51const fn check_spirv_len(data: &[u8]) {
52    assert!(
53        data.len() % size_of::<u32>() == 0,
54        "SPIRV data size must be a multiple of 4."
55    );
56    assert!(!data.is_empty(), "SPIRV data must not be empty.");
57}
58
59const fn verify_spirv_magic(words: &[u32]) {
60    assert!(
61        words[0] == SPIRV_MAGIC_NUMBER,
62        "Wrong magic word in data. Make sure you are using a binary SPIRV file.",
63    );
64}
65
66/// Version of `make_spirv` intended for use with [`Device::create_shader_module_passthrough`].
67/// Returns a raw slice instead of [`ShaderSource`](super::ShaderSource).
68///
69/// [`Device::create_shader_module_passthrough`]: crate::Device::create_shader_module_passthrough
70pub fn make_spirv_raw(data: &[u8]) -> Cow<'_, [u32]> {
71    check_spirv_len(data);
72
73    // If the data happens to be aligned, directly use the byte array,
74    // otherwise copy the byte array in an owned vector and use that instead.
75    let mut words = if data.as_ptr().align_offset(align_of::<u32>()) == 0 {
76        let (pre, words, post) = unsafe { data.align_to::<u32>() };
77        debug_assert!(pre.is_empty());
78        debug_assert!(post.is_empty());
79        Cow::from(words)
80    } else {
81        let mut words = vec![0u32; data.len() / size_of::<u32>()];
82        unsafe {
83            copy_nonoverlapping(data.as_ptr(), words.as_mut_ptr() as *mut u8, data.len());
84        }
85        Cow::from(words)
86    };
87
88    // Before checking if the data starts with the magic, check if it starts
89    // with the magic in non-native endianness, own & swap the data if so.
90    if words[0] == SPIRV_MAGIC_NUMBER.swap_bytes() {
91        for word in Cow::to_mut(&mut words) {
92            *word = word.swap_bytes();
93        }
94    }
95
96    verify_spirv_magic(&words);
97
98    words
99}
100
101/// Version of `make_spirv_raw` used for implementing [`include_spirv!`] and [`include_spirv_raw!`] macros.
102///
103/// Not public API. Also, don't even try calling at runtime; you'll get a stack overflow.
104///
105/// [`include_spirv!`]: crate::include_spirv
106#[doc(hidden)]
107pub const fn make_spirv_const<const IN: usize, const OUT: usize>(data: [u8; IN]) -> [u32; OUT] {
108    #[repr(align(4))]
109    struct Aligned<T: ?Sized>(T);
110
111    check_spirv_len(&data);
112
113    // NOTE: to get around lack of generic const expressions
114    assert!(IN / 4 == OUT);
115
116    let aligned = Aligned(data);
117    let mut words: [u32; OUT] = unsafe { mem::transmute_copy(&aligned) };
118
119    // Before checking if the data starts with the magic, check if it starts
120    // with the magic in non-native endianness, own & swap the data if so.
121    if words[0] == SPIRV_MAGIC_NUMBER.swap_bytes() {
122        let mut idx = 0;
123        while idx < words.len() {
124            words[idx] = words[idx].swap_bytes();
125            idx += 1;
126        }
127    }
128
129    verify_spirv_magic(&words);
130
131    words
132}
133
134#[should_panic = "multiple of 4"]
135#[test]
136fn make_spirv_le_fail() {
137    let _: [u32; 1] = make_spirv_const([0x03, 0x02, 0x23, 0x07, 0x44, 0x33]);
138}
139
140#[should_panic = "multiple of 4"]
141#[test]
142fn make_spirv_be_fail() {
143    let _: [u32; 1] = make_spirv_const([0x07, 0x23, 0x02, 0x03, 0x11, 0x22]);
144}
145
146#[should_panic = "empty"]
147#[test]
148fn make_spirv_empty() {
149    let _: [u32; 0] = make_spirv_const([]);
150}
151
152/// CPU accessible buffer used to download data back from the GPU.
153pub struct DownloadBuffer {
154    _gpu_buffer: super::Buffer,
155    mapped_range: dispatch::DispatchBufferMappedRange,
156}
157
158impl DownloadBuffer {
159    /// Asynchronously read the contents of a buffer.
160    pub fn read_buffer(
161        device: &super::Device,
162        queue: &super::Queue,
163        buffer: &super::BufferSlice<'_>,
164        callback: impl FnOnce(Result<Self, super::BufferAsyncError>) + Send + 'static,
165    ) {
166        let size = buffer.size.into();
167
168        let download = device.create_buffer(&super::BufferDescriptor {
169            size,
170            usage: super::BufferUsages::COPY_DST | super::BufferUsages::MAP_READ,
171            mapped_at_creation: false,
172            label: None,
173        });
174
175        let mut encoder =
176            device.create_command_encoder(&super::CommandEncoderDescriptor { label: None });
177        encoder.copy_buffer_to_buffer(buffer.buffer, buffer.offset, &download, 0, size);
178        let command_buffer: super::CommandBuffer = encoder.finish();
179        queue.submit(Some(command_buffer));
180
181        download
182            .clone()
183            .slice(..)
184            .map_async(super::MapMode::Read, move |result| {
185                if let Err(e) = result {
186                    callback(Err(e));
187                    return;
188                }
189
190                let mapped_range = download.inner.get_mapped_range(0..size);
191                callback(Ok(Self {
192                    _gpu_buffer: download,
193                    mapped_range,
194                }));
195            });
196    }
197}
198
199impl core::ops::Deref for DownloadBuffer {
200    type Target = [u8];
201    fn deref(&self) -> &[u8] {
202        self.mapped_range.slice()
203    }
204}
205
206/// A recommended key for storing [`PipelineCache`]s for the adapter
207/// associated with the given [`AdapterInfo`](wgt::AdapterInfo)
208/// This key will define a class of adapters for which the same cache
209/// might be valid.
210///
211/// If this returns `None`, the adapter doesn't support [`PipelineCache`].
212/// This may be because the API doesn't support application managed caches
213/// (such as browser WebGPU), or that `wgpu` hasn't implemented it for
214/// that API yet.
215///
216/// This key could be used as a filename, as seen in the example below.
217///
218/// # Examples
219///
220/// ```no_run
221/// # use std::path::PathBuf;
222/// use wgpu::PipelineCacheDescriptor;
223/// # let adapter_info = todo!();
224/// # let device: wgpu::Device = todo!();
225/// let cache_dir: PathBuf = unimplemented!("Some reasonable platform-specific cache directory for your app.");
226/// let filename = wgpu::util::pipeline_cache_key(&adapter_info);
227/// let (pipeline_cache, cache_file) = if let Some(filename) = filename {
228///     let cache_path = cache_dir.join(&filename);
229///     // If we failed to read the cache, for whatever reason, treat the data as lost.
230///     // In a real app, we'd probably avoid caching entirely unless the error was "file not found".
231///     let cache_data = std::fs::read(&cache_path).ok();
232///     let pipeline_cache = unsafe {
233///         device.create_pipeline_cache(&PipelineCacheDescriptor {
234///             data: cache_data.as_deref(),
235///             label: None,
236///             fallback: true
237///         })
238///     };
239///     (Some(pipeline_cache), Some(cache_path))
240/// } else {
241///     (None, None)
242/// };
243///
244/// // Run pipeline initialisation, making sure to set the `cache`
245/// // fields of your `*PipelineDescriptor` to `pipeline_cache`
246///
247/// // And then save the resulting cache (probably off the main thread).
248/// if let (Some(pipeline_cache), Some(cache_file)) = (pipeline_cache, cache_file) {
249///     let data = pipeline_cache.get_data();
250///     if let Some(data) = data {
251///         let temp_file = cache_file.with_extension("temp");
252///         std::fs::write(&temp_file, &data)?;
253///         std::fs::rename(&temp_file, &cache_file)?;
254///     }
255/// }
256/// # Ok::<_, std::io::Error>(())
257/// ```
258///
259/// [`PipelineCache`]: super::PipelineCache
260pub fn pipeline_cache_key(adapter_info: &wgt::AdapterInfo) -> Option<String> {
261    match adapter_info.backend {
262        wgt::Backend::Vulkan => Some(format!(
263            // The vendor/device should uniquely define a driver
264            // We/the driver will also later validate that the vendor/device and driver
265            // version match, which may lead to clearing an outdated
266            // cache for the same device.
267            "wgpu_pipeline_cache_vulkan_{}_{}",
268            adapter_info.vendor, adapter_info.device
269        )),
270        _ => None,
271    }
272}
273
274/// Adds extra conversion functions to `TextureFormat`.
275pub trait TextureFormatExt {
276    /// Finds the [`TextureFormat`](wgt::TextureFormat) corresponding to the given
277    /// [`StorageFormat`](wgc::naga::StorageFormat).
278    ///
279    /// # Examples
280    /// ```
281    /// use wgpu::util::TextureFormatExt;
282    /// assert_eq!(wgpu::TextureFormat::from_storage_format(wgpu::naga::StorageFormat::Bgra8Unorm), wgpu::TextureFormat::Bgra8Unorm);
283    /// ```
284    #[cfg(wgpu_core)]
285    fn from_storage_format(storage_format: crate::naga::StorageFormat) -> Self;
286
287    /// Finds the [`StorageFormat`](wgc::naga::StorageFormat) corresponding to the given [`TextureFormat`](wgt::TextureFormat).
288    /// Returns `None` if there is no matching storage format,
289    /// which typically indicates this format is not supported
290    /// for storage textures.
291    ///
292    /// # Examples
293    /// ```
294    /// use wgpu::util::TextureFormatExt;
295    /// assert_eq!(wgpu::TextureFormat::Bgra8Unorm.to_storage_format(), Some(wgpu::naga::StorageFormat::Bgra8Unorm));
296    /// ```
297    #[cfg(wgpu_core)]
298    fn to_storage_format(&self) -> Option<crate::naga::StorageFormat>;
299}
300
301impl TextureFormatExt for wgt::TextureFormat {
302    #[cfg(wgpu_core)]
303    fn from_storage_format(storage_format: crate::naga::StorageFormat) -> Self {
304        wgc::map_storage_format_from_naga(storage_format)
305    }
306
307    #[cfg(wgpu_core)]
308    fn to_storage_format(&self) -> Option<crate::naga::StorageFormat> {
309        wgc::map_storage_format_to_naga(*self)
310    }
311}