Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b00f733
Add initial macOS build support
yeetbot90 Nov 8, 2026
5d51820
Merge remote-tracking branch 'origin/main' into yeetbot90-macos-suppo…
yeetbot90 Nov 8, 2026
ec1ee06
Update macOS runner version in workflow
yeetbot90 Nov 8, 2026
8322747
Update macos-platform.yml
yeetbot90 Nov 8, 2026
3f949d8
Change macOS runner to macos-latest
yeetbot90 Nov 8, 2026
3833f36
Change macOS runner to macos-latest-intel
yeetbot90 Nov 8, 2026
84f16dd
Update macOS runner to use latest version
yeetbot90 Nov 8, 2026
40213a0
Fix macOS runtime compilation
yeetbot90 Nov 8, 2026
2a8f7f3
Fix macOS architecture verification
yeetbot90 Nov 8, 2026
cbf5562
Decouple macOS host smoke compile from lift generator
yeetbot90 Nov 8, 2026
c820d1a
Expose Darwin extensions in POSIX runtime
yeetbot90 Nov 8, 2026
5237d34
Use generated runtime stubs for SDL compile check
yeetbot90 Nov 8, 2026
a9b08da
Guard Vulkan window setup on macOS
yeetbot90 Nov 8, 2026
3be1d9d
Test macOS dependency architecture preflight
yeetbot90 Nov 8, 2026
938b6df
Package macOS CLI builder and dependencies
yeetbot90 Nov 8, 2026
d990dbb
Add macOS builder app and speed up CI
yeetbot90 Nov 8, 2026
eb820d6
Isolate macOS dependency preflight test
yeetbot90 Nov 8, 2026
d6b9a35
Pass the macOS SDK to direct clang builds
yeetbot90 Nov 8, 2026
5a45366
Pass macOS SDK to renderer check
yeetbot90 Nov 8, 2026
6f20cb1
Fix macOS renderer compiler validation
yeetbot90 Nov 8, 2026
11c73b4
Keep renderer write-watch state visible on macOS
yeetbot90 Nov 8, 2026
2b712ce
Use SDL window dimensions for macOS renderer
yeetbot90 Nov 8, 2026
8a30e75
Port macOS builder and game target to native arm64
yeetbot90 Nov 8, 2026
18467bb
Build macOS instruction translator with native SDK
yeetbot90 Nov 8, 2026
5453b51
Merge remote-tracking branch 'origin/main' into yeetbot90-macos-suppo…
yeetbot90 Nov 8, 2026
d3c46d8
Support Apple Silicon POSIX exception contexts
yeetbot90 Nov 8, 2026
6aefdaa
Fix output staging test scope
yeetbot90 Nov 8, 2026
3674f6c
Run source regressions for macOS changes
yeetbot90 Nov 8, 2026
2d22fa9
Merge pull request #2 from yeetbot90/yeetbot90-macos-support-slice
yeetbot90 Nov 8, 2026
183cf94
Merge branch 'main' into main
yeetbot90 Nov 8, 2026
026926b
Fix cross-platform builder regressions
yeetbot90 Nov 8, 2026
17e206d
Publish macOS builder in tagged releases
yeetbot90 Nov 8, 2026
f54b28c
Release the macOS app bundle
yeetbot90 Nov 8, 2026
87c0bce
Merge main into macOS support PR
yeetbot90 Nov 8, 2026
1dc58a8
Merge latest main into macOS support PR
yeetbot90 Nov 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .github/workflows/macos-platform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: macOS platform

on:
push:
tags: ['v*']
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
macos-posix:
# Apple Silicon runner: verify the native arm64 game and runtime target.
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build configuration test
run: |
python -I -B tests/test_macos_build_path.py
python -I third_party/a64-kit/lift/build_emit.py
python -I scripts/macos/check-arm64-translation.py

