1use crate::abi;
43use crate::resolve;
44use crate::views::ViewHandle;
45use crate::Color;
46use core::ffi::c_void;
47#[cfg(target_os = "macos")]
49use std::path::Path;
50use crate::mangle;
51
52mangle::modifier_types! {
53 SHADER_EFFECT_T = ty("_ShaderFilterEffect");
57}
58
59#[derive(Clone, Copy, Debug)]
62pub enum ShaderArg {
63 Float(f32),
64 Float2(f32, f32),
65 Color(Color),
66}
67
68struct SwiftValue {
72 bytes: Vec<u8>,
73}
74
75impl SwiftValue {
76 unsafe fn empty(meta: *const c_void) -> Self {
77 let n = unsafe { abi::type_bytes(meta) };
79 Self { bytes: vec![0u8; n.max(1)] }
80 }
81
82 fn as_ptr(&self) -> *mut u8 {
83 self.bytes.as_ptr() as *mut u8
84 }
85}
86
87unsafe fn describe_error(error: *mut c_void) -> String {
89 unsafe {
90 if error.is_null() {
91 return "the Metal compiler failed without saying why".into();
92 }
93 let text = crate::objc::send(error, crate::objc::sel(c"localizedDescription"));
94 if text.is_null() {
95 return "the Metal compiler failed without saying why".into();
96 }
97 let utf8 = crate::objc::send_ret_ptr(text, crate::objc::sel(c"UTF8String"));
98 if utf8.is_null() {
99 return "the Metal compiler failed without saying why".into();
100 }
101 core::ffi::CStr::from_ptr(utf8 as *const i8).to_string_lossy().into_owned()
102 }
103}
104
105pub struct ShaderLibrary(SwiftValue);
107
108impl ShaderLibrary {
109 #[doc(hidden)]
111 pub fn value_ptr(&self) -> *mut c_void {
112 self.0.as_ptr() as *mut c_void
113 }
114}
115
116pub struct ShaderFunction(SwiftValue);
118
119pub struct Shader(SwiftValue);
121
122impl Shader {
123 fn as_ptr(&self) -> *mut u8 {
124 self.0.as_ptr()
125 }
126}
127
128impl ShaderLibrary {
129 pub fn from_path(path: &str) -> Self {
131 unsafe {
132 let url_meta = abi::metadata0(resolve::url_ma());
135 let url = SwiftValue::empty(url_meta);
136 let ss = abi::swift_string(path);
137 let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
138 let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
139 core::arch::asm!("blr {f}", f = in(reg) resolve::url_file_path_init(),
140 in("x0") s0, in("x1") s1,
141 in("x8") url.as_ptr(),
142 in("x20") url_meta,
143 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
144 lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
145 lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
146 lateout("x15") _, lateout("x16") _, lateout("x17") _,
147 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
148 );
149
150 let lib_meta = abi::metadata0(resolve::shader_library_ma());
151 let lib = SwiftValue::empty(lib_meta);
152 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_library_url_init(),
154 in("x0") url.as_ptr(),
155 in("x8") lib.as_ptr(),
156 in("x20") lib_meta,
157 lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
158 lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
159 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
160 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
161 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
162 );
163 Self(lib)
164 }
165 }
166
167 pub fn from_source(source: &str) -> Result<Self, String> {
191 unsafe {
192 let _pool = crate::objc::AutoreleasePool::new();
193 unsafe extern "C" {
194 fn MTLCreateSystemDefaultDevice() -> *mut c_void;
195 }
196 let device = MTLCreateSystemDefaultDevice();
197 if device.is_null() {
198 return Err("no Metal device".into());
199 }
200
201 type NewLibrary = unsafe extern "C" fn(
203 *mut c_void, *const c_void, *mut c_void, *mut c_void, *mut *mut c_void,
204 ) -> *mut c_void;
205 let new_library: NewLibrary =
206 core::mem::transmute(crate::objc::msg_send_addr());
207 let mut error: *mut c_void = core::ptr::null_mut();
208 let library = new_library(
209 device,
210 crate::objc::sel(c"newLibraryWithSource:options:error:"),
211 crate::objc::nsstring(source),
212 core::ptr::null_mut(),
213 &mut error,
214 );
215 if library.is_null() {
216 return Err(describe_error(error));
217 }
218
219 let class = crate::objc::class_named(c"RBShaderLibrary");
220 if class.is_null() {
221 return Err("SwiftUI's RBShaderLibrary is not available".into());
222 }
223 let rb = crate::objc::send_id(
224 class,
225 crate::objc::sel(c"libraryWithMetalLibrary:"),
226 library,
227 );
228 if rb.is_null() {
229 return Err("RBShaderLibrary rejected the compiled library".into());
230 }
231 crate::objc::send(rb, crate::objc::sel(c"retain"));
236 crate::objc::send(library, crate::objc::sel(c"release"));
237
238 let meta = abi::metadata0(resolve::shader_library_ma());
239 let value = SwiftValue::empty(meta);
240 debug_assert_eq!(abi::type_size(meta), 8, "ShaderLibrary is one word");
241 *(value.as_ptr() as *mut u64) = rb as u64;
242 Ok(Self(value))
243 }
244 }
245
246 pub fn from_data(bytes: &[u8]) -> Self {
257 unsafe {
258 let _pool = crate::objc::AutoreleasePool::new();
259 type InitBytes =
263 unsafe extern "C" fn(*mut c_void, *const c_void, *const u8, u64) -> *mut c_void;
264 let init: InitBytes = core::mem::transmute(crate::objc::msg_send_addr());
265 let nsdata = init(
266 crate::objc::send(
267 crate::objc::class(c"OBJC_CLASS_$_NSData"),
268 crate::objc::sel(c"alloc"),
269 ),
270 crate::objc::sel(c"initWithBytes:length:"),
271 bytes.as_ptr(),
272 bytes.len() as u64,
273 );
274 assert!(!nsdata.is_null(), "NSData creation failed");
275
276 let data_meta = abi::metadata0(resolve::data_ma());
282 let (d0, d1): (u64, u64);
283 core::arch::asm!("blr {f}", f = in(reg) resolve::data_referencing_init(),
284 in("x0") nsdata,
285 in("x20") data_meta,
286 lateout("x0") d0, lateout("x1") d1,
287 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
288 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
289 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
290 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
291 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
292 );
293
294 let lib_meta = abi::metadata0(resolve::shader_library_ma());
295 let lib = SwiftValue::empty(lib_meta);
296 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_library_data_init(),
298 in("x0") d0,
299 in("x1") d1,
300 in("x8") lib.as_ptr(),
301 in("x20") lib_meta,
302 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
303 lateout("x6") _, lateout("x7") _, lateout("x9") _,
304 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
305 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
306 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
307 );
308 Self(lib)
309 }
310 }
311
312 pub fn function(&self, name: &str) -> ShaderFunction {
318 unsafe {
319 let fn_meta = abi::metadata0(resolve::shader_function_ma());
320 let out = SwiftValue::empty(fn_meta);
321 let ss = abi::swift_string(name);
322 let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
323 let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
324 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_library_subscript(),
327 in("x0") s0, in("x1") s1,
328 in("x8") out.as_ptr(),
329 in("x20") self.0.as_ptr(),
330 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
331 lateout("x6") _, lateout("x7") _, lateout("x9") _, lateout("x10") _,
332 lateout("x11") _, lateout("x12") _, lateout("x13") _, lateout("x14") _,
333 lateout("x15") _, lateout("x16") _, lateout("x17") _,
334 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
335 );
336 ShaderFunction(out)
337 }
338 }
339}
340
341impl ShaderFunction {
342 pub fn call(&self, args: &[ShaderArg]) -> Shader {
344 unsafe {
345 let arg_meta = abi::metadata0(resolve::shader_argument_ma());
346 let stride = abi::type_stride(arg_meta);
347
348 let mut packed = vec![0u8; stride * args.len()];
351 for (i, a) in args.iter().enumerate() {
352 let value = build_argument(*a, arg_meta);
353 packed[i * stride..i * stride + value.bytes.len()]
354 .copy_from_slice(&value.bytes);
355 }
356 let array = abi::swift_array(arg_meta, args.len(), &packed);
357
358 let shader_meta = abi::metadata0(resolve::shader_ma());
359 let out = SwiftValue::empty(shader_meta);
360 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_fn_call(),
363 in("x0") array,
364 in("x8") out.as_ptr(),
365 in("x20") self.0.as_ptr(),
366 lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
367 lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
368 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
369 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
370 lateout("lr") _, lateout("x21") _, clobber_abi("C"),
371 );
372 Shader(out)
373 }
374 }
375}
376
377unsafe fn build_argument(arg: ShaderArg, meta: *const c_void) -> SwiftValue {
379 unsafe {
380 let out = SwiftValue::empty(meta);
381 match arg {
382 ShaderArg::Float(v) => {
384 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_arg_float(),
385 in("s0") v,
386 in("x8") out.as_ptr(),
387 in("x20") meta,
388 lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
389 lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
390 lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
391 lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
392 lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
393 );
394 }
395 ShaderArg::Float2(x, y) => {
396 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_arg_float2(),
397 in("s0") x, in("s1") y,
398 in("x8") out.as_ptr(),
399 in("x20") meta,
400 lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
401 lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
402 lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
403 lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
404 lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
405 );
406 }
407 ShaderArg::Color(c) => {
408 let value = crate::views::color_value(c.r, c.g, c.b, c.a);
409 core::arch::asm!("blr {f}", f = in(reg) resolve::shader_arg_color(),
410 in("x0") value,
411 in("x8") out.as_ptr(),
412 in("x20") meta,
413 lateout("x1") _, lateout("x2") _, lateout("x3") _,
414 lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
415 lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
416 lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
417 lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
418 );
419 }
420 }
421 out
422 }
423}
424
425enum Kind {
427 Color,
428 Layer,
429 Distortion,
430}
431
432unsafe fn apply(view: &ViewHandle, shader: &Shader, kind: Kind, max: (f64, f64)) -> ViewHandle {
433 unsafe {
434 let (func, has_offset) = match kind {
435 Kind::Color => (resolve::color_effect_fn(), false),
436 Kind::Layer => (resolve::layer_effect_fn(), true),
437 Kind::Distortion => (resolve::distortion_effect_fn(), true),
438 };
439 let (meta, wt, size) = SHADER_EFFECT_T.applied_to(view.meta);
440 let mut result = vec![0u8; size];
441 let this = view.value_ptr();
442
443 if has_offset {
447 core::arch::asm!("blr {f}", f = in(reg) func,
448 in("x0") shader.as_ptr(),
449 in("d0") max.0, in("d1") max.1,
450 in("x1") 1u64,
451 in("x2") view.meta,
452 in("x3") view.wt,
453 in("x8") result.as_mut_ptr(),
454 in("x20") this,
455 lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
456 lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
457 lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
458 lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
459 );
460 } else {
461 core::arch::asm!("blr {f}", f = in(reg) func,
462 in("x0") shader.as_ptr(),
463 in("x1") 1u64,
464 in("x2") view.meta,
465 in("x3") view.wt,
466 in("x8") result.as_mut_ptr(),
467 in("x20") this,
468 lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
469 lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
470 lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
471 lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
472 );
473 }
474 crate::views::wrap_owned(result, meta, wt)
475 }
476}
477
478pub fn color_effect(view: &ViewHandle, shader: &Shader) -> ViewHandle {
480 unsafe { apply(view, shader, Kind::Color, (0.0, 0.0)) }
481}
482
483pub fn layer_effect(view: &ViewHandle, shader: &Shader, max_w: f64, max_h: f64) -> ViewHandle {
485 unsafe { apply(view, shader, Kind::Layer, (max_w, max_h)) }
486}
487
488pub fn distortion_effect(view: &ViewHandle, shader: &Shader, max_w: f64, max_h: f64) -> ViewHandle {
490 unsafe { apply(view, shader, Kind::Distortion, (max_w, max_h)) }
491}
492
493#[cfg(target_os = "macos")]
506pub fn compile(source: &str, out_path: impl AsRef<Path>) -> std::io::Result<()> {
507 compile_for("macosx", source, out_path)
508}
509
510#[cfg(target_os = "macos")]
516pub fn compile_for(sdk: &str, source: &str, out_path: impl AsRef<Path>) -> std::io::Result<()> {
517 use std::process::Command;
518 let out = out_path.as_ref();
519 let src = out.with_extension("metal");
520 std::fs::write(&src, source)?;
521 let status = Command::new("xcrun")
522 .args(["-sdk", sdk, "metal", "-o"])
523 .arg(out)
524 .arg(&src)
525 .output()?;
526 if !status.status.success() {
527 return Err(std::io::Error::other(String::from_utf8_lossy(&status.stderr).to_string()));
528 }
529 Ok(())
530}