Performance benchmarks

We try to make these comparisons fair, but numbers or projects may be wrong or out of date. Corrections? Updates? A new port or build we should add? Open an issue

Last measured: 2026-10-29.

The results spreadsheet holds every number. The methodology is at the bottom of this page.

Suite

The geometric mean of the suites that every drawn build has measured, each counting equally; a build that shares fewer than two with the others is left out.

The opening of two single-player levels, sampled for 60 seconds after a 15-second warm-up, with no input.

  • c10
  • b30

Scripted battles in a50 and b40, and a scripted bot player in b30.

  • a50 hangar
  • b40 exterior
  • b30 bot

Two whole in-engine cutscenes, sampled from start to finish.

  • x70 bridge
  • a50 rescue

A hosted Slayer match on each of two maps, played by a scripted bot.

  • Chill Out
  • Blood Gulch

Two-player split-screen campaign co-op on one device, with enemies at 1x. Online co-op is not measured.

  • c10
  • b30

Mac

Measured on an M6 Mac mini with the frame cap and vsync off, the matched-stock settings and a 2560×1920 drawable. Bars show average fps; the table lists median and 90th-percentile GPU time per rendered frame.

Mac: average fps per build, Campaign suite

Average fps

Higher is better.

M6 Mac mini.
BuildMeasuredMedian GPU time (ms)90th-percentile GPU time (ms)Notes
VISR 0.6.3 build 20261028.911502026-10-284.945.7
OpenCE-macOS v1.0.2 build 22026-10-285.177.57
CoreCE 0.3.0 build 442026-10-282.633.38
Chupathingy v0.8.0h build 0.8.0h2026-10-28––OpenGL app, timed at its frame swap; GPU time per frame not available (OpenGL)
Halo OG v0.5.1 build 97.12026-10-284.15.9Limited by CPU command submission, not by a frame cap
Builds
3 other versions
not measured here

Only measured with the frame cap on, at another size or with other settings.

not measured here

Only measured with the frame cap on, at another size or with other settings.

not measured here

Only measured with the frame cap on, at another size or with other settings.

1 other version
not measured here

Only measured with the frame cap on, at another size or with other settings.

iPhone and iPad

Measured on an M1 iPad Pro from runs presented at the display rate. Bars show average fps and 1% low fps (the slowest 1% of frames); the table lists the share of frames that arrive on time, within 5% of the display interval. GPU and CPU time per frame are not reported for iPhone and iPad builds yet.

iPhone and iPad: average fps and 1% low fps per build, Campaign suite

Average fps 1% low fps

Higher is better.

M1 iPad Pro.
BuildMeasuredOn timeNotes
VISR 0.6.3 build 20261028.0849492026-10-28–no on-time share reported; a10 not measured yet
117-vision b25332ef9 build 1302026-10-29–no on-time share reported; a10 not measured yet
Beeman apple-v0.3.0 build unknown2026-10-27–no on-time share reported; b30 not measured yet
ChupathingyCE iOS v0.2.0 (pre-release) build 0.8.0h-dev d7fafc82026-10-27–no on-time share reported; b30 not measured yet
Builds
1 other version
not measured here

Not run on this device yet.

not measured here

Not run on this device yet.

Apple TV

Measured on an Apple TV HD from runs presented at the frame cap of its profile, 30 fps. Bars show average fps and 1% low fps (the slowest 1% of frames); the table lists the share of frames that arrive on time, within 5% of the display interval. GPU and CPU time per frame are not reported for Apple TV builds yet.

Apple TV: average fps and 1% low fps per build, Campaign suite

Average fps 1% low fps

Higher is better.

Apple TV HD.
BuildMeasuredOn timeNotes
VISR 0.6.0 build 20261027.1119522026-10-27–no on-time share reported
Builds
2 other versions
not measured here

Not run on this platform's reference device.

not measured here

Not run on this platform's reference device.

Vision Pro

Measured on an M2 Vision Pro from runs presented at the display rate. Bars show average fps, with the 1% low fps directly below it where the app reports one; the table lists 95th-percentile frame time and the share of frames on time, within 5% of the display interval, where reported.

Vision Pro: average fps and 1% low fps per build, Campaign suite

Average fps 1% low fps

Higher is better.

M2 Vision Pro.
BuildMeasured95th-percentile frame time (ms)On timeNotes
VISR 0.6.3 (Vision Pro) build 20261028.911502026-10-2811.199.8%Replays a recorded route; a10 not measured yet
117-vision b25332ef9 build 1302026-10-29––no frame times reported; a10 not measured yet
master-chef 1.0.3 build 1032026-10-27––Runs with no input, fixed at 30 fps as shipped; it has no setting to run faster and reports no frame times
Builds
2 other versions

