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
@@ -5,6 +5,7 @@
|
||||
//! closure (see [`OpenGL::prepare_context`]) by the apprt once the context is
|
||||
//! current, and everything afterwards goes through the [`Gl`] handle.
|
||||
|
||||
pub mod api;
|
||||
pub mod buffer;
|
||||
pub mod frame;
|
||||
#[allow(unsafe_code)]
|
||||
@@ -16,7 +17,10 @@ pub mod shaders;
|
||||
pub mod target;
|
||||
pub mod texture;
|
||||
|
||||
use std::{ffi::c_void, sync::Arc};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
sync::{Arc, Mutex, PoisonError},
|
||||
};
|
||||
|
||||
use ghostty_font::atlas::{Atlas, Format as AtlasFormat};
|
||||
|
||||
@@ -32,6 +36,7 @@ use self::gl::{
|
||||
Wrap,
|
||||
};
|
||||
pub use self::{
|
||||
api::GlFrame,
|
||||
buffer::Buffer,
|
||||
frame::Frame,
|
||||
gl::Gl,
|
||||
@@ -43,8 +48,6 @@ pub use self::{
|
||||
};
|
||||
use crate::shadertoy;
|
||||
|
||||
pub type GraphicsApi = OpenGL;
|
||||
|
||||
/// Custom shaders are converted to GLSL for OpenGL.
|
||||
pub const CUSTOM_SHADER_TARGET: shadertoy::Target = shadertoy::Target::Glsl;
|
||||
/// The fragCoord for OpenGL shaders is +Y = up.
|
||||
@@ -85,27 +88,54 @@ pub struct OpenGL {
|
||||
pub blending: AlphaBlending,
|
||||
|
||||
/// The most recently presented target, in case we need to present it again.
|
||||
last_target: Option<Arc<Target>>,
|
||||
/// Shared with in-flight [`Frame`]s, which record what they present.
|
||||
last_target: LastTarget,
|
||||
}
|
||||
|
||||
/// Pixel format for image texture options.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ImageTextureFormat {
|
||||
/// 1 byte per pixel grayscale.
|
||||
Gray,
|
||||
/// 4 bytes per pixel RGBA.
|
||||
Rgba,
|
||||
/// 4 bytes per pixel BGRA.
|
||||
Bgra,
|
||||
/// Slot holding the most recently presented target.
|
||||
pub type LastTarget = Arc<Mutex<Option<Arc<Target>>>>;
|
||||
|
||||
/// Present `target` by blitting it to the default framebuffer, remembering it
|
||||
/// in `last_target` in case we need to repeat it.
|
||||
fn present_target(gl: &Gl, last_target: &LastTarget, target: &Arc<Target>) -> Result<(), Error> {
|
||||
// In order to present a target we blit it to the default framebuffer.
|
||||
|
||||
// We disable GL_FRAMEBUFFER_SRGB while doing this blit, otherwise the
|
||||
// values may be linearized as they're copied, but even though the draw
|
||||
// framebuffer has a linear internal format, the values in it should be
|
||||
// sRGB, not linear!
|
||||
gl.disable(glow::FRAMEBUFFER_SRGB)?;
|
||||
|
||||
// Bind the target for reading.
|
||||
let fbobind = gl.bind_framebuffer(target.framebuffer(), FramebufferTarget::Read);
|
||||
|
||||
// Blit
|
||||
let (w, h) = (target.width as i32, target.height as i32);
|
||||
gl.blit_framebuffer(
|
||||
(0, 0, w, h),
|
||||
(0, 0, w, h),
|
||||
glow::COLOR_BUFFER_BIT,
|
||||
glow::NEAREST,
|
||||
);
|
||||
|
||||
gl.unbind_framebuffer(fbobind);
|
||||
if let Err(err) = gl.enable(glow::FRAMEBUFFER_SRGB) {
|
||||
log::error!("Error re-enabling GL_FRAMEBUFFER_SRGB, err={err}");
|
||||
}
|
||||
|
||||
// Keep track of this target in case we need to repeat it.
|
||||
*last_target.lock().unwrap_or_else(PoisonError::into_inner) = Some(Arc::clone(target));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ImageTextureFormat {
|
||||
fn to_pixel_format(self) -> PixelFormat {
|
||||
match self {
|
||||
ImageTextureFormat::Gray => PixelFormat::Red,
|
||||
ImageTextureFormat::Rgba => PixelFormat::Rgba,
|
||||
ImageTextureFormat::Bgra => PixelFormat::Bgra,
|
||||
}
|
||||
pub use crate::graphics_api::ImageTextureFormat;
|
||||
|
||||
/// The GL pixel format of an [`ImageTextureFormat`].
|
||||
fn to_pixel_format(format: ImageTextureFormat) -> PixelFormat {
|
||||
match format {
|
||||
ImageTextureFormat::Gray => PixelFormat::Red,
|
||||
ImageTextureFormat::Rgba => PixelFormat::Rgba,
|
||||
ImageTextureFormat::Bgra => PixelFormat::Bgra,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +146,7 @@ impl OpenGL {
|
||||
OpenGL {
|
||||
gl: None,
|
||||
blending,
|
||||
last_target: None,
|
||||
last_target: LastTarget::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +156,7 @@ impl OpenGL {
|
||||
OpenGL {
|
||||
gl: Some(gl),
|
||||
blending,
|
||||
last_target: None,
|
||||
last_target: LastTarget::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,42 +277,18 @@ impl OpenGL {
|
||||
}
|
||||
|
||||
/// Present the provided target.
|
||||
pub fn present(&mut self, target: &Arc<Target>) -> Result<(), Error> {
|
||||
let gl = self.gl_or_err()?.clone();
|
||||
|
||||
// In order to present a target we blit it to the default framebuffer.
|
||||
|
||||
// We disable GL_FRAMEBUFFER_SRGB while doing this blit, otherwise the
|
||||
// values may be linearized as they're copied, but even though the draw
|
||||
// framebuffer has a linear internal format, the values in it should be
|
||||
// sRGB, not linear!
|
||||
gl.disable(glow::FRAMEBUFFER_SRGB)?;
|
||||
|
||||
// Bind the target for reading.
|
||||
let fbobind = gl.bind_framebuffer(target.framebuffer(), FramebufferTarget::Read);
|
||||
|
||||
// Blit
|
||||
let (w, h) = (target.width as i32, target.height as i32);
|
||||
gl.blit_framebuffer(
|
||||
(0, 0, w, h),
|
||||
(0, 0, w, h),
|
||||
glow::COLOR_BUFFER_BIT,
|
||||
glow::NEAREST,
|
||||
);
|
||||
|
||||
gl.unbind_framebuffer(fbobind);
|
||||
if let Err(err) = gl.enable(glow::FRAMEBUFFER_SRGB) {
|
||||
log::error!("Error re-enabling GL_FRAMEBUFFER_SRGB, err={err}");
|
||||
}
|
||||
|
||||
// Keep track of this target in case we need to repeat it.
|
||||
self.last_target = Some(Arc::clone(target));
|
||||
Ok(())
|
||||
pub fn present(&self, target: &Arc<Target>) -> Result<(), Error> {
|
||||
present_target(self.gl_or_err()?, &self.last_target, target)
|
||||
}
|
||||
|
||||
/// Present the last presented target again.
|
||||
pub fn present_last_target(&mut self) -> Result<(), Error> {
|
||||
if let Some(target) = self.last_target.clone() {
|
||||
pub fn present_last_target(&self) -> Result<(), Error> {
|
||||
let last = self
|
||||
.last_target
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.clone();
|
||||
if let Some(target) = last {
|
||||
self.present(&target)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -290,14 +296,25 @@ impl OpenGL {
|
||||
|
||||
/// The most recently presented target, if any (also set when the blit to
|
||||
/// the default framebuffer is a no-op, e.g. on a surfaceless context).
|
||||
pub fn last_target(&self) -> Option<&Arc<Target>> {
|
||||
self.last_target.as_ref()
|
||||
pub fn last_target(&self) -> Option<Arc<Target>> {
|
||||
self.last_target
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Drop the remembered last-presented target (call when the target it
|
||||
/// refers to is being replaced, e.g. on resize).
|
||||
pub fn clear_last_target(&mut self) {
|
||||
self.last_target = None;
|
||||
pub fn clear_last_target(&self) {
|
||||
*self
|
||||
.last_target
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner) = None;
|
||||
}
|
||||
|
||||
/// The slot [`Frame`]s record the target they present in.
|
||||
pub(crate) fn last_target_slot(&self) -> LastTarget {
|
||||
Arc::clone(&self.last_target)
|
||||
}
|
||||
|
||||
/// Returns the options to use when constructing buffers.
|
||||
@@ -357,7 +374,7 @@ impl OpenGL {
|
||||
srgb: bool,
|
||||
) -> texture::Options {
|
||||
texture::Options {
|
||||
format: format.to_pixel_format(),
|
||||
format: to_pixel_format(format),
|
||||
internal_format: if srgb {
|
||||
InternalFormat::Srgba
|
||||
} else {
|
||||
@@ -404,7 +421,7 @@ impl OpenGL {
|
||||
|
||||
/// Begin a frame. The target is presented via this API when the frame is
|
||||
/// completed (see [`Frame::complete`]).
|
||||
pub fn begin_frame(&mut self, target: Arc<Target>) -> Frame<'_> {
|
||||
pub fn begin_frame(&self, target: Arc<Target>) -> Frame {
|
||||
Frame::begin(frame::Options::default(), self, target)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user