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
3c8fae1089
commit
452b782149
124 files changed
+14936
-1242
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user