watchthelight labs

NvGfx fork notes

The nvgfx branch is NullMoth's 2.1.0 plus the changes in the README table. This file is the index of what the fork adds and how to switch it. The reasoning behind each change is in notes/.

Branches and versions

  • main: upstream as pulled (tags v2.1.0 .. v2.4.3).
  • nvgfx-2.4.3 (current): the fork's changes on top of the upstream 2.4.3 tag, packages labelled 2.4.3-nvgfx.N (package.sh writes the label into NVGFX-VERSION, REF = NullMoth's 2.4.3 pkgroot). 2.4.3 brought upstream's own fix for the thread_call pile-up (#295, every NVRMFB callback runs one at a time) and the merged PR 294 (AIR digest kept on the text), so the fork's digest half is upstream's now and only the per-module memo stays ours. The branch also carries the boot hold fix (nvrmholdcap). Earlier: nvgfx-2.4.1 (2.4.1-nvgfx.1, N-181 to N-184). It carries everything from nvgfx-2.3.7: the build harness (build/nvgfx), the NVRM power glue (nvrmtgp 115 W, nvrmgps/nvrmnvpcf, nvrmphysctl), the WindowServer shader-cache fallback, the plugin work from the 2.3.7 branch (a function that calls through a visible or intersection function table waits for its pipeline link; the per-module work of newFunctionWithName is kept across libraries; the commit-time ahead pass runs on its own queue; one framebuffer-fetch descriptor set per pass; refused pipeline links are remembered; CPU-written IOSurfaces upload asynchronously at bind; a repeated setRenderPipelineState inside a pass is skipped; the render encoder keeps its sampler-variant choice), the Dock badge mip fill, the unbound-slot sampler, and upstream PR 273 (Leaf-lsgtky: scaled desktop modes at the head viewport) ahead of its merge. For the 2.4.1 base: the fork's own _PSR answer to nv_acpi_get_powersource is gone (2.4.1 reads the battery service itself; nvrmgps/nvrmnvpcf/nvrmtgp still answer notebook and battery from ACPI), the harness links upstream's nv_xnu_power.cpp, and the TGP tick refreshes the power source itself. Every fork hunk is marked with <wtl> comment tags. Older branches: nvgfx-2.3.7 (2.3.7-nvgfx.1, N-169 to N-180 and the N-184/N-185 rollback), nvgfx-2.3.5 (2.3.5-nvgfx.1 to .3).
  • nvgfx: our changes on top of the upstream 2.1.0 tag (2.1.0-nvgfx.N, last tested 2.1.0-nvgfx.20). Kept for the trace tooling on dock-trace and as the rollback.

Boot-args added by the fork

All default off. With none set the kexts behave as upstream, apart from the Terminal fix and the present path, which are always on.

Boot-arg Kext Effect
nvrmtgp=1 NVRM at boot on AC, put the GSP in TGP mode (115 W on the RTX 5070 Laptop instead of the 50 W default) and follow _PSR afterwards
nvrmgps=1 NVRM answer the GPS _DSM on the GPU node, so the RM's platform request handler finds GPS_2X
nvrmnvpcf=1 NVRM answer the NVPCF _DSM on \_SB.NPCF, as Linux does
nvrmphysctl=1 NVRM root-only control escape (0xE4) used by nvgfx-smi --phys and --ctrl
nvrmholdcap=<s> NVRM ceiling for the boot hold while the bring-up is still running (40 to 600 s, default 120; upstream releases the hold at settle + 40 s regardless, which loses the desktop when the GSP bring-up is slow)
nvgfxwsfetch=1 plugin enable framebuffer fetch in WindowServer only (Dock blur investigation)
nvgfxfetchcopy=1 plugin emulate framebuffer fetch with copies in WindowServer when fetch is off

Upstream's own boot-args (nvfb, nvaccel, nvfbheads, nvrmacpibl, -nvrmnoflip, -nvaccelnoheadless and the rest) are unchanged.

Sysctls

  • debug.nvgfx_present_depth: how many flips may be in flight (default 2).
  • debug.nvgfx_fb_*: present path counters (submits, latched, refused, superseded, timeouts, commit and latch times, vblank hops).
  • debug.nvgfx_dirty_*: why NVAccel surface dirty marks were refused or re-wired (parked VRAM, no resource, scanout).
  • debug.nvgfx_tgp_*: TGP mode state and the last control results.

Environment switches (plugin)

Read from /Library/GPUBundles/nvmtl/nvrm610.conf in release builds, since the plugin unsets NVMTL_* variables at load.

