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,209 @@
|
||||
//! Shader pipelines and GPU data layouts for Metal (`metal/shaders.zig`).
|
||||
//!
|
||||
//! The CPU-side data layouts are shared with OpenGL
|
||||
//! ([`crate::shader_types`]) except for the uniform block, see
|
||||
//! [`crate::metal_layout::MetalUniforms`].
|
||||
use std::time::Instant;
|
||||
|
||||
use objc2::{rc::Retained, runtime::ProtocolObject};
|
||||
use objc2_foundation::NSString;
|
||||
use objc2_metal::{MTLDevice as _, MTLLibrary};
|
||||
|
||||
use super::{
|
||||
Device,
|
||||
Error,
|
||||
pipeline::{self, Attachment, Pipeline},
|
||||
};
|
||||
use crate::{
|
||||
graphics_api::GpuShaders,
|
||||
metal_layout::{
|
||||
FULL_SCREEN_VERTEX_FN,
|
||||
PIPELINE_DESCS,
|
||||
POST_FRAGMENT_FN,
|
||||
PipelineDesc,
|
||||
PixelFormat,
|
||||
VertexStepFunction,
|
||||
},
|
||||
shader_types::{BgImage, CellText, Image as ImageVertex},
|
||||
};
|
||||
|
||||
/// The Metal shading language source of all built-in shaders
|
||||
/// (`shaders/shaders.metal`). Ghostty compiles this to a `.metallib` at build
|
||||
/// time with `xcrun metal`; we compile it with `newLibraryWithSource` when the
|
||||
/// shaders are initialized.
|
||||
pub const SHADERS_METAL: &str = include_str!("../../shaders/shaders.metal");
|
||||
|
||||
/// This contains the state for the shaders used by the Metal renderer.
|
||||
pub struct Shaders {
|
||||
library: Retained<ProtocolObject<dyn MTLLibrary>>,
|
||||
|
||||
pub bg_color: Pipeline,
|
||||
pub cell_bg: Pipeline,
|
||||
pub cell_text: Pipeline,
|
||||
pub image: Pipeline,
|
||||
pub bg_image: Pipeline,
|
||||
|
||||
/// Custom shaders to run against the final drawable texture. This
|
||||
/// can be used to apply a lot of effects. Each shader is run in sequence
|
||||
/// against the output of the previous shader.
|
||||
pub post_pipelines: Vec<Pipeline>,
|
||||
}
|
||||
|
||||
// SAFETY: MTLLibrary objects are thread-safe, and pipelines are immutable.
|
||||
unsafe impl Send for Shaders {}
|
||||
// SAFETY: see above.
|
||||
unsafe impl Sync for Shaders {}
|
||||
|
||||
impl Shaders {
|
||||
/// Initialize our shader set.
|
||||
///
|
||||
/// "post_shaders" is an optional list of postprocess shaders to run
|
||||
/// against the final drawable texture. This is an array of shader source
|
||||
/// code, not file paths.
|
||||
pub fn init<S: AsRef<str>>(
|
||||
device: &Device,
|
||||
post_shaders: &[S],
|
||||
pixel_format: PixelFormat,
|
||||
) -> Result<Shaders, Error> {
|
||||
let library = init_library(device)?;
|
||||
|
||||
let make = |desc: &PipelineDesc| -> Result<Pipeline, Error> {
|
||||
let opts = pipeline::Options {
|
||||
device,
|
||||
vertex_fn: desc.vertex_fn,
|
||||
fragment_fn: desc.fragment_fn,
|
||||
vertex_library: &library,
|
||||
fragment_library: &library,
|
||||
step_fn: desc.step_fn,
|
||||
attachments: &[Attachment {
|
||||
pixel_format,
|
||||
blending_enabled: desc.blending_enabled,
|
||||
}],
|
||||
};
|
||||
match desc.name {
|
||||
"cell_text" => Pipeline::init::<CellText>(&opts),
|
||||
"image" => Pipeline::init::<ImageVertex>(&opts),
|
||||
"bg_image" => Pipeline::init::<BgImage>(&opts),
|
||||
_ => Pipeline::init_no_attributes(&opts),
|
||||
}
|
||||
};
|
||||
let [bg_color, cell_bg, cell_text, image, bg_image] = &PIPELINE_DESCS;
|
||||
|
||||
let bg_color = make(bg_color)?;
|
||||
let cell_bg = make(cell_bg)?;
|
||||
let cell_text = make(cell_text)?;
|
||||
let image = make(image)?;
|
||||
let bg_image = make(bg_image)?;
|
||||
|
||||
let post_pipelines = init_post_pipelines(device, &library, post_shaders, pixel_format)
|
||||
.unwrap_or_else(|err| {
|
||||
// If an error happens while building postprocess shaders we
|
||||
// want to just not use any postprocess shaders since we don't
|
||||
// want to block Ghostty from working.
|
||||
log::warn!("error initializing postprocess shaders err={err}");
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
Ok(Shaders {
|
||||
library,
|
||||
bg_color,
|
||||
cell_bg,
|
||||
cell_text,
|
||||
image,
|
||||
bg_image,
|
||||
post_pipelines,
|
||||
})
|
||||
}
|
||||
|
||||
/// The compiled library of the built-in shaders.
|
||||
pub fn library(&self) -> &ProtocolObject<dyn MTLLibrary> {
|
||||
&self.library
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuShaders<super::Metal> for Shaders {
|
||||
fn bg_color(&self) -> &Pipeline {
|
||||
&self.bg_color
|
||||
}
|
||||
|
||||
fn cell_bg(&self) -> &Pipeline {
|
||||
&self.cell_bg
|
||||
}
|
||||
|
||||
fn cell_text(&self) -> &Pipeline {
|
||||
&self.cell_text
|
||||
}
|
||||
|
||||
fn image(&self) -> &Pipeline {
|
||||
&self.image
|
||||
}
|
||||
|
||||
fn bg_image(&self) -> &Pipeline {
|
||||
&self.bg_image
|
||||
}
|
||||
|
||||
fn post_pipelines(&self) -> &[Pipeline] {
|
||||
&self.post_pipelines
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the MTLLibrary. A MTLLibrary is a collection of shaders.
|
||||
fn init_library(device: &Device) -> Result<Retained<ProtocolObject<dyn MTLLibrary>>, Error> {
|
||||
let start = Instant::now();
|
||||
|
||||
let source = NSString::from_str(SHADERS_METAL);
|
||||
let library = device
|
||||
.newLibraryWithSource_options_error(&source, None)
|
||||
.map_err(|err| Error::from_ns_error(&err))?;
|
||||
|
||||
log::debug!(
|
||||
"shader library loaded time={}us",
|
||||
start.elapsed().as_micros()
|
||||
);
|
||||
|
||||
Ok(library)
|
||||
}
|
||||
|
||||
/// Initialize our custom shader pipelines.
|
||||
///
|
||||
/// The shaders argument is a set of shader source code, not file paths.
|
||||
fn init_post_pipelines<S: AsRef<str>>(
|
||||
device: &Device,
|
||||
library: &ProtocolObject<dyn MTLLibrary>,
|
||||
shaders: &[S],
|
||||
pixel_format: PixelFormat,
|
||||
) -> Result<Vec<Pipeline>, Error> {
|
||||
// If we have no shaders, do nothing. If any error happens the
|
||||
// already-built pipelines are dropped (released).
|
||||
shaders
|
||||
.iter()
|
||||
.map(|source| init_post_pipeline(device, library, source.as_ref(), pixel_format))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Initialize a single custom shader pipeline from shader source.
|
||||
fn init_post_pipeline(
|
||||
device: &Device,
|
||||
library: &ProtocolObject<dyn MTLLibrary>,
|
||||
data: &str,
|
||||
pixel_format: PixelFormat,
|
||||
) -> Result<Pipeline, Error> {
|
||||
// Create our library which has the shader source
|
||||
let source = NSString::from_str(data);
|
||||
let post_library = device
|
||||
.newLibraryWithSource_options_error(&source, None)
|
||||
.map_err(|err| Error::from_ns_error(&err))?;
|
||||
|
||||
Pipeline::init_no_attributes(&pipeline::Options {
|
||||
device,
|
||||
vertex_fn: FULL_SCREEN_VERTEX_FN,
|
||||
fragment_fn: POST_FRAGMENT_FN,
|
||||
vertex_library: library,
|
||||
fragment_library: &post_library,
|
||||
step_fn: VertexStepFunction::PerVertex,
|
||||
attachments: &[Attachment {
|
||||
pixel_format,
|
||||
blending_enabled: false,
|
||||
}],
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user