How it's built, patched and set up for its played test; it runs on the ioquake3 engine. The numbers are under Test runs.
OpenArena runs on Walnut’s build of ioquake3, with both of its renderers. The fixed-function
opengl1 renderer is the default and plays at about 70 frames a second with no stalls; the GLSL
opengl2 renderer in its lean settings reaches 56 to 60. The patches that make this possible are
listed on the page (memory cuts for the GLSL renderer, CPU savings in the engine).
OpenArena 0.8.8 (Debian’s data) runs on our ioquake3 with both
of its renderers. The fixed-function opengl1 renderer is the suite’s baseline: 70.6 steady fps with
no gameplay stalls. The GLSL opengl2 renderer, in its lean gl2min settings, reaches 56-60 fps with
0-2.4 s of gameplay stalls since ioquake3 0027 sends its vertex colours in a format nv30 fetches. In
played sessions the GL 2.0 launcher did better than GL 1.2 (54 against 43 fps median), so the game’s
medal comes from GL 2.0.
Packages and settings
ioquake3,ioquake3-server: our engine build, replacing Debian’s; the patches and their cvars are on the engine’s page. Debian’sopenarenaand its data are used as they are.- Two menu entries (desktop/launchers): “OpenArena (GL 1.2)” starts
opengl1, “OpenArena (GL 2.0)” startsopengl2with thegl2minset on its command line (no normal, specular or deluxe mapping, sun, dynamic lights, depth prepass, HDR, post-processing, tone mapping, auto exposure, SSAO, cube maps or framebuffer objects). - The PS3 defaults, autoexec.cfg: fullscreen
at the desktop’s resolution, vsync on with a 60 fps cap, the game’s own texture compression
(
r_ext_compressed_textures 1: Mesa makes DXT1 of the opaque textures, on the SPEs through libspeq,SPEQ_S3TC;SPEQ_S3TC_RGB=ppesends RGB sources back to the PPE), and a DualShock 3 bound through SDL’s game controller (triggers fire and zoom, Cross jumps, the shoulders change weapon, Start the menu, Select the scores).
How it performs
| Renderer | Steady fps | Gameplay stalls / 1% low / longest | Free RAM / VRAM | Batch |
|---|---|---|---|---|
| opengl1 (base, tc1) | 70.6 | 0.0 s / 34.6 / 56 ms | 26 / 39 MB | batch 2026-10-18-ioquake3-396 |
opengl2 gl2min |
59.5 | 2.4 s / 21.9 / 868 ms | 15 / 117 MB | batch 2026-10-18-batch17 |
opengl2 gl2min, USB / SSD data / SSD data and swap |
55.9 / 56.1 / 56.1 | 0.3 / 0.0 / 0.1 s; longest 111 / 96 / 105 ms | 13 / 12 / 14 MB; 117 MB | batch 2026-10-18-ssd1 |
opengl2 gl2min, SSD root (n=3) |
60.4 (60.3-63.3) | 0.2 s / 25.6 / 199 ms | 15 / 119 MB | batch 2026-10-19-ssdroot1-baseline |
opengl2 gl2min_md3pack |
59.0 | 1.3 s / 23.5 / 943 ms | 17 / 98 MB | batch 2026-10-18-batch17 |
Later SSD-root batches put gl2min at 57-63 fps (batch 2026-10-19-ssdroot5-openal,
batch 2026-10-19-ssdroot6-fence-work-kick, batch 2026-10-20-ssdroot9-fence-work-kick).
Picture and sound are right in both renderers. opengl2 holds an Rss of 79-104 MB plus 58-68 MB swapped
(opengl1: 71 + 20).
- The load. opengl2’s load takes 28.1 s on the USB root from exec to the end of the loading screens
(10.2 s by the game’s own
CL_InitCGame), 10.9 s of it disk wait, and the demo’s second pass reads the map and players again (131 MB) because memory pressure evicted them. With the whole root on the SSD it takes 6.9 s by the game’s number and 14.2 s to the end of the loading screens, the disk wait 2.5 s against 6.3 s (PROFILING.md). Gameplay was already clean on USB, so the SSD root changed the reloads, not the frame rate. Its CPU in the load, all threads (batch 2026-10-19-ssdroot2-startup-profiles): the game 3.9 s (the shader scripts, mipmaps and pk3 scans; in batch 17’s profile files 1.95, game 1.91, mipmap 1.34), Mesa 0.55, linking 0.5, jpeg 0.29, DXT 0.27; 10.4 s in all. The lever is the game’s own load path (2.0 s of the main thread if it were twice as fast). - Where the CPU goes in play (batch 2026-10-19-ssdroot2-gameplay-profiles, gl2min, 44 fps
profiled): the renderer
renderer_opengl2.so24.1%, Mesa 23.1%, the kernel 14.2%, OpenAL 12.4% (the game mixes its sounds in OpenAL Soft’s mixer on the PPE), libc 8.6%, ps3rsx 5.7%, libdeflate 3.9% (the demo’s map reloads) (HOTPATHS.md). - Memory (opengl1, MEMORY.md’s
MEM=1batch of 2026-10-18, Mesa ps3.299): malloc used 21.4 / 33.5 / 20.8 MB at the window’s start, middle and end, a 12 MB transient around the middle whose pages are kept (free 15.9 / 3.8 / 16.5 MB); 13.9 MB of heap went to swap while 15.9 MB sat free in its arenas. The hunk holds 30.7 MB (low permanent 16.1, high 14.7; temp high-waters 17.7 and 15.7), so 2.7 MB of pages touched by loading are never used again (“unused highwater” in its own meminfo, in game.log). The hunk (128 MB) and the zone (48 MB) are one 176 MB mapping with 34.6 MB resident; the rest is never touched and costs nothing. Giving back the unused high-water after a map load (com_hunkTrim, 2.7 MB) is in the game’s todo. - What used to stall. In opengl1 the map reload at each pass and bots joining mid-match: 0025 and 0026 took gameplay stalls from 9-10 s to 0. Many of the remaining “random” stalls were the kernel’s lost decrementer, gone with the kernel fix (the investigation). In opengl2 they were memory: 0027 took them from 9-11 s to 0.7-2.6 s (the investigation).
The benchmark (recipe) loops the timedemo bench.dm_71 in one session
(PASSES=1; bench_loop and bench_again aliases in autoexec.cfg, since nextdemo is cleared before
each pass); every pass reloads the map, which the gameplay columns leave out. OpenArena keeps only 32
+ commands on its command line (Debian’s wrapper passes about 10), so the recipe writes its cvars into
autoexec.cfg. Its variants turn each engine patch back one at a time.
Known problems
- The gameplay options menu can draw wrong the first time it opens (the GL 1.2 session); Escape and opening it again fixes it.
- opengl2 runs with about 15 MB of RAM free at its tightest.
- Six
Failed to load sound ...wav/Using default soundpairs in almost every run (143 of 147: vulcan impacts, nailgun, yousuck, cl_ammoregen), probably gaps in the upstream data (not yet compared with a PC run); the engine plays a default sound.Couldn't read q3historyis a first-run file. - About one run in six played through OpenAL lost its sound to the sound card’s DMA stopping (the OpenAL “Wait timeout” wedge), fixed by the snd_ps3 watchdog, kernel local/0047 (batch 2026-10-20-ssdroot12-dma-watchdog).
- Short dropouts (OpenAL’s
mmap commit error: Broken pipe, 80-440 ms each) come from OpenAL’s mixer thread starving at normal priority: 1 to 17 in 150 s with 5 bots on oa_dm4, 3 with a real-time limit for the session (not shipped: the investigation).
What was tried
| Change | Result | Verdict |
|---|---|---|
Mesa 0038 NV30_FORCE_DXT=opaque on gl2min |
forced 0 textures: every one of 2238-2593 uploads was skip_format (sRGB or already compressed), fps, stalls and VRAM equal (batch 2026-10-19-ssdroot2-force-dxt, batch 2026-10-20-ssdroot8-force-dxt) |
does nothing here |
gl2min_tc0 (compression left to Mesa) with force-DXT |
145-149 MB of VRAM either way: the textures are made with a NULL glTexImage2D | nothing forced |
Mesa 0039 NV30_SHADER_CACHE=on, cold and warm |
an upper bound of 0.24 s of the load (PROFILING.md) | not worth it |
| Mesa 0040 staging pool | 57.65 off, 56.95 on (batch 2026-10-19-ssdroot5-staging-pool): the pool handles uploads over 256 KiB only | no regression |
Mesa 0041 NV30_FENCE_WORK_KICK=1024 |
62.9 against 62.0 (+1.5%), CPU not lower (batch 2026-10-20-ssdroot9-fence-work-kick) | neutral, off |
Mesa 0044 NV30_MINMAX_VMX=1 |
61.7 against 62.7 (batch 2026-10-20-ssdroot11-minmax-vmx) | neutral here |
| our openal-soft +ps3.549 against Debian’s | 57.3 against 56.3 (+2%) (batch 2026-10-19-ssdroot5-openal) | kept |
r_md3PackedFrames 1 (0029) |
20 MB less VRAM, the same stalls (batch 2026-10-18-batch17) | opt-in so far; a default candidate |
Mesa kick100 (ps3.282) |
no default: it cost X-Moto 9% (batch 2026-10-17-mesa-282-kick100) | rejected |
Open: whether 0029 becomes the default, why force-DXT skips every format here, and the proposals
(com_hunkTrim, r_keepImages for renderergl2, ADPCM sound effects) in the game’s todo.
Investigations
- 2026-10-17 opengl2 fits in memory: from “ran out of RAM compiling its shaders” to 26-32 fps with ioquake3 0001-0010.
- 2026-10-17 opengl1 batching and the atlas: 0019-0023 on the RSX, why the lightmap atlas went off by default (0024).
- 2026-10-17 reload stalls: the map reload and bots joining as stalls, 0025-0026, the kicker and the kernel’s lost decrementer.
- 2026-10-18 opengl2 freezes: the “16 s freezes”, the memory behind them, 0027-0029 and Mesa 0037.
Research
What the surveys and plans said about it, newest first; each line names its source, the section and the date.
- 2026-10-15, the user’s first wish list (backlog.txt, commit 7d58e3d), “2. GPU driver and game support”: listed first of the strong candidates (GL 1.x/2.1, light, a big-endian history from PowerPC Macs): the Quake III engine, which ran on G4/G5 Macs; a classic GPU test that should hit 60 fps at 1080p; RAM roughly 100-150 MB, tight but plausible.
Build
| Packages | openarena |
|---|---|
| Suite | in the game suite (known to run with its sound), free data |
| Game data | comes with the packages |
| Diverted | /usr/share/applications/openarena.desktop (our launchers stand for them) |
Our source packages
- ioquake3 source
30 patches
- renderergl2: delete GLSL shader objects once their program is linked
- renderergl2: r_glslMaxBones, no GPU bone animation by default
- renderergl2: no shadow and post-processing programs without framebuffer objects
- renderergl2: keep compiled GLSL programs across renderer restarts
- renderergl2: r_glslLazy, build each GLSL program at its first use
- renderergl2: allocate the VAO cache only with r_vaoCache 1
- renderergl2: no permanent CPU copy of MD3 vertices that went to a VBO
- renderergl2: no tangents in MD3 VBOs without normal mapping
- renderergl2: r_md3Lods, and no MD3 LODs that r_lodbias rules out
- renderergl2: r_gpuVertexAnimation, to animate MD3 models on the CPU
- renderergl1: no scan of the whole shader text after a miss in the script hash table
- unzip: no crc32 of the pk3 entries read, fs_zipCrc 1 brings it back
- renderergl1: build the AltiVec code (com_altivec), fixed for vec4 normals
- renderergl1: R_ChopPolyBehindPlane doesn't clear its two work arrays
- OpenAL: s_alLazy, the sounds of a player are loaded when first played
- unzip: inflate whole pk3 entries with libdeflate (USE_LIBDEFLATE, fs_zipFast)
- msg: the message Huffman trees from tables (msg_huffSlow 1 builds them as before)
- renderergl1: r_mergeLightmaps, the map's lightmaps in a few atlas textures
- renderergl1: r_glStateCache, texture unit and client array state sent only when a draw needs it
- renderergl1: r_lockArrays 0, no glLockArraysEXT around every batch
- renderergl1: r_skyArrays, a sky box side in one glDrawElements
- renderergl1: r_mergeLightmaps 2 and r_mergeLightmapsSize, to tell the atlas textures' cost from the batching's
- renderergl1: r_mergeLightmaps defaults to 0, any atlas page adds about 10 s of hitches per run on the PS3
- renderergl1: r_keepImages, the textures of image files kept across a map change
- client: cl_preloadLate, what the cgame registered during play loaded with the next same map
- renderergl2: r_vaoSnormColor, vertex colours as signed normalized shorts
- renderergl2: no powf per texel channel in the gamma-correct mipmaps
- renderergl2: r_md3PackedFrames, animated MD3 frames in 12 bytes a vertex
- client: cl_cdkeyCheck 0, the UI takes any CD key as valid
- sys: sys_signalQuit, a quit signal is handled by the main loop
Played test
What a played session sets up (rating/test.toml); the medal comes from these sessions only.
| Process | ioquake3 |
|---|---|
| Set aside | ~/.openarena (the game starts on fresh defaults) |
| Command | /usr/games/openarena |
| Play starts at | CL_InitCGame |
| Sound read at | 25 s |
Investigations
- OpenArena opengl2 (gl2min): the 16 s freezes and the memory (2026-10-18) 2026-10-18
- What stalls in OpenArena: map reloads, bots joining and the lost decrementer (2026-10-17) 2026-10-17
- OpenArena opengl2: making the GLSL renderer fit in memory (2026-10-15 to 10-17) 2026-10-17
- OpenArena opengl1: batching, and why the lightmap atlas went off (2026-10-17/18) 2026-10-17