wgpu_types/shader.rs
1use alloc::borrow::Cow;
2
3/// Describes how shader bound checks should be performed.
4#[derive(Copy, Clone, Debug)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct ShaderRuntimeChecks {
7 /// Enforce bounds checks in shaders, even if the underlying driver doesn't
8 /// support doing so natively.
9 ///
10 /// When this is `true`, `wgpu` promises that shaders can only read or
11 /// write the accessible region of a bindgroup's buffer bindings. If
12 /// the underlying graphics platform cannot implement these bounds checks
13 /// itself, `wgpu` will inject bounds checks before presenting the
14 /// shader to the platform.
15 ///
16 /// When this is `false`, `wgpu` only enforces such bounds checks if the
17 /// underlying platform provides a way to do so itself. `wgpu` does not
18 /// itself add any bounds checks to generated shader code.
19 ///
20 /// Note that `wgpu` users may try to initialize only those portions of
21 /// buffers that they anticipate might be read from. Passing `false` here
22 /// may allow shaders to see wider regions of the buffers than expected,
23 /// making such deferred initialization visible to the application.
24 pub bounds_checks: bool,
25 ///
26 /// If false, the caller MUST ensure that all passed shaders do not contain any infinite loops.
27 ///
28 /// If it does, backend compilers MAY treat such a loop as unreachable code and draw
29 /// conclusions about other safety-critical code paths. This option SHOULD NOT be disabled
30 /// when running untrusted code.
31 pub force_loop_bounding: bool,
32 /// If false, the caller **MUST** ensure that in all passed shaders every function operating
33 /// on a ray query must obey these rules (functions using wgsl naming)
34 /// - `rayQueryInitialize` must have called before `rayQueryProceed`
35 /// - `rayQueryProceed` must have been called, returned true and have hit an AABB before
36 /// `rayQueryGenerateIntersection` is called
37 /// - `rayQueryProceed` must have been called, returned true and have hit a triangle before
38 /// `rayQueryConfirmIntersection` is called
39 /// - `rayQueryProceed` must have been called and have returned true before `rayQueryTerminate`,
40 /// `getCandidateHitVertexPositions` or `rayQueryGetCandidateIntersection` is called
41 /// - `rayQueryProceed` must have been called and have returned false before `rayQueryGetCommittedIntersection`
42 /// or `getCommittedHitVertexPositions` are called
43 ///
44 /// It is the aim that these cases will not cause UB if this is set to true, but currently this will still happen on DX12 and Metal.
45 pub ray_query_initialization_tracking: bool,
46
47 /// If false, task shaders will not validate that the mesh shader grid they dispatch is within legal limits.
48 pub task_shader_dispatch_tracking: bool,
49
50 /// If false, mesh shaders won't clamp the output primitives' vertex indices, which can lead to
51 /// undefined behavior and arbitrary memory access.
52 pub mesh_shader_primitive_indices_clamp: bool,
53}
54
55impl ShaderRuntimeChecks {
56 /// Creates a new configuration where the shader is fully checked.
57 #[must_use]
58 pub const fn checked() -> Self {
59 unsafe { Self::all(true) }
60 }
61
62 /// Creates a new configuration where none of the checks are performed.
63 ///
64 /// # Safety
65 ///
66 /// See the documentation for the `set_*` methods for the safety requirements
67 /// of each sub-configuration.
68 #[must_use]
69 pub const fn unchecked() -> Self {
70 unsafe { Self::all(false) }
71 }
72
73 /// Creates a new configuration where all checks are enabled or disabled. To safely
74 /// create a configuration with all checks enabled, use [`ShaderRuntimeChecks::checked`].
75 ///
76 /// # Safety
77 ///
78 /// See the documentation for the `set_*` methods for the safety requirements
79 /// of each sub-configuration.
80 #[must_use]
81 pub const unsafe fn all(all_checks: bool) -> Self {
82 Self {
83 bounds_checks: all_checks,
84 force_loop_bounding: all_checks,
85 ray_query_initialization_tracking: all_checks,
86 task_shader_dispatch_tracking: all_checks,
87 mesh_shader_primitive_indices_clamp: all_checks,
88 }
89 }
90}
91
92impl Default for ShaderRuntimeChecks {
93 fn default() -> Self {
94 Self::checked()
95 }
96}
97
98/// Describes a single entry point in a passthrough shader descriptor.
99#[derive(Debug, Clone)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub struct PassthroughShaderEntryPoint<'a> {
102 /// The name of the entry point. Only used in validation and for GLSL or DXIL.
103 pub name: Cow<'a, str>,
104 /// Number of workgroups in each dimension x, y and z. Only used for metal with
105 /// compute-like shader stages.
106 pub workgroup_size: (u32, u32, u32),
107}
108
109/// Descriptor for a shader module given by any of several sources.
110/// These shaders are passed through directly to the underlying api.
111/// At least one shader type that may be used by the backend must be `Some` or a panic is raised.
112///
113/// Note that you shouldn't expect this to work with bindings except on SPIR-V, and even on SPIR-V
114/// there will be some caveats.
115#[derive(Debug, Clone)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub struct CreateShaderModuleDescriptorPassthrough<'a, L> {
118 /// Debug label of the shader module. This will show up in graphics debuggers for easy identification.
119 pub label: L,
120 /// The list of entry points and their corresponding workgroup sizes.
121 pub entry_points: Cow<'a, [PassthroughShaderEntryPoint<'a>]>,
122
123 /// Binary SPIR-V data, in 4-byte words.
124 pub spirv: Option<Cow<'a, [u32]>>,
125 /// Shader DXIL source.
126 pub dxil: Option<Cow<'a, [u8]>>,
127 /// Shader HLSL source.
128 pub hlsl: Option<Cow<'a, str>>,
129 /// Shader MetalLib source.
130 pub metallib: Option<Cow<'a, [u8]>>,
131 /// Shader MSL source.
132 pub msl: Option<Cow<'a, str>>,
133 /// Shader GLSL source (currently unused).
134 pub glsl: Option<Cow<'a, str>>,
135 /// Shader WGSL source.
136 pub wgsl: Option<Cow<'a, str>>,
137}
138
139// This is so people don't have to fill in fields they don't use, like num_workgroups,
140// entry_point, or other shader languages they didn't compile for
141impl<'a, L: Default> Default for CreateShaderModuleDescriptorPassthrough<'a, L> {
142 fn default() -> Self {
143 Self {
144 label: Default::default(),
145 entry_points: Cow::Borrowed(&[]),
146 spirv: None,
147 dxil: None,
148 metallib: None,
149 msl: None,
150 hlsl: None,
151 glsl: None,
152 wgsl: None,
153 }
154 }
155}
156
157impl<'a, L> CreateShaderModuleDescriptorPassthrough<'a, L> {
158 /// Takes a closure and maps the label of the shader module descriptor into another.
159 pub fn map_label<K>(
160 &self,
161 fun: impl FnOnce(&L) -> K,
162 ) -> CreateShaderModuleDescriptorPassthrough<'a, K> {
163 CreateShaderModuleDescriptorPassthrough {
164 label: fun(&self.label),
165 entry_points: self.entry_points.clone(),
166 spirv: self.spirv.clone(),
167 metallib: self.metallib.clone(),
168 dxil: self.dxil.clone(),
169 msl: self.msl.clone(),
170 hlsl: self.hlsl.clone(),
171 glsl: self.glsl.clone(),
172 wgsl: self.wgsl.clone(),
173 }
174 }
175
176 #[cfg(feature = "trace")]
177 /// Returns the source data for tracing purpose.
178 pub fn trace_data(&self) -> &[u8] {
179 if let Some(spirv) = &self.spirv {
180 bytemuck::cast_slice(spirv)
181 } else if let Some(metallib) = &self.metallib {
182 metallib
183 } else if let Some(msl) = &self.msl {
184 msl.as_bytes()
185 } else if let Some(dxil) = &self.dxil {
186 dxil
187 } else if let Some(hlsl) = &self.hlsl {
188 hlsl.as_bytes()
189 } else if let Some(glsl) = &self.glsl {
190 glsl.as_bytes()
191 } else if let Some(wgsl) = &self.wgsl {
192 wgsl.as_bytes()
193 } else {
194 panic!("No binary data provided to `ShaderModuleDescriptorGeneric`")
195 }
196 }
197
198 #[cfg(feature = "trace")]
199 /// Returns the binary file extension for tracing purpose.
200 pub fn trace_binary_ext(&self) -> &'static str {
201 if self.spirv.is_some() {
202 "spv"
203 } else if self.metallib.is_some() {
204 "metallib"
205 } else if self.msl.is_some() {
206 "metal"
207 } else if self.dxil.is_some() {
208 "dxil"
209 } else if self.hlsl.is_some() {
210 "hlsl"
211 } else if self.glsl.is_some() {
212 "glsl"
213 } else if self.wgsl.is_some() {
214 "wgsl"
215 } else {
216 panic!("No binary data provided to `ShaderModuleDescriptorGeneric`")
217 }
218 }
219}