Vision Pro: GPU time

Measured on an M2 Vision Pro from runs presented at the display rate. Bars show median and 90th-percentile GPU time per rendered frame in milliseconds, on an axis that ends at the frame budget at the display rate. Lower is better. Builds that do not report GPU time are listed as not reported.

Vision Pro: GPU time: median gpu time (p50) and 90th-percentile gpu time (p90) per build, Campaign suite

Median GPU time (p50)

Lower is better.

M2 Vision Pro.
BuildMeasuredNotes
VISR 0.6.3 (Vision Pro) build 20261028.911502026-10-28Replays a recorded route
Builds
2 other versions
not measured here

Not reported: this build does not report GPU time per frame.

not measured here

Not reported: this build does not report GPU time per frame.

not measured here

Not reported: this build does not report GPU time per frame.

Vision Pro: CPU time

Measured on an M2 Vision Pro from runs presented at the display rate. Bars show median main-thread CPU time per frame in milliseconds, on an axis that ends at the frame budget at the display rate. Lower is better. Builds that do not report CPU time are listed as not reported.

Vision Pro: CPU time: median cpu time per frame per build, Campaign suite

Median CPU time per frame

Lower is better.

M2 Vision Pro.
BuildMeasuredNotes
VISR 0.6.3 (Vision Pro) build 20261028.911502026-10-28Replays a recorded route
Builds
2 other versions
not measured here

Not reported: this build does not report CPU time per frame.

not measured here

Not reported: this build does not report CPU time per frame.

not measured here

Not reported: this build does not report CPU time per frame.

Methodology

The snapshot holds 210 measured windows for 10 ports on 4 platforms (Mac, iPhone/iPad, Apple TV, Vision Pro). Each window's value is the mean over its valid rounds. A bar is the geometric mean of the windows' average presentation rates, drawn beside the geometric mean of their 1% low rates (1000 divided by the 99th percentile interval). When a build was measured on several dates, the newest set is charted. Sets with fewer than three rounds, and targets that could not run, stay in the spreadsheet with their status. On-time share uses the recorded display rate with a 5% interval tolerance, is omitted for uncapped rows and does not establish a display tier. The methods measure different events and are not ranked against one another. Only released builds are shown. Web builds run in a browser that always syncs to the display, so they have capped numbers only and do not appear in the uncapped charts. The spreadsheet's README tab describes each column.

Timing by platform

master-chef runs at a fixed 30 fps as shipped, below the 90 Hz display, so it has no uncapped rows.

iPhone/iPad, Apple TV, and Vision Pro charts use runs presented at the device's display rate or, where its profile sets a lower frame cap, at that cap, not an uncapped mode. Their on-time share (frames within 5% of the display interval) is in the spreadsheet, and a device that holds its display rate shows an average near that rate. The Mac chart uses the uncapped, matched-stock runs described below.

Where at least two ports report GPU time, a device section leads with the cost of a frame: median and 90th-percentile GPU time per rendered frame and median main-thread CPU time per frame, in milliseconds, on an axis that ends at the frame budget (1000 divided by the display rate or frame cap). Lower is better: less time per frame leaves more headroom before frames are missed and draws less power, which lengthens battery life and delays thermal throttling. GPU time on pipelined Metal can include time spent waiting for a drawable, so read it as a cost indicator. Frame rate and 1% low follow, since a missed frame still counts. A build that reports no GPU or CPU time is listed as not reported; otherwise the section leads with frame rate and 1% low, and a frame-cost chart that has at least two builds follows it.

Halo OG's frame rate is limited by its CPU command submission, not by a frame cap: its uncapped settings are in force (the frame limit is 0 and vsync is off), its GPU is about half idle, and its rate does not change with the drawable size. It lacks several settings the other ports expose, among them shadow size, model LOD and field of view; its rows use its defaults.

Uncapped charts

Uncapped charts show average fps and GPU time per rendered frame, not 1% low rates. When a game renders faster than the display refreshes, macOS hands drawables to the window server in bursts, so the on-glass intervals record when the window server latches drawables rather than the frames a player sees; VISR's intervals alternate between about 0 ms and about 5 ms. Percentiles of those intervals, the 1% low included, describe that latching. The average counts frames shown per second and is not affected. GPU time per rendered frame comes from the same Metal trace. A game that renders several frames for each frame it shows has all of their GPU time added to the next shown frame, so each shown interval's GPU time is split by its command buffers, one per rendered frame, and reported as the median and the 90th percentile over rendered frames. Capped and device charts keep the 1% low and add the share of frames that arrive on time. The spreadsheet keeps every column.

Suites

