1use jgenesis_common::frontend::Rotation; 2use jgenesis_proc_macros::{ConfigDisplay, EnumAll, EnumDisplay, EnumFromStr}; 3use std::fmt::{Display, Formatter}; 4use std::num::NonZeroU32; 5 6pub const DXCOMPILER_PATH: &str = "dxcompiler.dll"; 7 8#[must_use] 9pub fn dx12_backend_options() -> wgpu::Dx12BackendOptions { 10 wgpu::Dx12BackendOptions { 11 shader_compiler: wgpu::Dx12Compiler::DynamicDxc { dxc_path: DXCOMPILER_PATH.into() }, 12 ..wgpu::Dx12BackendOptions::default() 13 } 14} 15 16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumAll)] 17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 18#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 19pub enum WgpuBackend { 20 #[default] 21 Auto, 22 Vulkan, 23 DirectX12, 24} 25 26impl WgpuBackend { 27 #[must_use] 28 pub fn to_wgpu(self) -> wgpu::Backends { 29 #[cfg(target_os = "windows")] 30 if self == WgpuBackend::Auto && supports_dx12() { 31 // Prefer DX12 on Windows if supported (necessary because wgpu prefers Vulkan over DX12) 32 // AMD GPUs seem to sometimes have color space bugs on Windows w/ Vulkan 33 return wgpu::Backends::DX12; 34 } 35 36 match self { 37 WgpuBackend::Auto => wgpu::Backends::PRIMARY, 38 WgpuBackend::Vulkan => wgpu::Backends::VULKAN, 39 WgpuBackend::DirectX12 => wgpu::Backends::DX12, 40 } 41 } 42} 43 44#[cfg(target_os = "windows")] 45fn supports_dx12() -> bool { 46 use std::sync::LazyLock; 47 48 static SUPPORTS_DX12: LazyLock<bool> = LazyLock::new(|| { 49 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { 50 backends: wgpu::Backends::DX12, 51 backend_options: wgpu::BackendOptions { 52 dx12: dx12_backend_options(), 53 ..wgpu::BackendOptions::default() 54 }, 55 ..wgpu::InstanceDescriptor::new_without_display_handle() 56 }); 57 58 let adapter = 59 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())); 60 adapter.is_ok() 61 }); 62 63 *SUPPORTS_DX12 64} 65 66#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumAll)] 67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 68#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 69pub enum WgpuPowerPreference { 70 #[default] 71 HighPerformance, 72 LowPower, 73 None, 74} 75 76impl WgpuPowerPreference { 77 #[must_use] 78 pub fn to_wgpu(self) -> wgpu::PowerPreference { 79 match self { 80 Self::HighPerformance => wgpu::PowerPreference::HighPerformance, 81 Self::LowPower => wgpu::PowerPreference::LowPower, 82 Self::None => wgpu::PowerPreference::None, 83 } 84 } 85} 86 87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumAll)] 88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 89#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 90pub enum VSyncMode { 91 Enabled, 92 #[default] 93 Disabled, 94 Fast, 95} 96 97impl VSyncMode { 98 pub(crate) fn to_wgpu_present_mode(self) -> wgpu::PresentMode { 99 match self { 100 Self::Enabled => wgpu::PresentMode::Fifo, 101 Self::Disabled => wgpu::PresentMode::AutoNoVsync, 102 Self::Fast => wgpu::PresentMode::Mailbox, 103 } 104 } 105} 106 107#[derive(Debug, Clone, Copy, PartialEq, Eq)] 108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 109pub struct PrescaleFactor(u32); 110 111impl PrescaleFactor { 112 pub const ONE: Self = Self(1); 113 114 #[must_use] 115 pub fn get(self) -> u32 { 116 self.0 117 } 118} 119 120impl From<PrescaleFactor> for u32 { 121 fn from(value: PrescaleFactor) -> Self { 122 value.0 123 } 124} 125 126impl TryFrom<u32> for PrescaleFactor { 127 type Error = String; 128 129 fn try_from(value: u32) -> Result<Self, Self::Error> { 130 match value { 131 0 => Err(format!("invalid prescale factor: {value}")), 132 _ => Ok(Self(value)), 133 } 134 } 135} 136 137impl From<NonZeroU32> for PrescaleFactor { 138 fn from(value: NonZeroU32) -> Self { 139 Self(value.get()) 140 } 141} 142 143impl Default for PrescaleFactor { 144 fn default() -> Self { 145 Self::ONE 146 } 147} 148 149impl Display for PrescaleFactor { 150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 151 write!(f, "{}x", self.0) 152 } 153} 154 155#[derive(Debug, Clone, Copy, PartialEq, Eq)] 156pub enum PrescaleMode { 157 Auto, 158 Manual { width: PrescaleFactor, height: PrescaleFactor }, 159} 160 161impl Display for PrescaleMode { 162 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 163 match self { 164 Self::Auto => write!(f, "Auto"), 165 Self::Manual { width, height } => write!(f, "Manual(width={width}, height={height})"), 166 } 167 } 168} 169 170#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumFromStr, EnumAll)] 171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 172#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 173pub enum FilterMode { 174 Nearest, 175 #[default] 176 Linear, 177} 178 179impl FilterMode { 180 pub(crate) fn to_wgpu_filter_mode(self) -> wgpu::FilterMode { 181 match self { 182 Self::Nearest => wgpu::FilterMode::Nearest, 183 Self::Linear => wgpu::FilterMode::Linear, 184 } 185 } 186} 187 188#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumFromStr, EnumAll)] 189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 190#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 191pub enum AntiDitherShader { 192 #[default] 193 None, 194 Weak, 195 Strong, 196} 197 198#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumFromStr, EnumAll)] 199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 200#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 201pub enum PreprocessShader { 202 #[default] 203 None, 204 HorizontalBlurTwoPixels, 205 HorizontalBlurThreePixels, 206 HorizontalBlurSnesAdaptive, 207 NtscComposite, 208 Xbrz2x, 209 Xbrz3x, 210 Xbrz4x, 211 Xbrz5x, 212 Xbrz6x, 213 Mmpx, 214 MmpxEnhanced, 215} 216 217impl PreprocessShader { 218 // Certain shaders don't make sense to use in combination with an anti-dither filter 219 #[must_use] 220 pub fn exclude_anti_dither(self) -> bool { 221 matches!( 222 self, 223 Self::NtscComposite 224 | Self::HorizontalBlurTwoPixels 225 | Self::HorizontalBlurThreePixels 226 | Self::HorizontalBlurSnesAdaptive 227 ) 228 } 229 230 pub(crate) fn xbrz_scale_factor(self) -> Option<u32> { 231 match self { 232 Self::Xbrz2x => Some(2), 233 Self::Xbrz3x => Some(3), 234 Self::Xbrz4x => Some(4), 235 Self::Xbrz5x => Some(5), 236 Self::Xbrz6x => Some(6), 237 _ => None, 238 } 239 } 240} 241 242#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumDisplay, EnumFromStr, EnumAll)] 243#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 244#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 245pub enum FrameRotation { 246 #[default] 247 None, 248 Clockwise, 249 OneEighty, 250 Counterclockwise, 251} 252 253impl From<FrameRotation> for Rotation { 254 fn from(value: FrameRotation) -> Self { 255 match value { 256 FrameRotation::None => Self::None, 257 FrameRotation::Clockwise => Self::Clockwise, 258 FrameRotation::OneEighty => Self::OneEighty, 259 FrameRotation::Counterclockwise => Self::Counterclockwise, 260 } 261 } 262} 263 264#[derive(Debug, Clone, Copy, PartialEq, ConfigDisplay)] 265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 266pub struct NtscShaderConfig { 267 pub brightness: f64, 268 pub saturation: f64, 269 pub gamma: f64, 270} 271 272impl Default for NtscShaderConfig { 273 fn default() -> Self { 274 Self { brightness: 1.0, saturation: 1.0, gamma: 2.2 } 275 } 276} 277 278#[derive(Debug, Clone, Copy, ConfigDisplay)] 279pub struct RendererConfig { 280 pub wgpu_backend: WgpuBackend, 281 pub wgpu_power_preference: WgpuPowerPreference, 282 pub vsync_mode: VSyncMode, 283 pub frame_time_sync: bool, 284 pub prescale_mode: PrescaleMode, 285 pub scanlines_enabled: bool, 286 pub scanlines_brightness: f64, 287 pub force_integer_height_scaling: bool, 288 pub filter_mode: FilterMode, 289 pub supersample_minification: bool, 290 pub preprocess_shader: PreprocessShader, 291 pub anti_dither_shader: AntiDitherShader, 292 pub frame_rotation: FrameRotation, 293 #[cfg_display(indent_nested)] 294 pub ntsc_config: NtscShaderConfig, 295}