raylib performance — backend comparison

rlvk (moltenvk)  vs  rlmtl_mesa  |  GPU Apple M5 (24576 MB)  |  Driver MoltenVK 1.4.2  |  OS macOS 26.5.1  |  3 runs × 10000 ms full speed

Green = best backend for that example/metric, red = worst. Frame time & FPS are the representative run; software (rlsw) has no GPU so its VRAM is ~0. Shader-heavy examples may not execute custom shaders on the software backend.

° = at that backend's floor (median within 15% of its own bench_idle median): the scene finishes its real work faster than one present/loop turnaround, so the number measures presentation pacing or loop overhead, not rendering cost, and cross-backend ratios there compare present plumbing. On macOS, Metal-backed Vulkan presents pace on drawable acquire (~1.6-1.8 ms) while GL's IOSurface flush has no floor - only rows without ° compare backend rendering cost.

Read this before comparing FPS on macOS. Uncapped FPS on light scenes here measures presentation, not rendering. Apple GL presents by flushing an IOSurface — no per-frame handshake, essentially free — while Vulkan on macOS (MoltenVK or KosmicKrisp) can only present through Metal, which must acquire a CAMetalDrawable from CoreAnimation each frame, paced at ~1.6–1.8 ms on a composited window no matter how little the frame draws. Measured on shapes_basic_shapes, uncapped: frame interval avg 1.95 ms, of which 1.76 ms (90%) is "Retrieve a CAMetalDrawable" (MoltenVK's own performance tracking; the drawing itself costs ~0.12 ms and the GPU is nearly idle). Every native Metal app pays the same pacing; no driver knob removes it (KosmicKrisp's is lowered from ~3.3 to ~1.6 ms by MESA_VK_WSI_PRESENT_MODE=mailbox, MoltenVK's config surface is exhausted). Cells marked ° sit at that backend's floor: their cross-backend ratios compare macOS present plumbing. Rows above the floor compare real rendering — there the Vulkan backend leads (e.g. 8000 draw calls: 120 fps GL vs 581 fps rlvk). Under vsync all backends lock to the display rate and the floor is invisible.
rlmtl_mesa = the native Metal backend + Mesa's shader compiler. This column runs rlmtl's stock pipeline (GLSL → shaderc → SPIR-V → SPIRV-Cross → MSL) on every scene except performance_stress_test_direct, whose fragment shader is precompiled offline by raylib/tools/nir2msl through Mesa's NIR→MSL compiler (the KosmicKrisp backend's kosmicomp, MIT) and injected via RLMTL_MSL_OVERRIDE. On that scene SPIRV-Cross's flattened-SSA output defeats Metal's optimizer; Mesa's NIR pipeline is the only automatic GLSL translation measured faster than Apple's own GL compiler — 231.6 ±14 vs 247.4 ±9 ms over 10 interleaved cycles (2026-08-18), ahead of hand-written MSL (250), ANGLE (281), Slang (289), naga (322) and stock SPIRV-Cross (442). Pixel drift vs SPIRV-Cross is ULP-class only (84% of differing channels off by 1, max 17/255; Mesa lowers sin/cos to conformant polynomials).

speedup = rlmtl_mesa relative to rlvk (moltenvk), per metric direction (>1x favors rlmtl_mesa_macos_apple/bench_instanced/run_16.rini); ° on a ratio means at least one side sits at its floor, so it compares present plumbing.

Frames per second (higher is better)

Sustained FPS at full speed (uncapped).
Examplerlvk (moltenvk)rlmtl_mesarlmtl_mesa speedup
shapes_basic_shapes552°1793832.52x°
textures_tiled_drawing407°1511537.09x°
textures_particles_blending532°989618.61x°
shapes_starfield_effect535°1882435.19x°
models_loading31113760°44.20x°
models_heightmap_rendering552°1256422.77x°
models_skybox_rendering396°1578539.84x°
models_waving_cubes470°12372.63x°
models_first_person_maze35613734°38.59x°
core_3d_camera_free411°1790543.59x°
shaders_basic_lighting497°902618.17x°
shaders_basic_pbr281491517.48x
shaders_mandelbrot_set445°41469.32x°
shaders_raymarching_rendering2092321.11x
performance_stress_test1971630.83x
performance_stress_test_direct341.46x
bench_idle380°14756°38.82x°
bench_drawcalls461°21514.67x°
bench_instanced378°819921.67x°

Median frame time, ms (lower is better)

