Experimental macOS graphics · reproducible field note

A path tracer through the translation stack

How the Windows Steam build of Quake II RTX reached Apple M4 hardware ray tracing through CrossOver, a custom x86_64 MoltenVK sidecar, and a shader-level replacement for one unsupported sampler operation.

The short version

It works, but not by persuading stock CrossOver that the M4 is an NVIDIA GPU. The working route gives the unmodified Steam executable an experimental Vulkan implementation that exposes acceleration structures and ray queries, then lets MoltenVK translate those operations to Metal’s native ray-tracing facilities.

Verified outcome

The stock q2rtx.exe selected “Apple M4,” enabled VK_KHR_acceleration_structure and VK_KHR_ray_query, chose the ray-query API, initialized renderer, audio, and input, and produced real gameplay frames with visible god rays.

Quake II RTX rendering an outdoor industrial scene beneath a low sun with the patched god-ray pass active on Apple M4.
God-rays result. The patched pass remained active in this outdoor scene at a 70% render scale; the overlay reads 57 FPS. The observed test session remained stable and did not crash. This frame records the result rather than serving as a benchmark or long-duration soak test.

This is a sidecar, not an in-place CrossOver modification. CrossOver’s signed app bundle, q2rtx.exe, and the packaged Steam shaders remain untouched. A launcher selects one custom libMoltenVK.dylib and a loose shader file overrides one packaged Q2RTX shader. Removing the launcher, sidecar directory, and loose override reverses the experiment.

The M4 had the ray-tracing hardware all along. The missing piece was the Vulkan-to-Metal path that could expose it to this Windows game.

Read the boundary before copying commands. The MoltenVK branch is an unmerged experimental pull request, and this build advertises two Vulkan features more generously than the M4 implementation supports. Q2RTX’s only sampler-minimum use is replaced with explicit shader reads; the pipeline-statistics request appears unused. Those exceptions are bounded to this game and these pinned artifacts, not a generic recipe for other ray-traced titles.

M4 Metal ray tracing: Verified
Native arm64 and Rosetta x86_64 probes both reported ray tracing and function-pointer support.
Stock CrossOver path: Fails
Its bundled MoltenVK does not expose the Vulkan ray-tracing extensions Q2RTX requires.
Custom ray-query path: Verified
Q2RTX 1.8 selected Apple M4 and initialized with VK_KHR_ray_query.
God rays on M4: Visually verified
A loose SPIR-V override manually reduces the four contributing shadow-map texels. The patched pass rendered coherent volumetric shafts in demo1.
Observed test-session stability: Verified
The game remained stable and did not crash during the observed session. Production-grade performance and longer soak stability remain untested.

Why the normal Steam button stops before the first frame

Quake II RTX is unusual in a useful way: although the “RTX” name is associated with NVIDIA, its renderer speaks Vulkan. It does not need DirectX Raytracing, and it does not need the machine to masquerade as an RTX card. It needs a Vulkan device that exposes a compatible acceleration-structure and ray-tracing API.

CrossOver 26.3 ships an x86_64 MoltenVK 1.2.10 library for Vulkan-on-Metal, but that stock library does not advertise the Vulkan ray-tracing extensions. The ordinary Steam launch therefore reaches Q2RTX’s device scan and ends at:

Stock launch result

No ray tracing capable GPU found.

That message is easy to misread. It does not mean the Apple GPU lacks ray tracing. On this machine, a tiny Metal probe compiled for arm64 and again for x86_64 under Rosetta returned supportsRaytracing=1, supportsFunctionPointers=1, and supportsRaytracingFromRender=1 in both cases. CPU translation was not the blocker either.

The failure was an API-exposure gap: Metal could do the work, but the bundled Vulkan bridge did not offer the contract the game asks for. CodeWeavers’ own compatibility entry correspondingly rates the title “Installs, Will Not Run.”

There are five layers between the game and the M4

The useful mental model is a translation stack, not a GPU spoof. The Windows executable remains x86_64; Wine provides Win32 services; MoltenVK maps Vulkan resources and commands to Metal; Metal targets the M4.

