1use std::path::PathBuf;
12use std::sync::OnceLock;
13use std::sync::atomic::{AtomicBool, Ordering};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum Dx12Compiler {
18 Fxc,
20 Dxc {
22 library: PathBuf,
24 },
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct InstanceSettings {
30 pub dx12_compiler: Dx12Compiler,
32}
33
34impl InstanceSettings {
35 #[must_use]
38 pub fn from_environment() -> Self {
39 Self::from_lookup(|name| std::env::var_os(name))
40 }
41
42 fn from_lookup(lookup: impl Fn(&str) -> Option<std::ffi::OsString>) -> Self {
43 let dx12_compiler = match lookup("ALPHA2_DXC_PATH") {
44 Some(path) if !path.is_empty() => Dx12Compiler::Dxc {
45 library: PathBuf::from(path),
46 },
47 _ => Dx12Compiler::Fxc,
48 };
49 Self { dx12_compiler }
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55#[non_exhaustive]
56pub enum InstanceSettingsError {
57 AlreadyFrozen,
59}
60
61impl std::fmt::Display for InstanceSettingsError {
62 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::AlreadyFrozen => formatter.write_str(
65 "the GPU instance settings are fixed once the first instance is made or settings \
66 were configured",
67 ),
68 }
69 }
70}
71
72impl std::error::Error for InstanceSettingsError {}
73
74struct SettingsCell {
75 settings: OnceLock<InstanceSettings>,
76 frozen: AtomicBool,
77}
78
79impl SettingsCell {
80 const fn new() -> Self {
81 Self {
82 settings: OnceLock::new(),
83 frozen: AtomicBool::new(false),
84 }
85 }
86
87 fn configure(&self, settings: InstanceSettings) -> Result<(), InstanceSettingsError> {
88 if self.frozen.load(Ordering::Acquire) {
89 return Err(InstanceSettingsError::AlreadyFrozen);
90 }
91 self.settings
92 .set(settings)
93 .map_err(|_| InstanceSettingsError::AlreadyFrozen)
94 }
95
96 fn get(&self, environment: impl FnOnce() -> InstanceSettings) -> &InstanceSettings {
97 self.settings.get_or_init(environment)
98 }
99
100 fn freeze(&self, environment: impl FnOnce() -> InstanceSettings) -> &InstanceSettings {
101 let settings = self.get(environment);
102 self.frozen.store(true, Ordering::Release);
103 settings
104 }
105}
106
107static SETTINGS: SettingsCell = SettingsCell::new();
108
109pub fn configure_instances(settings: InstanceSettings) -> Result<(), InstanceSettingsError> {
117 SETTINGS.configure(settings)
118}
119
120#[must_use]
122pub fn instance_settings() -> &'static InstanceSettings {
123 SETTINGS.get(InstanceSettings::from_environment)
124}
125
126#[must_use]
129#[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
130pub fn instance_descriptor(backends: wgpu::Backends) -> wgpu::InstanceDescriptor {
131 descriptor_for(
132 backends,
133 SETTINGS.freeze(InstanceSettings::from_environment),
134 )
135}
136
137fn descriptor_for(
138 backends: wgpu::Backends,
139 settings: &InstanceSettings,
140) -> wgpu::InstanceDescriptor {
141 let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
142 descriptor.backends = backends;
143 descriptor.flags =
144 wgpu::InstanceFlags::from_build_config() - wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL;
145 if cfg!(target_os = "windows") {
146 descriptor.flags -= wgpu::InstanceFlags::DEBUG;
147 }
148 descriptor.backend_options.dx12.shader_compiler = match &settings.dx12_compiler {
149 Dx12Compiler::Fxc => wgpu::Dx12Compiler::Fxc,
150 Dx12Compiler::Dxc { library } => wgpu::Dx12Compiler::DynamicDxc {
151 dxc_path: library.to_string_lossy().into_owned(),
152 },
153 };
154 descriptor.with_env()
155}
156
157#[must_use]
159#[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
160pub fn new_instance(backends: wgpu::Backends) -> wgpu::Instance {
161 let _turn = crate::serialize_acquisition();
162 keep(backends);
163 wgpu::Instance::new(instance_descriptor(backends))
164}
165
166#[allow(clippy::missing_const_for_fn, unused_variables)]
169pub(crate) fn keep(backends: wgpu::Backends) {
170 #[cfg(all(feature = "serial-acquisition", not(target_arch = "wasm32")))]
171 {
172 use std::sync::{Mutex, PoisonError};
173 static KEPT: Mutex<Vec<(wgpu::Backends, wgpu::Instance)>> = Mutex::new(Vec::new());
174 let backends = wgpu::Backends::from_env().unwrap_or(backends);
175 let mut kept = KEPT.lock().unwrap_or_else(PoisonError::into_inner);
176 if kept.iter().any(|(covered, _)| covered.contains(backends)) {
177 return;
178 }
179 let instance = wgpu::Instance::new(instance_descriptor(backends));
180 kept.push((backends, instance));
181 }
182}
183
184#[derive(Clone, Debug, PartialEq, Eq)]
186#[non_exhaustive]
187pub enum ShaderCompiler {
188 NotDx12,
190 Fxc,
192 Dxc {
194 library: String,
196 },
197 DxcFromSearchPath,
199 StaticDxc,
201 Auto,
203}
204
205impl std::fmt::Display for ShaderCompiler {
206 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 match self {
208 Self::NotDx12 => Ok(()),
209 Self::Fxc => formatter.write_str("FXC"),
210 Self::Dxc { library } => write!(formatter, "DXC {library}"),
211 Self::DxcFromSearchPath => formatter.write_str("DXC from the DLL search path"),
212 Self::StaticDxc => formatter.write_str("static DXC"),
213 Self::Auto => formatter.write_str("auto"),
214 }
215 }
216}
217
218#[must_use]
220pub fn shader_compiler_of(descriptor: &wgpu::InstanceDescriptor) -> ShaderCompiler {
221 if !descriptor.backends.contains(wgpu::Backends::DX12) {
222 return ShaderCompiler::NotDx12;
223 }
224 match &descriptor.backend_options.dx12.shader_compiler {
225 wgpu::Dx12Compiler::Fxc => ShaderCompiler::Fxc,
226 wgpu::Dx12Compiler::DynamicDxc { dxc_path } if dxc_path == "dxcompiler.dll" => {
228 ShaderCompiler::DxcFromSearchPath
229 }
230 wgpu::Dx12Compiler::DynamicDxc { dxc_path } => ShaderCompiler::Dxc {
231 library: dxc_path.clone(),
232 },
233 wgpu::Dx12Compiler::StaticDxc => ShaderCompiler::StaticDxc,
234 wgpu::Dx12Compiler::Auto => ShaderCompiler::Auto,
235 }
236}
237
238#[must_use]
240pub fn dx12_shader_compiler() -> ShaderCompiler {
241 shader_compiler_of(&instance_descriptor(wgpu::Backends::DX12))
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn settings_take_the_dxc_path_from_the_environment_and_otherwise_fxc() {
250 let unset = InstanceSettings::from_lookup(|_| None);
251 assert_eq!(unset.dx12_compiler, Dx12Compiler::Fxc);
252 let empty = InstanceSettings::from_lookup(|_| Some("".into()));
253 assert_eq!(empty.dx12_compiler, Dx12Compiler::Fxc);
254 let set = InstanceSettings::from_lookup(|name| {
255 (name == "ALPHA2_DXC_PATH").then(|| r"C:\alpha2\dxcompiler.dll".into())
256 });
257 assert_eq!(
258 set.dx12_compiler,
259 Dx12Compiler::Dxc {
260 library: PathBuf::from(r"C:\alpha2\dxcompiler.dll")
261 }
262 );
263 }
264
265 #[test]
266 fn the_descriptor_carries_the_compiler_and_drops_debug_on_windows_and_indirect_validation() {
267 if std::env::var_os("WGPU_DX12_COMPILER").is_some()
269 || std::env::var_os("WGPU_DEBUG").is_some()
270 || std::env::var_os("WGPU_VALIDATION_INDIRECT_CALL").is_some()
271 {
272 return;
273 }
274 let dxc = InstanceSettings {
275 dx12_compiler: Dx12Compiler::Dxc {
276 library: PathBuf::from("/opt/dxc/dxcompiler.dll"),
277 },
278 };
279 let descriptor = descriptor_for(wgpu::Backends::DX12, &dxc);
280 assert_eq!(
281 descriptor.flags.contains(wgpu::InstanceFlags::DEBUG),
282 cfg!(debug_assertions) && !cfg!(target_os = "windows")
283 );
284 assert!(
285 !descriptor
286 .flags
287 .contains(wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL)
288 );
289 assert_eq!(
290 shader_compiler_of(&descriptor),
291 ShaderCompiler::Dxc {
292 library: "/opt/dxc/dxcompiler.dll".to_owned()
293 }
294 );
295 assert_eq!(
296 shader_compiler_of(&descriptor).to_string(),
297 "DXC /opt/dxc/dxcompiler.dll"
298 );
299 let fxc = InstanceSettings {
300 dx12_compiler: Dx12Compiler::Fxc,
301 };
302 assert_eq!(
303 shader_compiler_of(&descriptor_for(wgpu::Backends::DX12, &fxc)).to_string(),
304 "FXC"
305 );
306 if std::env::var_os("WGPU_BACKEND").is_none() {
307 assert_eq!(
308 shader_compiler_of(&descriptor_for(wgpu::Backends::VULKAN, &fxc)),
309 ShaderCompiler::NotDx12
310 );
311 }
312 }
313
314 #[test]
315 fn settings_cannot_change_after_the_first_instance() {
316 let fxc = || InstanceSettings {
317 dx12_compiler: Dx12Compiler::Fxc,
318 };
319 let cell = SettingsCell::new();
320 cell.freeze(fxc);
321 assert_eq!(
322 cell.configure(fxc()),
323 Err(InstanceSettingsError::AlreadyFrozen)
324 );
325 let configured = SettingsCell::new();
326 assert_eq!(configured.configure(fxc()), Ok(()));
327 assert_eq!(
328 configured.configure(fxc()),
329 Err(InstanceSettingsError::AlreadyFrozen)
330 );
331 assert_eq!(configured.freeze(|| unreachable!()), &fxc());
332 }
333}