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)
This commit is contained in:
1 parent
76771bed0d
commit
bab49ee596
124 files changed
+14936
-1242
No files matched your search
@@ -0,0 +1,168 @@
|
||||
//! Wrapper for handling render pipelines (`metal/Pipeline.zig`).
|
||||
use objc2::{rc::Retained, runtime::ProtocolObject};
|
||||
use objc2_foundation::NSString;
|
||||
use objc2_metal::{
|
||||
MTLBlendFactor,
|
||||
MTLBlendOperation,
|
||||
MTLDevice as _,
|
||||
MTLLibrary,
|
||||
MTLPixelFormat,
|
||||
MTLRenderPipelineDescriptor,
|
||||
MTLRenderPipelineState,
|
||||
MTLVertexDescriptor,
|
||||
MTLVertexFormat,
|
||||
MTLVertexStepFunction,
|
||||
};
|
||||
|
||||
use super::{Device, Error};
|
||||
use crate::metal_layout::{
|
||||
MetalVertex,
|
||||
PREMULTIPLIED_ALPHA,
|
||||
PixelFormat,
|
||||
VertexAttribute,
|
||||
VertexStepFunction,
|
||||
};
|
||||
|
||||
/// Describes a color attachment.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Attachment {
|
||||
pub pixel_format: PixelFormat,
|
||||
pub blending_enabled: bool,
|
||||
}
|
||||
|
||||
/// Options for initializing a render pipeline.
|
||||
pub struct Options<'a> {
|
||||
/// MTLDevice
|
||||
pub device: &'a Device,
|
||||
|
||||
/// Name of the vertex function
|
||||
pub vertex_fn: &'a str,
|
||||
/// Name of the fragment function
|
||||
pub fragment_fn: &'a str,
|
||||
|
||||
/// MTLLibrary to get the vertex function from
|
||||
pub vertex_library: &'a ProtocolObject<dyn MTLLibrary>,
|
||||
/// MTLLibrary to get the fragment function from
|
||||
pub fragment_library: &'a ProtocolObject<dyn MTLLibrary>,
|
||||
|
||||
/// Vertex step function
|
||||
pub step_fn: VertexStepFunction,
|
||||
|
||||
/// Info about the color attachments used by this render pipeline.
|
||||
pub attachments: &'a [Attachment],
|
||||
}
|
||||
|
||||
/// A render pipeline state.
|
||||
pub struct Pipeline {
|
||||
/// MTLRenderPipelineState
|
||||
state: Retained<ProtocolObject<dyn MTLRenderPipelineState>>,
|
||||
}
|
||||
|
||||
// SAFETY: MTLRenderPipelineState objects are immutable and thread-safe.
|
||||
unsafe impl Send for Pipeline {}
|
||||
// SAFETY: see above.
|
||||
unsafe impl Sync for Pipeline {}
|
||||
|
||||
impl Pipeline {
|
||||
/// The underlying MTLRenderPipelineState.
|
||||
pub fn state(&self) -> &ProtocolObject<dyn MTLRenderPipelineState> {
|
||||
&self.state
|
||||
}
|
||||
|
||||
/// Create a pipeline whose vertex attributes are those of `V`.
|
||||
pub fn init<V: MetalVertex>(opts: &Options<'_>) -> Result<Pipeline, Error> {
|
||||
Self::init_impl(Some((V::ATTRIBUTES, size_of::<V>())), opts)
|
||||
}
|
||||
|
||||
/// Create a pipeline without a vertex descriptor.
|
||||
pub fn init_no_attributes(opts: &Options<'_>) -> Result<Pipeline, Error> {
|
||||
Self::init_impl(None, opts)
|
||||
}
|
||||
|
||||
fn init_impl(
|
||||
vertex_attributes: Option<(&[VertexAttribute], usize)>,
|
||||
opts: &Options<'_>,
|
||||
) -> Result<Pipeline, Error> {
|
||||
// Create our descriptor
|
||||
let desc = MTLRenderPipelineDescriptor::new();
|
||||
|
||||
// Get our vertex and fragment functions and add them to the descriptor.
|
||||
{
|
||||
let name = NSString::from_str(opts.vertex_fn);
|
||||
let func_vert = opts
|
||||
.vertex_library
|
||||
.newFunctionWithName(&name)
|
||||
.ok_or_else(|| Error::MissingFunction(opts.vertex_fn.to_owned()))?;
|
||||
desc.setVertexFunction(Some(&func_vert));
|
||||
}
|
||||
{
|
||||
let name = NSString::from_str(opts.fragment_fn);
|
||||
let func_frag = opts
|
||||
.fragment_library
|
||||
.newFunctionWithName(&name)
|
||||
.ok_or_else(|| Error::MissingFunction(opts.fragment_fn.to_owned()))?;
|
||||
desc.setFragmentFunction(Some(&func_frag));
|
||||
}
|
||||
|
||||
// If we have vertex attributes, create and add a vertex descriptor.
|
||||
if let Some((attributes, stride)) = vertex_attributes {
|
||||
let vertex_desc = MTLVertexDescriptor::new();
|
||||
|
||||
// Our attributes are the fields of the input
|
||||
let attrs = vertex_desc.attributes();
|
||||
for (i, a) in attributes.iter().enumerate() {
|
||||
// SAFETY: indexing the attribute array with a valid index
|
||||
// (the descriptor array has 31 slots); offsets and buffer
|
||||
// index are plain numbers validated by pipeline creation.
|
||||
unsafe {
|
||||
let attr = attrs.objectAtIndexedSubscript(i);
|
||||
attr.setFormat(MTLVertexFormat(a.format as usize));
|
||||
attr.setOffset(a.offset);
|
||||
attr.setBufferIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
// The layout describes how and when we fetch the next vertex input.
|
||||
let layouts = vertex_desc.layouts();
|
||||
// SAFETY: buffer layout 0 exists; the stride is a plain number.
|
||||
unsafe {
|
||||
let layout = layouts.objectAtIndexedSubscript(0);
|
||||
layout.setStepFunction(MTLVertexStepFunction(opts.step_fn as usize));
|
||||
layout.setStride(stride);
|
||||
}
|
||||
|
||||
desc.setVertexDescriptor(Some(&vertex_desc));
|
||||
}
|
||||
|
||||
// Set our color attachment
|
||||
let attachments = desc.colorAttachments();
|
||||
for (i, at) in opts.attachments.iter().enumerate() {
|
||||
// SAFETY: color attachment indexes are validated by Metal; the
|
||||
// pipelines use a single attachment.
|
||||
let attachment = unsafe { attachments.objectAtIndexedSubscript(i) };
|
||||
|
||||
attachment.setPixelFormat(MTLPixelFormat(at.pixel_format as usize));
|
||||
|
||||
attachment.setBlendingEnabled(at.blending_enabled);
|
||||
// We always use premultiplied alpha blending for now.
|
||||
if at.blending_enabled {
|
||||
let b = PREMULTIPLIED_ALPHA;
|
||||
attachment.setRgbBlendOperation(MTLBlendOperation(b.rgb_operation as usize));
|
||||
attachment.setAlphaBlendOperation(MTLBlendOperation(b.alpha_operation as usize));
|
||||
attachment.setSourceRGBBlendFactor(MTLBlendFactor(b.source_rgb as usize));
|
||||
attachment.setSourceAlphaBlendFactor(MTLBlendFactor(b.source_alpha as usize));
|
||||
attachment.setDestinationRGBBlendFactor(MTLBlendFactor(b.destination_rgb as usize));
|
||||
attachment
|
||||
.setDestinationAlphaBlendFactor(MTLBlendFactor(b.destination_alpha as usize));
|
||||
}
|
||||
}
|
||||
|
||||
// Make our state
|
||||
let state = opts
|
||||
.device
|
||||
.newRenderPipelineStateWithDescriptor_error(&desc)
|
||||
.map_err(|err| Error::from_ns_error(&err))?;
|
||||
|
||||
Ok(Pipeline { state })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user