Game
Steam q2rtx.exe · x86_64 Windows · path-traced renderer
Graphics API
Vulkan acceleration structures + VK_KHR_ray_query
Compatibility
CrossOver Wine · Win32/SDL surface and process environment
Translation
Experimental x86_64 MoltenVK sidecar · Vulkan → Metal
Hardware
Metal ray tracing · Apple M4 GPU

The launcher changes the fourth layer for this process. It does not patch the first layer or replace CrossOver’s bundled library.

Rosetta matters because CrossOver’s Wine loader and the sidecar are x86_64 Mach-O binaries. It is not emulating the GPU. MoltenVK still submits Metal work to the native Apple GPU driver.

The route exists in an experimental MoltenVK pull request

The enabling work is MoltenVK pull request #2771, carried in the macgaming/ray-query-pr branch of dttdrv/MoltenVK. The test documented here pins commit 25440e4e1d54419a27a72bab3663068c3d797bfe; do not silently build whatever the branch happens to contain later.

The branch exposes experimental Vulkan ray-tracing functionality backed by Metal. Q2RTX can choose between a ray-tracing-pipeline route and a ray-query route. The launcher deliberately forces:

Intentional Q2RTX mode

+set ray_tracing_api query

That selects VK_KHR_ray_query, the path proven by the run log. This article does not claim that the pipeline path is equally viable.

Why a simple CX_LIBVULKAN override was not enough

CrossOver’s active graphics backend still selected its bundled MoltenVK when only CX_LIBVULKAN was supplied. The successful launch sets all three host variables:

Host-side library selection

CX_ACTIVE_GRAPHICS_BACKEND=wined3d
CX_GRAPHICS_BACKEND=wined3d
CX_LIBVULKAN=$HOME/Library/Application Support/CrossOver/Q2RTX-RT/libMoltenVK.dylib

The wined3d name is misleading in this context: it does not turn Q2RTX into a Direct3D game. It makes CrossOver honor the explicit host Vulkan library instead of taking its normal backend path. The game itself continues to issue Vulkan commands.

Three sidecar edits open the door; one shader patch restores god rays

The experimental branch still refuses the exact device feature set Q2RTX requests. The sidecar changes three defaults: one enables the branch’s intended experiment, while two advertise features specifically to satisfy Q2RTX’s device creation. A separate Q2RTX shader patch replaces the only operation that would otherwise consume unsupported sampler behavior.

Experimental RT default
Compile MVK_CONFIG_ENABLE_EXPERIMENTAL_RAY_TRACING as 1. A runtime environment setting did not make the tested library advertise the extensions, so the sidecar bakes the opt-in into this build.
Pipeline statistics
Q2RTX requests pipelineStatisticsQuery = VK_TRUE, but a source search found no pipeline-statistics query pool. Its profiler creates timestamp queries. Advertising this feature gets past device creation without redirecting an observed runtime operation.
Sampler feature advertisement
Q2RTX requests samplerFilterMinmax and creates one minimum-reduction sampler in the god-rays subsystem. M4 is an Apple9 GPU, while Metal sampler minimum and maximum reduction starts with Apple10. The sidecar advertises the feature so the unchanged executable can create its device, then ignores the unsupported reduction descriptor.
Manual minimum reduction
Q2RTX’s god-ray shader samples a fixed 4096×4096, two-layer, one-mip shadow map. The loose override replaces each filtered lookup with the component-wise minimum of the four contributing LOD-0 texels, including opaque-white border behavior. The shader therefore no longer executes the unsupported sampler operation.

This remains the riskiest boundary in the guide. Feature advertisement normally promises correct behavior. Here, the pipeline-statistics operation appears unused, while Q2RTX’s only minimum-reduction lookup is replaced at the shader level. The replacement is specific to this shadow-map shape and Q2RTX commit. Reusing the library globally or applying the patch to another title escapes the inspected boundary.

Reproduce the tested shape before changing it

