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.
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_TRACINGas1. 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
samplerFilterMinmaxand 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.
-
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. -
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.
-
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.
-
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.
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
- KhronosGroup/MoltenVK pull request #2771. Experimental Metal-backed Vulkan ray-tracing implementation and review discussion.
- dttdrv/MoltenVK,
macgaming/ray-query-pr. Fork and branch used for the sidecar; the build pins commit25440e4e…. - NVIDIA/Q2RTX at
d65761f2. Exact engine source used to trace requested features and build the god-rays SPIR-V override. - CodeWeavers CrossOver compatibility entry: Quake II RTX. Stock compatibility is listed as “Installs, Will Not Run.”
- Apple Metal documentation:
MTLDevice.supportsRaytracing. Host API capability tested natively and under Rosetta. - Apple Metal feature set tables. M4-series GPUs are Apple9; sampler minimum and maximum reduction starts with Apple10.
- 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
Steamunder~/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· commit25440e4e1d54419a27a72bab3663068c3d797bfe- 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.