Repository navigation
feat(raylib): prefer IOSurface mode on macOS (#121) - #123
Conversation
The raylib desktop template's per-frame PBO readback now picks the zero-copy IOSurface variant of the engine's preview lifecycle when built for macOS, falling back to the SHM path everywhere else. The switch lives in three template blocks in `src/main_zig.zig` — PREVIEW_READBACK_SETUP (deferred teardown), the resize branch in PREVIEW_READBACK_LOOP (beginFrameStream), and the publish branch in the same loop. All three use `comptime @import("builtin").os.tag == .macos`, mirroring the existing `CLOCK_MONOTONIC` inline pattern already in the same file. Other backends (sdl/sokol/bgfx/wgpu) are untouched — their lifecycle stays control-plane-only. Producer-side pixel buffer stays RGBA8: glReadPixels still writes RGBA8 into the PBO, and `publishFrameIOSurface` does the RGBA->BGRA swizzle internally during the IOSurface lock/copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR SummaryMedium Risk Overview Adds regression tests asserting the raylib template emits both SHM and IOSurface callsites (and the comptime switch sentinel) and that non-raylib loop backends still emit neither SHM nor IOSurface preview lifecycle calls. Reviewed by Cursor Bugbot for commit b2665e6. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Code Review
This pull request introduces macOS-specific support for zero-copy frame transport using IOSurface within the preview readback loop for the Raylib backend. The implementation utilizes comptime branching to select between standard shared memory (SHM) and IOSurface lifecycle methods (beginFrameStreamIOSurface, publishFrameIOSurface, and endFrameStreamIOSurface) based on the target operating system. Additionally, new tests verify that the generated Zig source correctly includes both paths for Raylib while ensuring that non-Raylib backends remain unaffected. I have no feedback to provide as no review comments were submitted.
Closes #121.
Summary
Wires the per-frame preview lifecycle that landed in #120 to the macOS IOSurface variant of the engine's
PreviewAPI (labelle-engine v1.37.3 / #547). On non-macOS targets nothing changes — the SHMbeginFrameStream/publishFrame/endFrameStreamtriple is still emitted and used; on macOS the generated raylib game now usesbeginFrameStreamIOSurface/publishFrameIOSurface/endFrameStreamIOSurfacefor zero-copy frame transport via mach ports.Where the switch lives
All three call sites are template constants in
src/main_zig.zig:PREVIEW_READBACK_SETUP— the deferred teardown picks the matchingend*variant.PREVIEW_READBACK_LOOPresize branch — selectsbeginFrameStreamvsbeginFrameStreamIOSurfaceon (re)negotiate.PREVIEW_READBACK_LOOPpublish branch — selectspublishFramevspublishFrameIOSurfaceon each mature PBO.All three use
if (comptime @import("builtin").os.tag == .macos) { ... } else { ... }, mirroring the existing inline pattern already used inPREVIEW_HELPERSforCLOCK_MONOTONIC. Header doc comments on the twoconstblocks are extended to call out the macOS path and reference the ticket.The PBO ring math, the resize gating, the 2-frame priming, the bottom-up→top-down strided flip-on-copy, and the
_GL_RGBA/_GL_UNSIGNED_BYTEglReadPixels parameters are all untouched.Why RGBA8 stays the producer-side format
glReadPixels(GL_RGBA, GL_UNSIGNED_BYTE)is what raylib's GL context produces cheaply, andpublishFrameIOSurface(engine-side, inpreview_mode.zig) takes a[]const u8RGBA8 input and does the RGBA→BGRA swizzle internally during the IOSurface lock/copy. So no producer-side format change, no extra CPU swizzle pass — the existing pixel buffer is handed straight to the IOSurface publisher on macOS, exactly the way it was being handed to the SHM publisher elsewhere.Test additions
Two regression tests appended to
test/tests.zig(alongside the #120 PBO readback tests):raylib desktop emits both SHM and IOSurface preview lifecycle calls— asserts the generated raylib desktop template contains BOTH_p.beginFrameStream(AND_p.beginFrameStreamIOSurface((and the publish / end counterparts), plus a comptime-switch sentinel and the RGBA8 readback constant. Also re-runsstd.zig.Ast.parsefor syntactic validity.non-raylib loop backends still do not pull in IOSurface lifecycle— loops over.sdland.sokol, asserts that NEITHER the SHM nor the IOSurface lifecycle strings appear in their generated mains. Locks the macOS gating from accidentally widening scope.Verified locally
zig buildof the assembler — clean.zig build test --summary all—296/300 tests passed (4 skipped), up from 294/298 before the additions (the two new tests + the existing feat(raylib): per-frame PBO readback + publishFrame for PIE viewport #120 tests still pass).examples/raylibvia the assembler binary directly (thelabelleCLI route is blocked by Zig 0.15 in the system PATH); inspected the resultingmain.zigand confirmed all three comptime switches emit with both arms present.zig ast-checkon the regeneratedmain.zig— clean.zig buildof the regenerated example on macOS (Zig 0.16) — links cleanly. The engine already pullsIOSurface.framework+CoreFoundationvia its build script, so no template-side build wiring is needed.Deferred / out of scope
Test plan
zig build(assembler) — passes.zig build test— 296/300 pass (4 pre-existing skips).examples/raylibandzig ast-checkthe output.zig buildthe regenerated example on macOS.LABELLE_PREVIEW=host:portand confirm the editor receivesiosurface_bgra8frames (defer to the gui v0.3.0 side; the editor-side dispatch already exists per feat(sokol): GLCORE/GLES3 PBO readback + publishFrame (#122 slice 1) #124).