The successful machine was a 10-core Apple M4 running macOS 26.6.1, with CrossOver 26.3 installed at /Applications/CrossOver.app. Steam lived in a CrossOver bottle named Steam, and the game used Steam’s default library inside that bottle.

  1. Install CrossOver, Steam, and Quake II RTX

    Create or use a CrossOver bottle named Steam, install Steam in it, then install Quake II RTX from the Steam library. Run Steam only long enough to finish installation and updates.

  2. Quit the entire Steam bottle

    Close Steam and every application in that bottle. CrossOver’s Quit All Applications action is appropriate. Long-lived Wine processes retain the host Vulkan library they loaded. Closing only the game can leave the wrong MoltenVK resident.

  3. Confirm Rosetta and command-line tools

    CrossOver normally arranges Rosetta itself. The sidecar must be x86_64, matching the Wine loader. Apple Command Line Tools, Git, CMake, Python, and internet access are needed for the build. Full Xcode was not installed on the tested machine.

  4. Confirm the expected paths

    If either test below fails, adapt the later launcher variables to the real CrossOver location, bottle name, or Steam library path rather than copying blindly.

Prerequisite checks

arch -x86_64 /usr/bin/true && echo "Rosetta x86_64 execution: OK"
xcode-select -p
command -v git cmake python3 clang

test -x "/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wine" \
  && echo "CrossOver: OK"

game="$HOME/Library/Application Support/CrossOver/Bottles/Steam/drive_c/Program Files (x86)/Steam/steamapps/common/Quake II RTX/q2rtx.exe"
test -f "$game" && echo "Quake II RTX: OK"

If xcode-select -p fails, run xcode-select --install, complete Apple’s installer, and repeat the checks. If the Rosetta test fails, let CrossOver install Rosetta or use softwareupdate --install-rosetta --agree-to-license after reviewing Apple’s license prompt and terms.

If CMake, Git, or Python is missing and Homebrew is already installed, brew install cmake git python supplies them. CMake’s CPM dependency step downloads build inputs, so an offline build will not work from a fresh clone.

Build a pinned MoltenVK sidecar and one Q2RTX shader

The sequence below is intentionally explicit: clone the fork, fetch the exact tested commit, apply three checked replacements, build only x86_64, and copy the real dylib rather than its build-tree symlink.

Clone and detach at the tested commit

mkdir -p "$HOME/src"

git clone --filter=blob:none --no-checkout \
  https://github.com/dttdrv/MoltenVK.git \
  "$HOME/src/MoltenVK-q2rtx"

cd "$HOME/src/MoltenVK-q2rtx"
git fetch --depth 1 origin 25440e4e1d54419a27a72bab3663068c3d797bfe
git checkout --detach FETCH_HEAD

test "$(git rev-parse HEAD)" = \
  "25440e4e1d54419a27a72bab3663068c3d797bfe" \
  && echo "Pinned MoltenVK commit: OK"

If $HOME/src/MoltenVK-q2rtx already exists, do not delete it without inspecting it. Choose a different empty directory or verify what is already there.

Apply the Q2RTX-specific compatibility edits

This script refuses to continue unless each original fragment occurs exactly once. That makes a changed source tree fail visibly instead of applying a fuzzy edit to the wrong version.

cd "$HOME/src/MoltenVK-q2rtx"

python3 - <<'PY'
from pathlib import Path

device = Path("MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm")
environment = Path("MoltenVK/MoltenVK/Utility/MVKEnvironment.h")

edits = {
    device: [
        (
            "\t_features.vertexPipelineStoresAndAtomics = true;\n"
            "\t_features.fragmentStoresAndAtomics = true;\n",
            "\t_features.vertexPipelineStoresAndAtomics = true;\n"
            "\t_features.fragmentStoresAndAtomics = true;\n"
            "\t// Q2RTX requests this but does not create a pipeline-statistics query.\n"
            "\t_features.pipelineStatisticsQuery = true;\n",
        ),
        (
            "\t_vulkan12NoExtFeatures.samplerFilterMinmax = "
            "_gpuCapabilities.supportsSamplerReduction;\n",
            "\t// Q2RTX creates one min-reduction sampler for god rays. On M4 the\n"
            "\t// descriptor is ignored; a loose shader emulates the operation.\n"
            "\t_vulkan12NoExtFeatures.samplerFilterMinmax = true;\n",
        ),
    ],
    environment: [
        (
            "/** Expose the experimental ray tracing extensions. Disabled by default. */\n"
            "#ifndef MVK_CONFIG_ENABLE_EXPERIMENTAL_RAY_TRACING\n"
            "#   define MVK_CONFIG_ENABLE_EXPERIMENTAL_RAY_TRACING 0\n"
            "#endif\n",
            "/** Expose experimental ray tracing for the Q2RTX sidecar build. */\n"
            "#ifndef MVK_CONFIG_ENABLE_EXPERIMENTAL_RAY_TRACING\n"
            "#   define MVK_CONFIG_ENABLE_EXPERIMENTAL_RAY_TRACING 1\n"
            "#endif\n",
        ),
    ],
}

