Files
ghostty-rs/crates/ghostty-renderer/src/metal/target.rs
T
ddidderr 452b782149 feat(macos): port Ghostty's macOS code paths (Metal, CoreText, Darwin)
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)
2026-10-12 06:02:39 +02:00

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"));
}
}