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.
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.
Average fps
Higher is better.
| Build | Measured | Median GPU time (ms) | 90th-percentile GPU time (ms) | Notes |
|---|---|---|---|---|
| VISR 0.6.3 | 2026-10-28 | 4.94 | 5.7 | |
| OpenCE-macOS v1.0.2 | 2026-10-28 | 5.17 | 7.57 | |
| CoreCE 0.3.0 | 2026-10-28 | 2.63 | 3.38 | |
| Chupathingy v0.8.0h | 2026-10-28 | – | – | OpenGL app, timed at its frame swap; GPU time per frame not available (OpenGL) |
| Halo OG v0.5.1 | 2026-10-28 | 4.1 | 5.9 | Limited by CPU command submission, not by a frame cap |
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.
Average fps 1% low fps
Higher is better.
| Build | Measured | On time | Notes |
|---|---|---|---|
| VISR 0.6.3 | 2026-10-28 | – | no on-time share reported; a10 not measured yet |
| 117-vision b25332ef9 | 2026-10-29 | – | no on-time share reported; a10 not measured yet |
| Beeman apple-v0.3.0 | 2026-10-27 | – | no on-time share reported; b30 not measured yet |
| ChupathingyCE iOS v0.2.0 (pre-release) | 2026-10-27 | – | no on-time share reported; b30 not measured 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.
Average fps 1% low fps
Higher is better.
| Build | Measured | On time | Notes |
|---|---|---|---|
| VISR 0.6.0 | 2026-10-27 | – | no on-time share reported |
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.
Average fps 1% low fps
Higher is better.
| Build | Measured | 95th-percentile frame time (ms) | On time | Notes |
|---|---|---|---|---|
| VISR 0.6.3 (Vision Pro) | 2026-10-28 | 11.1 | 99.8% | Replays a recorded route; a10 not measured yet |
| 117-vision b25332ef9 | 2026-10-29 | – | – | no frame times reported; a10 not measured yet |
| master-chef 1.0.3 | 2026-10-27 | – | – | Runs with no input, fixed at 30 fps as shipped; it has no setting to run faster and reports no frame times |
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.
Median GPU time (p50)
Lower is better.
| Build | Measured | Notes |
|---|---|---|
| VISR 0.6.3 (Vision Pro) | 2026-10-28 | Replays a recorded route |
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.
Median CPU time per frame
Lower is better.
| Build | Measured | Notes |
|---|---|---|
| VISR 0.6.3 (Vision Pro) | 2026-10-28 | Replays a recorded route |
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
- Mac (M4 Mac mini, M6 Mac mini; frame cap on and uncapped): Metal's per-frame timing:
metalperftraceon-glass drawable intervals for the app's process. Ports: VISR, OpenCE-macOS, CoreCE, Halo OG. - Mac (M4 Mac mini, M6 Mac mini; uncapped, vsync off): The OpenGL frame swap, timestamped by a shim in the app's process (
flushBuffer/CGLFlushDrawable), for a port thatmetalperftracedoes not see. GPU time per frame is not available for these ports (OpenGL). Ports: Chupathingy. - Mac (M4 Mac mini, M6 Mac mini; frame cap on): Safari
requestAnimationFramecallbacks, which measure browser scheduling. Ports: OpenCE web. - iPhone/iPad (A10X iPad Pro, A14 iPhone 12 Pro, A19 Pro iPhone 17 Pro, M1 iPad Pro; frame cap on): Instruments Metal System Trace recorded over the network; each frame's time is a row of the
displayed-surfaces-intervaltable. Against VISR's own frame log over the same window, average rate and p95/p99 agree within 1%. Ports: VISR, Beeman, ChupathingyCE iOS. - iPhone/iPad (A10X iPad Pro, A14 iPhone 12 Pro, A19 Pro iPhone 17 Pro, M1 iPad Pro; frame cap on): The app's own benchmark report: VISR's report, or the telemetry of a port that has no timing tool, counted over the same warm-up and sampling window. Ports: 117-vision.
- Apple TV (Apple TV HD; frame cap on): Instruments Metal System Trace recorded over the network; each frame's time is a row of the
displayed-surfaces-intervaltable. Against VISR's own frame log over the same window, average rate and p95/p99 agree within 1%. Ports: VISR. - Apple TV (Apple TV HD; frame cap on): Another method, named in the
methodcolumn. Ports: VISR. - Vision Pro (Apple Vision Pro, M2 Vision Pro; frame cap on): The app's own benchmark report: VISR's report, or the telemetry of a port that has no timing tool, counted over the same warm-up and sampling window. Ports: master-chef, VISR, 117-vision.
- Vision Pro (Apple Vision Pro, M2 Vision Pro; frame cap on): Another method, named in the
methodcolumn. Ports: VISR.
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
- Campaign: the openings of c10 (343 Guilty Spark) and b30 (The Silent Cartographer). One player, no input, default enemies. Each window warms up for 15 seconds after player readiness, takes a five-second background CPU baseline, then samples for 60 seconds. Three rounds per set. A set with fewer than three valid rounds is marked incomplete. Earlier sets used the a10 opening and a b30 window 90 seconds later (
b30 +90 s); those rows stay in the spreadsheet. Measured on 10 ports: VISR, OpenCE-macOS, OpenCE web, CoreCE, master-chef, Beeman, ChupathingyCE iOS, Chupathingy, Halo OG, 117-vision. - Combat: the player is made deathless and placed in a battle from the game console: the a50 hangar (
switch_bsp,object_teleport,ai_placefor the Covenant floors and the Marines,ai_magically_see_encounterandai_magically_see_playersso the sides engage at once) and Exterior A of b40 (the same commands), each sampled for 60 seconds after a 15-second warm-up, plus the b30 bot window: the scripted bot player for 1,800 game ticks (60 seconds of game time) from tick 900. Battles and the bot window are equivalent workloads, not replays: the AI's outcome differs by a few actors between runs, so each round's end counts (ai_living_count, player health, ticks) are kept in thefingerprintcolumn and a round outside the other rounds' range is flagged innotes. A round is rejected when an encounter was not placed, the game reloaded a checkpoint, or the bot did not cover its ticks or throw a grenade. A port with no unattended console shows the statusnot_measured. Measured on 5 ports: VISR, OpenCE-macOS, CoreCE, Chupathingy, Halo OG. - Cutscenes: two whole in-engine cutscenes called from the console,
x70_bridgeon d40 andcutscene_rescueon a50, sampled from the call to the script'sprint. Their length is set by the game's ticks, so it is the same on every port. A round is rejected when the print does not arrive within 1.5 times the expected length or the sample covers less than 95% of it. Measured on 5 ports: VISR, OpenCE-macOS, CoreCE, Chupathingy, Halo OG. - Multiplayer: a hosted Slayer match on Chill Out and one on Blood Gulch, each a sampling window; the suite value is the mean over both maps' windows. One local player, driven by the scripted bot input, hosts and starts the match with no other humans. Bot input is paced from the wall clock, so runs on different ports, and repeated runs on one port, play equivalent workloads, not identical replays. Measured on 4 ports: VISR, OpenCE-macOS, CoreCE, Chupathingy.
- Split-screen co-op: local split-screen campaign co-op on one device, two players. Enemy scaling is set explicitly to static 1x on every port that has the option: VISR sets
debug.enemy_multiplier = 1, and OpenCE-macOS and CoreCE set their enemies-per-player scaling to static 1x. A port without the option runs its own fixed behavior, and its notes column in the spreadsheet says so. A port without split screen shows "Does not support split screen" and the statusnot_supported; that differs from "not measured", which means the suite has not been run for the build. Measured on 1 port: VISR. - Overall: per build, the geometric mean of the suites' average rates and the geometric mean of their 1% low rates, each suite weighted equally, so every suite counts the same whatever its frame-rate scale. Each chart averages the suites that every build drawn in it has measured, so its bars stay comparable; the caption names them. A build that shares fewer than two suites with the others is left out and named in the caption. Ticking or unticking a build can change which suites a chart covers.
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.