use std::path::PathBuf;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Dx12Compiler {
Fxc,
Dxc {
library: PathBuf,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InstanceSettings {
pub dx12_compiler: Dx12Compiler,
}
impl InstanceSettings {
#[must_use]
pub fn from_environment() -> Self {
Self::from_lookup(|name| std::env::var_os(name))
}
fn from_lookup(lookup: impl Fn(&str) -> Option<std::ffi::OsString>) -> Self {
let dx12_compiler = match lookup("ALPHA2_DXC_PATH") {
Some(path) if !path.is_empty() => Dx12Compiler::Dxc {
library: PathBuf::from(path),
},
_ => Dx12Compiler::Fxc,
};
Self { dx12_compiler }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum InstanceSettingsError {
AlreadyFrozen,
}
impl std::fmt::Display for InstanceSettingsError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AlreadyFrozen => formatter.write_str(
"the GPU instance settings are fixed once the first instance is made or settings \
were configured",
),
}
}
}
impl std::error::Error for InstanceSettingsError {}
struct SettingsCell {
settings: OnceLock<InstanceSettings>,
frozen: AtomicBool,
}
impl SettingsCell {
const fn new() -> Self {
Self {
settings: OnceLock::new(),
frozen: AtomicBool::new(false),
}
}
fn configure(&self, settings: InstanceSettings) -> Result<(), InstanceSettingsError> {
if self.frozen.load(Ordering::Acquire) {
return Err(InstanceSettingsError::AlreadyFrozen);
}
self.settings
.set(settings)
.map_err(|_| InstanceSettingsError::AlreadyFrozen)
}
fn get(&self, environment: impl FnOnce() -> InstanceSettings) -> &InstanceSettings {
self.settings.get_or_init(environment)
}
fn freeze(&self, environment: impl FnOnce() -> InstanceSettings) -> &InstanceSettings {
let settings = self.get(environment);
self.frozen.store(true, Ordering::Release);
settings
}
}
static SETTINGS: SettingsCell = SettingsCell::new();
pub fn configure_instances(settings: InstanceSettings) -> Result<(), InstanceSettingsError> {
SETTINGS.configure(settings)
}
#[must_use]
pub fn instance_settings() -> &'static InstanceSettings {
SETTINGS.get(InstanceSettings::from_environment)
}
#[must_use]
#[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
pub fn instance_descriptor(backends: wgpu::Backends) -> wgpu::InstanceDescriptor {
descriptor_for(
backends,
SETTINGS.freeze(InstanceSettings::from_environment),
)
}
fn descriptor_for(
backends: wgpu::Backends,
settings: &InstanceSettings,
) -> wgpu::InstanceDescriptor {
let mut descriptor = wgpu::InstanceDescriptor::new_without_display_handle();
descriptor.backends = backends;
descriptor.flags =
wgpu::InstanceFlags::from_build_config() - wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL;
if cfg!(target_os = "windows") {
descriptor.flags -= wgpu::InstanceFlags::DEBUG;
}
descriptor.backend_options.dx12.shader_compiler = match &settings.dx12_compiler {
Dx12Compiler::Fxc => wgpu::Dx12Compiler::Fxc,
Dx12Compiler::Dxc { library } => wgpu::Dx12Compiler::DynamicDxc {
dxc_path: library.to_string_lossy().into_owned(),
},
};
descriptor.with_env()
}
#[must_use]
#[cfg_attr(not(feature = "wgpu-interop"), doc(hidden))]
pub fn new_instance(backends: wgpu::Backends) -> wgpu::Instance {
let _turn = crate::serialize_acquisition();
keep(backends);
wgpu::Instance::new(instance_descriptor(backends))
}
#[allow(clippy::missing_const_for_fn, unused_variables)]
pub(crate) fn keep(backends: wgpu::Backends) {
#[cfg(all(feature = "serial-acquisition", not(target_arch = "wasm32")))]
{
use std::sync::{Mutex, PoisonError};
static KEPT: Mutex<Vec<(wgpu::Backends, wgpu::Instance)>> = Mutex::new(Vec::new());
let backends = wgpu::Backends::from_env().unwrap_or(backends);
let mut kept = KEPT.lock().unwrap_or_else(PoisonError::into_inner);
if kept.iter().any(|(covered, _)| covered.contains(backends)) {
return;
}
let instance = wgpu::Instance::new(instance_descriptor(backends));
kept.push((backends, instance));
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ShaderCompiler {
NotDx12,
Fxc,
Dxc {
library: String,
},
DxcFromSearchPath,
StaticDxc,
Auto,
}
impl std::fmt::Display for ShaderCompiler {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotDx12 => Ok(()),
Self::Fxc => formatter.write_str("FXC"),
Self::Dxc { library } => write!(formatter, "DXC {library}"),
Self::DxcFromSearchPath => formatter.write_str("DXC from the DLL search path"),
Self::StaticDxc => formatter.write_str("static DXC"),
Self::Auto => formatter.write_str("auto"),
}
}
}
#[must_use]
pub fn shader_compiler_of(descriptor: &wgpu::InstanceDescriptor) -> ShaderCompiler {
if !descriptor.backends.contains(wgpu::Backends::DX12) {
return ShaderCompiler::NotDx12;
}
match &descriptor.backend_options.dx12.shader_compiler {
wgpu::Dx12Compiler::Fxc => ShaderCompiler::Fxc,
wgpu::Dx12Compiler::DynamicDxc { dxc_path } if dxc_path == "dxcompiler.dll" => {
ShaderCompiler::DxcFromSearchPath
}
wgpu::Dx12Compiler::DynamicDxc { dxc_path } => ShaderCompiler::Dxc {
library: dxc_path.clone(),
},
wgpu::Dx12Compiler::StaticDxc => ShaderCompiler::StaticDxc,
wgpu::Dx12Compiler::Auto => ShaderCompiler::Auto,
}
}
#[must_use]
pub fn dx12_shader_compiler() -> ShaderCompiler {
shader_compiler_of(&instance_descriptor(wgpu::Backends::DX12))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn settings_take_the_dxc_path_from_the_environment_and_otherwise_fxc() {
let unset = InstanceSettings::from_lookup(|_| None);
assert_eq!(unset.dx12_compiler, Dx12Compiler::Fxc);
let empty = InstanceSettings::from_lookup(|_| Some("".into()));
assert_eq!(empty.dx12_compiler, Dx12Compiler::Fxc);
let set = InstanceSettings::from_lookup(|name| {
(name == "ALPHA2_DXC_PATH").then(|| r"C:\alpha2\dxcompiler.dll".into())
});
assert_eq!(
set.dx12_compiler,
Dx12Compiler::Dxc {
library: PathBuf::from(r"C:\alpha2\dxcompiler.dll")
}
);
}
#[test]
fn the_descriptor_carries_the_compiler_and_drops_debug_on_windows_and_indirect_validation() {
if std::env::var_os("WGPU_DX12_COMPILER").is_some()
|| std::env::var_os("WGPU_DEBUG").is_some()
|| std::env::var_os("WGPU_VALIDATION_INDIRECT_CALL").is_some()
{
return;
}
let dxc = InstanceSettings {
dx12_compiler: Dx12Compiler::Dxc {
library: PathBuf::from("/opt/dxc/dxcompiler.dll"),
},
};
let descriptor = descriptor_for(wgpu::Backends::DX12, &dxc);
assert_eq!(
descriptor.flags.contains(wgpu::InstanceFlags::DEBUG),
cfg!(debug_assertions) && !cfg!(target_os = "windows")
);
assert!(
!descriptor
.flags
.contains(wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL)
);
assert_eq!(
shader_compiler_of(&descriptor),
ShaderCompiler::Dxc {
library: "/opt/dxc/dxcompiler.dll".to_owned()
}
);
assert_eq!(
shader_compiler_of(&descriptor).to_string(),
"DXC /opt/dxc/dxcompiler.dll"
);
let fxc = InstanceSettings {
dx12_compiler: Dx12Compiler::Fxc,
};
assert_eq!(
shader_compiler_of(&descriptor_for(wgpu::Backends::DX12, &fxc)).to_string(),
"FXC"
);
if std::env::var_os("WGPU_BACKEND").is_none() {
assert_eq!(
shader_compiler_of(&descriptor_for(wgpu::Backends::VULKAN, &fxc)),
ShaderCompiler::NotDx12
);
}
}
#[test]
fn settings_cannot_change_after_the_first_instance() {
let fxc = || InstanceSettings {
dx12_compiler: Dx12Compiler::Fxc,
};
let cell = SettingsCell::new();
cell.freeze(fxc);
assert_eq!(
cell.configure(fxc()),
Err(InstanceSettingsError::AlreadyFrozen)
);
let configured = SettingsCell::new();
assert_eq!(configured.configure(fxc()), Ok(()));
assert_eq!(
configured.configure(fxc()),
Err(InstanceSettingsError::AlreadyFrozen)
);
assert_eq!(configured.freeze(|| unreachable!()), &fxc());
}
}