- name: Link native arm64 macOS executable
run: |
printf 'int main(void) { return 0; }\n' > "$RUNNER_TEMP/macos-probe.c"
clang -target arm64-apple-macos13.3 -mcpu=apple-m1 "$RUNNER_TEMP/macos-probe.c" -o "$RUNNER_TEMP/macos-probe"
lipo "$RUNNER_TEMP/macos-probe" -verify_arch arm64
printf 'int main(void) { unsigned __int128 v = 0, e = 0; return __atomic_compare_exchange_n(&v, &e, 1, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); }\n' \
> "$RUNNER_TEMP/arm64-atomic-probe.c"
clang -target arm64-apple-macos13.3 -mcpu=apple-m1 "$RUNNER_TEMP/arm64-atomic-probe.c" \
-o "$RUNNER_TEMP/arm64-atomic-probe"

- name: Compile macOS POSIX host shims
run: |
mkdir -p "$RUNNER_TEMP/a64-generated"
printf '/* Generated lift helpers are unused by these host translation units. */\n' \
> "$RUNNER_TEMP/a64-generated/a64_suyu.h"
printf '/* Generated per-game floating-point helpers are unused by these host translation units. */\n' \
> "$RUNNER_TEMP/a64-generated/a64_fpx.h"
test "$(uname -m)" = arm64
python -c "import platform; assert platform.machine() == 'arm64', platform.machine()"
clang -target arm64-apple-macos13.3 -mcpu=apple-m1 -fsyntax-only -D_GNU_SOURCE -Wno-everything \
-I"$RUNNER_TEMP/a64-generated" -Ihost/posix -Ihost -Ithird_party/a64-kit/rt \
host/posix/win32_posix.c host/hos_main.c host/rx_backend.c host/hos_setjmp.c
clang -target arm64-apple-macos13.3 -mcpu=apple-m1 -fsyntax-only -D_GNU_SOURCE -Wno-everything \
-I"$RUNNER_TEMP/a64-generated" -Ihost/posix -Ihost -Ithird_party/a64-kit/rt \
host/posix/display_sdl.c host/posix/macos_stubs.c

macos-builder:
# Builds a native arm64 downloadable CLI and dependencies; it never receives or builds game data.
runs-on: macos-latest
timeout-minutes: 150
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Verify Apple Silicon runner
run: |
test "$(uname -m)" = arm64
python -c "import platform; assert platform.machine() == 'arm64', platform.machine()"

- uses: actions/cache@v4
with:
path: build-macos/deps
key: macos-builder-deps-${{ hashFiles('scripts/macos/deps.sh') }}

- name: Install dependency build tools
run: brew install cmake nasm

- name: Build builder dependencies
run: bash scripts/macos/deps.sh