for path, replacements in edits.items():
    text = path.read_text()
    for old, new in replacements:
        count = text.count(old)
        if count != 1:
            raise SystemExit(f"Expected one match in {path}, found {count}")
        text = text.replace(old, new)
    path.write_text(text)

print("Applied three Q2RTX sidecar edits.")
PY

git diff --check
git diff -- MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm \
  MoltenVK/MoltenVK/Utility/MVKEnvironment.h

Configure and compile

The build used AppleClang 21 and CMake 4.4.1 from Command Line Tools only, with no xcodebuild, metal, metallib, or Ninja. MVK_EXCLUDE_SPIRV_TOOLS keeps the build lean enough for this sidecar.

cd "$HOME/src/MoltenVK-q2rtx"

cmake -S . -B build-q2rtx \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_OSX_ARCHITECTURES=x86_64 \
  -DMVK_EXCLUDE_SPIRV_TOOLS=ON \
  -DMOLTEN_VK_WITH_CCACHE=OFF

cmake --build build-q2rtx --parallel 8

file build-q2rtx/MoltenVK/libMoltenVK.dylib

The final command must report an x86_64 Mach-O dylib. An arm64-only build will not load into CrossOver’s x86_64 Wine process.

Install it outside the CrossOver application

cd "$HOME/src/MoltenVK-q2rtx"

sidecar="$HOME/Library/Application Support/CrossOver/Q2RTX-RT"
mkdir -p "$sidecar"

cp -fL \
  build-q2rtx/MoltenVK/libMoltenVK.dylib \
  "$sidecar/libMoltenVK.dylib"

chmod 755 "$sidecar/libMoltenVK.dylib"

file "$sidecar/libMoltenVK.dylib"
shasum -a 256 "$sidecar/libMoltenVK.dylib"

cp -L dereferences the CMake output symlink so the installed file remains valid if the source tree is moved or removed. The tested sidecar checksum is fa87ce46c60880202d36b1d64e8239e7fb774b7eec318ad77700544e636ccaf1.

The custom dylib is not code-signed. CrossOver’s signed wineloader carries com.apple.security.cs.disable-library-validation, which allowed the sidecar to load in this test. Do not overwrite /Applications/CrossOver.app/…/libMoltenVK.dylib; doing so is unnecessary, less reversible, and interferes with the signed app bundle.

Build the manual minimum-reduction shader

The Steam executable loads SPIR-V from Q2RTX’s virtual filesystem. A loose file under baseq2/shader_vkpt takes priority over the copy inside shaders.pkz, so one shader can be replaced without rebuilding or modifying q2rtx.exe. The downloadable patch applies only to the exact engine commit printed by the tested game.

q2src="$HOME/src/Q2RTX-m4-god-rays"

git clone --filter=blob:none --no-checkout \
  https://github.com/NVIDIA/Q2RTX.git \
  "$q2src"

cd "$q2src"
git fetch --depth 1 origin d65761f27be5cece1d36b21d03dd1437ef383399
git checkout --detach FETCH_HEAD

test "$(git rev-parse HEAD)" = \
  "d65761f27be5cece1d36b21d03dd1437ef383399" \
  && echo "Pinned Q2RTX commit: OK"

git submodule update --init --depth 1 extern/glslang

