use std::ffi::{c_void, CStr};
use std::ptr::NonNull;
use crate::cm::IOSurface;
use crate::FourCharCode;
pub mod pixel_format {
use crate::FourCharCode;
pub const BGRA: FourCharCode = FourCharCode::from_bytes(*b"BGRA");
pub const L10R: FourCharCode = FourCharCode::from_bytes(*b"l10r");
pub const YCBCR_420V: FourCharCode = FourCharCode::from_bytes(*b"420v");
pub const YCBCR_420F: FourCharCode = FourCharCode::from_bytes(*b"420f");
#[must_use]
pub fn is_ycbcr_biplanar(format: impl Into<FourCharCode>) -> bool {
let f = format.into();
f.equals(YCBCR_420V) || f.equals(YCBCR_420F)
}
#[must_use]
pub fn is_full_range(format: impl Into<FourCharCode>) -> bool {
format.into().equals(YCBCR_420F)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u64)]
pub enum MetalPixelFormat {
BGRA8Unorm = 80,
BGR10A2Unorm = 94,
R8Unorm = 10,
RG8Unorm = 30,
}
impl MetalPixelFormat {
#[must_use]
pub const fn raw(self) -> u64 {
self as u64
}
#[must_use]
pub const fn from_raw(value: u64) -> Option<Self> {
match value {
80 => Some(Self::BGRA8Unorm),
94 => Some(Self::BGR10A2Unorm),
10 => Some(Self::R8Unorm),
30 => Some(Self::RG8Unorm),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct IOSurfaceInfo {
pub width: usize,
pub height: usize,
pub bytes_per_row: usize,
pub pixel_format: FourCharCode,
pub plane_count: usize,
pub planes: Vec<PlaneInfo>,
}
#[derive(Debug, Clone)]
pub struct PlaneInfo {
pub index: usize,
pub width: usize,
pub height: usize,
pub bytes_per_row: usize,
}
impl IOSurface {
#[must_use]
pub fn info(&self) -> IOSurfaceInfo {
let width = self.width();
let height = self.height();
let bytes_per_row = self.bytes_per_row();
let pix_format: FourCharCode = self.pixel_format().into();
let plane_count = self.plane_count();
let planes = if plane_count > 0 {
(0..plane_count)
.map(|i| PlaneInfo {
index: i,
width: self.width_of_plane(i),
height: self.height_of_plane(i),
bytes_per_row: self.bytes_per_row_of_plane(i),
})
.collect()
} else {
vec![]
};
IOSurfaceInfo {
width,
height,
bytes_per_row,
pixel_format: pix_format,
plane_count,
planes,
}
}
#[must_use]
pub fn is_ycbcr_biplanar(&self) -> bool {
pixel_format::is_ycbcr_biplanar(self.pixel_format())
}
}
#[derive(Debug, Clone, Copy)]
pub struct TextureParams {
pub width: usize,
pub height: usize,
pub format: MetalPixelFormat,
pub plane: usize,
}
impl TextureParams {
#[must_use]
pub const fn metal_pixel_format(&self) -> u64 {
self.format.raw()
}
}
impl IOSurface {
#[must_use]
pub fn texture_params(&self) -> Vec<TextureParams> {
let pix_format: FourCharCode = self.pixel_format().into();
let plane_count = self.plane_count();
if pix_format == pixel_format::BGRA {
vec![TextureParams {
width: self.width(),
height: self.height(),
format: MetalPixelFormat::BGRA8Unorm,
plane: 0,
}]
} else if pix_format == pixel_format::L10R {
vec![TextureParams {
width: self.width(),
height: self.height(),
format: MetalPixelFormat::BGR10A2Unorm,
plane: 0,
}]
} else if pixel_format::is_ycbcr_biplanar(pix_format) && plane_count >= 2 {
vec![
TextureParams {
width: self.width_of_plane(0),
height: self.height_of_plane(0),
format: MetalPixelFormat::R8Unorm,
plane: 0,
},
TextureParams {
width: self.width_of_plane(1),
height: self.height_of_plane(1),
format: MetalPixelFormat::RG8Unorm,
plane: 1,
},
]
} else {
vec![TextureParams {
width: self.width(),
height: self.height(),
format: MetalPixelFormat::BGRA8Unorm,
plane: 0,
}]
}
}
}
#[derive(Debug)]
pub struct CapturedTextures<T> {
pub plane0: T,
pub plane1: Option<T>,
pub pixel_format: FourCharCode,
pub width: usize,
pub height: usize,
}
impl<T> CapturedTextures<T> {
#[must_use]
pub fn is_ycbcr(&self) -> bool {
pixel_format::is_ycbcr_biplanar(self.pixel_format)
}
}
impl IOSurface {
pub fn metal_textures<T, F>(&self, create_texture: F) -> Option<CapturedTextures<T>>
where
F: Fn(&TextureParams, *const c_void) -> Option<T>,
{
let width = self.width();
let height = self.height();
let pix_format: FourCharCode = self.pixel_format().into();
if width == 0 || height == 0 {
return None;
}
let iosurface_ptr = self.as_ptr();
let params = self.texture_params();
if params.len() == 1 {
let texture = create_texture(¶ms[0], iosurface_ptr)?;
Some(CapturedTextures {
plane0: texture,
plane1: None,
pixel_format: pix_format,
width,
height,
})
} else if params.len() >= 2 {
let y_texture = create_texture(¶ms[0], iosurface_ptr)?;
let uv_texture = create_texture(¶ms[1], iosurface_ptr)?;
Some(CapturedTextures {
plane0: y_texture,
plane1: Some(uv_texture),
pixel_format: pix_format,
width,
height,
})
} else {
None
}
}
}
pub const SHADER_SOURCE: &str = r"
#include <metal_stdlib>
using namespace metal;
struct Uniforms {
float2 viewport_size;
float2 texture_size;
float time;
uint pixel_format;
float padding[2];
};
struct TexturedVertexOut {
float4 position [[position]];
float2 texcoord;
};
// Fullscreen quad vertex shader with aspect ratio correction
vertex TexturedVertexOut vertex_fullscreen(uint vid [[vertex_id]], constant Uniforms& uniforms [[buffer(0)]]) {
TexturedVertexOut out;
float va = uniforms.viewport_size.x / uniforms.viewport_size.y;
float ta = uniforms.texture_size.x / uniforms.texture_size.y;
float sx = ta > va ? 1.0 : ta / va;
float sy = ta > va ? va / ta : 1.0;
float2 positions[4] = { float2(-sx, -sy), float2(sx, -sy), float2(-sx, sy), float2(sx, sy) };
float2 texcoords[4] = { float2(0.0, 1.0), float2(1.0, 1.0), float2(0.0, 0.0), float2(1.0, 0.0) };
out.position = float4(positions[vid], 0.0, 1.0);
out.texcoord = texcoords[vid];
return out;
}
// BGRA/RGB texture fragment shader
fragment float4 fragment_textured(TexturedVertexOut in [[stage_in]], texture2d<float> tex [[texture(0)]]) {
constexpr sampler s(mag_filter::linear, min_filter::linear);
return tex.sample(s, in.texcoord);
}
// YCbCr to RGB conversion (BT.709 matrix for HD video)
float4 ycbcr_to_rgb(float y, float2 cbcr, bool full_range) {
float y_adj = full_range ? y : (y - 16.0/255.0) * (255.0/219.0);
float cb = cbcr.x - 0.5;
float cr = cbcr.y - 0.5;
// BT.709 conversion matrix
float r = y_adj + 1.5748 * cr;
float g = y_adj - 0.1873 * cb - 0.4681 * cr;
float b = y_adj + 1.8556 * cb;
return float4(saturate(float3(r, g, b)), 1.0);
}
// YCbCr biplanar (420v/420f) fragment shader
fragment float4 fragment_ycbcr(TexturedVertexOut in [[stage_in]],
texture2d<float> y_tex [[texture(0)]],
texture2d<float> cbcr_tex [[texture(1)]],
constant Uniforms& uniforms [[buffer(0)]]) {
constexpr sampler s(mag_filter::linear, min_filter::linear);
float y = y_tex.sample(s, in.texcoord).r;
float2 cbcr = cbcr_tex.sample(s, in.texcoord).rg;
bool full_range = (uniforms.pixel_format == 0x34323066); // '420f'
return ycbcr_to_rgb(y, cbcr, full_range);
}
// Colored vertex input/output for UI overlays
struct ColoredVertex {
float2 position [[attribute(0)]];
float4 color [[attribute(1)]];
};
struct ColoredVertexOut {
float4 position [[position]];
float4 color;
};
// Colored vertex shader for UI elements (position in pixels, converted to NDC)
vertex ColoredVertexOut vertex_colored(ColoredVertex in [[stage_in]], constant Uniforms& uniforms [[buffer(1)]]) {
ColoredVertexOut out;
float2 ndc = (in.position / uniforms.viewport_size) * 2.0 - 1.0;
ndc.y = -ndc.y;
out.position = float4(ndc, 0.0, 1.0);
out.color = in.color;
return out;
}
// Colored fragment shader for UI elements
fragment float4 fragment_colored(ColoredVertexOut in [[stage_in]]) {
return in.color;
}
";
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct Uniforms {
pub viewport_size: [f32; 2],
pub texture_size: [f32; 2],
pub time: f32,
pub pixel_format: u32,
#[doc(hidden)]
pub _padding: [f32; 2],
}
impl Uniforms {
#[must_use]
pub fn new(
viewport_width: f32,
viewport_height: f32,
texture_width: f32,
texture_height: f32,
) -> Self {
Self {
viewport_size: [viewport_width, viewport_height],
texture_size: [texture_width, texture_height],
time: 0.0,
pixel_format: 0,
_padding: [0.0; 2],
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)] pub fn from_captured_textures<T>(
viewport_width: f32,
viewport_height: f32,
textures: &CapturedTextures<T>,
) -> Self {
Self {
viewport_size: [viewport_width, viewport_height],
texture_size: [textures.width as f32, textures.height as f32],
time: 0.0,
pixel_format: textures.pixel_format.as_u32(),
_padding: [0.0; 2],
}
}
#[must_use]
pub fn with_pixel_format(mut self, format: impl Into<FourCharCode>) -> Self {
self.pixel_format = format.into().as_u32();
self
}
#[must_use]
pub fn with_time(mut self, time: f32) -> Self {
self.time = time;
self
}
}
#[link(name = "Metal", kind = "framework")]
extern "C" {}
#[link(name = "QuartzCore", kind = "framework")]
extern "C" {}
extern "C" {
fn metal_create_system_default_device() -> *mut c_void;
fn metal_device_release(device: *mut c_void);
fn metal_device_get_name(device: *mut c_void) -> *const std::ffi::c_char;
fn metal_device_create_command_queue(device: *mut c_void) -> *mut c_void;
fn metal_device_create_render_pipeline_state(
device: *mut c_void,
desc: *mut c_void,
) -> *mut c_void;
fn metal_create_texture_from_iosurface(
device: *mut c_void,
iosurface: *mut c_void,
plane: usize,
width: usize,
height: usize,
pixel_format: u64,
) -> *mut c_void;
fn metal_texture_release(texture: *mut c_void);
fn metal_texture_retain(texture: *mut c_void) -> *mut c_void;
fn metal_texture_get_width(texture: *mut c_void) -> usize;
fn metal_texture_get_height(texture: *mut c_void) -> usize;
fn metal_texture_get_pixel_format(texture: *mut c_void) -> u64;
fn metal_command_queue_release(queue: *mut c_void);
fn metal_command_queue_command_buffer(queue: *mut c_void) -> *mut c_void;
fn metal_device_create_library_with_source(
device: *mut c_void,
source: *const std::ffi::c_char,
error_out: *mut *const std::ffi::c_char,
) -> *mut c_void;
fn metal_library_release(library: *mut c_void);
fn metal_library_get_function(
library: *mut c_void,
name: *const std::ffi::c_char,
) -> *mut c_void;
fn metal_function_release(function: *mut c_void);
fn metal_device_create_buffer(device: *mut c_void, length: usize, options: u64) -> *mut c_void;
fn metal_buffer_contents(buffer: *mut c_void) -> *mut c_void;
fn metal_buffer_length(buffer: *mut c_void) -> usize;
fn metal_buffer_did_modify_range(buffer: *mut c_void, location: usize, length: usize);
fn metal_buffer_release(buffer: *mut c_void);
fn metal_layer_create() -> *mut c_void;
fn metal_layer_set_device(layer: *mut c_void, device: *mut c_void);
fn metal_layer_set_pixel_format(layer: *mut c_void, format: u64);
fn metal_layer_set_drawable_size(layer: *mut c_void, width: f64, height: f64);
fn metal_layer_set_presents_with_transaction(layer: *mut c_void, value: bool);
fn metal_layer_next_drawable(layer: *mut c_void) -> *mut c_void;
fn metal_layer_release(layer: *mut c_void);
fn metal_drawable_texture(drawable: *mut c_void) -> *mut c_void;
fn metal_drawable_release(drawable: *mut c_void);
fn metal_command_buffer_present_drawable(cmd_buffer: *mut c_void, drawable: *mut c_void);
fn metal_command_buffer_commit(cmd_buffer: *mut c_void);
fn metal_command_buffer_release(cmd_buffer: *mut c_void);
fn metal_render_pass_descriptor_create() -> *mut c_void;
fn metal_render_pass_set_color_attachment_texture(
desc: *mut c_void,
index: usize,
texture: *mut c_void,
);
fn metal_render_pass_set_color_attachment_load_action(
desc: *mut c_void,
index: usize,
action: u64,
);
fn metal_render_pass_set_color_attachment_store_action(
desc: *mut c_void,
index: usize,
action: u64,
);
fn metal_render_pass_set_color_attachment_clear_color(
desc: *mut c_void,
index: usize,
r: f64,
g: f64,
b: f64,
a: f64,
);
fn metal_render_pass_descriptor_release(desc: *mut c_void);
fn metal_vertex_descriptor_create() -> *mut c_void;
fn metal_vertex_descriptor_set_attribute(
desc: *mut c_void,
index: usize,
format: u64,
offset: usize,
buffer_index: usize,
);
fn metal_vertex_descriptor_set_layout(
desc: *mut c_void,
buffer_index: usize,
stride: usize,
step_function: u64,
);
fn metal_vertex_descriptor_release(desc: *mut c_void);
fn metal_render_pipeline_descriptor_create() -> *mut c_void;
fn metal_render_pipeline_descriptor_set_vertex_function(
desc: *mut c_void,
function: *mut c_void,
);
fn metal_render_pipeline_descriptor_set_fragment_function(
desc: *mut c_void,
function: *mut c_void,
);
fn metal_render_pipeline_descriptor_set_vertex_descriptor(
desc: *mut c_void,
vertex_descriptor: *mut c_void,
);
fn metal_render_pipeline_descriptor_set_color_attachment_pixel_format(
desc: *mut c_void,
index: usize,
format: u64,
);
fn metal_render_pipeline_descriptor_set_blending_enabled(
desc: *mut c_void,
index: usize,
enabled: bool,
);
fn metal_render_pipeline_descriptor_set_blend_operations(
desc: *mut c_void,
index: usize,
rgb_op: u64,
alpha_op: u64,
);
fn metal_render_pipeline_descriptor_set_blend_factors(
desc: *mut c_void,
index: usize,
src_rgb: u64,
dst_rgb: u64,
src_alpha: u64,
dst_alpha: u64,
);
fn metal_render_pipeline_descriptor_release(desc: *mut c_void);
fn metal_render_pipeline_state_release(state: *mut c_void);
fn metal_command_buffer_render_command_encoder(
cmd_buffer: *mut c_void,
render_pass: *mut c_void,
) -> *mut c_void;
fn metal_render_encoder_set_pipeline_state(encoder: *mut c_void, state: *mut c_void);
fn metal_render_encoder_set_vertex_buffer(
encoder: *mut c_void,
buffer: *mut c_void,
offset: usize,
index: usize,
);
fn metal_render_encoder_set_fragment_buffer(
encoder: *mut c_void,
buffer: *mut c_void,
offset: usize,
index: usize,
);
fn metal_render_encoder_set_fragment_texture(
encoder: *mut c_void,
texture: *mut c_void,
index: usize,
);
fn metal_render_encoder_draw_primitives(
encoder: *mut c_void,
primitive_type: u64,
vertex_start: usize,
vertex_count: usize,
);
fn metal_render_encoder_end_encoding(encoder: *mut c_void);
fn metal_render_encoder_release(encoder: *mut c_void);
fn nsview_set_wants_layer(view: *mut c_void);
fn nsview_set_layer(view: *mut c_void, layer: *mut c_void);
}
#[derive(Debug)]
pub struct MetalDevice {
ptr: NonNull<c_void>,
}
impl MetalDevice {
#[must_use]
pub fn system_default() -> Option<Self> {
let ptr = unsafe { metal_create_system_default_device() };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
#[must_use]
pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
NonNull::new(ptr).map(|ptr| Self { ptr })
}
#[must_use]
pub unsafe fn from_ptr_retained(ptr: *mut c_void) -> Option<Self> {
if ptr.is_null() {
return None;
}
NonNull::new(ptr).map(|ptr| Self { ptr })
}
#[must_use]
pub fn name(&self) -> String {
unsafe {
let name_ptr = metal_device_get_name(self.ptr.as_ptr());
if name_ptr.is_null() {
return String::new();
}
CStr::from_ptr(name_ptr).to_string_lossy().into_owned()
}
}
#[must_use]
pub fn create_command_queue(&self) -> Option<MetalCommandQueue> {
let ptr = unsafe { metal_device_create_command_queue(self.ptr.as_ptr()) };
NonNull::new(ptr).map(|ptr| MetalCommandQueue { ptr })
}
pub fn create_library_with_source(&self, source: &str) -> Result<MetalLibrary, String> {
use std::ffi::CString;
let source_c = CString::new(source).map_err(|e| e.to_string())?;
let mut error_ptr: *const std::ffi::c_char = std::ptr::null();
let ptr = unsafe {
metal_device_create_library_with_source(
self.ptr.as_ptr(),
source_c.as_ptr(),
&mut error_ptr,
)
};
NonNull::new(ptr).map_or_else(
|| {
let error = if error_ptr.is_null() {
"Unknown shader compilation error".to_string()
} else {
unsafe { CStr::from_ptr(error_ptr).to_string_lossy().into_owned() }
};
Err(error)
},
|ptr| Ok(MetalLibrary { ptr }),
)
}
#[must_use]
pub fn create_buffer(&self, length: usize, options: ResourceOptions) -> Option<MetalBuffer> {
let ptr = unsafe { metal_device_create_buffer(self.ptr.as_ptr(), length, options.0) };
NonNull::new(ptr).map(|ptr| MetalBuffer { ptr })
}
#[must_use]
pub fn create_buffer_with_data<T>(&self, data: &T) -> Option<MetalBuffer> {
let size = std::mem::size_of::<T>();
let buffer = self.create_buffer(size, ResourceOptions::CPU_CACHE_MODE_DEFAULT_CACHE)?;
unsafe {
std::ptr::copy_nonoverlapping(
std::ptr::addr_of!(*data).cast::<u8>(),
buffer.contents().cast(),
size,
);
}
Some(buffer)
}
#[must_use]
pub fn create_render_pipeline_state(
&self,
descriptor: &MetalRenderPipelineDescriptor,
) -> Option<MetalRenderPipelineState> {
let ptr = unsafe {
metal_device_create_render_pipeline_state(self.ptr.as_ptr(), descriptor.as_ptr())
};
NonNull::new(ptr).map(|ptr| MetalRenderPipelineState { ptr })
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalDevice {
fn drop(&mut self) {
unsafe { metal_device_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalDevice {}
unsafe impl Sync for MetalDevice {}
#[derive(Debug)]
pub struct MetalTexture {
ptr: NonNull<c_void>,
}
impl MetalTexture {
#[must_use]
pub fn width(&self) -> usize {
unsafe { metal_texture_get_width(self.ptr.as_ptr()) }
}
#[must_use]
pub fn height(&self) -> usize {
unsafe { metal_texture_get_height(self.ptr.as_ptr()) }
}
#[must_use]
pub fn pixel_format(&self) -> MetalPixelFormat {
let raw = unsafe { metal_texture_get_pixel_format(self.ptr.as_ptr()) };
MetalPixelFormat::from_raw(raw).unwrap_or(MetalPixelFormat::BGRA8Unorm)
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Clone for MetalTexture {
fn clone(&self) -> Self {
let ptr = unsafe { metal_texture_retain(self.ptr.as_ptr()) };
Self {
ptr: NonNull::new(ptr).expect("metal_texture_retain returned null"),
}
}
}
impl Drop for MetalTexture {
fn drop(&mut self) {
unsafe { metal_texture_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalTexture {}
unsafe impl Sync for MetalTexture {}
#[derive(Debug)]
pub struct MetalCommandQueue {
ptr: NonNull<c_void>,
}
impl MetalCommandQueue {
#[must_use]
pub fn command_buffer(&self) -> Option<MetalCommandBuffer> {
let ptr = unsafe { metal_command_queue_command_buffer(self.ptr.as_ptr()) };
NonNull::new(ptr).map(|ptr| MetalCommandBuffer { ptr })
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalCommandQueue {
fn drop(&mut self) {
unsafe { metal_command_queue_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalCommandQueue {}
unsafe impl Sync for MetalCommandQueue {}
#[derive(Debug)]
pub struct MetalLibrary {
ptr: NonNull<c_void>,
}
impl MetalLibrary {
#[must_use]
pub fn get_function(&self, name: &str) -> Option<MetalFunction> {
use std::ffi::CString;
let name_c = CString::new(name).ok()?;
let ptr = unsafe { metal_library_get_function(self.ptr.as_ptr(), name_c.as_ptr()) };
NonNull::new(ptr).map(|ptr| MetalFunction { ptr })
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalLibrary {
fn drop(&mut self) {
unsafe { metal_library_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalLibrary {}
unsafe impl Sync for MetalLibrary {}
#[derive(Debug)]
pub struct MetalFunction {
ptr: NonNull<c_void>,
}
impl MetalFunction {
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalFunction {
fn drop(&mut self) {
unsafe { metal_function_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalFunction {}
unsafe impl Sync for MetalFunction {}
#[derive(Debug)]
pub struct MetalBuffer {
ptr: NonNull<c_void>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ResourceOptions(u64);
impl ResourceOptions {
pub const CPU_CACHE_MODE_DEFAULT_CACHE: Self = Self(0);
pub const STORAGE_MODE_SHARED: Self = Self(0);
pub const STORAGE_MODE_MANAGED: Self = Self(1 << 4);
}
impl MetalBuffer {
#[must_use]
pub fn contents(&self) -> *mut c_void {
unsafe { metal_buffer_contents(self.ptr.as_ptr()) }
}
#[must_use]
pub fn length(&self) -> usize {
unsafe { metal_buffer_length(self.ptr.as_ptr()) }
}
pub fn did_modify_range(&self, range: std::ops::Range<usize>) {
unsafe { metal_buffer_did_modify_range(self.ptr.as_ptr(), range.start, range.len()) }
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalBuffer {
fn drop(&mut self) {
unsafe { metal_buffer_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalBuffer {}
unsafe impl Sync for MetalBuffer {}
#[derive(Debug)]
pub struct MetalLayer {
ptr: NonNull<c_void>,
}
impl MetalLayer {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { metal_layer_create() };
Self {
ptr: NonNull::new(ptr).expect("metal_layer_create returned null"),
}
}
pub fn set_device(&self, device: &MetalDevice) {
unsafe { metal_layer_set_device(self.ptr.as_ptr(), device.as_ptr()) }
}
pub fn set_pixel_format(&self, format: MTLPixelFormat) {
unsafe { metal_layer_set_pixel_format(self.ptr.as_ptr(), format.raw()) }
}
pub fn set_drawable_size(&self, width: f64, height: f64) {
unsafe { metal_layer_set_drawable_size(self.ptr.as_ptr(), width, height) }
}
pub fn set_presents_with_transaction(&self, value: bool) {
unsafe { metal_layer_set_presents_with_transaction(self.ptr.as_ptr(), value) }
}
#[must_use]
pub fn next_drawable(&self) -> Option<MetalDrawable> {
let ptr = unsafe { metal_layer_next_drawable(self.ptr.as_ptr()) };
NonNull::new(ptr).map(|ptr| MetalDrawable { ptr })
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Default for MetalLayer {
fn default() -> Self {
Self::new()
}
}
impl Drop for MetalLayer {
fn drop(&mut self) {
unsafe { metal_layer_release(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct MetalDrawable {
ptr: NonNull<c_void>,
}
impl MetalDrawable {
#[must_use]
pub fn texture(&self) -> MetalTexture {
let ptr = unsafe { metal_drawable_texture(self.ptr.as_ptr()) };
let ptr = unsafe { metal_texture_retain(ptr) };
MetalTexture {
ptr: NonNull::new(ptr).expect("drawable texture is null"),
}
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalDrawable {
fn drop(&mut self) {
unsafe { metal_drawable_release(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct MetalCommandBuffer {
ptr: NonNull<c_void>,
}
impl MetalCommandBuffer {
#[must_use]
pub fn render_command_encoder(
&self,
render_pass: &MetalRenderPassDescriptor,
) -> Option<MetalRenderCommandEncoder> {
let ptr = unsafe {
metal_command_buffer_render_command_encoder(self.ptr.as_ptr(), render_pass.as_ptr())
};
NonNull::new(ptr).map(|ptr| MetalRenderCommandEncoder { ptr })
}
pub fn present_drawable(&self, drawable: &MetalDrawable) {
unsafe { metal_command_buffer_present_drawable(self.ptr.as_ptr(), drawable.as_ptr()) }
}
pub fn commit(&self) {
unsafe { metal_command_buffer_commit(self.ptr.as_ptr()) }
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalCommandBuffer {
fn drop(&mut self) {
unsafe { metal_command_buffer_release(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct MetalRenderPassDescriptor {
ptr: NonNull<c_void>,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLLoadAction {
DontCare = 0,
Load = 1,
#[default]
Clear = 2,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLStoreAction {
DontCare = 0,
#[default]
Store = 1,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLPixelFormat {
Invalid = 0,
#[default]
BGRA8Unorm = 80,
BGR10A2Unorm = 94,
R8Unorm = 10,
RG8Unorm = 30,
}
impl MTLPixelFormat {
#[must_use]
pub const fn raw(self) -> u64 {
self as u64
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLVertexFormat {
Invalid = 0,
#[default]
Float2 = 29,
Float3 = 30,
Float4 = 31,
}
impl MTLVertexFormat {
#[must_use]
pub const fn raw(self) -> u64 {
self as u64
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLVertexStepFunction {
Constant = 0,
#[default]
PerVertex = 1,
PerInstance = 2,
}
impl MTLVertexStepFunction {
#[must_use]
pub const fn raw(self) -> u64 {
self as u64
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLPrimitiveType {
Point = 0,
Line = 1,
LineStrip = 2,
#[default]
Triangle = 3,
TriangleStrip = 4,
}
impl MTLPrimitiveType {
#[must_use]
pub const fn raw(self) -> u64 {
self as u64
}
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLBlendOperation {
#[default]
Add = 0,
Subtract = 1,
ReverseSubtract = 2,
Min = 3,
Max = 4,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u64)]
pub enum MTLBlendFactor {
Zero = 0,
#[default]
One = 1,
SourceColor = 2,
OneMinusSourceColor = 3,
SourceAlpha = 4,
OneMinusSourceAlpha = 5,
DestinationColor = 6,
OneMinusDestinationColor = 7,
DestinationAlpha = 8,
OneMinusDestinationAlpha = 9,
}
impl MetalRenderPassDescriptor {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { metal_render_pass_descriptor_create() };
Self {
ptr: NonNull::new(ptr).expect("render pass descriptor create failed"),
}
}
pub fn set_color_attachment_texture(&self, index: usize, texture: &MetalTexture) {
unsafe {
metal_render_pass_set_color_attachment_texture(
self.ptr.as_ptr(),
index,
texture.as_ptr(),
);
}
}
pub fn set_color_attachment_load_action(&self, index: usize, action: MTLLoadAction) {
unsafe {
metal_render_pass_set_color_attachment_load_action(
self.ptr.as_ptr(),
index,
action as u64,
);
}
}
pub fn set_color_attachment_store_action(&self, index: usize, action: MTLStoreAction) {
unsafe {
metal_render_pass_set_color_attachment_store_action(
self.ptr.as_ptr(),
index,
action as u64,
);
}
}
pub fn set_color_attachment_clear_color(&self, index: usize, r: f64, g: f64, b: f64, a: f64) {
unsafe {
metal_render_pass_set_color_attachment_clear_color(
self.ptr.as_ptr(),
index,
r,
g,
b,
a,
);
}
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Default for MetalRenderPassDescriptor {
fn default() -> Self {
Self::new()
}
}
impl Drop for MetalRenderPassDescriptor {
fn drop(&mut self) {
unsafe { metal_render_pass_descriptor_release(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct MetalVertexDescriptor {
ptr: NonNull<c_void>,
}
impl MetalVertexDescriptor {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { metal_vertex_descriptor_create() };
Self {
ptr: NonNull::new(ptr).expect("vertex descriptor create failed"),
}
}
pub fn set_attribute(
&self,
index: usize,
format: MTLVertexFormat,
offset: usize,
buffer_index: usize,
) {
unsafe {
metal_vertex_descriptor_set_attribute(
self.ptr.as_ptr(),
index,
format.raw(),
offset,
buffer_index,
);
}
}
pub fn set_layout(
&self,
buffer_index: usize,
stride: usize,
step_function: MTLVertexStepFunction,
) {
unsafe {
metal_vertex_descriptor_set_layout(
self.ptr.as_ptr(),
buffer_index,
stride,
step_function.raw(),
);
}
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Default for MetalVertexDescriptor {
fn default() -> Self {
Self::new()
}
}
impl Drop for MetalVertexDescriptor {
fn drop(&mut self) {
unsafe { metal_vertex_descriptor_release(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct MetalRenderPipelineDescriptor {
ptr: NonNull<c_void>,
}
impl MetalRenderPipelineDescriptor {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { metal_render_pipeline_descriptor_create() };
Self {
ptr: NonNull::new(ptr).expect("render pipeline descriptor create failed"),
}
}
pub fn set_vertex_function(&self, function: &MetalFunction) {
unsafe {
metal_render_pipeline_descriptor_set_vertex_function(
self.ptr.as_ptr(),
function.as_ptr(),
);
}
}
pub fn set_fragment_function(&self, function: &MetalFunction) {
unsafe {
metal_render_pipeline_descriptor_set_fragment_function(
self.ptr.as_ptr(),
function.as_ptr(),
);
}
}
pub fn set_vertex_descriptor(&self, descriptor: &MetalVertexDescriptor) {
unsafe {
metal_render_pipeline_descriptor_set_vertex_descriptor(
self.ptr.as_ptr(),
descriptor.as_ptr(),
);
}
}
pub fn set_color_attachment_pixel_format(&self, index: usize, format: MTLPixelFormat) {
unsafe {
metal_render_pipeline_descriptor_set_color_attachment_pixel_format(
self.ptr.as_ptr(),
index,
format.raw(),
);
}
}
pub fn set_blending_enabled(&self, index: usize, enabled: bool) {
unsafe {
metal_render_pipeline_descriptor_set_blending_enabled(
self.ptr.as_ptr(),
index,
enabled,
);
}
}
pub fn set_blend_operations(
&self,
index: usize,
rgb_op: MTLBlendOperation,
alpha_op: MTLBlendOperation,
) {
unsafe {
metal_render_pipeline_descriptor_set_blend_operations(
self.ptr.as_ptr(),
index,
rgb_op as u64,
alpha_op as u64,
);
}
}
pub fn set_blend_factors(
&self,
index: usize,
src_rgb: MTLBlendFactor,
dst_rgb: MTLBlendFactor,
src_alpha: MTLBlendFactor,
dst_alpha: MTLBlendFactor,
) {
unsafe {
metal_render_pipeline_descriptor_set_blend_factors(
self.ptr.as_ptr(),
index,
src_rgb as u64,
dst_rgb as u64,
src_alpha as u64,
dst_alpha as u64,
);
}
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Default for MetalRenderPipelineDescriptor {
fn default() -> Self {
Self::new()
}
}
impl Drop for MetalRenderPipelineDescriptor {
fn drop(&mut self) {
unsafe { metal_render_pipeline_descriptor_release(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct MetalRenderPipelineState {
ptr: NonNull<c_void>,
}
impl MetalRenderPipelineState {
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalRenderPipelineState {
fn drop(&mut self) {
unsafe { metal_render_pipeline_state_release(self.ptr.as_ptr()) }
}
}
unsafe impl Send for MetalRenderPipelineState {}
unsafe impl Sync for MetalRenderPipelineState {}
#[derive(Debug)]
pub struct MetalRenderCommandEncoder {
ptr: NonNull<c_void>,
}
impl MetalRenderCommandEncoder {
pub fn set_render_pipeline_state(&self, state: &MetalRenderPipelineState) {
unsafe { metal_render_encoder_set_pipeline_state(self.ptr.as_ptr(), state.as_ptr()) }
}
pub fn set_vertex_buffer(&self, buffer: &MetalBuffer, offset: usize, index: usize) {
unsafe {
metal_render_encoder_set_vertex_buffer(
self.ptr.as_ptr(),
buffer.as_ptr(),
offset,
index,
);
}
}
pub fn set_fragment_buffer(&self, buffer: &MetalBuffer, offset: usize, index: usize) {
unsafe {
metal_render_encoder_set_fragment_buffer(
self.ptr.as_ptr(),
buffer.as_ptr(),
offset,
index,
);
}
}
pub fn set_fragment_texture(&self, texture: &MetalTexture, index: usize) {
unsafe {
metal_render_encoder_set_fragment_texture(self.ptr.as_ptr(), texture.as_ptr(), index);
}
}
pub fn draw_primitives(
&self,
primitive_type: MTLPrimitiveType,
vertex_start: usize,
vertex_count: usize,
) {
unsafe {
metal_render_encoder_draw_primitives(
self.ptr.as_ptr(),
primitive_type.raw(),
vertex_start,
vertex_count,
);
}
}
pub fn end_encoding(&self) {
unsafe { metal_render_encoder_end_encoding(self.ptr.as_ptr()) }
}
#[must_use]
pub fn as_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for MetalRenderCommandEncoder {
fn drop(&mut self) {
unsafe { metal_render_encoder_release(self.ptr.as_ptr()) }
}
}
pub type MetalCapturedTextures = CapturedTextures<MetalTexture>;
impl IOSurface {
#[must_use]
pub fn create_metal_textures(&self, device: &MetalDevice) -> Option<MetalCapturedTextures> {
let width = self.width();
let height = self.height();
let pix_format: FourCharCode = self.pixel_format().into();
if width == 0 || height == 0 {
return None;
}
let params = self.texture_params();
if params.len() == 1 {
let texture = self.create_texture_for_plane(device, ¶ms[0])?;
Some(CapturedTextures {
plane0: texture,
plane1: None,
pixel_format: pix_format,
width,
height,
})
} else if params.len() >= 2 {
let y_texture = self.create_texture_for_plane(device, ¶ms[0])?;
let uv_texture = self.create_texture_for_plane(device, ¶ms[1])?;
Some(CapturedTextures {
plane0: y_texture,
plane1: Some(uv_texture),
pixel_format: pix_format,
width,
height,
})
} else {
None
}
}
fn create_texture_for_plane(
&self,
device: &MetalDevice,
params: &TextureParams,
) -> Option<MetalTexture> {
let ptr = unsafe {
metal_create_texture_from_iosurface(
device.as_ptr(),
self.as_ptr(),
params.plane,
params.width,
params.height,
params.format.raw(),
)
};
NonNull::new(ptr).map(|ptr| MetalTexture { ptr })
}
}
#[link(name = "Foundation", kind = "framework")]
extern "C" {
fn objc_autoreleasePoolPush() -> *mut c_void;
fn objc_autoreleasePoolPop(pool: *mut c_void);
}
pub fn autoreleasepool<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
unsafe {
let pool = objc_autoreleasePoolPush();
let result = f();
objc_autoreleasePoolPop(pool);
result
}
}
pub unsafe fn setup_metal_view(view: *mut c_void, layer: &MetalLayer) {
nsview_set_wants_layer(view);
nsview_set_layer(view, layer.as_ptr());
}