Files
ghostty-rs/crates/ghostty-renderer/src/opengl/render_pass.rs
T
ddidderr bab49ee596 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)
2026-10-12 06:02:39 +02:00

166 lines
5.0 KiB
Rust

//! Wrapper for handling render passes (`opengl/RenderPass.zig`).
use super::{
OpenGL,
gl::{Attachment as GlAttachment, BufferTarget, FramebufferTarget, Gl},
};
/// What a color attachment renders into.
pub type AttachmentTarget<'a> = crate::graphics_api::AttachmentTarget<'a, OpenGL>;
/// Describes a color attachment.
pub type Attachment<'a> = crate::graphics_api::Attachment<'a, OpenGL>;
/// Options for beginning a render pass.
#[derive(Clone, Copy)]
pub struct Options<'a> {
/// Color attachments for this render pass.
pub attachments: &'a [Attachment<'a>],
}
/// Describes the draw call for a step.
pub use crate::graphics_api::Draw;
/// Describes a step in a render pass.
pub type Step<'a> = crate::graphics_api::Step<'a, OpenGL>;
pub struct RenderPass<'a> {
gl: Gl,
attachments: &'a [Attachment<'a>],
step_number: usize,
}
impl<'a> RenderPass<'a> {
/// Begin a render pass.
pub fn begin(gl: &Gl, opts: Options<'a>) -> RenderPass<'a> {
RenderPass {
gl: gl.clone(),
attachments: opts.attachments,
step_number: 0,
}
}
/// Add a step to this render pass.
///
/// TODO: Errors are silently ignored in this function, maybe they shouldn't be?
/// (Same as upstream; failures are logged at debug level here.)
pub fn step(&mut self, s: &Step<'_>) {
if s.draw.instance_count == 0 {
return;
}
if self.step_(s).is_none() {
log::debug!("render pass step failed");
}
self.step_number += 1;
}
fn step_(&mut self, s: &Step<'_>) -> Option<()> {
let gl = self.gl.clone();
gl.use_program(Some(s.pipeline.program)).ok()?;
// Unbinding on all exit paths.
let r = (|| {
gl.bind_vertex_array(Some(s.pipeline.vao)).ok()?;
let r = self.step_with_vao(s);
let _ = gl.bind_vertex_array(None);
r
})();
let _ = gl.use_program(None);
r
}
fn step_with_vao(&mut self, s: &Step<'_>) -> Option<()> {
let gl = self.gl.clone();
let fbobind = match &self.attachments[0].target {
AttachmentTarget::Target(t) => {
gl.bind_framebuffer(t.framebuffer(), FramebufferTarget::Framebuffer)
}
AttachmentTarget::Texture(t) => {
let fbobind = gl.bind_framebuffer(s.pipeline.fbo, FramebufferTarget::Framebuffer);
if gl
.framebuffer_texture_2d(&fbobind, GlAttachment::Color0, t.target, t.raw(), 0)
.is_err()
{
gl.unbind_framebuffer(fbobind);
return None;
}
fbobind
}
};
let r = self.step_with_fbo(s);
self.gl.unbind_framebuffer(fbobind);
r
}
fn step_with_fbo(&mut self, s: &Step<'_>) -> Option<()> {
let gl = &self.gl;
// If we have a clear color and this is the
// first step in the pass, go ahead and clear.
if self.step_number == 0
&& let Some(c) = self.attachments[0].clear_color
{
gl.clear_color(c[0], c[1], c[2], c[3]);
gl.clear(glow::COLOR_BUFFER_BIT);
}
// Bind the uniform buffer we bind at index 1 to align with Metal.
if let Some(ubo) = s.uniforms {
gl.bind_buffer_base(BufferTarget::Uniform, 1, ubo).ok()?;
}
// Bind relevant texture units.
for (i, t) in s.textures.iter().enumerate() {
if let Some(tex) = t {
gl.active_texture(i as u32).ok()?;
gl.bind_texture(tex.target, Some(tex.raw())).ok()?;
}
}
// Bind relevant samplers.
for (i, sampler) in s.samplers.iter().enumerate() {
if let Some(sampler) = sampler {
gl.bind_sampler(i as u32, sampler.raw()).ok()?;
}
}
// Bind 0th buffer as the vertex buffer,
// and bind the rest as storage buffers.
if !s.buffers.is_empty() {
if let Some(vbo) = s.buffers[0] {
gl.bind_vertex_buffer(0, vbo, 0, s.pipeline.stride as i32)
.ok()?;
}
for (i, b) in s.buffers.iter().enumerate().skip(1) {
if let Some(buf) = b {
gl.bind_buffer_base(BufferTarget::Storage, i as u32, *buf)
.ok()?;
}
}
}
if s.pipeline.blending_enabled {
gl.enable(glow::BLEND).ok()?;
gl.blend_func(glow::ONE, glow::ONE_MINUS_SRC_ALPHA).ok()?;
} else {
gl.disable(glow::BLEND).ok()?;
}
gl.draw_arrays_instanced(
s.draw.ty,
0,
s.draw.vertex_count as i32,
s.draw.instance_count as i32,
)
.ok()
}
/// Complete this render pass.
/// This struct can no longer be used after calling this.
pub fn complete(self) {
self.gl.flush();
}
}