curl -fL \
  https://ark.marianposaceanu.com/assets/articles/quake-ii-rtx-m4-crossover/q2rtx-1.8.0-m4-god-rays.patch \
  -o q2rtx-1.8.0-m4-god-rays.patch

git apply --check q2rtx-1.8.0-m4-god-rays.patch
git apply q2rtx-1.8.0-m4-god-rays.patch
git diff --check

The patch maps each normalized shadow coordinate to its four linear-filter texels, excludes neighbors whose interpolation weight is zero, takes their minimum, and returns 1.0 outside the image to reproduce Q2RTX’s opaque-white border. Both shadow-map layers use explicit LOD-0 texelFetch operations, so the ignored Metal reduction descriptor no longer affects the pass.

Build Q2RTX’s pinned glslang revision in isolation, then compile only god_rays.comp:

cd "$q2src"

cmake -S extern/glslang -B build-glslang \
  -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_TESTING=OFF \
  -DENABLE_OPT=OFF \
  -DENABLE_GLSLANG_BINARIES=ON

cmake --build build-glslang \
  --target glslangValidator \
  --parallel 8

compiler="$q2src/build-glslang/StandAlone/glslangValidator"
shader="$q2src/src/refresh/vkpt/shader/god_rays.comp"
output="$q2src/build-glslang/god_rays.comp.spv"

"$compiler" \
  --target-env vulkan1.2 \
  --quiet \
  -DVKPT_SHADER \
  -V "$shader" \
  -o "$output"

file "$output"
shasum -a 256 "$output"

The tested SPIR-V checksum is d74178d2dc08646911c9cd63443f3e89ae595aab8c01ab520721c9a453b3ad6d. Install one audit copy beside the MoltenVK sidecar and one loose copy where Q2RTX will find it first:

sidecar="$HOME/Library/Application Support/CrossOver/Q2RTX-RT"
game_dir="$HOME/Library/Application Support/CrossOver/Bottles/Steam/drive_c/Program Files (x86)/Steam/steamapps/common/Quake II RTX"
override_dir="$game_dir/baseq2/shader_vkpt"

mkdir -p "$sidecar" "$override_dir"

install -m 0644 \
  "$output" \
  "$sidecar/god_rays.comp.m4.spv"

install -m 0644 \
  "$output" \
  "$override_dir/god_rays.comp.spv"

install -m 0644 \
  "$q2src/q2rtx-1.8.0-m4-god-rays.patch" \
  "$sidecar/q2rtx-1.8.0-m4-god-rays.patch"

shasum -a 256 \
  "$sidecar/god_rays.comp.m4.spv" \
  "$override_dir/god_rays.comp.spv"

unzip -p "$game_dir/baseq2/shaders.pkz" \
  shader_vkpt/god_rays.comp.spv | shasum -a 256

The final command should still report the packaged original checksum 3441599ade30b103cb642653ee89be0096c55e1274222ad5fc72731931fbb6ca. The loose override adds a higher-priority file; it does not rewrite shaders.pkz.

Install the launcher that selects both overrides

The launcher starts the unmodified executable directly through CrossOver’s Wine wrapper, sets both backend selectors so CX_LIBVULKAN is honored, confirms the loose god-rays shader exists, chooses ray query, and puts gr_enable 1 last.

mkdir -p "$HOME/bin"

cat > "$HOME/bin/q2rtx-m4" <<'EOF'
#!/bin/zsh
set -euo pipefail

# Experimental Q2RTX sidecar built from MoltenVK PR #2771 at 25440e4.
# A loose Q2RTX shader emulates the M4's missing min sampler reduction.
readonly crossover="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver"
readonly game_dir="$HOME/Library/Application Support/CrossOver/Bottles/Steam/drive_c/Program Files (x86)/Steam/steamapps/common/Quake II RTX"
readonly moltenvk="$HOME/Library/Application Support/CrossOver/Q2RTX-RT/libMoltenVK.dylib"
readonly god_rays_shader="$game_dir/baseq2/shader_vkpt/god_rays.comp.spv"

if [[ ! -x "$crossover/bin/wine" ]]; then
    print -u2 "CrossOver was not found at /Applications/CrossOver.app"
    exit 1
