diff --git a/src/graphics.rs b/src/graphics.rs index dbdfffc65..894d9ae0e 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1042,6 +1042,13 @@ impl<'a> UniformsSource<'a> { pub enum ShaderSource<'a> { Glsl { vertex: &'a str, fragment: &'a str }, Msl { program: &'a str }, + /// A Metal library compiled ahead of time, as `xcrun metal` writes + /// one, holding `vertexShader` and `fragmentShader`. Loading one skips + /// compiling MSL at runtime, which can take tens of milliseconds per + /// shader on a device that hasn't cached it yet. If Metal can't load + /// it, `new_shader` returns a `ShaderError::LinkError`, so the app + /// can compile the MSL instead. + MetalLibrary { data: &'a [u8] }, } #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] @@ -1099,7 +1106,8 @@ impl ContextInfo { pub trait RenderingBackend { fn info(&self) -> ContextInfo; - /// For metal context's ShaderSource should contain MSL source string, for GL - glsl. + /// For metal context's ShaderSource should contain MSL source string or a compiled + /// Metal library, for GL - glsl. /// /// If in doubt, _most_ OpenGL contexts support "#version 100" glsl shaders. /// So far miniquad never encountered where it can create a rendering context, diff --git a/src/graphics/metal.rs b/src/graphics/metal.rs index e18ec9f75..719413916 100644 --- a/src/graphics/metal.rs +++ b/src/graphics/metal.rs @@ -755,18 +755,37 @@ impl RenderingBackend for MetalContext { _meta: ShaderMeta, ) -> Result { unsafe { - let program = match shader { - ShaderSource::Msl { program } => program, + let mut error: ObjcId = nil; + let library: ObjcId = match shader { + ShaderSource::Msl { program } => { + let shader = apple_util::str_to_nsstring(program); + msg_send![ + self.device, + newLibraryWithSource: shader + options:nil + error: &mut error + ] + } + ShaderSource::MetalLibrary { data } => { + let data = dispatch_data_create( + data.as_ptr() as *const c_void, + data.len(), + nil, + DISPATCH_DATA_DESTRUCTOR_DEFAULT, + ); + let library: ObjcId = + msg_send![self.device, newLibraryWithData: data error: &mut error]; + dispatch_release(data); + if library.is_null() { + let description: ObjcId = msg_send![error, localizedDescription]; + return Err(ShaderError::LinkError(apple_util::nsstring_to_string( + description, + ))); + } + library + } _ => panic!("OpenGl source on Metal context"), }; - let shader = apple_util::str_to_nsstring(program); - let mut error: ObjcId = nil; - let library: ObjcId = msg_send![ - self.device, - newLibraryWithSource: shader - options:nil - error: &mut error - ]; if library.is_null() { let description: ObjcId = msg_send![error, localizedDescription]; let string = apple_util::nsstring_to_string(description); diff --git a/src/native/apple/frameworks.rs b/src/native/apple/frameworks.rs index b82187d7b..23f87f859 100644 --- a/src/native/apple/frameworks.rs +++ b/src/native/apple/frameworks.rs @@ -69,8 +69,18 @@ impl Drop for RcObjcId { extern "C" { pub static _NSConcreteStackBlock: [*const c_void; 32]; pub static _NSConcreteBogusBlock: [*const c_void; 32]; + pub fn dispatch_data_create( + buffer: *const c_void, + size: usize, + queue: ObjcId, + destructor: ObjcId, + ) -> ObjcId; + pub fn dispatch_release(object: ObjcId); } +/// Has `dispatch_data_create` copy the buffer it's given. +pub const DISPATCH_DATA_DESTRUCTOR_DEFAULT: ObjcId = nil; + #[link(name = "Foundation", kind = "framework")] extern "C" { pub static NSRunLoopCommonModes: ObjcId;