Mac charts use a 2560×1920 drawable (a 1280×960-point window at 2×), where the ports' frame times separate; 1280×960 rows stay in the spreadsheet. Device sets run the same windows through a hosted one-player game, with the console commands held until the level runs. Their cpu_p50_ms column is VISR's own median frame CPU time, which shows the work a window adds on a device where the CPU matters.

A round is rejected when the outside-process CPU average rises more than half a core above the baseline taken before the window. Each round allows three attempts. Rejected attempts appear in the spreadsheet with the status rejected and their reason. This snapshot has 0 rejected attempts.

Suite definitions and the harness are in tools/perf_compare (adapters.py, refresh.py).

Background CPU is sampled for five seconds immediately before each native sampling window. Runs are rejected when average outside-process CPU rises more than half a core above that baseline. Steady background work remains active and may affect these estimates. Each round allows up to three attempts; rejected attempts do not enter the ranges.

Opening runs use a10 and b30 with no input: 15 seconds of warm-up after player readiness, a five-second background CPU baseline, then 60 seconds of sampling. Opening cinematics are included. Native apps use 1280×960 drawables on a 60 Hz display with interpolation. Capped rows request a 60 fps cap and keep each app's other graphics defaults. These runs do not establish combat performance or identical image quality. Player readiness does not align cinematic clocks, so opening sequences can have phase offsets between apps.

Uncapped runs request display.frame_rate = "unlimited" in VISR, display.max_fps = -1 in OpenCE-macOS, and display.frame_limit = 0 in CoreCE, with display.vsync = false and interpolation enabled in each. The frame_mode and delay_s columns of the spreadsheet identify each mode and extra delay after warm-up (0 s is the opening window). The 60 Hz display still limits visible refreshes; presentation intervals do not establish simulation speed or unique displayed frames. Above-refresh trace rates have unverified physical meaning and are not verified submission throughput. The same elapsed window does not prove identical gameplay. Each launch uses fresh isolated data/save folders; OS and driver caches are not reset or equalized.

The matched-stock preset disables antialiasing, replacement HUD/text, and first-person legs in each native app, and each uses direct camera and 128-pixel shadow maps. VISR also uses automatic model LOD, the Xbox-size effect buffer, 1× anisotropic filtering, no replacement textures, and no first-person body. OpenCE-macOS uses native resolution scaling, stock world/weapon FOV, and disables optional per-pixel lighting, weapon shields, and shield glow. CoreCE uses the same options as OpenCE-macOS and model_detail = "stock". Other defaults remain unchanged. VISR uses native Metal and compressed GPU textures; OpenCE-macOS uses ANGLE over Metal. These runs could not verify the other ports' internal filtering, LOD, effect-buffer, or texture-storage policy, so identical image quality remains unverified. Weapon framing and texture filtering still differ visibly. Capped rows keep each app's graphics defaults and are not quality-matched to the matched-stock rows.

Configuration references: VISR settings, OpenCE-macOS v1.0.2 settings, CoreCE graphics settings.

The later b30 window adds 90 seconds after warm-up, reaching the beach encounter at about 110 seconds after player readiness once the CPU baseline is included. It samples 60 seconds with a stationary player and no input. Start/end screenshots qualify the camera location, but AI and combat state can vary between apps and rounds; this is not a deterministic combat replay.

OpenCE web runs import the disc image into each fresh Safari automation session, start the level with the documented ?map=<level>&play, wait 6 seconds for loading, then warm up for 15 seconds and sample requestAnimationFrame callbacks for 60 seconds with no input. The page viewport is 640×480 points, so the game canvas is 1280×960 pixels on the same 60 Hz display as the native rows. The page does not report player readiness, so its sampling window can be offset from the native windows within the opening sequences. Browser callbacks are not the same event as native on-glass intervals.

CoreCE rows come from separate matrices run after the VISR and OpenCE-macOS matrices on the same hosts and dates, so they do not share round rotation with them. CoreCE reads the same maps from a copied folder, because its Mac host does not accept a linked maps folder.

Apple's metalperftrace records per-frame on-glass drawable intervals. CPU/GPU durations and aggregate FPS are excluded. Repeated image content is not detected.

A shim in the app's process timestamps each OpenGL frame swap (flushBuffer / CGLFlushDrawable) for ports that metalperftrace does not see. On two Metal ports its average rate matched metalperftrace to the digit. It reports no GPU time per frame.

Instruments Metal System Trace records the displayed-surfaces-interval table over the network: the time each frame is swapped onto the display. Repeated image content is not detected.

The app's own benchmark report counts the frames it presents; its intervals are the app's, not the display's.

Safari callbacks measure browser scheduling and may include repeated game images; they do not prove unique game frames.

These records use another method; their relationship to displayed pacing is unverified.