fi

if [[ ! -f "$game_dir/q2rtx.exe" ]]; then
    print -u2 "The Steam Quake II RTX installation was not found in the Steam bottle"
    exit 1
fi

if [[ ! -f "$moltenvk" ]]; then
    print -u2 "The experimental MoltenVK sidecar is missing: $moltenvk"
    exit 1
fi

if [[ ! -f "$god_rays_shader" ]]; then
    print -u2 "The M4 god-rays shader override is missing: $god_rays_shader"
    exit 1
fi

exec "$crossover/bin/wine" \
    --bottle Steam \
    --env "CX_ACTIVE_GRAPHICS_BACKEND=wined3d CX_GRAPHICS_BACKEND=wined3d CX_LIBVULKAN='$moltenvk' MVK_CONFIG_LOG_LEVEL=2" \
    --workdir "$game_dir" \
    "$game_dir/q2rtx.exe" \
    "$@" \
    +set vid_rtx 1 \
    +set ray_tracing_api query \
    +set gr_enable 1
EOF

chmod 755 "$HOME/bin/q2rtx-m4"
zsh -n "$HOME/bin/q2rtx-m4"

If q2rtx-m4 is not on the shell path, run it as "$HOME/bin/q2rtx-m4" or add export PATH="$HOME/bin:$PATH" to ~/.zshrc and open a new Terminal. On the tested account, ~/bin was already in the login-shell path.

Close the bottle, launch directly, then read the evidence

Do not click Steam’s normal Play button. That route still loads CrossOver’s bundled MoltenVK and fails RT detection. First ensure every process in the Steam bottle is closed; then start with a conservative window and render scale:

q2rtx-m4 \
  +set vid_fullscreen 0 \
  +set vid_geometry 1280x720 \
  +set viewsize 25 \
  +set pt_num_bounce_rays 1 \
  +set flt_fsr_enable 0

The launcher forwards those optional settings, then appends the three non-negotiable ones. Q2RTX writes a persistent config, so later launches may retain settings changed in the game. The fixed arguments continue to force RTX, ray query, and the patched god-ray pass.

What success looks like

The game log is under the installed game at baseq2/logs/console.log. The successful run contained this sequence:

Physical device 0: Apple M4
  VK_KHR_acceleration_structure
  VK_KHR_ray_query
Picked physical device 0: Apple M4
Using VK_KHR_ray_query
OpenAL initialized.
SDL mouse initialized.
====== Quake II RTX initialized ======

The terminal also prints ./baseq2 before ./baseq2/shaders.pkz in the current search path. That ordering confirms the loose SPIR-V has priority. During the visual test, the process command line ended with +set gr_enable 1.

Extract those lines after a launch with:

log="$HOME/Library/Application Support/CrossOver/Bottles/Steam/drive_c/Program Files (x86)/Steam/steamapps/common/Quake II RTX/baseq2/logs/console.log"

grep -E \
  'Apple M4|VK_KHR_acceleration_structure|VK_KHR_ray_query|Quake II RTX initialized' \
  "$log"

Warnings reading VK_ERROR_FEATURE_NOT_PRESENT: Metal does not support disabling primitive restart were observed repeatedly in the successful session. They are an experimental-translation warning, not the same as Q2RTX failing its RT device selection. Treat new errors seriously, but use the final initialization line and visible rendering as the distinction.

The path reached rendered gameplay, not just device creation

The initialization log establishes the API route; the captures establish that the route reached complete on-screen frames. The observed session remained stable and crash-free, but neither the images nor that session constitute a frame-time chart, correctness suite, or long-duration soak test.

Quake II RTX rendering an in-game industrial scene in a window on the Apple M4 test machine.
Capture 1. A real frame from the experimental M4/CrossOver run. The stock Steam executable is rendering through the custom ray-query sidecar; the image is evidence of frame output, not a performance claim.
A second Quake II RTX gameplay scene rendered during the experimental Apple M4 CrossOver test.
Capture 2. A second scene from the same experimental path. It shows reproducible gameplay output while leaving artifact-free extended play and sustained speed as open questions.
Quake II RTX showing warm volumetric god rays beneath a bright opening during the Apple M4 shader-override test.
Capture 3. Visual confirmation of the manual minimum-reduction path. Coherent volumetric shafts appear beneath the opening in demo1; the overlay reads 48 FPS at 57% in this single frame, which is evidence of execution rather than a benchmark.

