use crate::abi;
use crate::resolve;
use crate::views::ViewHandle;
use crate::Color;
use core::ffi::c_void;
#[cfg(target_os = "macos")]
use std::path::Path;
use crate::mangle;
mangle::modifier_types! {
SHADER_EFFECT_T = ty("_ShaderFilterEffect");
}
#[derive(Clone, Copy, Debug)]
pub enum ShaderArg {
Float(f32),
Float2(f32, f32),
Color(Color),
}
struct SwiftValue {
bytes: Vec<u8>,
}
impl SwiftValue {
unsafe fn empty(meta: *const c_void) -> Self {
let n = unsafe { abi::type_bytes(meta) };
Self { bytes: vec![0u8; n.max(1)] }
}
fn as_ptr(&self) -> *mut u8 {
self.bytes.as_ptr() as *mut u8
}
}
unsafe fn describe_error(error: *mut c_void) -> String {
unsafe {
if error.is_null() {
return "the Metal compiler failed without saying why".into();
}
let text = crate::objc::send(error, crate::objc::sel(c"localizedDescription"));
if text.is_null() {
return "the Metal compiler failed without saying why".into();
}
let utf8 = crate::objc::send_ret_ptr(text, crate::objc::sel(c"UTF8String"));
if utf8.is_null() {
return "the Metal compiler failed without saying why".into();
}
core::ffi::CStr::from_ptr(utf8 as *const i8).to_string_lossy().into_owned()
}
}
pub struct ShaderLibrary(SwiftValue);
impl ShaderLibrary {
#[doc(hidden)]
pub fn value_ptr(&self) -> *mut c_void {
self.0.as_ptr() as *mut c_void
}
}
pub struct ShaderFunction(SwiftValue);
pub struct Shader(SwiftValue);
impl Shader {
fn as_ptr(&self) -> *mut u8 {
self.0.as_ptr()
}
}
impl ShaderLibrary {
pub fn from_path(path: &str) -> Self {
unsafe {
let url_meta = abi::metadata0(resolve::url_ma());
let url = SwiftValue::empty(url_meta);
let ss = abi::swift_string(path);
let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
core::arch::asm!("blr {f}", f = in(reg) resolve::url_file_path_init(),
in("x0") s0, in("x1") s1,
in("x8") url.as_ptr(),
in("x20") url_meta,
lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
let lib_meta = abi::metadata0(resolve::shader_library_ma());
let lib = SwiftValue::empty(lib_meta);
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_library_url_init(),
in("x0") url.as_ptr(),
in("x8") lib.as_ptr(),
in("x20") lib_meta,
lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
Self(lib)
}
}
pub fn from_source(source: &str) -> Result<Self, String> {
unsafe {
let _pool = crate::objc::AutoreleasePool::new();
unsafe extern "C" {
fn MTLCreateSystemDefaultDevice() -> *mut c_void;
}
let device = MTLCreateSystemDefaultDevice();
if device.is_null() {
return Err("no Metal device".into());
}
type NewLibrary = unsafe extern "C" fn(
*mut c_void, *const c_void, *mut c_void, *mut c_void, *mut *mut c_void,
) -> *mut c_void;
let new_library: NewLibrary =
core::mem::transmute(crate::objc::msg_send_addr());
let mut error: *mut c_void = core::ptr::null_mut();
let library = new_library(
device,
crate::objc::sel(c"newLibraryWithSource:options:error:"),
crate::objc::nsstring(source),
core::ptr::null_mut(),
&mut error,
);
if library.is_null() {
return Err(describe_error(error));
}
let class = crate::objc::class_named(c"RBShaderLibrary");
if class.is_null() {
return Err("SwiftUI's RBShaderLibrary is not available".into());
}
let rb = crate::objc::send_id(
class,
crate::objc::sel(c"libraryWithMetalLibrary:"),
library,
);
if rb.is_null() {
return Err("RBShaderLibrary rejected the compiled library".into());
}
crate::objc::send(rb, crate::objc::sel(c"retain"));
crate::objc::send(library, crate::objc::sel(c"release"));
let meta = abi::metadata0(resolve::shader_library_ma());
let value = SwiftValue::empty(meta);
debug_assert_eq!(abi::type_size(meta), 8, "ShaderLibrary is one word");
*(value.as_ptr() as *mut u64) = rb as u64;
Ok(Self(value))
}
}
pub fn from_data(bytes: &[u8]) -> Self {
unsafe {
let _pool = crate::objc::AutoreleasePool::new();
type InitBytes =
unsafe extern "C" fn(*mut c_void, *const c_void, *const u8, u64) -> *mut c_void;
let init: InitBytes = core::mem::transmute(crate::objc::msg_send_addr());
let nsdata = init(
crate::objc::send(
crate::objc::class(c"OBJC_CLASS_$_NSData"),
crate::objc::sel(c"alloc"),
),
crate::objc::sel(c"initWithBytes:length:"),
bytes.as_ptr(),
bytes.len() as u64,
);
assert!(!nsdata.is_null(), "NSData creation failed");
let data_meta = abi::metadata0(resolve::data_ma());
let (d0, d1): (u64, u64);
core::arch::asm!("blr {f}", f = in(reg) resolve::data_referencing_init(),
in("x0") nsdata,
in("x20") data_meta,
lateout("x0") d0, lateout("x1") d1,
lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
let lib_meta = abi::metadata0(resolve::shader_library_ma());
let lib = SwiftValue::empty(lib_meta);
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_library_data_init(),
in("x0") d0,
in("x1") d1,
in("x8") lib.as_ptr(),
in("x20") lib_meta,
lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
lateout("x6") _, lateout("x7") _, lateout("x9") _,
lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
Self(lib)
}
}
pub fn function(&self, name: &str) -> ShaderFunction {
unsafe {
let fn_meta = abi::metadata0(resolve::shader_function_ma());
let out = SwiftValue::empty(fn_meta);
let ss = abi::swift_string(name);
let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_library_subscript(),
in("x0") s0, in("x1") s1,
in("x8") out.as_ptr(),
in("x20") self.0.as_ptr(),
lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
ShaderFunction(out)
}
}
}
impl ShaderFunction {
pub fn call(&self, args: &[ShaderArg]) -> Shader {
unsafe {
let arg_meta = abi::metadata0(resolve::shader_argument_ma());
let stride = abi::type_stride(arg_meta);
let mut packed = vec![0u8; stride * args.len()];
for (i, a) in args.iter().enumerate() {
let value = build_argument(*a, arg_meta);
packed[i * stride..i * stride + value.bytes.len()]
.copy_from_slice(&value.bytes);
}
let array = abi::swift_array(arg_meta, args.len(), &packed);
let shader_meta = abi::metadata0(resolve::shader_ma());
let out = SwiftValue::empty(shader_meta);
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_fn_call(),
in("x0") array,
in("x8") out.as_ptr(),
in("x20") self.0.as_ptr(),
lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
Shader(out)
}
}
}
unsafe fn build_argument(arg: ShaderArg, meta: *const c_void) -> SwiftValue {
unsafe {
let out = SwiftValue::empty(meta);
match arg {
ShaderArg::Float(v) => {
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_arg_float(),
in("s0") v,
in("x8") out.as_ptr(),
in("x20") meta,
lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
}
ShaderArg::Float2(x, y) => {
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_arg_float2(),
in("s0") x, in("s1") y,
in("x8") out.as_ptr(),
in("x20") meta,
lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
}
ShaderArg::Color(c) => {
let value = crate::views::color_value(c.r, c.g, c.b, c.a);
core::arch::asm!("blr {f}", f = in(reg) resolve::shader_arg_color(),
in("x0") value,
in("x8") out.as_ptr(),
in("x20") meta,
lateout("x1") _, lateout("x2") _, lateout("x3") _,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
}
}
out
}
}
enum Kind {
Color,
Layer,
Distortion,
}
unsafe fn apply(view: &ViewHandle, shader: &Shader, kind: Kind, max: (f64, f64)) -> ViewHandle {
unsafe {
let (func, has_offset) = match kind {
Kind::Color => (resolve::color_effect_fn(), false),
Kind::Layer => (resolve::layer_effect_fn(), true),
Kind::Distortion => (resolve::distortion_effect_fn(), true),
};
let (meta, wt, size) = SHADER_EFFECT_T.applied_to(view.meta);
let mut result = vec![0u8; size];
let this = view.value_ptr();
if has_offset {
core::arch::asm!("blr {f}", f = in(reg) func,
in("x0") shader.as_ptr(),
in("d0") max.0, in("d1") max.1,
in("x1") 1u64,
in("x2") view.meta,
in("x3") view.wt,
in("x8") result.as_mut_ptr(),
in("x20") this,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
} else {
core::arch::asm!("blr {f}", f = in(reg) func,
in("x0") shader.as_ptr(),
in("x1") 1u64,
in("x2") view.meta,
in("x3") view.wt,
in("x8") result.as_mut_ptr(),
in("x20") this,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
);
}
crate::views::wrap_owned(result, meta, wt)
}
}
pub fn color_effect(view: &ViewHandle, shader: &Shader) -> ViewHandle {
unsafe { apply(view, shader, Kind::Color, (0.0, 0.0)) }
}
pub fn layer_effect(view: &ViewHandle, shader: &Shader, max_w: f64, max_h: f64) -> ViewHandle {
unsafe { apply(view, shader, Kind::Layer, (max_w, max_h)) }
}
pub fn distortion_effect(view: &ViewHandle, shader: &Shader, max_w: f64, max_h: f64) -> ViewHandle {
unsafe { apply(view, shader, Kind::Distortion, (max_w, max_h)) }
}
#[cfg(target_os = "macos")]
pub fn compile(source: &str, out_path: impl AsRef<Path>) -> std::io::Result<()> {
compile_for("macosx", source, out_path)
}
#[cfg(target_os = "macos")]
pub fn compile_for(sdk: &str, source: &str, out_path: impl AsRef<Path>) -> std::io::Result<()> {
use std::process::Command;
let out = out_path.as_ref();
let src = out.with_extension("metal");
std::fs::write(&src, source)?;
let status = Command::new("xcrun")
.args(["-sdk", sdk, "metal", "-o"])
.arg(out)
.arg(&src)
.output()?;
if !status.status.success() {
return Err(std::io::Error::other(String::from_utf8_lossy(&status.stderr).to_string()));
}
Ok(())
}