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).| Example | rlvk (moltenvk) | rlmtl_mesa | rlmtl_mesa speedup |
|---|
| shapes_basic_shapes | 552° | 17938 | 32.52x° |
| textures_tiled_drawing | 407° | 15115 | 37.09x° |
| textures_particles_blending | 532° | 9896 | 18.61x° |
| shapes_starfield_effect | 535° | 18824 | 35.19x° |
| models_loading | 311 | 13760° | 44.20x° |
| models_heightmap_rendering | 552° | 12564 | 22.77x° |
| models_skybox_rendering | 396° | 15785 | 39.84x° |
| models_waving_cubes | 470° | 1237 | 2.63x° |
| models_first_person_maze | 356 | 13734° | 38.59x° |
| core_3d_camera_free | 411° | 17905 | 43.59x° |
| shaders_basic_lighting | 497° | 9026 | 18.17x° |
| shaders_basic_pbr | 281 | 4915 | 17.48x |
| shaders_mandelbrot_set | 445° | 4146 | 9.32x° |
| shaders_raymarching_rendering | 209 | 232 | 1.11x |
| performance_stress_test | 197 | 163 | 0.83x |
| performance_stress_test_direct | 3 | 4 | 1.46x |
| bench_idle | 380° | 14756° | 38.82x° |
| bench_drawcalls | 461° | 2151 | 4.67x° |
| bench_instanced | 378° | 8199 | 21.67x° |
Median frame time, ms (lower is better)
Median per-frame CPU wall time.| Example | rlvk (moltenvk) | rlmtl_mesa | rlmtl_mesa speedup |
|---|
| shapes_basic_shapes | 1.601° | 0.041 | 39.14x° |
| textures_tiled_drawing | 2.235° | 0.040 | 55.60x° |
| textures_particles_blending | 1.603° | 0.071 | 22.71x° |
| shapes_starfield_effect | 1.603° | 0.042 | 38.44x° |
| models_loading | 3.242 | 0.033° | 99.44x° |
| models_heightmap_rendering | 1.679° | 0.047 | 35.35x° |
| models_skybox_rendering | 2.701° | 0.039 | 69.80x° |
| models_waving_cubes | 2.213° | 0.803 | 2.75x° |
| models_first_person_maze | 2.789 | 0.035° | 79.70x° |
| core_3d_camera_free | 2.666° | 0.041 | 65.51x° |
| shaders_basic_lighting | 1.785° | 0.082 | 21.69x° |
| shaders_basic_pbr | 3.409 | 0.177 | 19.20x |
| shaders_mandelbrot_set | 1.868° | 0.236 | 7.92x° |
| shaders_raymarching_rendering | 3.640 | 4.014 | 0.91x |
| performance_stress_test | 5.012 | 5.965 | 0.84x |
| performance_stress_test_direct | 328.157 | 231.606 | 1.42x |
| bench_idle | 2.370° | 0.031° | 75.49x° |
| bench_drawcalls | 1.888° | 0.474 | 3.98x° |
| bench_instanced | 2.430° | 0.114 | 21.26x° |
Average frame time, ms (lower is better)
Mean per-frame wall time.| Example | rlvk (moltenvk) | rlmtl_mesa | rlmtl_mesa speedup |
|---|
| shapes_basic_shapes | 1.817° | 0.056 | 32.56x° |
| textures_tiled_drawing | 2.583° | 0.066 | 39.03x° |
| textures_particles_blending | 1.890° | 0.101 | 18.64x° |
| shapes_starfield_effect | 1.870° | 0.053 | 35.17x° |
| models_loading | 3.245 | 0.073° | 44.63x° |
| models_heightmap_rendering | 1.813° | 0.080 | 22.76x° |
| models_skybox_rendering | 2.744° | 0.063 | 43.28x° |
| models_waving_cubes | 2.337° | 0.809 | 2.89x° |
| models_first_person_maze | 2.945 | 0.073° | 40.41x° |
| core_3d_camera_free | 2.666° | 0.056 | 47.70x° |
| shaders_basic_lighting | 2.068° | 0.111 | 18.58x° |
| shaders_basic_pbr | 3.558 | 0.204 | 17.47x |
| shaders_mandelbrot_set | 2.287° | 0.241 | 9.48x° |
| shaders_raymarching_rendering | 5.081 | 4.306 | 1.18x |
| performance_stress_test | 5.086 | 6.147 | 0.83x |
| performance_stress_test_direct | 332.794 | 228.219 | 1.46x |
| bench_idle | 2.731° | 0.068° | 40.29x° |
| bench_drawcalls | 2.205° | 0.465 | 4.74x° |
| bench_instanced | 2.658° | 0.122 | 21.79x° |
CPU utilization, %
Average process CPU as percent of the whole machine.| Example | rlvk (moltenvk) | rlmtl_mesa | rlmtl_mesa speedup |
|---|
| shapes_basic_shapes | 4.2 | 13.5 | 0.31x |
| textures_tiled_drawing | 3.0 | 13.4 | 0.22x |
| textures_particles_blending | 4.2 | 16.6 | 0.25x |
| shapes_starfield_effect | 4.1 | 15.2 | 0.27x |
| models_loading | 2.1 | 10.0 | 0.21x |
| models_heightmap_rendering | 4.3 | 12.4 | 0.34x |
| models_skybox_rendering | 2.8 | 11.7 | 0.24x |
| models_waving_cubes | 7.3 | 11.6 | 0.63x |
| models_first_person_maze | 2.4 | 10.4 | 0.23x |
| core_3d_camera_free | 2.9 | 12.8 | 0.23x |
| shaders_basic_lighting | 4.1 | 14.9 | 0.28x |
| shaders_basic_pbr | 2.1 | 8.5 | 0.24x |
| shaders_mandelbrot_set | 3.7 | 8.4 | 0.44x |
| shaders_raymarching_rendering | 1.2 | 0.5 | 2.27x |
| performance_stress_test | 10.3 | 10.4 | 0.99x |
| performance_stress_test_direct | 0.0 | 0.0 | 0.86x |
| bench_idle | 2.7 | 9.3 | 0.29x |
| bench_drawcalls | 7.6 | 12.1 | 0.63x |
| bench_instanced | 3.4 | 16.8 | 0.20x |
RAM, MB
Average working-set memory.| Example | rlvk (moltenvk) | rlmtl_mesa | rlmtl_mesa speedup |
|---|
| shapes_basic_shapes | 85.4 | 83.8 | 1.02x |
| textures_tiled_drawing | 85.6 | 84.3 | 1.02x |
| textures_particles_blending | 85.5 | 83.9 | 1.02x |
| shapes_starfield_effect | 85.3 | 83.9 | 1.02x |
| models_loading | 101.2 | 99.6 | 1.02x |
| models_heightmap_rendering | 88.8 | 90.1 | 0.99x |
| models_skybox_rendering | 161.8 | 161.9 | 1.00x |
| models_waving_cubes | 92.7 | 100.7 | 0.92x |
| models_first_person_maze | 86.2 | 84.9 | 1.02x |
| core_3d_camera_free | 85.2 | 83.7 | 1.02x |
| shaders_basic_lighting | 102.4 | 102.2 | 1.00x |
| shaders_basic_pbr | 140.8 | 140.8 | 1.00x |
| shaders_mandelbrot_set | 103.7 | 100.8 | 1.03x |
| shaders_raymarching_rendering | 118.6 | 119.1 | 1.00x |
| performance_stress_test | 152.7 | 164.1 | 0.93x |
| performance_stress_test_direct | 104.0 | 102.9 | 1.01x |
| bench_idle | 84.9 | 83.4 | 1.02x |
| bench_drawcalls | 86.6 | 84.8 | 1.02x |
| bench_instanced | 103.6 | 103.9 | 1.00x |
GPU VRAM, MB
Average per-process video memory.| Example | rlvk (moltenvk) | rlmtl_mesa | rlmtl_mesa speedup |
|---|
| shapes_basic_shapes | 0.0 | 0.0 | — |
| textures_tiled_drawing | 0.0 | 0.0 | — |
| textures_particles_blending | 0.0 | 0.0 | — |
| shapes_starfield_effect | 0.0 | 0.0 | — |
| models_loading | 0.0 | 0.0 | — |
| models_heightmap_rendering | 0.0 | 0.0 | — |
| models_skybox_rendering | 0.0 | 0.0 | — |
| models_waving_cubes | 0.0 | 0.0 | — |
| models_first_person_maze | 0.0 | 0.0 | — |
| core_3d_camera_free | 0.0 | 0.0 | — |
| shaders_basic_lighting | 0.0 | 0.0 | — |
| shaders_basic_pbr | 0.0 | 0.0 | — |
| shaders_mandelbrot_set | 0.0 | 0.0 | — |
| shaders_raymarching_rendering | 0.0 | 0.0 | — |
| performance_stress_test | 0.0 | 0.0 | — |
| performance_stress_test_direct | 0.0 | 0.0 | — |
| bench_idle | 0.0 | 0.0 | — |
| bench_drawcalls | 0.0 | 0.0 | — |
| bench_instanced | 0.0 | 0.0 | — |