Start low, prove the route, then raise quality one variable at a time

Path tracing is expensive even without translation. A 1280×720 window at 25% render scale is intentionally diagnostic rather than pretty: it answers “does this stack work?” before asking what it can sustain. Once initialization and a playable scene are reliable, increase viewsize gradually.

A cleaner low-resolution experiment

Q2RTX’s temporal upscaling and FSR can reconstruct a more presentable image from a low internal resolution:

q2rtx-m4 +set flt_taa 2 +set flt_fsr_enable 1

pt_num_bounce_rays 1 is a cautious starting point. The current test config was later changed interactively, up to two bounce rays and a higher render scale, but this article does not attach a performance verdict to those settings.

God rays depend on the loose override. The launcher refuses to start if the shader file is missing, and the tested file must hash to d74178d2dc08646911c9cd63443f3e89ae595aab8c01ab520721c9a453b3ad6d. The manual reduction adds explicit texture reads, so assess performance and stability before raising other quality settings.

Troubleshoot the selected layer, not the whole stack

Symptom Likely cause Fix
No ray tracing capable GPU found. Normal Steam launch, stock MoltenVK, or a resident Wine process retained the old library. Quit all applications in the Steam bottle. Launch only with q2rtx-m4. Confirm both CX_*GRAPHICS_BACKEND variables remain in the script.
The log identifies stock MoltenVK instead of the sidecar. CX_LIBVULKAN was ignored by the active CrossOver backend. Use all three environment assignments exactly as shown and start a fresh bottle process.
The sidecar will not load. Wrong CPU architecture, symlink copied without its target, or path mismatch. file must report x86_64. Reinstall with cp -fL. Check the quoted path under Q2RTX-RT.
Vulkan device creation reports a missing requested feature. One of the checked source edits was omitted, the wrong commit was built, or CMake reused a stale tree. Verify the commit SHA and git diff; rebuild in a fresh build-q2rtx directory if the source is correct.
The launcher says the game is missing. The bottle is not named Steam, a non-default Steam library was used, or CrossOver lives elsewhere. Edit only the launcher’s crossover, game_dir, and --bottle values to match the installation.
The launcher says the M4 god-rays shader is missing. The loose override was not installed, was removed, or the game lives in a different Steam library. Install god_rays.comp.spv under the active game’s baseq2/shader_vkpt directory and verify its SHA-256 checksum.
Primitive-restart warnings repeat. Known Metal/Vulkan semantic mismatch observed in the successful experimental run. Do not confuse the warning with successful RT selection. Look for Apple M4, Using VK_KHR_ray_query, and the final initialized line.
God-ray artifacts, a failure around the reduction sampler, or instability after changing settings. The wrong shader loaded, the patch does not match the installed Q2RTX build, or the manual path exposed a scene-specific problem. Verify both shader checksums and the d65761f2 engine version. Temporarily change the launcher’s final setting to gr_enable 0 to isolate the pass. Do not use this sidecar globally.
It worked before a CrossOver update. Internal Wine path or graphics-backend selection changed. Recheck /Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wine, then reassess the environment override rather than replacing app-bundle libraries.

The result is real; the support boundary is narrow

This experiment verifies an Apple M4, one macOS build, CrossOver 26.3, Q2RTX 1.8, one pinned experimental MoltenVK commit, the ray-query path, renderer initialization, visible gameplay, visually coherent god rays in multiple scenes, and a crash-free observed test session. It does not establish:

  • stable performance over long sessions;
  • correct rendering in every map, material, or effect;
  • pixel-equivalence with native sampler reduction or correct god rays in every scene;
  • equivalent behavior on M3, M4 Pro/Max, M5, older macOS releases, or future CrossOver versions;
  • support for other Vulkan RT games; or
  • that pull request #2771 will merge in its current form.

