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::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
49/// Version of `make_spirv` intended for use with [`Device::create_shader_module_passthrough`].
50/// Returns a raw slice instead of [`ShaderSource`](super::ShaderSource).
51///
52/// [`Device::create_shader_module_passthrough`]: crate::Device::create_shader_module_passthrough
53pub fn make_spirv_raw(data: &[u8]) -> Cow<'_, [u32]> {
54 const MAGIC_NUMBER: u32 = 0x0723_0203;
55 assert_eq!(
56 data.len() % size_of::<u32>(),
57 0,
58 "data size is not a multiple of 4"
59 );
60 assert_ne!(data.len(), 0, "data size must be larger than zero");
61
62 // If the data happens to be aligned, directly use the byte array,
63 // otherwise copy the byte array in an owned vector and use that instead.
64 let mut words = if data.as_ptr().align_offset(align_of::<u32>()) == 0 {
65 let (pre, words, post) = unsafe { data.align_to::<u32>() };
66 debug_assert!(pre.is_empty());
67 debug_assert!(post.is_empty());
68 Cow::from(words)
69 } else {
70 let mut words = vec![0u32; data.len() / size_of::<u32>()];
71 unsafe {
72 copy_nonoverlapping(data.as_ptr(), words.as_mut_ptr() as *mut u8, data.len());
73 }
74 Cow::from(words)
75 };
76
77 // Before checking if the data starts with the magic, check if it starts
78 // with the magic in non-native endianness, own & swap the data if so.
79 if words[0] == MAGIC_NUMBER.swap_bytes() {
80 for word in Cow::to_mut(&mut words) {
81 *word = word.swap_bytes();
82 }
83 }
84
85 assert_eq!(
86 words[0], MAGIC_NUMBER,
87 "wrong magic word {:x}. Make sure you are using a binary SPIRV file.",
88 words[0]
89 );
90
91 words
92}
93
94/// CPU accessible buffer used to download data back from the GPU.
95pub struct DownloadBuffer {
96 _gpu_buffer: super::Buffer,
97 mapped_range: dispatch::DispatchBufferMappedRange,
98}
99
100impl DownloadBuffer {
101 /// Asynchronously read the contents of a buffer.
102 pub fn read_buffer(
103 device: &super::Device,
104 queue: &super::Queue,
105 buffer: &super::BufferSlice<'_>,
106 callback: impl FnOnce(Result<Self, super::BufferAsyncError>) + Send + 'static,
107 ) {
108 let size = buffer.size.into();
109
110 let download = device.create_buffer(&super::BufferDescriptor {
111 size,
112 usage: super::BufferUsages::COPY_DST | super::BufferUsages::MAP_READ,
113 mapped_at_creation: false,
114 label: None,
115 });
116
117 let mut encoder =
118 device.create_command_encoder(&super::CommandEncoderDescriptor { label: None });
119 encoder.copy_buffer_to_buffer(buffer.buffer, buffer.offset, &download, 0, size);
120 let command_buffer: super::CommandBuffer = encoder.finish();
121 queue.submit(Some(command_buffer));
122
123 download
124 .clone()
125 .slice(..)
126 .map_async(super::MapMode::Read, move |result| {
127 if let Err(e) = result {
128 callback(Err(e));
129 return;
130 }
131
132 let mapped_range = download.inner.get_mapped_range(0..size);
133 callback(Ok(Self {
134 _gpu_buffer: download,
135 mapped_range,
136 }));
137 });
138 }
139}
140
141impl core::ops::Deref for DownloadBuffer {
142 type Target = [u8];
143 fn deref(&self) -> &[u8] {
144 self.mapped_range.slice()
145 }
146}
147
148/// A recommended key for storing [`PipelineCache`]s for the adapter
149/// associated with the given [`AdapterInfo`](wgt::AdapterInfo)
150/// This key will define a class of adapters for which the same cache
151/// might be valid.
152///
153/// If this returns `None`, the adapter doesn't support [`PipelineCache`].
154/// This may be because the API doesn't support application managed caches
155/// (such as browser WebGPU), or that `wgpu` hasn't implemented it for
156/// that API yet.
157///
158/// This key could be used as a filename, as seen in the example below.
159///
160/// # Examples
161///
162/// ```no_run
163/// # use std::path::PathBuf;
164/// use wgpu::PipelineCacheDescriptor;
165/// # let adapter_info = todo!();
166/// # let device: wgpu::Device = todo!();
167/// let cache_dir: PathBuf = unimplemented!("Some reasonable platform-specific cache directory for your app.");
168/// let filename = wgpu::util::pipeline_cache_key(&adapter_info);
169/// let (pipeline_cache, cache_file) = if let Some(filename) = filename {
170/// let cache_path = cache_dir.join(&filename);
171/// // If we failed to read the cache, for whatever reason, treat the data as lost.
172/// // In a real app, we'd probably avoid caching entirely unless the error was "file not found".
173/// let cache_data = std::fs::read(&cache_path).ok();
174/// let pipeline_cache = unsafe {
175/// device.create_pipeline_cache(&PipelineCacheDescriptor {
176/// data: cache_data.as_deref(),
177/// label: None,
178/// fallback: true
179/// })
180/// };
181/// (Some(pipeline_cache), Some(cache_path))
182/// } else {
183/// (None, None)
184/// };
185///
186/// // Run pipeline initialisation, making sure to set the `cache`
187/// // fields of your `*PipelineDescriptor` to `pipeline_cache`
188///
189/// // And then save the resulting cache (probably off the main thread).
190/// if let (Some(pipeline_cache), Some(cache_file)) = (pipeline_cache, cache_file) {
191/// let data = pipeline_cache.get_data();
192/// if let Some(data) = data {
193/// let temp_file = cache_file.with_extension("temp");
194/// std::fs::write(&temp_file, &data)?;
195/// std::fs::rename(&temp_file, &cache_file)?;
196/// }
197/// }
198/// # Ok::<_, std::io::Error>(())
199/// ```
200///
201/// [`PipelineCache`]: super::PipelineCache
202pub fn pipeline_cache_key(adapter_info: &wgt::AdapterInfo) -> Option<String> {
203 match adapter_info.backend {
204 wgt::Backend::Vulkan => Some(format!(
205 // The vendor/device should uniquely define a driver
206 // We/the driver will also later validate that the vendor/device and driver
207 // version match, which may lead to clearing an outdated
208 // cache for the same device.
209 "wgpu_pipeline_cache_vulkan_{}_{}",
210 adapter_info.vendor, adapter_info.device
211 )),
212 _ => None,
213 }
214}
215
216/// Adds extra conversion functions to `TextureFormat`.
217pub trait TextureFormatExt {
218 /// Finds the [`TextureFormat`](wgt::TextureFormat) corresponding to the given
219 /// [`StorageFormat`](wgc::naga::StorageFormat).
220 ///
221 /// # Examples
222 /// ```
223 /// use wgpu::util::TextureFormatExt;
224 /// assert_eq!(wgpu::TextureFormat::from_storage_format(wgpu::naga::StorageFormat::Bgra8Unorm), wgpu::TextureFormat::Bgra8Unorm);
225 /// ```
226 #[cfg(wgpu_core)]
227 fn from_storage_format(storage_format: crate::naga::StorageFormat) -> Self;
228
229 /// Finds the [`StorageFormat`](wgc::naga::StorageFormat) corresponding to the given [`TextureFormat`](wgt::TextureFormat).
230 /// Returns `None` if there is no matching storage format,
231 /// which typically indicates this format is not supported
232 /// for storage textures.
233 ///
234 /// # Examples
235 /// ```
236 /// use wgpu::util::TextureFormatExt;
237 /// assert_eq!(wgpu::TextureFormat::Bgra8Unorm.to_storage_format(), Some(wgpu::naga::StorageFormat::Bgra8Unorm));
238 /// ```
239 #[cfg(wgpu_core)]
240 fn to_storage_format(&self) -> Option<crate::naga::StorageFormat>;
241}
242
243impl TextureFormatExt for wgt::TextureFormat {
244 #[cfg(wgpu_core)]
245 fn from_storage_format(storage_format: crate::naga::StorageFormat) -> Self {
246 wgc::map_storage_format_from_naga(storage_format)
247 }
248
249 #[cfg(wgpu_core)]
250 fn to_storage_format(&self) -> Option<crate::naga::StorageFormat> {
251 wgc::map_storage_format_to_naga(*self)
252 }
253}