- name: Validate bundled builder toolchain
env:
WGPU_NATIVE: ${{ github.workspace }}/build-macos/deps/wgpu-native
HOOKSHOT_SDL2: ${{ github.workspace }}/build-macos/deps/sdl2/lib/libSDL2-2.0.0.dylib
HOOKSHOT_FFMPEG: ${{ github.workspace }}/build-macos/deps/ffmpeg
run: |
build-macos/deps/python/bin/python3 -I -B tests/test_macos_build_path.py
build-macos/deps/python/bin/python3 -I -c "import tkinter"
build-macos/deps/python/bin/python3 -I -c \
"import platform; assert platform.machine() == 'arm64', platform.machine()"
build-macos/deps/python/bin/python3 -I -c \
"import runpy; runpy.run_path('nx2/make_game.py')['check_macos_toolchain']()"
python -I scripts/macos/check-renderer.py
lipo build-macos/deps/python/bin/python3 -verify_arch arm64
lipo build-macos/deps/sdl2/lib/libSDL2-2.0.0.dylib -verify_arch arm64
lipo build-macos/deps/wgpu-native/lib/libwgpu_native.dylib -verify_arch arm64
for lib in build-macos/deps/ffmpeg/lib/*.a; do lipo "$lib" -verify_arch arm64; done

- name: Package CLI builder
run: bash scripts/macos/package-builder.sh

- name: Verify packaged builder starts
run: |
test -d build-macos/package/HookshotBuilder/HookshotBuilder.app
test -x build-macos/package/HookshotBuilder/HookshotBuilder.app/Contents/Resources/tools/python/bin/python3
test -f build-macos/package/HookshotBuilder/HookshotBuilder.app/Contents/Resources/tools/source/builder/linux/hookshot_builder.py
test ! -L build-macos/package/HookshotBuilder/HookshotBuilder.app/Contents/Resources/tools
lipo build-macos/package/HookshotBuilder/HookshotBuilder.app/Contents/MacOS/applet -verify_arch arm64
cd build-macos/package/HookshotBuilder
./HookshotBuilder.command --help

- uses: actions/upload-artifact@v4
with:
name: HookshotBuilder-macos-arm64
path: |
build-macos/HookshotBuilder-macos-arm64.zip
build-macos/HookshotBuilder-macos-arm64.app.zip
if-no-files-found: error

release:
name: macOS release asset
if: startsWith(github.ref, 'refs/tags/v')
needs: macos-builder
runs-on: ubuntu-24.04
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: HookshotBuilder-macos-arm64
path: dist
- name: macOS builder to the release
run: scripts/release_upload.sh "$GITHUB_REF_NAME" dist/HookshotBuilder-macos-arm64.app.zip
8 changes: 6 additions & 2 deletions builder/linux/hookshot_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
import make_game # noqa: E402 (LANGUAGES, BUILD_ID)
import toolchain # noqa: E402 (default_jobs: RX_JOBS)

CONFIG = os.path.join(os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config"), "hookshot", "builder.ini")
CONFIG = os.path.join(os.path.expanduser("~/Library/Application Support"), "Hookshot", "builder.ini") if sys.platform == "darwin" else \
os.path.join(os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config"), "hookshot", "builder.ini")
STAGES = {"read": "Reading your game files", "lift": "Translating the game to PC code", "compile": "Compiling",
"finish": "Putting the game folder together"}

Expand Down Expand Up @@ -88,6 +89,8 @@ def desktop_exec(path):

def add_to_menu(folder):
"""an entry in the applications menu that starts the game"""
if sys.platform == "darwin":
return
icon = os.path.join(folder, "hookshot.png")
src = os.path.join(ROOT, "assets", "hookshot.png")
if os.path.exists(src):
Expand Down Expand Up @@ -607,7 +610,8 @@ def on_exit(self, code):
except OSError:
pass
ttk.Button(self.buttons, text="Play", command=lambda: self.play_and_close(folder)).pack(side="right", padx=self.px(4))
ttk.Button(self.buttons, text="Open folder", command=lambda: subprocess.Popen(["xdg-open", folder])).pack(side="right", padx=self.px(4))
opener = "open" if sys.platform == "darwin" else "xdg-open"
ttk.Button(self.buttons, text="Open folder", command=lambda: subprocess.Popen([opener, folder])).pack(side="right", padx=self.px(4))
else:
self.stage_text.set("The build stopped: %s" % getattr(self, "error", "see the lines above"))
ttk.Button(self.buttons, text="Back", command=self.show_form).pack(side="right", padx=self.px(4))
Expand Down
39 changes: 38 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,43 @@ background when the existing "Controller in background" option or `HOS_INPUT_BAC
`HOS_INPUT_BACKGROUND=0` overrides that option. `HOS_INPUT_EXCLUSIVE=1` controls merging the player's devices while
the script's existing exclusive interval is active; it is not a focus override.

## macOS (Apple Silicon)

The macOS builder and generated game are native arm64, with no Rosetta dependency. It uses SDL2 for the window and
input, and the existing WebGPU renderer through `wgpu-native`'s Metal backend; the Vulkan backend is not built on
macOS. The executable targets macOS 13.3 or newer because Apple's C++20 floating-point formatting support requires it.

The `macOS platform` workflow uploads a `HookshotBuilder-macos-arm64` artifact containing a command-line builder,
the Hookshot source, a native arm64 Python runtime and modules, SDL2, static FFmpeg, and Metal-enabled `wgpu-native`.
It does not contain game data or a built game. Download and unpack it on the target Mac; Xcode Command Line Tools
must be installed there. Double-click `HookshotBuilder.app` to choose your own `.nup` dump and output folder, or
use the command-line launcher:

```sh
./HookshotBuilder/HookshotBuilder.command --nup /path/to/game.nup --out /path/to/game-folder --jobs 4
```

For development outside the artifact, use arm64 builds of SDL2 2.26 or newer and `wgpu-native`. `wgpu-native` must
include its matching `include/webgpu/webgpu.h` and `lib/libwgpu_native.dylib`, built with Metal support. Set
`HOOKSHOT_FFMPEG` to the static FFmpeg prefix (or provide FFmpeg through `pkg-config`) and build natively:

```sh
export WGPU_NATIVE=/path/to/arm64-wgpu-native
export HOOKSHOT_SDL2=/path/to/arm64/libSDL2-2.0.0.dylib
export HOOKSHOT_FFMPEG=/path/to/arm64/ffmpeg
python3 -I nx2/make_game.py --nup /path/to/game.nup --out /path/to/game-folder --jobs 4
```

The normal builder command validates the arm64 compiler, SDL2, FFmpeg and `wgpu-native` before lifting the game,
then places `hookshot`, `libSDL2-2.0.0.dylib` and `libwgpu_native.dylib` beside `image.bin` in the output folder;
FFmpeg is statically linked when `HOOKSHOT_FFMPEG` points to the packaged static libraries. The macOS FFmpeg build
uses its native AV1 decoder to avoid compiling an additional dav1d dependency in CI.
For lower-level development, `nx2/lift.py --standalone --write` followed by `nx2/build.py --standalone --jobs 4`
writes the executable and libraries under `work/sa/build/`. Set `RX_BACKEND=metal` to make the renderer choice
explicit (Metal is the macOS default). As on other platforms, the local game dump is required to generate and build
the game. The workflow packages and validates builder dependencies without running a game build; it is not a
notarized app release.

## Layout

| Path | What |
Expand All @@ -53,7 +90,7 @@ the script's existing exclusive interval is active; it is not a focus override.
| `nx2/` | the lift pipeline and its tests (mkw-recomp's, made game-neutral; `kit.py` has the paths) |
| `host/` | the standalone Horizon runtime (TOTKPort's `hos/` as adapted by mkw-recomp), with the native NVN2 layer `nvn_native.c` |
| `gpu/vk/` | the Vulkan back end (`hookshot_vk.dll`), with Eden's shader recompiler and sirit |
| `gpu/render/`, `gpu/third_party/` | the WebGPU renderer the exe falls back to, its op stream (`rx_nvn.h`), suyu's decoders |
| `gpu/render/`, `gpu/third_party/` | the WebGPU renderer (Metal on macOS), its op stream (`rx_nvn.h`), suyu's decoders |
| `scripts/` | test runs and the profile reader |
| `third_party/` | a64-kit (unmodified), libopus, zbic (Atmosphere's zstd with ZBIC, for unpacking only) |

Expand Down
21 changes: 14 additions & 7 deletions gpu/render/rx_buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
// below it). A segment is created when a draw first needs it; its pages are uploaded when a draw needs them (one
// valid bit per 4 KB page; pages a dispatch wrote are copied from the neighbour that holds them).
//
// Changes: the guest arena is allocated with MEM_WRITE_WATCH (hos_main.c). At the start of each GPU submission the
// pages written since the last one are invalidated (GetWriteWatch), which catches every CPU write the submission's
// data needs. Writes the command processor makes while it runs a submission (inline uploads, copies: hos/gpu.c)
// are reported through rx_memory_written; if a page they change was read earlier in the open batch, the batch is
// submitted first, so earlier draws see the old contents.
// Changes: A64_WRITE_TRACK marks guest pages written by the CPU; at the start of each GPU submission those pages are
// invalidated. Windows also uses MEM_WRITE_WATCH for writes that bypass the translated stores. Writes the command
// processor makes while it runs a submission (inline uploads, copies: hos/gpu.c) are reported through
// rx_memory_written; if a page they change was read earlier in the open batch, the batch is submitted first, so
// earlier draws see the old contents.
#include <algorithm>
#include <array>
#include <cstring>
Expand Down Expand Up @@ -362,8 +362,8 @@ BufferRef guest_buffer(uint64_t gpu_va, uint64_t size) {
return {s->buffer, guest - s->base, size};
}

/// The guest pages the CPU wrote since the last call (GetWriteWatch, which also resets them): any thread. False when
/// write watching does not work (then nothing is tracked).
/// The guest pages the CPU wrote since the last call (the active write watch is reset): any thread. False when write
/// watching does not work (then nothing is tracked).
bool collect_written_pages(std::vector<uint32_t>& out) {
out.clear();
#if defined(A64_WRITE_TRACK) && !defined(__EMSCRIPTEN__)
Expand Down Expand Up @@ -392,6 +392,7 @@ bool collect_written_pages(std::vector<uint32_t>& out) {
return true;
}
#endif
#if !defined(__APPLE__) || !defined(A64_WRITE_TRACK) || defined(__EMSCRIPTEN__)
if (!watch_ok) return false;
// only the memory mapped into the GPU's address space (what the renderer can read); the cost of GetWriteWatch
// grows with the range, not with the pages written
Expand Down Expand Up @@ -448,6 +449,7 @@ bool collect_written_pages(std::vector<uint32_t>& out) {
hos_log("rx: change tracking watches %d ranges, %llu MB", nranges, (unsigned long long)(total >> 20));
}
return true;
#endif
}

/// Pages the CPU wrote (collect_written_pages) are invalidated (called with no batch open).
Expand Down Expand Up @@ -687,10 +689,13 @@ void buffer_cache_sync_to_guest(uint64_t gpu_va, uint64_t size) {
uint8_t* dst = hos_guest_base() + p0 * kPage;
// (pages the CPU wrote since the last scan keep their write-watch mark; the others are not reported as CPU
// writes: the segment already holds this)
// macOS keeps these marks for collect_written_pages; only the native write-watch path resets marks here.
#if !defined(__APPLE__) || !defined(A64_WRITE_TRACK) || defined(__EMSCRIPTEN__)
std::vector<void*> dirty(p1 - p0 + 1);
ULONG_PTR ndirty = dirty.size();
DWORD gran = 0;
const bool watched = !GetWriteWatch(0, dst, n, dirty.data(), &ndirty, &gran);
#endif
const auto* src = static_cast<const uint8_t*>(wgpuBufferGetConstMappedRange(staging, 0, n));
std::lock_guard lk(shadow_mu);
for (uint64_t p = p0; p <= p1; p++) {
Expand All @@ -708,7 +713,9 @@ void buffer_cache_sync_to_guest(uint64_t gpu_va, uint64_t size) {
for (uint32_t w = 0; w < kPage / 4; w++)
if (gw[w] != sw[w]) dw[w] = gw[w];
}
#if !defined(__APPLE__) || !defined(A64_WRITE_TRACK) || defined(__EMSCRIPTEN__)
if (watched && ndirty == 0) ResetWriteWatch(dst, n);
#endif
wgpuBufferUnmap(staging);
for (uint64_t p = p0; p <= p1; p++) {
gpu_pages[p] = 0;
Expand Down
30 changes: 30 additions & 0 deletions gpu/render/rx_core.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// The renderer's core: the WebGPU device and the window's surface, batches (one command encoder and its upload
// memory per GPU submission), render targets, clears, the 3D engine's draw methods (suyu's DrawManager), present.
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(__x86_64__)
#include <x86intrin.h>
#endif
#include <algorithm>
#include <cmath>
#include <atomic>
Expand Down Expand Up @@ -53,6 +57,9 @@ bool wgpuDevicePoll(WGPUDevice device, bool wait, const void* submission) {
#endif

namespace rx {
#ifdef __APPLE__
extern "C" void hos_posix_window_pixel_size(int* width, int* height);
#endif
bool draw_must_run = false;
bool rt_history_on() { static const bool on = getenv("RX_RT_HISTORY") != nullptr; return on; }

Expand Down Expand Up @@ -340,12 +347,17 @@ bool init_device(HWND hwnd) {
#else
WGPUInstanceExtras extras{};
extras.chain.sType = (WGPUSType)WGPUSType_InstanceExtras;
#ifdef __APPLE__
extras.backends = WGPUInstanceBackend_Metal;
#else
const char* be = getenv("RX_BACKEND");
extras.backends = !be ? WGPUInstanceBackend_Primary
: !_stricmp(be, "vulkan") ? WGPUInstanceBackend_Vulkan
: !_stricmp(be, "dx12") ? WGPUInstanceBackend_DX12
: !_stricmp(be, "gl") ? WGPUInstanceBackend_GL
: !_stricmp(be, "metal") ? WGPUInstanceBackend_Metal
: WGPUInstanceBackend_Primary;
#endif
if (getenv("RX_VALIDATION")) extras.flags = WGPUInstanceFlag_Validation | WGPUInstanceFlag_Debug;
// RX_SHADER_NAMES=1: the debug flag alone (no validation layers): naga names SPIR-V variables after the WGSL ones,
// so RenderDoc's shader debugger shows f_0, u_12, in_attr3... of the dumped WGSL (RX_DUMP_WGSL)
Expand Down Expand Up @@ -383,13 +395,22 @@ bool init_device(HWND hwnd) {
return false;
}
if (hwnd) {
#ifdef __APPLE__
WGPUSurfaceSourceMetalLayer src{};
src.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
src.layer = hwnd;
WGPUSurfaceDescriptor sd{};
sd.nextInChain = &src.chain;
surface = wgpuInstanceCreateSurface(dev.instance, &sd);
#else
WGPUSurfaceSourceWindowsHWND src{};
src.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
src.hinstance = GetModuleHandleA(nullptr);
src.hwnd = hwnd;
WGPUSurfaceDescriptor sd{};
sd.nextInChain = &src.chain;
surface = wgpuInstanceCreateSurface(dev.instance, &sd);
#endif
}
#endif

Expand Down Expand Up @@ -560,6 +581,11 @@ bool init_device(HWND hwnd) {
#ifdef __EMSCRIPTEN__
present_mode = WGPUPresentMode_Fifo;
configure_surface(1280, 720);
#elif defined(__APPLE__)
int width = 0, height = 0;
hos_posix_window_pixel_size(&width, &height);
configure_surface((uint32_t)std::max(width, 1), (uint32_t)std::max(height, 1));
present_window_size(width, height);
#else
RECT rc;
GetClientRect(hwnd, &rc);
Expand Down Expand Up @@ -1344,6 +1370,7 @@ bool stats_on = false;

int64_t clock_ns() {
if (!stats_on) return 0;
#if defined(__x86_64__) || defined(_M_X64)
static const double ns_per_tick = [] {
const int64_t t0 = hos_now_ns();
const uint64_t c0 = __rdtsc();
Expand All @@ -1353,6 +1380,9 @@ int64_t clock_ns() {
return c1 > c0 ? double(t1 - t0) / double(c1 - c0) : 0.0;
}();
return (int64_t)(double(__rdtsc()) * ns_per_tick);
#else
return hos_now_ns();
#endif
}

bool gpu_read(uint64_t va, void* dst, uint64_t n) {
Expand Down
2 changes: 1 addition & 1 deletion gpu/render/rx_draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3473,7 +3473,7 @@ void nvn_draw(NvnState& s, const NvnDraw& d) {
// the frame rate depends on the replay's time (what making it faster could give)
if (static const int64_t spin = getenv("RX_DEBUG_DRAW_SPIN_NS") ? atoll(getenv("RX_DEBUG_DRAW_SPIN_NS")) : 0; spin) {
const int64_t until = clock_ns() + spin;
while (clock_ns() < until) _mm_pause();
while (clock_ns() < until) cpu_relax();
}
struct DrawTimer {
int64_t t0 = clock_ns();
Expand Down
Loading
Loading