Review discussion around the experimental branch has raised performance and memory concerns. A crash, visual corruption, or severe slowdown should therefore be treated as an experimental implementation result, not “fixed” by advertising more unsupported features.

Removal restores three user-owned paths

Quit the Steam bottle, then remove the launcher, sidecar directory, and loose shader override. The packaged shader remains inside shaders.pkz and becomes active again automatically. The source checkouts can be retained for audit or removed separately when no longer needed.

game_dir="$HOME/Library/Application Support/CrossOver/Bottles/Steam/drive_c/Program Files (x86)/Steam/steamapps/common/Quake II RTX"

rm "$HOME/bin/q2rtx-m4"
rm -r "$HOME/Library/Application Support/CrossOver/Q2RTX-RT"
rm "$game_dir/baseq2/shader_vkpt/god_rays.comp.spv"
rmdir "$game_dir/baseq2/shader_vkpt" 2>/dev/null || true

These commands target only the paths created by this guide. They do not remove the Steam bottle, packaged game data, CrossOver, or source checkouts. Inspect the paths before confirming any removal command.

Sources

  1. KhronosGroup/MoltenVK pull request #2771. Experimental Metal-backed Vulkan ray-tracing implementation and review discussion.
  2. dttdrv/MoltenVK, macgaming/ray-query-pr. Fork and branch used for the sidecar; the build pins commit 25440e4e….
  3. NVIDIA/Q2RTX at d65761f2. Exact engine source used to trace requested features and build the god-rays SPIR-V override.
  4. CodeWeavers CrossOver compatibility entry: Quake II RTX. Stock compatibility is listed as “Installs, Will Not Run.”
  5. Apple Metal documentation: MTLDevice.supportsRaytracing. Host API capability tested natively and under Rosetta.
  6. Apple Metal feature set tables. M4-series GPUs are Apple9; sampler minimum and maximum reduction starts with Apple10.
  7. Quake II RTX on Steam. The installed Windows executable was used unchanged.

Tested manifest

Host GPU
Apple M4 · 10 GPU cores · Metal 4
Host OS
macOS 26.6.1 · build 25G76 · arm64
CrossOver
26.3 · bundle build 26.3.0.39832
Stock Vulkan bridge
MoltenVK 1.2.10 · no Vulkan RT extensions exposed to Q2RTX
Bottle
Steam under ~/Library/Application Support/CrossOver/Bottles/Steam
Q2RTX engine
q2rtx 1.8.0-master-d65761f2 · built 26 March 2025
Game SHA-256
b88891ed4aa71a79ad158840aaccc27a54aa0d992359a54696ac6905f5db57b3
MoltenVK source
dttdrv/MoltenVK · macgaming/ray-query-pr · commit 25440e4e1d54419a27a72bab3663068c3d797bfe
Sidecar
x86_64 Mach-O dylib · installed outside app bundle
Sidecar SHA-256
fa87ce46c60880202d36b1d64e8239e7fb774b7eec318ad77700544e636ccaf1
God-rays source patch
Q2RTX d65761f2 · manual four-texel minimum at LOD 0
God-rays SPIR-V SHA-256
d74178d2dc08646911c9cd63443f3e89ae595aab8c01ab520721c9a453b3ad6d
Packaged shader SHA-256
3441599ade30b103cb642653ee89be0096c55e1274222ad5fc72731931fbb6ca
Verified API
VK_KHR_acceleration_structure + VK_KHR_ray_query
God-rays visual check
demo1 · 960×600 window · 25% starting render scale · one bounce · FSR off
Observed stability
Stable and crash-free during the test session · no long-duration soak claim
Evidence date
7 August 2026

Method. The stock and sidecar routes were distinguished by Q2RTX’s own device/extension log. Host Metal capabilities were probed in native and Rosetta processes. Source inspection found no pipeline-statistics query and only one minimum-reduction sampler. The patched SPIR-V was checked for explicit image fetches and absence of the original filtered shadow reads; Q2RTX then initialized, loaded demo1, rendered visible volumetric effects with gr_enable 1, and remained stable without crashing during the observed session. Checksums record the exact successful artifacts.