Ports the macOS-specific Zig code behind cfg(target_os = "macos"): - renderer: a GraphicsApi trait with generic::Renderer<G>; Metal backend (all renderer/metal/* files, shaders.metal verbatim, runtime library compile, IOSurface layer, CVDisplayLink), shadertoy -> MSL via naga; OpenGL remains the backend elsewhere. - font: CoreText face, shaper and discovery with build.rs backend selection (coretext default; coretext-freetype / coretext-harfbuzz / coretext-noshape features), no fontconfig/freetype/harfbuzz on Apple. - input: KeymapDarwin via UCKeyTranslate, option-as-alt, mac keycodes. - os: macos, mach, cf_release_thread, NSProcessInfo args and macOS branches; config: macOS defaults, keybinds, Application Support paths. - termio/core: login(1) wrapping, openpty/execvpe, Darwin child exit handling, os_log logging. Compile-checked for aarch64/x86_64-apple-darwin (clippy -D warnings clean) using the cross setup in docs/rewrite/notes/macos-M1.md; cannot run here. The embedded runtime/C API (embedded.zig) is the next step. Also includes the cross-platform integration edits shared by the crash, verification, Windows and macOS work (Cargo target-gating, thread crash-context hooks, apprt-gtk surface/IME refactor, termio/os cfg gates), a cfg fix so ghostty-config builds on wasm32, and an allow for the intentional recursion in the crash overflow test. Test Plan: - cargo test --workspace (3224 passed) - cargo clippy --workspace --all-targets -- -D warnings - cargo check --target aarch64-apple-darwin -p ghostty-renderer (with the documented cross env)
200 lines
5.9 KiB
Rust
200 lines
5.9 KiB
Rust
//! Represents a render target (`metal/Target.zig`).
|
|
//!
|
|
//! In this case, an IOSurface-backed MTLTexture.
|
|
use objc2::{rc::Retained, runtime::ProtocolObject};
|
|
use objc2_core_foundation::{CFDictionary, CFNumber, CFRetained, CFString, CFType};
|
|
use objc2_core_graphics::{CGColorSpace, kCGColorSpaceDisplayP3};
|
|
use objc2_io_surface::{
|
|
IOSurfaceRef,
|
|
kIOSurfaceBytesPerElement,
|
|
kIOSurfaceColorSpace,
|
|
kIOSurfaceHeight,
|
|
kIOSurfacePixelFormat,
|
|
kIOSurfaceWidth,
|
|
};
|
|
use objc2_metal::{
|
|
MTLDevice as _,
|
|
MTLPixelFormat,
|
|
MTLResourceOptions,
|
|
MTLTexture,
|
|
MTLTextureDescriptor,
|
|
MTLTextureUsage,
|
|
};
|
|
|
|
use super::{Device, Error};
|
|
use crate::{
|
|
graphics_api::GpuTarget,
|
|
metal_layout::{PixelFormat, ResourceOptions, StorageMode, TextureUsage},
|
|
};
|
|
|
|
/// `kCVPixelFormatType_32BGRA` ('BGRA'): `video.PixelFormat.@"32BGRA"`.
|
|
pub const PIXEL_FORMAT_32BGRA: i32 = 0x4247_5241;
|
|
|
|
/// `kIOSurfacePurgeableEmpty`.
|
|
const IOSURFACE_PURGEABLE_EMPTY: u32 = 2;
|
|
|
|
/// Options for initializing a Target
|
|
pub struct Options {
|
|
/// MTLDevice
|
|
pub device: Device,
|
|
|
|
/// Desired width
|
|
pub width: usize,
|
|
/// Desired height
|
|
pub height: usize,
|
|
|
|
/// Pixel format for the MTLTexture
|
|
pub pixel_format: PixelFormat,
|
|
/// Storage mode for the MTLTexture
|
|
pub storage_mode: StorageMode,
|
|
}
|
|
|
|
/// An IOSurface-backed render target.
|
|
pub struct Target {
|
|
/// The underlying IOSurface.
|
|
surface: CFRetained<IOSurfaceRef>,
|
|
|
|
/// The underlying MTLTexture.
|
|
texture: Retained<ProtocolObject<dyn MTLTexture>>,
|
|
|
|
/// Current width of this target.
|
|
pub width: usize,
|
|
/// Current height of this target.
|
|
pub height: usize,
|
|
}
|
|
|
|
// SAFETY: IOSurfaces and MTLTextures are thread-safe reference counted
|
|
// objects; the target is only mutated by the GPU.
|
|
unsafe impl Send for Target {}
|
|
// SAFETY: see above.
|
|
unsafe impl Sync for Target {}
|
|
|
|
impl Target {
|
|
/// The underlying IOSurface.
|
|
pub fn surface(&self) -> &CFRetained<IOSurfaceRef> {
|
|
&self.surface
|
|
}
|
|
|
|
/// The underlying MTLTexture.
|
|
pub fn texture(&self) -> &ProtocolObject<dyn MTLTexture> {
|
|
&self.texture
|
|
}
|
|
|
|
pub fn init(opts: &Options) -> Result<Target, Error> {
|
|
// We set our surface's color space to Display P3.
|
|
// This allows us to have "Apple-style" alpha blending,
|
|
// since it seems to be the case that Apple apps like
|
|
// Terminal and TextEdit render text in the display's
|
|
// color space using converted colors, which reduces,
|
|
// but does not fully eliminate blending artifacts.
|
|
//
|
|
// SAFETY: a constant CFString exported by CoreGraphics.
|
|
let colorspace = CGColorSpace::with_name(Some(unsafe { kCGColorSpaceDisplayP3 }))
|
|
.ok_or(Error::MetalFailed)?;
|
|
|
|
let surface = create_surface(opts.width, opts.height, &colorspace)?;
|
|
|
|
// Create our descriptor and set our properties
|
|
let desc = MTLTextureDescriptor::new();
|
|
// SAFETY: plain numbers, validated when the texture is created.
|
|
unsafe {
|
|
desc.setWidth(opts.width);
|
|
desc.setHeight(opts.height);
|
|
}
|
|
desc.setPixelFormat(MTLPixelFormat(opts.pixel_format as usize));
|
|
desc.setUsage(MTLTextureUsage(
|
|
TextureUsage {
|
|
render_target: true,
|
|
..TextureUsage::default()
|
|
}
|
|
.bits() as usize,
|
|
));
|
|
desc.setResourceOptions(MTLResourceOptions(
|
|
// Indicate that the CPU writes to this resource but never reads it.
|
|
ResourceOptions::write_combined(opts.storage_mode).bits() as usize,
|
|
));
|
|
|
|
let texture = opts
|
|
.device
|
|
.newTextureWithDescriptor_iosurface_plane(&desc, &surface, 0)
|
|
.ok_or(Error::MetalFailed)?;
|
|
|
|
Ok(Target {
|
|
surface,
|
|
texture,
|
|
width: opts.width,
|
|
height: opts.height,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Drop for Target {
|
|
fn drop(&mut self) {
|
|
// We mark it purgeable so that it is immediately unloaded, so that we
|
|
// don't have to wait for CoreFoundation garbage collection to trigger.
|
|
//
|
|
// SAFETY: a null `old_state` is allowed.
|
|
unsafe {
|
|
self.surface
|
|
.set_purgeable(IOSURFACE_PURGEABLE_EMPTY, std::ptr::null_mut());
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GpuTarget for Target {
|
|
fn width(&self) -> usize {
|
|
self.width
|
|
}
|
|
|
|
fn height(&self) -> usize {
|
|
self.height
|
|
}
|
|
}
|
|
|
|
/// `IOSurface.init`: a BGRA IOSurface of the given size with a color space.
|
|
fn create_surface(
|
|
width: usize,
|
|
height: usize,
|
|
colorspace: &CGColorSpace,
|
|
) -> Result<CFRetained<IOSurfaceRef>, Error> {
|
|
let w = CFNumber::new_i32(i32::try_from(width).map_err(|_| Error::MetalFailed)?);
|
|
let h = CFNumber::new_i32(i32::try_from(height).map_err(|_| Error::MetalFailed)?);
|
|
let pf = CFNumber::new_i32(PIXEL_FORMAT_32BGRA);
|
|
let bpe = CFNumber::new_i32(4);
|
|
|
|
// SAFETY: constant CFStrings exported by IOSurface.
|
|
let keys: [&CFString; 4] = unsafe {
|
|
[
|
|
kIOSurfaceWidth,
|
|
kIOSurfaceHeight,
|
|
kIOSurfacePixelFormat,
|
|
kIOSurfaceBytesPerElement,
|
|
]
|
|
};
|
|
let values: [&CFNumber; 4] = [&w, &h, &pf, &bpe];
|
|
let properties = CFDictionary::<CFString, CFNumber>::from_slices(&keys, &values);
|
|
|
|
// SAFETY: the dictionary has the CFString keys / CFNumber values
|
|
// IOSurfaceCreate expects.
|
|
let surface = unsafe { IOSurfaceRef::new(properties.as_opaque()) }.ok_or(Error::MetalFailed)?;
|
|
|
|
// setColorSpace: serialize the colorspace into the surface properties.
|
|
if let Some(serialized) = colorspace.property_list() {
|
|
let value: &CFType = serialized.as_ref();
|
|
// SAFETY: the color space key takes a property list value.
|
|
unsafe { surface.set_value(kIOSurfaceColorSpace, value) };
|
|
}
|
|
|
|
Ok(surface)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn bgra_fourcc() {
|
|
assert_eq!(PIXEL_FORMAT_32BGRA, i32::from_be_bytes(*b"BGRA"));
|
|
}
|
|
}
|