diff --git a/Cargo.toml b/Cargo.toml index 74753b5..b87c5a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ "librashader-cache", "librashader-capi", "librashader-build-script" -, "librashader-runtime-wgpu"] +, "librashader-runtime-wgpu", "librashader-runtime-metal"] resolver = "2" [workspace.metadata.release] diff --git a/librashader-common/Cargo.toml b/librashader-common/Cargo.toml index f73eced..e90e94d 100644 --- a/librashader-common/Cargo.toml +++ b/librashader-common/Cargo.toml @@ -19,6 +19,7 @@ d3d12 = ["windows", "dxgi"] dxgi = ["windows"] vulkan = ["ash"] wgpu = ["wgpu-types"] +metal = ["icrate"] [dependencies] gl = { version = "0.14.0", optional = true } @@ -37,3 +38,8 @@ features = [ "Win32_Graphics_Direct3D11", "Win32_Graphics_Direct3D12", ] + +[target.'cfg(macos)'.dependencies.icrate] +optional = true +version = "0.1.0" +features = ["Metal"] \ No newline at end of file diff --git a/librashader-common/src/lib.rs b/librashader-common/src/lib.rs index 477750b..ffebe60 100644 --- a/librashader-common/src/lib.rs +++ b/librashader-common/src/lib.rs @@ -24,8 +24,12 @@ pub mod d3d11; #[cfg(all(target_os = "windows", feature = "d3d12"))] pub mod d3d12; +#[cfg(all(target_os = "macos", feature = "metal"))] +pub mod metal; + mod viewport; + pub use viewport::Viewport; use num_traits::AsPrimitive; diff --git a/librashader-common/src/metal.rs b/librashader-common/src/metal.rs new file mode 100644 index 0000000..408bde6 --- /dev/null +++ b/librashader-common/src/metal.rs @@ -0,0 +1,95 @@ +use icrate::Metal; +use crate::{Size, ImageFormat, FilterMode, WrapMode}; + +impl From for Metal::MTLPixelFormat { + fn from(format: ImageFormat) -> Self { + match format { + ImageFormat::Unknown => 0 as Metal::MTLPixelFormat, + ImageFormat::R8Unorm => Metal::MTLPixelFormatR8Unorm, + ImageFormat::R8Uint => Metal::MTLPixelFormatR8Uint, + ImageFormat::R8Sint => Metal::MTLPixelFormatR8Sint, + ImageFormat::R8G8Unorm => Metal::MTLPixelFormatRG8Unorm, + ImageFormat::R8G8Uint => Metal::MTLPixelFormatRG8Uint, + ImageFormat::R8G8Sint => Metal::MTLPixelFormatRG8Sint, + ImageFormat::R8G8B8A8Unorm => Metal::MTLPixelFormatRGBA8Unorm, + ImageFormat::R8G8B8A8Uint => Metal::MTLPixelFormatRGBA8Uint, + ImageFormat::R8G8B8A8Sint => Metal::MTLPixelFormatRGBA8Sint, + ImageFormat::R8G8B8A8Srgb => Metal::MTLPixelFormatRGBA8Unorm_sRGB, + ImageFormat::A2B10G10R10UnormPack32 => Metal::MTLPixelFormatRGB10A2Unorm, + ImageFormat::A2B10G10R10UintPack32 => Metal::MTLPixelFormatRGB10A2Uint, + ImageFormat::R16Uint => Metal::MTLPixelFormatR16Uint, + ImageFormat::R16Sint => Metal::MTLPixelFormatR16Sint, + ImageFormat::R16Sfloat => Metal::MTLPixelFormatR16Float, + ImageFormat::R16G16Uint => Metal::MTLPixelFormatRG16Uint, + ImageFormat::R16G16Sint => Metal::MTLPixelFormatRG16Sint, + ImageFormat::R16G16Sfloat => Metal::MTLPixelFormatRG16Float, + ImageFormat::R16G16B16A16Uint => Metal::MTLPixelFormatRGBA16Uint, + ImageFormat::R16G16B16A16Sint => Metal::MTLPixelFormatRGBA16Sint, + ImageFormat::R16G16B16A16Sfloat => Metal::MTLPixelFormatRGBA16Float, + ImageFormat::R32Uint => Metal::MTLPixelFormatR32Uint, + ImageFormat::R32Sint => Metal::MTLPixelFormatR32Sint, + ImageFormat::R32Sfloat => Metal::MTLPixelFormatR32Float, + ImageFormat::R32G32Uint => Metal::MTLPixelFormatRG32Uint, + ImageFormat::R32G32Sint => Metal::MTLPixelFormatRG32Sint, + ImageFormat::R32G32Sfloat => Metal::MTLPixelFormatRG32Float, + ImageFormat::R32G32B32A32Uint => Metal::MTLPixelFormatRGBA32Uint, + ImageFormat::R32G32B32A32Sint => Metal::MTLPixelFormatRGBA32Sint, + ImageFormat::R32G32B32A32Sfloat => Metal::MTLPixelFormatRGBA32Float, + } + } +} + +impl From for Size { + fn from(value: Metal::MTLViewport) -> Self { + Size { + width: value.width as u32, + height: value.height as u32, + } + } +} + +impl From> for Metal::MTLViewport { + fn from(value: Size) -> Self { + Metal::MTLViewport { + originX: 0.0, + originY: 0.0, + width: value.width as f64, + height: value.height as f64, + znear: -1.0, + zfar: 1.0, + } + } +} + + +impl From for Metal::MTLSamplerAddressMode { + fn from(value: WrapMode) -> Self { + match value { + WrapMode::ClampToBorder => Metal::MTLSamplerAddressModeClampToBorderColor, + WrapMode::ClampToEdge => Metal::MTLSamplerAddressModeClampToEdge, + WrapMode::Repeat => Metal::MTLSamplerAddressModeRepeat, + WrapMode::MirroredRepeat => Metal::MTLSamplerAddressModeMirrorRepeat, + } + } +} + +impl From for Metal::MTLSamplerMinMagFilter { + fn from(value: FilterMode) -> Self { + match value { + FilterMode::Linear => Metal::MTLSamplerMinMagFilterLinear, + _ => Metal::MTLSamplerMipFilterNearest, + } + } +} + +impl FilterMode { + /// Get the mipmap filtering mode for the given combination. + pub fn mtl_mip(&self, mip: FilterMode) -> Metal::MTLSamplerMipFilter { + match self { + FilterMode::Linear => Metal::MTLSamplerMipFilterLinear, + FilterMode::Nearest => Metal::MTLSamplerMipFilterNearest, + } + } +} + + diff --git a/librashader-runtime-metal/Cargo.toml b/librashader-runtime-metal/Cargo.toml new file mode 100644 index 0000000..e230e6d --- /dev/null +++ b/librashader-runtime-metal/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "librashader-runtime-metal" +edition = "2021" + +license = "MPL-2.0 OR GPL-3.0-only" +version = "0.2.0-beta.7" +authors = ["Ronny Chan "] +repository = "https://github.com/SnowflakePowered/librashader" +readme = "../README.md" +categories = ["emulators", "compilers", "graphics"] +keywords = ["shader", "retroarch", "SPIR-V"] +description = "RetroArch shaders for all." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +librashader-common = { path = "../librashader-common", features = ["vulkan"], version = "0.2.0-beta.7" } +librashader-presets = { path = "../librashader-presets", version = "0.2.0-beta.7" } +librashader-preprocess = { path = "../librashader-preprocess", version = "0.2.0-beta.7" } +librashader-reflect = { path = "../librashader-reflect", version = "0.2.0-beta.7" } +librashader-runtime = { path = "../librashader-runtime" , version = "0.2.0-beta.7" } +librashader-cache = { path = "../librashader-cache", version = "0.2.0-beta.7" } + +icrate = { version = "0.1.0" , features = [ "Metal", "Metal_all", "MetalKit" ]} + +[dev-dependencies] + + +[package.metadata.docs.rs] +features = ["librashader-cache/docsrs"] diff --git a/librashader-runtime-metal/src/lib.rs b/librashader-runtime-metal/src/lib.rs new file mode 100644 index 0000000..5dc20bb --- /dev/null +++ b/librashader-runtime-metal/src/lib.rs @@ -0,0 +1 @@ +mod samplers; \ No newline at end of file diff --git a/librashader-runtime-metal/src/samplers.rs b/librashader-runtime-metal/src/samplers.rs new file mode 100644 index 0000000..390a296 --- /dev/null +++ b/librashader-runtime-metal/src/samplers.rs @@ -0,0 +1,71 @@ +use icrate::Metal::{MTLDevice, MTLSamplerDescriptor, MTLSamplerState}; +use objc2::rc::Id; +use objc2::runtime::ProtocolObject; +use rustc_hash::FxHashMap; +use librashader_common::{FilterMode, WrapMode}; + +pub struct SamplerSet { + // todo: may need to deal with differences in mip filter. + samplers: FxHashMap<(WrapMode, FilterMode, FilterMode), Id>>, +} + +impl SamplerSet { + #[inline(always)] + pub fn get(&self, wrap: WrapMode, filter: FilterMode, mipmap: FilterMode) -> Id>> { + // eprintln!("{wrap}, {filter}, {mip}"); + // SAFETY: the sampler set is complete for the matrix + // wrap x filter x mipmap + unsafe { + Id::clone( + &self + .samplers + .get(&(wrap, filter, mipmap)) + .unwrap_unchecked(), + ) + } + } + + pub fn new(device: &dyn MTLDevice) -> SamplerSet { + let mut samplers = FxHashMap::default(); + let wrap_modes = &[ + WrapMode::ClampToBorder, + WrapMode::ClampToEdge, + WrapMode::Repeat, + WrapMode::MirroredRepeat, + ]; + for wrap_mode in wrap_modes { + for filter_mode in &[FilterMode::Linear, FilterMode::Nearest] { + for mipmap_filter in &[FilterMode::Linear, FilterMode::Nearest] { + let descriptor = MTLSamplerDescriptor::new(); + descriptor + .setRAddressMode(MTLSamplerA) + samplers.insert( + (*wrap_mode, *filter_mode, *mipmap_filter), + + device.newSamplerStateWithDescriptor(&MTLSamplerDescriptor { + + }) + Arc::new(device.create_sampler(&SamplerDescriptor { + label: None, + address_mode_u: (*wrap_mode).into(), + address_mode_v: (*wrap_mode).into(), + address_mode_w: (*wrap_mode).into(), + mag_filter: (*filter_mode).into(), + min_filter: (*filter_mode).into(), + mipmap_filter: (*mipmap_filter).into(), + lod_min_clamp: 0.0, + lod_max_clamp: 1000.0, + compare: None, + anisotropy_clamp: 1, + border_color: Some(SamplerBorderColor::TransparentBlack), + })), + ); + } + } + } + + // assert all samplers were created. + assert_eq!(samplers.len(), wrap_modes.len() * 2 * 2); + SamplerSet { samplers } + } +} diff --git a/librashader-runtime-mtl/src/samplers.rs b/librashader-runtime-mtl/src/samplers.rs new file mode 100644 index 0000000..d0cff8e --- /dev/null +++ b/librashader-runtime-mtl/src/samplers.rs @@ -0,0 +1,84 @@ +use icrate::Metal::{ + MTLCompareFunctionNever, MTLDevice, MTLSamplerAddressMode, + MTLSamplerBorderColorTransparentBlack, MTLSamplerDescriptor, MTLSamplerMinMagFilter, + MTLSamplerState, +}; +use librashader_common::{FilterMode, WrapMode}; +use objc2::rc::Id; +use objc2::runtime::ProtocolObject; +use rustc_hash::FxHashMap; + +use crate::error::{FilterChainError, Result}; + +pub struct SamplerSet { + // todo: may need to deal with differences in mip filter. + samplers: + FxHashMap<(WrapMode, FilterMode, FilterMode), Id>>, +} + +impl SamplerSet { + #[inline(always)] + pub fn get( + &self, + wrap: WrapMode, + filter: FilterMode, + mipmap: FilterMode, + ) -> &ProtocolObject { + // eprintln!("{wrap}, {filter}, {mip}"); + // SAFETY: the sampler set is complete for the matrix + // wrap x filter x mipmap + let id: &Id> = unsafe { + self.samplers + .get(&(wrap, filter, mipmap)) + .unwrap_unchecked() + }; + + id.as_ref() + } + + pub fn new(device: &ProtocolObject) -> Result { + let mut samplers = FxHashMap::default(); + let wrap_modes = &[ + WrapMode::ClampToBorder, + WrapMode::ClampToEdge, + WrapMode::Repeat, + WrapMode::MirroredRepeat, + ]; + for wrap_mode in wrap_modes { + for filter_mode in &[FilterMode::Linear, FilterMode::Nearest] { + for mipmap_filter in &[FilterMode::Linear, FilterMode::Nearest] { + let descriptor = MTLSamplerDescriptor::new(); + descriptor.setRAddressMode(MTLSamplerAddressMode::from(*wrap_mode)); + descriptor.setSAddressMode(MTLSamplerAddressMode::from(*wrap_mode)); + descriptor.setTAddressMode(MTLSamplerAddressMode::from(*wrap_mode)); + + descriptor.setMagFilter(MTLSamplerMinMagFilter::from(*filter_mode)); + + descriptor.setMinFilter(MTLSamplerMinMagFilter::from(*filter_mode)); + descriptor.setMipFilter(filter_mode.mtl_mip(*mipmap_filter)); + descriptor.setLodMinClamp(0.0); + descriptor.setLodMaxClamp(1000.0); + descriptor.setCompareFunction(MTLCompareFunctionNever); + descriptor.setMaxAnisotropy(1); + descriptor.setBorderColor(MTLSamplerBorderColorTransparentBlack); + descriptor.setNormalizedCoordinates(true); + + let Some(sampler_state) = device.newSamplerStateWithDescriptor(&descriptor) + else { + return Err(FilterChainError::SamplerError( + *wrap_mode, + *filter_mode, + *mipmap_filter, + )); + }; + + samplers.insert((*wrap_mode, *filter_mode, *mipmap_filter), sampler_state); + } + } + } + + // assert all samplers were created. + assert_eq!(samplers.len(), wrap_modes.len() * 2 * 2); + Ok(SamplerSet { samplers }) + } +}