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