Median per-frame CPU wall time.
Examplerlvk (moltenvk)rlmtl_mesarlmtl_mesa speedup
shapes_basic_shapes1.601°0.04139.14x°
textures_tiled_drawing2.235°0.04055.60x°
textures_particles_blending1.603°0.07122.71x°
shapes_starfield_effect1.603°0.04238.44x°
models_loading3.2420.033°99.44x°
models_heightmap_rendering1.679°0.04735.35x°
models_skybox_rendering2.701°0.03969.80x°
models_waving_cubes2.213°0.8032.75x°
models_first_person_maze2.7890.035°79.70x°
core_3d_camera_free2.666°0.04165.51x°
shaders_basic_lighting1.785°0.08221.69x°
shaders_basic_pbr3.4090.17719.20x
shaders_mandelbrot_set1.868°0.2367.92x°
shaders_raymarching_rendering3.6404.0140.91x
performance_stress_test5.0125.9650.84x
performance_stress_test_direct328.157231.6061.42x
bench_idle2.370°0.031°75.49x°
bench_drawcalls1.888°0.4743.98x°
bench_instanced2.430°0.11421.26x°

Average frame time, ms (lower is better)

Mean per-frame wall time.
Examplerlvk (moltenvk)rlmtl_mesarlmtl_mesa speedup
shapes_basic_shapes1.817°0.05632.56x°
textures_tiled_drawing2.583°0.06639.03x°
textures_particles_blending1.890°0.10118.64x°
shapes_starfield_effect1.870°0.05335.17x°
models_loading3.2450.073°44.63x°
models_heightmap_rendering1.813°0.08022.76x°
models_skybox_rendering2.744°0.06343.28x°
models_waving_cubes2.337°0.8092.89x°
models_first_person_maze2.9450.073°40.41x°
core_3d_camera_free2.666°0.05647.70x°
shaders_basic_lighting2.068°0.11118.58x°
shaders_basic_pbr3.5580.20417.47x
shaders_mandelbrot_set2.287°0.2419.48x°
shaders_raymarching_rendering5.0814.3061.18x
performance_stress_test5.0866.1470.83x
performance_stress_test_direct332.794228.2191.46x
bench_idle2.731°0.068°40.29x°
bench_drawcalls2.205°0.4654.74x°
bench_instanced2.658°0.12221.79x°

CPU utilization, %

Average process CPU as percent of the whole machine.
Examplerlvk (moltenvk)rlmtl_mesarlmtl_mesa speedup
shapes_basic_shapes4.213.50.31x
textures_tiled_drawing3.013.40.22x
textures_particles_blending4.216.60.25x
shapes_starfield_effect4.115.20.27x
models_loading2.110.00.21x
models_heightmap_rendering4.312.40.34x
models_skybox_rendering2.811.70.24x
models_waving_cubes7.311.60.63x
models_first_person_maze2.410.40.23x
core_3d_camera_free2.912.80.23x
shaders_basic_lighting4.114.90.28x
shaders_basic_pbr2.18.50.24x
shaders_mandelbrot_set3.78.40.44x
shaders_raymarching_rendering1.20.52.27x
performance_stress_test10.310.40.99x
performance_stress_test_direct0.00.00.86x
bench_idle2.79.30.29x
bench_drawcalls7.612.10.63x
bench_instanced3.416.80.20x

RAM, MB

Average working-set memory.
Examplerlvk (moltenvk)rlmtl_mesarlmtl_mesa speedup
shapes_basic_shapes85.483.81.02x
textures_tiled_drawing85.684.31.02x
textures_particles_blending85.583.91.02x
shapes_starfield_effect85.383.91.02x
models_loading101.299.61.02x
models_heightmap_rendering88.890.10.99x
models_skybox_rendering161.8161.91.00x
models_waving_cubes92.7100.70.92x
models_first_person_maze86.284.91.02x
core_3d_camera_free85.283.71.02x
shaders_basic_lighting102.4102.21.00x
shaders_basic_pbr140.8140.81.00x
shaders_mandelbrot_set103.7100.81.03x
shaders_raymarching_rendering118.6119.11.00x
performance_stress_test152.7164.10.93x
performance_stress_test_direct104.0102.91.01x
bench_idle84.983.41.02x
bench_drawcalls86.684.81.02x
bench_instanced103.6103.91.00x

GPU VRAM, MB

Average per-process video memory.
Examplerlvk (moltenvk)rlmtl_mesarlmtl_mesa speedup
shapes_basic_shapes0.00.0
textures_tiled_drawing0.00.0
textures_particles_blending0.00.0
shapes_starfield_effect0.00.0
models_loading0.00.0
models_heightmap_rendering0.00.0
models_skybox_rendering0.00.0
models_waving_cubes0.00.0
models_first_person_maze0.00.0
core_3d_camera_free0.00.0
shaders_basic_lighting0.00.0
shaders_basic_pbr0.00.0
shaders_mandelbrot_set0.00.0
shaders_raymarching_rendering0.00.0
performance_stress_test0.00.0
performance_stress_test_direct0.00.0
bench_idle0.00.0
bench_drawcalls0.00.0
bench_instanced0.00.0