use crate::bindings as br; use crate::{compiler, spirv, ErrorCode}; use std::collections::BTreeMap; use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::ptr; use std::u8; /// A MSL target. #[derive(Debug, Clone)] pub enum Target {} pub struct TargetData { vertex_attribute_overrides: Vec, resource_binding_overrides: Vec, const_samplers: Vec, } impl spirv::Target for Target { type Data = TargetData; } /// Location of a vertex attribute to override #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct VertexAttributeLocation(pub u32); /// Format of the vertex attribute #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum Format { Other, Uint8, Uint16, } impl Format { fn as_raw(&self) -> br::spirv_cross::MSLShaderInputFormat { use self::Format::*; match self { Other => br::spirv_cross::MSLShaderInputFormat_MSL_SHADER_INPUT_FORMAT_OTHER, Uint8 => br::spirv_cross::MSLShaderInputFormat_MSL_SHADER_INPUT_FORMAT_UINT8, Uint16 => br::spirv_cross::MSLShaderInputFormat_MSL_SHADER_INPUT_FORMAT_UINT16, } } } /// Vertex attribute description for overriding #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct VertexAttribute { pub buffer_id: u32, pub format: Format, pub built_in: Option, pub vecsize: u32, } /// Location of a resource binding to override #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct ResourceBindingLocation { pub stage: spirv::ExecutionModel, pub desc_set: u32, pub binding: u32, } /// Resource binding description for overriding #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct ResourceBinding { pub buffer_id: u32, pub texture_id: u32, pub sampler_id: u32, } /// Location of a sampler binding to override #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct SamplerLocation { pub desc_set: u32, pub binding: u32, } #[repr(C)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum SamplerCoord { Normalized = 0, Pixel = 1, } #[repr(C)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum SamplerFilter { Nearest = 0, Linear = 1, } #[repr(C)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum SamplerMipFilter { None = 0, Nearest = 1, Linear = 2, } #[repr(C)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum SamplerAddress { ClampToZero = 0, ClampToEdge = 1, ClampToBorder = 2, Repeat = 3, MirroredRepeat = 4, } #[repr(C)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum SamplerCompareFunc { Never = 0, Less = 1, LessEqual = 2, Greater = 3, GreaterEqual = 4, Equal = 5, NotEqual = 6, Always = 7, } #[repr(C)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub enum SamplerBorderColor { TransparentBlack = 0, OpaqueBlack = 1, OpaqueWhite = 2, } #[repr(transparent)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] pub struct LodBase16(u8); impl LodBase16 { pub const ZERO: Self = LodBase16(0); pub const MAX: Self = LodBase16(!0); } impl From for LodBase16 { fn from(v: f32) -> Self { LodBase16((v * 16.0).max(0.0).min(u8::MAX as f32) as u8) } } impl Into for LodBase16 { fn into(self) -> f32 { self.0 as f32 / 16.0 } } /// MSL format resolution. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum FormatResolution { _444 = 0, _422 = 1, _420 = 2, } /// MSL chroma location. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ChromaLocation { CositedEven = 0, LocationMidpoint = 1, } /// MSL component swizzle. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ComponentSwizzle { Identity = 0, Zero = 1, One = 2, R = 3, G = 4, B = 5, A = 6, } /// Data fully defining a constant sampler. #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct SamplerData { pub coord: SamplerCoord, pub min_filter: SamplerFilter, pub mag_filter: SamplerFilter, pub mip_filter: SamplerMipFilter, pub s_address: SamplerAddress, pub t_address: SamplerAddress, pub r_address: SamplerAddress, pub compare_func: SamplerCompareFunc, pub border_color: SamplerBorderColor, pub lod_clamp_min: LodBase16, pub lod_clamp_max: LodBase16, pub max_anisotropy: i32, // Sampler YCbCr conversion parameters pub planes: u32, pub resolution: FormatResolution, pub chroma_filter: SamplerFilter, pub x_chroma_offset: ChromaLocation, pub y_chroma_offset: ChromaLocation, pub swizzle: [ComponentSwizzle; 4], pub ycbcr_conversion_enable: bool, pub ycbcr_model: SamplerYCbCrModelConversion, pub ycbcr_range: SamplerYCbCrRange, pub bpc: u32, } /// A MSL sampler YCbCr model conversion. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum SamplerYCbCrModelConversion { RgbIdentity = 0, YCbCrIdentity = 1, YCbCrBt709 = 2, YCbCrBt601 = 3, YCbCrBt2020 = 4, } /// A MSL sampler YCbCr range. #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum SamplerYCbCrRange { ItuFull = 0, ItuNarrow = 1, } /// A MSL shader platform. #[repr(u8)] #[allow(non_snake_case, non_camel_case_types)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] pub enum Platform { iOS = 0, macOS = 1, } /// A MSL shader model version. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] pub enum Version { V1_0, V1_1, V1_2, V2_0, V2_1, V2_2, } impl Version { fn as_raw(self) -> u32 { use self::Version::*; match self { V1_0 => 10000, V1_1 => 10100, V1_2 => 10200, V2_0 => 20000, V2_1 => 20100, V2_2 => 20200, } } } #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct CompilerVertexOptions { pub invert_y: bool, pub transform_clip_space: bool, } impl Default for CompilerVertexOptions { fn default() -> Self { CompilerVertexOptions { invert_y: false, transform_clip_space: false, } } } /// MSL compiler options. #[non_exhaustive] #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct CompilerOptions { /// The target platform. pub platform: Platform, /// The target MSL version. pub version: Version, /// Vertex compiler options. pub vertex: CompilerVertexOptions, /// The buffer index to use for swizzle. pub swizzle_buffer_index: u32, // The buffer index to use for indirect params. pub indirect_params_buffer_index: u32, /// The buffer index to use for output. pub output_buffer_index: u32, /// The buffer index to use for patch output. pub patch_output_buffer_index: u32, /// The buffer index to use for tessellation factor. pub tessellation_factor_buffer_index: u32, /// The buffer index to use for buffer size. pub buffer_size_buffer_index: u32, /// Whether the built-in point size should be enabled. pub enable_point_size_builtin: bool, /// Whether rasterization should be enabled. pub enable_rasterization: bool, /// Whether to capture output to buffer. pub capture_output_to_buffer: bool, /// Whether to swizzle texture samples. pub swizzle_texture_samples: bool, /// Whether to place the origin of tessellation domain shaders in the lower left. pub tessellation_domain_origin_lower_left: bool, /// Whether to enable use of argument buffers (only compatible with MSL 2.0). pub enable_argument_buffers: bool, /// Whether to pad fragment output to have at least the number of components as the render pass. pub pad_fragment_output_components: bool, /// MSL resource bindings overrides. pub resource_binding_overrides: BTreeMap, /// MSL vertex attribute overrides. pub vertex_attribute_overrides: BTreeMap, /// MSL const sampler mappings. pub const_samplers: BTreeMap, /// Whether to force native arrays (useful to workaround issues on some hardware). pub force_native_arrays: bool, /// Whether to force all uninitialized variables to be initialized to zero. pub force_zero_initialized_variables: bool, /// The name and execution model of the entry point to use. If no entry /// point is specified, then the first entry point found will be used. pub entry_point: Option<(String, spirv::ExecutionModel)>, } impl Default for CompilerOptions { fn default() -> Self { CompilerOptions { platform: Platform::macOS, version: Version::V1_2, vertex: CompilerVertexOptions::default(), swizzle_buffer_index: 30, indirect_params_buffer_index: 29, output_buffer_index: 28, patch_output_buffer_index: 27, tessellation_factor_buffer_index: 26, buffer_size_buffer_index: 25, enable_point_size_builtin: true, enable_rasterization: true, capture_output_to_buffer: false, swizzle_texture_samples: false, tessellation_domain_origin_lower_left: false, enable_argument_buffers: false, pad_fragment_output_components: false, resource_binding_overrides: Default::default(), vertex_attribute_overrides: Default::default(), const_samplers: Default::default(), force_native_arrays: false, force_zero_initialized_variables: false, entry_point: None, } } } impl<'a> spirv::Parse for spirv::Ast { fn parse(module: &spirv::Module) -> Result { let mut sc_compiler = ptr::null_mut(); unsafe { check!(br::sc_internal_compiler_msl_new( &mut sc_compiler, module.words.as_ptr(), module.words.len(), )); } Ok(spirv::Ast { compiler: compiler::Compiler { sc_compiler, target_data: TargetData { resource_binding_overrides: Vec::new(), vertex_attribute_overrides: Vec::new(), const_samplers: Vec::new(), }, has_been_compiled: false, }, target_type: PhantomData, }) } } impl spirv::Compile for spirv::Ast { type CompilerOptions = CompilerOptions; /// Set MSL compiler specific compilation settings. fn set_compiler_options(&mut self, options: &CompilerOptions) -> Result<(), ErrorCode> { if let Some((name, model)) = &options.entry_point { let name_raw = CString::new(name.as_str()).map_err(|_| ErrorCode::Unhandled)?; let model = model.as_raw(); unsafe { check!(br::sc_internal_compiler_set_entry_point( self.compiler.sc_compiler, name_raw.as_ptr(), model, )); } }; let raw_options = br::ScMslCompilerOptions { vertex_invert_y: options.vertex.invert_y, vertex_transform_clip_space: options.vertex.transform_clip_space, platform: options.platform as _, version: options.version.as_raw(), enable_point_size_builtin: options.enable_point_size_builtin, disable_rasterization: !options.enable_rasterization, swizzle_buffer_index: options.swizzle_buffer_index, indirect_params_buffer_index: options.indirect_params_buffer_index, shader_output_buffer_index: options.output_buffer_index, shader_patch_output_buffer_index: options.patch_output_buffer_index, shader_tess_factor_buffer_index: options.tessellation_factor_buffer_index, buffer_size_buffer_index: options.buffer_size_buffer_index, capture_output_to_buffer: options.capture_output_to_buffer, swizzle_texture_samples: options.swizzle_texture_samples, tess_domain_origin_lower_left: options.tessellation_domain_origin_lower_left, argument_buffers: options.enable_argument_buffers, pad_fragment_output_components: options.pad_fragment_output_components, force_native_arrays: options.force_native_arrays, force_zero_initialized_variables: options.force_zero_initialized_variables, }; unsafe { check!(br::sc_internal_compiler_msl_set_options( self.compiler.sc_compiler, &raw_options, )); } self.compiler.target_data.resource_binding_overrides.clear(); self.compiler.target_data.resource_binding_overrides.extend( options.resource_binding_overrides.iter().map(|(loc, res)| { br::spirv_cross::MSLResourceBinding { stage: loc.stage.as_raw(), desc_set: loc.desc_set, binding: loc.binding, msl_buffer: res.buffer_id, msl_texture: res.texture_id, msl_sampler: res.sampler_id, } }), ); self.compiler.target_data.vertex_attribute_overrides.clear(); self.compiler.target_data.vertex_attribute_overrides.extend( options.vertex_attribute_overrides.iter().map(|(loc, vat)| { br::spirv_cross::MSLShaderInput { location: loc.0, format: vat.format.as_raw(), builtin: spirv::built_in_as_raw(vat.built_in), vecsize: vat.vecsize, } }), ); self.compiler.target_data.const_samplers.clear(); self.compiler .target_data .const_samplers .extend(options.const_samplers.iter().map(|(loc, data)| unsafe { use std::mem::transmute; br::ScMslConstSamplerMapping { desc_set: loc.desc_set, binding: loc.binding, sampler: br::spirv_cross::MSLConstexprSampler { coord: transmute(data.coord), min_filter: transmute(data.min_filter), mag_filter: transmute(data.mag_filter), mip_filter: transmute(data.mip_filter), s_address: transmute(data.s_address), t_address: transmute(data.t_address), r_address: transmute(data.r_address), compare_func: transmute(data.compare_func), border_color: transmute(data.border_color), lod_clamp_min: data.lod_clamp_min.into(), lod_clamp_max: data.lod_clamp_max.into(), max_anisotropy: data.max_anisotropy, compare_enable: data.compare_func != SamplerCompareFunc::Always, lod_clamp_enable: data.lod_clamp_min != LodBase16::ZERO || data.lod_clamp_max != LodBase16::MAX, anisotropy_enable: data.max_anisotropy != 0, bpc: data.bpc, chroma_filter: transmute(data.chroma_filter), planes: data.planes, resolution: transmute(data.resolution), swizzle: transmute(data.swizzle), x_chroma_offset: transmute(data.x_chroma_offset), y_chroma_offset: transmute(data.y_chroma_offset), ycbcr_conversion_enable: data.ycbcr_conversion_enable, ycbcr_model: transmute(data.ycbcr_model), ycbcr_range: transmute(data.ycbcr_range), }, } })); Ok(()) } /// Generate MSL shader from the AST. fn compile(&mut self) -> Result { self.compile_internal() } } impl spirv::Ast { fn compile_internal(&self) -> Result { let vat_overrides = &self.compiler.target_data.vertex_attribute_overrides; let res_overrides = &self.compiler.target_data.resource_binding_overrides; let const_samplers = &self.compiler.target_data.const_samplers; unsafe { let mut shader_ptr = ptr::null(); check!(br::sc_internal_compiler_msl_compile( self.compiler.sc_compiler, &mut shader_ptr, vat_overrides.as_ptr(), vat_overrides.len(), res_overrides.as_ptr(), res_overrides.len(), const_samplers.as_ptr(), const_samplers.len(), )); let shader = match CStr::from_ptr(shader_ptr).to_str() { Ok(v) => v.to_owned(), Err(_) => return Err(ErrorCode::Unhandled), }; check!(br::sc_internal_free_pointer( shader_ptr as *mut std::os::raw::c_void )); Ok(shader) } } pub fn is_rasterization_enabled(&self) -> Result { unsafe { let mut is_disabled = false; check!(br::sc_internal_compiler_msl_get_is_rasterization_disabled( self.compiler.sc_compiler, &mut is_disabled )); Ok(!is_disabled) } } } // TODO: Generate with bindgen pub const ARGUMENT_BUFFER_BINDING: u32 = !3;