Download and run
Self-contained: game, path tracer, patched MoltenVK, SDL, audio libraries. You supply Doom2.wad.
prboom-rt-macos-arm64.zip (28.9 MB, arm64). Release: macos-rt-1.0.0.
unzip prboom-rt-macos-arm64.zip && cd prboom-rt-macos-arm64
xattr -dr com.apple.quarantine ./prboom-plus ./run.sh *.dylib 2>/dev/null || true # unsigned
./run.sh -wad "/path/to/Doom2.wad"
-wad selects the IWAD (default Doom2.wad beside the script); the rest goes to the game (-fullscreen, -warp 1 -skill 3, -file mymap.wad). Command-F toggles fullscreen. Hangs on I_InitSound? DOOMRT_NOSOUND=1. Audio stutter (Bluetooth, virtual drivers)? DOOMRT_AUDIO_DEVICE="MacBook Pro Speakers".
Requirements: Apple Silicon; verified on
Mac16,5, M4 Max, macOS 26.6, Command Line Tools only.
The short version
There is no Vulkan ray tracing on macOS. Apple ships Metal, so a Vulkan RT app runs only through MoltenVK, whose ray tracing is an experimental, unmerged pull request — not an installable driver. On that missing layer the 1993 game’s path tracer runs, every pixel traced per frame: direct and bounced light, reflections, soft shadows — at ~44 FPS at 800p and 51.7 FPS at 450p internal on an M4 Max. It translates SPIR-V to Metal Shading Language and runs the same RTGL1 path tracer and Doom port as the Intel Arc article.
The Intel Arc write-up is its mirror image: device creation, shaders, memory and the swapchain all learned Metal. Patches: SPIRV-Cross MSL codegen (1 file, +21/−0), RTGL1 (7, +75/−18), PrBoom+/game (13, +141/−13), optional example smoke test (7, +117/−16), plus seven port-branch commits and four pinned revisions.
Why macOS is different
On Arc the bug class was assumptions. Arc does not re-enumerate extensions promoted into Vulkan 1.2 core, so VK_KHR_shader_float16_int8 by name failed. macOS has no Vulkan loader: the game links MoltenVK, Vulkan-on-Metal, which stops at Vulkan 1.2 graphics/compute. Ray tracing (VK_KHR_acceleration_structure, VK_KHR_ray_tracing_pipeline) is not in any MoltenVK release — only on dttdrv/MoltenVK, commit d5e5032, branch macgaming/ray-query-pr, behind the open PR #2771, “WIP: Add Metal ray query and ray tracing support”. It maps the Vulkan RT entry points onto Metal, exposed through the same vkCmdTraceRaysKHR the renderer calls, enabled only with MVK_CONFIG_ENABLE_EXPERIMENTAL_RAY_TRACING=1.
Stack: dttdrv/MoltenVK (d5e5032, PR #2771); dttdrv/SPIRV-Cross (6c153db + local patch), SPIR-V→MSL including RT built-ins; renderer lleqsnoom/RayTracedGL1, branch arc-a770-fixes (RTGL1, API 1.01 — GI, reflections, SVGF denoising, bloom); game lleqsnoom/prboom-plus-rt, branch doom2-rt-improvements. The same Arc A770 fork, one patch set per platform: a renderer assuming it controlled every feature it asked for.
What broke, layer by layer
Driver. MoltenVK fails vkCreateDevice if any requested feature is unsupported and RTGL1 asks for its full set up front, so the patch clears every feature bit the device does not report. Unified memory broke the memory-type search: the right-looking type carried flags the old search rejected; a fallback is added. Vulkan Memory Allocator’s first allocation copies a user-data string via aligned_alloc, which on macOS returns NULL unless the size is a multiple of the alignment (glibc differs — aligned_alloc); the patch uses posix_memalign. SPIRV-Cross’s MSL backend lacked Metal types for ray-tracing built-ins like gl_LaunchIDEXT, so passing one as a parameter emitted unknown type name 'unsupported-built-in-type' and every ray-generation and helper shader failed to compile; patches/spirv-cross-msl-raytracing.patch (+21 lines, one file) fixes it. Smaller: missing <algorithm>/<type_traits>, a std::min mixing uint64_t and size_t, an unused sampler2D helper newer glslang rejects.
Renderer output. RTGL1 prefers MoltenVK’s advertised sRGB formats, but its final image is already display-ready, so an sRGB swapchain encoded it twice; the patch prefers non-sRGB (*_UNORM). Blocks flickered over HUD, menus and FPS counter: the swapchain raster pass uses loadOp = LOAD on an image the effects and blits just wrote, but its dependency declared srcAccessMask = 0, so stale tiles loaded — 8×8 and 16×16 artifacts on a tile-based Apple GPU; fix: the missing BarrierOne before DrawToSwapchain.
Game. The swapchain surface comes from an SDL_Metal_CreateView layer via RgMetalSurfaceCreateInfo (destroyed on shutdown) instead of Linux Xlib; CMake selects the Metal path and .dylib on Apple. True fullscreen went into the SDL_WINDOW_FULLSCREEN branch (desktop-fullscreen undersized the window under the menu bar). RT windows set SDL_WINDOW_ALLOW_HIGHDPI, matching the panel backing store (3456×2234, not a 1×-point 1728×1117 window upscaled by the compositor); 3D detail remains rt_renderscale. Mouse look defaults on (movement_mouselook = 1, toggled with Mouse Look, default \). A green FPS: n HUD counter from SDL_GetTicks, since renderer_fps updates only via R_ShowStats, which reads zero in RT mode. Playback opens with SDL_OpenAudioDevice and honours DOOMRT_AUDIO_DEVICE. SDL never cleared the buffer handed to the audio callback while the mixer added into it, and the old code zeroed it only without a MIDI player, so every sound stacked into a continuous buzz; now cleared each callback. Uncapped rendering is capped at cap_fps; the fallback light scales 1.0 → 0.8. Build fixes: <OpenGL/glu.h>, a _GLUfuncptr shim, <stddef.h> for size_t, an NSInteger comparator in the Cocoa launcher.
Performance
Doom 2 demo1 on the M4 Max: rt_renderscale 5/rt_fsr 0 → 800p, 43.8 FPS; rt_renderscale 4/rt_fsr 0 → 640p, 50.0 FPS; rt_renderscale 6/rt_fsr 4 → 450p, 51.7 FPS. rt_renderscale sets internal render height and RT cost scales with it, not the window; rt_fsr is stretch mode, a fixed fraction of rt_renderscale (quality 0.77 / 0.67 / 0.59 / 0.50) upscaled nearest-neighbour, so windowed and fullscreen cost the same. rt_bounce_quality 1 is single-bounce GI, roughly halving indirect ray cost; render_vsync 0 uncaps, then the limiter holds cap_fps. Different resolutions, different architectures: the Arc A770 reaches 90 FPS at 450p internal → 1080p window on defaults rt_renderscale 6 + rt_fsr 4; the Mac defaults to rt_renderscale 5, rt_fsr 0 (800p). For a crisp image, run the window at 720p (screen_resolution "1280x720") with rt_renderscale 5: native 1:1.
Stills are the Arc A770 build; the Mac build is the video.

Arc A770 capture. Warm bounced light at 59 FPS; glow and doorway shadow traced per-pixel.

Arc A770 capture. Emissive wall grates at 60 FPS; the light between the slats is what a path tracer buys and a rasteriser cannot fake.
In-game notes
- No music — compiled out (
I_InitMusic: Was compiled without SDL_Mixer support); effects use the default output device. - Doom 2 sector lights are approximate without the community addon (
ovrd/map_metainfo_doom2.txt, thedoom2rt-0.9addon via ModDB, a browser download); without it the game warnsMaps won't have additional light sourcesand uses the fork’s slow, dimmed fallback white light. - The MoltenVK ray-tracing path is experimental and unmerged upstream: pinned commits are part of the build.
- Fullscreen is true fullscreen in RT mode (Command-F);
Invert Mouseflips the vertical axis,Max View Pitchclamps the angle.
Should you bother?
Only if the Mac is the point. It works, but if you do not already want the Mac, Linux is easier. It runs 44–52 FPS at low internal resolutions, has no music, stands on an open, unmerged pull request, and needs a commercial Doom 2 IWAD plus the ModDB lighting addon. If nothing ties you to macOS, Arc/Linux or an RTX machine is smoother; same renderer either way.
Building it yourself
The zip is complete; to build from source, everything is scripted in macos/:
git clone https://github.com/lleqsnoom/doom-rt.git
cd doom-rt/macos
./build-all.sh
cp /path/to/Doom2.wad "$HOME/Documents/GitHub/lleqsnoom/macos-spike/doom-rt-run/"
./run.sh -wad "$HOME/Documents/GitHub/lleqsnoom/macos-spike/doom-rt-run/Doom2.wad"
Requirements: Apple Silicon, Python 3, cmake, ninja, glslangValidator, SDL2 (Homebrew sdl2 / sdl2-compat), libvorbis, libogg, libmad. build-all.sh runs four re-runnable steps caching sources under WORK (default ~/Documents/GitHub/lleqsnoom/macos-spike): build-moltenvk.sh (MoltenVK + SPIRV-Cross patch), build-rtgl1.sh (RTGL1 renderer, 46 SPIR-V shaders), build-game.sh (prboom-plus + run directory), fetch-assets.sh (RT pack ovrd/, prboom-plus.wad, shaders). Smoke test, no game data: ./build-rtgl1-example.sh then ./run-rtgl1-test.sh.
What this port says
All three platforms this renderer has run on — Nvidia, Intel Arc, Apple Silicon — demanded the same work: don’t assume the device is the one you tested on; query it, adapt. Arc stripped an Nvidia-shaped approximation from a Vulkan renderer; the Mac had to build the Vulkan underneath it first. One fork with a patch set per platform is the payoff: RTGL1 was close enough to vendor-neutral that this was adaptation, not rewriting.
Built on a MacBook Pro (Mac16,5), Apple M4 Max, macOS 26.6, Apple Command Line Tools (no Xcode), on 2026-09-12–13. Benchmarks are Doom 2 timedemo demo1, single run — ballpark, not gospel.
References
- lleqsnoom/doom-rt — build scripts, patches
- MoltenVK — Vulkan-on-Metal layer
- MoltenVK PR #2771 — experimental ray tracing
- SPIRV-Cross — SPIR-V to MSL
- MoltenVK releases — shipped builds
aligned_alloc— alignment semantics- RayTracedGL1 — the Vulkan path tracer
- prboom-plus-rt — the Doom source port
- Apple: MacBook Pro specifications — M4 Max platform
- doom2rt-0.9 by rellik66 — Doom 2 lighting addon
- Path-Tracing Doom on Intel Arc — companion write-up