Switch Effect
NVMTL_NVGFX_LEGACY_FBFETCH=1 every render pass is a fetch pass again (2.1.0 behaviour, brings the Terminal overdraw back)
NVMTL_NVGFX_LEGACY_SEMU=1 2.1.0's broad stencil-export detector
NVMTL_NVGFX_FETCH_COPY=1 framebuffer fetch emulated by copies for every process
NVMTL_NVGFX_FCOPY_FORCE_FAIL=1 make the fetch-copy pipeline fail, to test the rebuild-without-it path
NVMTL_NVGFX_SURFOUT_TRACE=1 syslog trace of surface copy-outs
NVMTL_NVGFX_WSTRACE=1 WindowServer trace: samplers, pipelines, small render targets, the backdrop composite draw, draws that sample small square textures
NVMTL_NO_POOL_REUSE=1 upstream switch: command pools and address pages are created and destroyed per commit instead of being kept behind the completion witness; set on the test machine since 2026-10-20 as the workaround for the per-process backend shutdown below (I-085)

Where things are

Path What
plugin/nvmtl_vk.c, plugin/nvmtl_vk.h render pass kinds per pipeline, pass restart, fetch-copy emulation
plugin/NVMTLObjects.m encoder changes for the pass restart, WindowServer trace
plugin/NVMTLLibraryLoad.m shader cache fallback folder
plugin/nvgfx_fetch_copy.h, plugin/nvgfx_wstrace.h the fetch-copy and trace helpers
kexts/NVRMFB/fb/nvrm-fb.cpp, kexts/NVRMFB/fb/nvgfx_present_abi.h the present path
kexts/NVRM/accel/nvrm-accel.cpp dirty-mark accounting, parked VRAM re-wiring
kexts/NVRM/nvrm_power.cpp, nvrm_physctl.c, nvrm_physctl_abi.h TGP mode, platform _DSMs, control escape
kexts/NVRM/os-xnu.cpp, os-xnu2.cpp, NVRM.cpp the glue the harness needed to rebuild NVRM from source
nvk/nvgfx/01-nv_push-opencl.patch one build fix on top of NullMoth's NVK patches
build/nvgfx/ build scripts, the NVRM harness, comparison tools, package.sh
tools/nvgfx-smi, tools/nvgfx-burn telemetry and load tools
notes/ the working notes behind each change

Measured on the test machine

RTX 5070 Laptop (10DE:2D18), 2560x1600 at 165 Hz, macOS 26.7.1 (25G241), OpenCore 1.0.8, package 2.1.0-nvgfx.20:

  • Metal 3 device, accelerated desktop, Liquid Glass renders.
  • Present path: flips latched at the panel rate (165/s), window drags about 148 fps; shipped 2.1.0 on the same machine did about 54.
  • Power: 115 W enforced with nvrmtgp=1 (VBIOS default 50 W; Windows runs the same GPU at 115 W with Dynamic Boost).
  • Terminal overdraw from 2.1.0: gone since nvgfx.1.
  • Geekbench 6.7.1 Metal: 103094 on 2.4.3-nvgfx.1 (2026-10-20, https://browser.geekbench.com/v6/compute/6944564); Background Blur completes here (upstream #292 reports a hang at that subtest on other machines).
  • WindowServer CPU after boot: normal since nvgfx.20 (before: about 150 % while it re-translated every shader).
  • Still wrong (I-085, 2026-10-20): Terminal.app and iTerm2 froze for good three times in two hours under heavy text output. The plugin logged "vk: completion witness missing; disabling backend and retaining all in-flight resources" in the process (upstream nvmtl_vk_submit_finish, unchanged here), every later command buffer was nil and CoreAnimation's CG glyph queue waited forever. Workaround: NVMTL_NO_POOL_REUSE=1 in nvrm610.conf (not yet proven). Upstream issue to be written by hand; record in project-legion evidence/native/I-085-completion-witness-hang/.
  • Still wrong: the Dock's blur draws as blocks (a WindowServer framebuffer-fetch problem, under investigation with the trace switches).

Credit

All of the driver is NullMoth's work unless a commit on the nvgfx branch says otherwise. The full upstream history is kept here unchanged. Bug reports and fixes that apply to everyone go to NullMoth first.

Required Notice: Copyright (c) 2026 NullMoth Systems (https://nullmothsystems.com)

Distributed under the PolyForm Noncommercial License 1.0.0 (LICENSE, https://polyformproject.org/licenses/noncommercial/1.0.0): noncommercial use only. Third-party parts keep their own terms, listed in NOTICE.