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:
ddidderr committed 2026-10-12 06:02:39 +02:00
1 parent 76771bed0d
commit bab49ee596
124 files changed
+14936 -1242

No files matched your search

+249
View File
@@ -0,0 +1,249 @@
//! `impl GraphicsApi for OpenGL`: adapts the OpenGL backend to the interface
//! the generic renderer is written against (see [`crate::graphics_api`]).
//!
//! The adapter delegates to the inherent OpenGL API (which keeps its richer,
//! GL-typed signatures for the apprt and the integration tests). Inherent
//! methods are always called with path syntax so they win over the trait
//! methods of the same name.
use std::sync::Arc;
use bytemuck::Pod;
use ghostty_config::types_b::AlphaBlending;
use ghostty_font::atlas::Atlas;
use super::{
Buffer,
Frame,
OpenGL,
Pipeline,
RenderPass,
Sampler,
Target,
Texture,
gl::RawBuffer,
shaders::Shaders,
};
use crate::{
backend::Health,
graphics_api::{
ApiError,
Attachment,
FrameCompletion,
GpuBuffer,
GpuFrame,
GpuRenderPass,
GpuShaders,
GpuTarget,
GpuTexture,
GraphicsApi,
Step,
TextureKind,
},
shader_types::Uniforms,
shadertoy,
};
impl GraphicsApi for OpenGL {
/// Custom shaders are converted to GLSL for OpenGL.
const CUSTOM_SHADER_TARGET: shadertoy::Target = shadertoy::Target::Glsl;
/// The fragCoord for OpenGL shaders is +Y = up.
const CUSTOM_SHADER_Y_IS_DOWN: bool = false;
/// Because OpenGL's frame completion is always
/// sync, we have no need for multi-buffering.
const SWAP_CHAIN_COUNT: usize = 1;
type Target = Target;
type Texture = Texture;
type Sampler = Sampler;
type Buffer<T: Pod> = Buffer<T>;
type BufferRef = RawBuffer;
type Pipeline = Pipeline;
type Shaders = Shaders;
type GpuUniforms = Uniforms;
type Frame = GlFrame;
type RenderPass<'a> = RenderPass<'a>;
fn gpu_uniforms(uniforms: &Uniforms) -> Uniforms {
*uniforms
}
fn blending(&self) -> AlphaBlending {
self.blending
}
fn set_blending(&mut self, blending: AlphaBlending) {
self.blending = blending;
}
fn init_shaders(&self, custom_shaders: &[String]) -> Result<Shaders, ApiError> {
OpenGL::init_shaders(self, custom_shaders).map_err(ApiError::new)
}
fn surface_size(&self) -> Result<(u32, u32), ApiError> {
OpenGL::surface_size(self).map_err(ApiError::new)
}
fn init_target(&self, width: usize, height: usize) -> Result<Target, ApiError> {
OpenGL::init_target(self, width, height).map_err(ApiError::new)
}
fn init_buffer<T: Pod>(&self, len: usize) -> Result<Buffer<T>, ApiError> {
let gl = self.gl_or_err().map_err(ApiError::new)?;
Buffer::init(gl, self.buffer_options(), len).map_err(ApiError::new)
}
fn init_buffer_fill<T: Pod>(&self, data: &[T]) -> Result<Buffer<T>, ApiError> {
let gl = self.gl_or_err().map_err(ApiError::new)?;
Buffer::init_fill(gl, self.buffer_options(), data).map_err(ApiError::new)
}
fn init_texture(
&self,
kind: TextureKind,
width: usize,
height: usize,
data: Option<&[u8]>,
) -> Result<Texture, ApiError> {
let gl = self.gl_or_err().map_err(ApiError::new)?;
let opts = match kind {
TextureKind::CustomShader => self.texture_options(),
TextureKind::Image { format, srgb } => self.image_texture_options(format, srgb),
};
Texture::init(gl, opts, width, height, data).map_err(ApiError::new)
}
fn init_atlas_texture(&self, atlas: &Atlas) -> Result<Texture, ApiError> {
OpenGL::init_atlas_texture(self, atlas).map_err(ApiError::new)
}
fn init_sampler(&self) -> Result<Sampler, ApiError> {
let gl = self.gl_or_err().map_err(ApiError::new)?;
Sampler::init(gl, self.sampler_options()).map_err(ApiError::new)
}
fn begin_frame(&self, target: Arc<Target>) -> GlFrame {
GlFrame(OpenGL::begin_frame(self, target))
}
fn present_last_target(&self) -> Result<(), ApiError> {
OpenGL::present_last_target(self).map_err(ApiError::new)
}
fn clear_last_target(&self) {
OpenGL::clear_last_target(self);
}
fn draw_frame_start(&mut self) {
OpenGL::draw_frame_start(self);
}
fn draw_frame_end(&mut self) {
OpenGL::draw_frame_end(self);
}
fn thread_enter(&self) {
OpenGL::thread_enter(self);
}
fn thread_exit(&self) {
OpenGL::thread_exit(self);
}
}
impl<T: Pod> GpuBuffer<OpenGL, T> for Buffer<T> {
fn raw(&self) -> RawBuffer {
Buffer::raw(self)
}
fn sync(&mut self, data: &[T]) -> Result<(), ApiError> {
Buffer::sync(self, data).map_err(ApiError::new)
}
fn sync_from_slices(&mut self, lists: &[&[T]]) -> Result<usize, ApiError> {
Buffer::sync_from_slices(self, lists).map_err(ApiError::new)
}
}
impl GpuTexture for Texture {
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
fn replace_region(
&self,
x: usize,
y: usize,
width: usize,
height: usize,
data: &[u8],
) -> Result<(), ApiError> {
Texture::replace_region(self, x, y, width, height, data).map_err(ApiError::new)
}
}
impl GpuTarget for Target {
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
}
impl GpuShaders<OpenGL> 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
}
}
/// An OpenGL [`Frame`] as seen by the generic renderer.
pub struct GlFrame(Frame);
impl GpuFrame<OpenGL> for GlFrame {
fn render_pass<'a>(&'a self, attachments: &'a [Attachment<'a, OpenGL>]) -> RenderPass<'a>
where
OpenGL: 'a,
{
self.0.render_pass(attachments)
}
fn complete(self, sync: bool, completion: FrameCompletion) {
let health: Health = self.0.complete(sync);
completion(health);
}
}
impl<'a> GpuRenderPass<'a, OpenGL> for RenderPass<'a> {
fn step(&mut self, step: &Step<'_, OpenGL>) {
RenderPass::step(self, step);
}
fn complete(self) {
RenderPass::complete(self);
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ pub struct Buffer<T: Pod> {
/// Note this is the number of `T`s, not the size in bytes.
pub len: usize,
_marker: PhantomData<T>,
_marker: PhantomData<fn() -> T>,
}
impl<T: Pod> Buffer<T> {
+19 -15
View File
@@ -2,7 +2,9 @@
use std::sync::Arc;
use super::{
LastTarget,
OpenGL,
gl::Gl,
render_pass::{Attachment, Options as RenderPassOptions, RenderPass},
target::Target,
};
@@ -15,26 +17,31 @@ pub struct Options {}
/// A frame being encoded.
///
/// Upstream holds `*Renderer` and calls `renderer.frameCompleted(health)`
/// after presenting; here the frame borrows the graphics API and
/// [`Frame::complete`] returns the [`Health`] for the renderer to report.
pub struct Frame<'a> {
api: &'a mut OpenGL,
/// after presenting; here [`Frame::complete`] returns the [`Health`] for the
/// renderer to report. The frame holds its own handles (GL context and the
/// API's last-presented-target slot) rather than borrowing the API.
pub struct Frame {
gl: Gl,
last_target: LastTarget,
target: Arc<Target>,
}
impl<'a> Frame<'a> {
impl Frame {
/// Begin encoding a frame.
///
/// The target is presented via the provided API when completed.
pub fn begin(_opts: Options, api: &'a mut OpenGL, target: Arc<Target>) -> Frame<'a> {
Frame { api, target }
pub fn begin(_opts: Options, api: &OpenGL, target: Arc<Target>) -> Frame {
Frame {
gl: api.gl().expect("OpenGL context not initialized").clone(),
last_target: api.last_target_slot(),
target,
}
}
/// Add a render pass to this frame with the provided attachments.
/// Returns a RenderPass which allows render steps to be added.
pub fn render_pass<'b>(&self, attachments: &'b [Attachment<'b>]) -> RenderPass<'b> {
let gl = self.api.gl().expect("OpenGL context not initialized");
RenderPass::begin(gl, RenderPassOptions { attachments })
RenderPass::begin(&self.gl, RenderPassOptions { attachments })
}
/// Complete this frame and present the target.
@@ -43,13 +50,10 @@ impl<'a> Frame<'a> {
///
/// NOTE: For OpenGL, `sync` is ignored and we always block.
pub fn complete(self, _sync: bool) -> Health {
let Some(gl) = self.api.gl().cloned() else {
return Health::Unhealthy;
};
gl.finish();
self.gl.finish();
// If there are any GL errors, consider the frame unhealthy.
let health = if gl.get_error().is_ok() {
let health = if self.gl.get_error().is_ok() {
Health::Healthy
} else {
Health::Unhealthy
@@ -57,7 +61,7 @@ impl<'a> Frame<'a> {
// If the frame is healthy, present it.
if health == Health::Healthy
&& let Err(err) = self.api.present(&self.target)
&& let Err(err) = super::present_target(&self.gl, &self.last_target, &self.target)
{
log::error!("Failed to present render target: err={err}");
}
+12 -9
View File
@@ -160,14 +160,17 @@ pub enum Wrap {
}
/// Primitive types we support drawing (same subset as Metal).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Primitive {
Point = glow::POINTS,
Line = glow::LINES,
LineStrip = glow::LINE_STRIP,
Triangle = glow::TRIANGLES,
TriangleStrip = glow::TRIANGLE_STRIP,
pub use crate::graphics_api::Primitive;
/// The GL draw mode of a [`Primitive`].
fn gl_mode(mode: Primitive) -> u32 {
match mode {
Primitive::Point => glow::POINTS,
Primitive::Line => glow::LINES,
Primitive::LineStrip => glow::LINE_STRIP,
Primitive::Triangle => glow::TRIANGLES,
Primitive::TriangleStrip => glow::TRIANGLE_STRIP,
}
}
/// Framebuffer binding targets.
@@ -341,7 +344,7 @@ impl Gl {
// SAFETY: see module docs; vertex data comes from bound GL buffers.
unsafe {
self.ctx
.draw_arrays_instanced(mode as u32, first, count, instances);
.draw_arrays_instanced(gl_mode(mode), first, count, instances);
}
self.get_error()
}
+78 -61
View File
@@ -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)
}
}
@@ -1,23 +1,14 @@
//! Wrapper for handling render passes (`opengl/RenderPass.zig`).
use super::{
gl::{Attachment as GlAttachment, BufferTarget, FramebufferTarget, Gl, Primitive, RawBuffer},
pipeline::Pipeline,
sampler::Sampler,
target::Target,
texture::Texture,
OpenGL,
gl::{Attachment as GlAttachment, BufferTarget, FramebufferTarget, Gl},
};
/// What a color attachment renders into.
pub enum AttachmentTarget<'a> {
Texture(&'a Texture),
Target(&'a Target),
}
pub type AttachmentTarget<'a> = crate::graphics_api::AttachmentTarget<'a, OpenGL>;
/// Describes a color attachment.
pub struct Attachment<'a> {
pub target: AttachmentTarget<'a>,
pub clear_color: Option<[f32; 4]>,
}
pub type Attachment<'a> = crate::graphics_api::Attachment<'a, OpenGL>;
/// Options for beginning a render pass.
#[derive(Clone, Copy)]
@@ -27,32 +18,10 @@ pub struct Options<'a> {
}
/// Describes the draw call for a step.
#[derive(Debug, Clone, Copy)]
pub struct Draw {
pub ty: Primitive,
pub vertex_count: usize,
pub instance_count: usize,
}
impl Draw {
pub fn new(ty: Primitive, vertex_count: usize) -> Self {
Draw {
ty,
vertex_count,
instance_count: 1,
}
}
}
pub use crate::graphics_api::Draw;
/// Describes a step in a render pass.
pub struct Step<'a> {
pub pipeline: &'a Pipeline,
pub uniforms: Option<RawBuffer>,
pub buffers: &'a [Option<RawBuffer>],
pub textures: &'a [Option<&'a Texture>],
pub samplers: &'a [Option<&'a Sampler>],
pub draw: Draw,
}
pub type Step<'a> = crate::graphics_api::Step<'a, OpenGL>;
pub struct RenderPass<'a> {
gl: Gl,
+13 -226
View File
@@ -1,11 +1,8 @@
//! Shader pipelines and GPU data layouts for OpenGL (`opengl/shaders.zig`).
use bytemuck::{Pod, Zeroable};
use super::{
gl::{self, AttribType, Gl},
pipeline::{Attribute, Options as PipelineOptions, Pipeline, StepFunction, Vertex},
};
use crate::math::Mat;
/// `shaders/glsl/common.glsl`, the only `#include`d file.
const COMMON_GLSL: &str = include_str!("../../shaders/glsl/common.glsl");
@@ -160,167 +157,19 @@ pub fn process_includes(contents: &str) -> String {
contents.to_owned()
}
/// The uniforms that are passed to our shaders (std140 layout, 144 bytes).
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Pod, Zeroable)]
pub struct Uniforms {
/// The projection matrix for turning world coordinates to normalized.
/// This is calculated based on the size of the screen.
pub projection_matrix: Mat,
/// Size of the screen (render target) in pixels.
pub screen_size: [f32; 2],
/// Size of a single cell in pixels, unscaled.
pub cell_size: [f32; 2],
/// Size of the grid in columns and rows.
pub grid_size: [u16; 2],
// Explicit padding so that `grid_padding` is 16-byte aligned (std140).
pub pad0: [u8; 12],
/// The padding around the terminal grid in pixels. In order:
/// top, right, bottom, left.
pub grid_padding: [f32; 4],
/// Bit mask defining which directions to
/// extend cell colors in to the padding.
/// Order, LSB first: left, right, up, down. See [`PaddingExtend`].
pub padding_extend: u32,
/// The minimum contrast ratio for text. The contrast ratio is calculated
/// according to the WCAG 2.0 spec.
pub min_contrast: f32,
/// The cursor position and color.
pub cursor_pos: [u16; 2],
pub cursor_color: [u8; 4],
/// The background color for the whole surface.
pub bg_color: [u8; 4],
/// Various booleans, in a packed bitfield for space efficiency. See [`Bools`].
pub bools: u32,
// Tail padding to the struct's 16-byte alignment (extern struct size).
pub pad1: [u8; 8],
}
/// The `bools` bitfield of [`Uniforms`] (a `packed struct(u32)` upstream).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Bools {
/// Whether the cursor is 2 cells wide.
pub cursor_wide: bool,
/// Indicates that colors provided to the shader are already in
/// the P3 color space, so they don't need to be converted from
/// sRGB.
pub use_display_p3: bool,
/// Indicates that the color attachments for the shaders have
/// an `*_srgb` pixel format, which means the shaders need to
/// output linear RGB colors rather than gamma encoded colors,
/// since blending will be performed in linear space and then
/// the GPU itself will re-encode the colors for storage.
pub use_linear_blending: bool,
/// Enables a weight correction step that makes text rendered
/// with linear alpha blending have a similar apparent weight
/// (thickness) to gamma-incorrect blending.
pub use_linear_correction: bool,
}
impl Bools {
pub const CURSOR_WIDE: u32 = 1;
pub const USE_DISPLAY_P3: u32 = 2;
pub const USE_LINEAR_BLENDING: u32 = 4;
pub const USE_LINEAR_CORRECTION: u32 = 8;
pub fn to_bits(self) -> u32 {
u32::from(self.cursor_wide)
| u32::from(self.use_display_p3) << 1
| u32::from(self.use_linear_blending) << 2
| u32::from(self.use_linear_correction) << 3
}
pub fn from_bits(bits: u32) -> Self {
Bools {
cursor_wide: bits & Self::CURSOR_WIDE != 0,
use_display_p3: bits & Self::USE_DISPLAY_P3 != 0,
use_linear_blending: bits & Self::USE_LINEAR_BLENDING != 0,
use_linear_correction: bits & Self::USE_LINEAR_CORRECTION != 0,
}
}
}
/// The `padding_extend` bitfield of [`Uniforms`] (a `packed struct(u32)` upstream).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PaddingExtend {
pub left: bool,
pub right: bool,
pub up: bool,
pub down: bool,
}
impl PaddingExtend {
pub fn to_bits(self) -> u32 {
u32::from(self.left)
| u32::from(self.right) << 1
| u32::from(self.up) << 2
| u32::from(self.down) << 3
}
pub fn from_bits(bits: u32) -> Self {
PaddingExtend {
left: bits & 1 != 0,
right: bits & 2 != 0,
up: bits & 4 != 0,
down: bits & 8 != 0,
}
}
}
/// Which atlas a glyph lives in (`CellText.Atlas`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CellTextAtlas {
Grayscale = 0,
Color = 1,
}
/// This is a single parameter for the terminal cell shader (32 bytes).
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Pod, Zeroable)]
pub struct CellText {
pub glyph_pos: [u32; 2],
pub glyph_size: [u32; 2],
pub bearings: [i16; 2],
pub grid_pos: [u16; 2],
pub color: [u8; 4],
/// A [`CellTextAtlas`] value.
pub atlas: u8,
/// Bit 0: `no_min_contrast`, bit 1: `is_cursor_glyph`. See the consts below.
pub bools: u8,
pub pad: [u8; 2],
}
impl CellText {
pub const NO_MIN_CONTRAST: u8 = 1;
pub const IS_CURSOR_GLYPH: u8 = 2;
pub fn atlas(&self) -> CellTextAtlas {
if self.atlas == 0 {
CellTextAtlas::Grayscale
} else {
CellTextAtlas::Color
}
}
pub fn set_atlas(&mut self, a: CellTextAtlas) {
self.atlas = a as u8;
}
}
pub use crate::shader_types::{
BgImage,
BgImageFit,
BgImageInfo,
BgImagePosition,
Bools,
CellBg,
CellText,
CellTextAtlas,
Image,
PaddingExtend,
Uniforms,
};
impl Vertex for CellText {
const ATTRIBUTES: &'static [Attribute] = &[
@@ -362,21 +211,6 @@ impl Vertex for CellText {
];
}
/// This is a single parameter for the cell bg shader.
pub type CellBg = [u8; 4];
/// Single parameter for the image shader. See shader for field details.
/// (48 bytes: the Zig extern struct is 16-byte aligned.)
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Pod, Zeroable)]
pub struct Image {
pub grid_pos: [f32; 2],
pub cell_offset: [f32; 2],
pub source_rect: [f32; 4],
pub dest_size: [f32; 2],
pub pad: [f32; 2],
}
impl Vertex for Image {
const ATTRIBUTES: &'static [Attribute] = &[
Attribute {
@@ -402,53 +236,6 @@ impl Vertex for Image {
];
}
/// Single parameter for the bg image shader (8 bytes).
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, Pod, Zeroable)]
pub struct BgImage {
pub opacity: f32,
/// A packed [`BgImageInfo`] byte.
pub info: u8,
pub pad: [u8; 3],
}
/// `BgImage.Info`, a `packed struct(u8)`: position:u4, fit:u2, repeat:u1, pad:u1.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BgImageInfo {
pub position: BgImagePosition,
pub fit: BgImageFit,
pub repeat: bool,
}
impl BgImageInfo {
pub fn to_bits(self) -> u8 {
(self.position as u8) | (self.fit as u8) << 4 | u8::from(self.repeat) << 6
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BgImagePosition {
Tl = 0,
Tc = 1,
Tr = 2,
Ml = 3,
Mc = 4,
Mr = 5,
Bl = 6,
Bc = 7,
Br = 8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BgImageFit {
Contain = 0,
Cover = 1,
Stretch = 2,
None = 3,
}
impl Vertex for BgImage {
const ATTRIBUTES: &'static [Attribute] = &[
Attribute {