Expose exact iOS render presentation tokens - #116
Conversation
📝 WalkthroughWalkthroughThe embedded rendering API adds tokenized forced renders and presentation callbacks. Tokens flow through renderer frame creation and platform presentation paths, reaching completion callbacks after successful surface presentation. ChangesRender presentation flow
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Embedder
participant SurfaceAPI
participant RenderThread
participant Renderer
participant Metal
participant IOSurfaceLayer
Embedder->>SurfaceAPI: ghostty_surface_render_now_with_token(token)
SurfaceAPI->>RenderThread: renderNowWithPresentation
RenderThread->>Renderer: drawFrameWithPresentation(token)
Renderer->>Metal: beginFrameWithPresentation
Metal->>IOSurfaceLayer: setSurfaceWithPresentation
IOSurfaceLayer-->>Embedder: render_presented_cb(userdata, token)
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/apprt/embedded.zig (1)
912-921: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winFallback to standard render if the callback is missing.
If
renderNowWithTokenis called but the embedder forgot to configure the presentation callback, returning early silently skips the render altogether. Since the caller explicitly invoked arender_nowfunction, falling back to a standard render is much safer and avoids a silent no-op.♻️ Proposed fallback
pub fn renderNowWithToken(self: *Surface, token: u64) void { - const callback = self.render_presented_cb orelse return; + const callback = self.render_presented_cb orelse { + self.renderNow(); + return; + }; self.core_surface.applyPendingResizeIfNeeded();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/apprt/embedded.zig` around lines 912 - 921, Update Surface.renderNowWithToken so a missing render_presented_cb falls back to the standard renderNow path instead of returning without rendering. Preserve the existing presentation callback flow, resize handling, userdata, and token behavior when the callback is configured.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/renderer/Metal.zig`:
- Around line 275-291: Update presentWithPresentation so non-iOS calls with sync
== false route target.surface and presentation through
setSurfaceWithPresentation, allowing the callback to run on the main thread only
after successful assignment. Preserve the existing self.present(target, sync)
behavior for sync == true, but do not invoke presentation.callback directly in
the asynchronous path.
---
Nitpick comments:
In `@src/apprt/embedded.zig`:
- Around line 912-921: Update Surface.renderNowWithToken so a missing
render_presented_cb falls back to the standard renderNow path instead of
returning without rendering. Preserve the existing presentation callback flow,
resize handling, userdata, and token behavior when the callback is configured.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 87840465-2221-437a-a044-caa62f02ddc5
📒 Files selected for processing (8)
include/ghostty.hsrc/apprt/embedded.zigsrc/renderer.zigsrc/renderer/Metal.zigsrc/renderer/Thread.zigsrc/renderer/generic.zigsrc/renderer/metal/Frame.zigsrc/renderer/metal/IOSurfaceLayer.zig
| var frame_ctx = if (presentation) |value| | ||
| if (@hasDecl(GraphicsAPI, "beginFrameWithPresentation")) | ||
| try self.api.beginFrameWithPresentation(self, &frame.target, value) | ||
| else | ||
| try self.api.beginFrame(self, &frame.target) | ||
| else | ||
| try self.api.beginFrame(self, &frame.target); |
There was a problem hiding this comment.
Presentation Token Gets Dropped
When ghostty_surface_render_now_with_token reaches a renderer backend without beginFrameWithPresentation, this branch still draws the frame but falls back to beginFrame and discards the callback payload. The caller sees a successful tokened render request, but no render_presented_cb can fire after presentation, so embedders waiting for that exact token can hang or keep stale pixels visible.
Adds a tokened render callback that reports the IOSurface identity and presentation only after the matching Metal submission reaches the renderer layer.
cmux uses this to keep last-good terminal pixels visible until an atomic replay has verified the exact rendered frame.
Tracks manaflow-ai/cmux#7160
Verification: cmux iOS VerifiedReplaySubmissionTests and tagged macOS/iOS builds.
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds a tokened render-present callback on iOS that fires only after the exact IOSurface is assigned to the CALayer on the main thread. Enables cmux to keep last-good pixels until replay verifies the presented frame, addressing cmux issue ghostty-org#7160 across Metal and OpenGL.
New Features
ghostty_render_presented_cb,render_presented_cbandrender_presented_userdatainghostty_surface_config_s, plusghostty_surface_render_now_with_token(surface, token).Surface.Options.render_presented_cb,render_presented_userdata, andSurface.renderNowWithToken(token).FramePresentation; addsdrawFrameWithPresentationand threadrenderNowWithPresentation;beginFrameWithPresentationfor Metal/OpenGL; MetalpresentWithPresentation; iOSIOSurfaceLayer.setSurfaceWithPresentation.Migration
render_presented_cband useghostty_surface_render_now_with_token(orSurface.renderNowWithToken).Written for commit 24284c3. Summary will update on new commits.
Summary by CodeRabbit