DomeDoom is a ZDoom-family fork that renders the scene as a 6-face cubemap and either streams the raw cubemap strip or warps it into a square fisheye domemaster or a 2:1 equirectangular panorama, feeding a fulldome planetarium pipeline (PipeWire, Sh4lt, NDI) for the Satosphère dome at the Société des Arts Technologiques (SAT). It also sends per-source 3D positions to SpatGRIS over OSC for object-based dome audio.
Lineage: DomeDoom started as a fork of GZDoom (master, late 2025). In July 2026 the project switched its upstream to UZDoom — the community continuation of GZDoom by its long-time development team — and now tracks UZDoom trunk. Mod/WAD compatibility is unchanged (UZDoom is a direct continuation of the same engine).
Both render backends are supported: the cubemap compositing, readback, and
domemaster warp run on OpenGL and Vulkan (Vulkan uses vkCmdBlitImage
plus an image→staging-buffer readback; zero-copy DMA-BUF export remains an
OpenGL-only optimization). Local builds produce a domedoom binary
(ZDOOM_EXE_NAME default).
All options live in the r_cubemap_* namespace and can be set in the console
or in a startup config file. Most are also exposed in-game under
Options → Fulldome Output.
| CVar | Default | Description |
|---|---|---|
r_cubemap |
false |
Enable cubemap rendering pipeline |
r_cubemap_mode |
1 |
Output projection: 0 = 6-face strip (6144x1024), 1 = square fisheye domemaster (2048x2048), 2 = equirectangular panorama (4096x2048). Needs restart — re-inits PipeWire + readback buffers. Replaces the old r_cubemap_domemaster bool. |
r_cubemap_pipewire |
true |
PipeWire DMA-BUF output |
r_cubemap_sh4lt |
false |
Sh4lt video output |
r_cubemap_sh4lt_label |
"domedoom" |
Sh4lt video stream label |
r_cubemap_sh4lt_audio |
false |
Sh4lt audio tap output |
r_cubemap_sh4lt_audio_label |
"domedoom-audio" |
Sh4lt audio stream label |
r_cubemap_ndi |
false |
NDI video output |
r_cubemap_ndi_label |
"DomeDoom" |
NDI source name |
r_cubemap_debug |
false |
Debug logging |
Only active when r_cubemap_mode is 1 (domemaster). The warp assembles the
six cube faces into a fisheye; orientation and flips are exposed live because
OpenGL and Vulkan differ in NDC and texture origin, so tuned values vary per
machine/backend. The flip and face-swap toggles also apply to the
equirectangular warp (r_cubemap_mode 2), which has its own rotation:
| CVar | Default | Description |
|---|---|---|
r_cubemap_equi_yaw |
0 |
Equirect content yaw, degrees |
r_cubemap_equi_pitch |
0 |
Equirect content pitch, degrees |
r_cubemap_equi_roll |
0 |
Equirect content roll, degrees |
r_cubemap_equi_flip_h |
false |
Flip equirect output horizontally |
r_cubemap_equi_flip_v |
true |
Flip equirect output vertically (correct for Vulkan) |
| CVar | Default | Description |
|---|---|---|
r_cubemap_dome_fov |
270 |
Fisheye field of view, degrees |
r_cubemap_dome_yaw |
180 |
Orientation yaw, degrees |
r_cubemap_dome_pitch |
90 |
Orientation pitch, degrees |
r_cubemap_dome_roll |
180 |
Orientation roll, degrees |
r_cubemap_dome_flip_h |
false |
Flip output horizontally |
r_cubemap_dome_flip_v |
false |
Flip output vertically |
r_cubemap_dome_flip_ud |
false |
Swap ceiling/floor |
r_cubemap_dome_swap_ud |
true |
Swap up/down faces |
r_cubemap_dome_lock_yaw |
false |
Lock the scene to a fixed dome heading. The domemaster output is counter-rotated by the player's yaw change (latched on enable), so the projected world stays still while the weapon orbits around the dome to show the player's aim. Domemaster and equirect. |
Each projection places the status bar differently so it stays readable:
- Domemaster — the status bar is warped into a band along the front rim, auto-following the forward view. The band shape is controlled by the arc/band/strip/offset/crop/flip parameters below (all shader-driven).
- Equirectangular — the status bar is baked onto the front face, where
the weapon is, so it warps through the panorama with the gun instead of
sitting flat at the bottom. Only
r_cubemap_dome_hud,r_cubemap_dome_hud_scale, andr_cubemap_dome_hud_cropapply here; the arc/band/strip/offset/flip parameters are domemaster-only. - Cubemap strip — the status bar is baked full-size onto the face selected
by
r_cubemap_hud_face.
The 2D menu / console (and the no-scene screen: title, intermission) is
baked onto the r_cubemap_hud_face face in every projection, so it is live
from launch — before any level loads. On the equirect the menu uses the
selected face; the status bar always stays on the front face with the weapon.
| CVar | Default | Projection | Description |
|---|---|---|---|
r_cubemap_dome_hud |
true |
all | Enable the status-bar overlay |
r_cubemap_hud_face |
0 (Front) |
all | Face the 2D overlays (strip HUD, menu, no-scene screen) bake onto: 0=Front 1=Left 2=Right 3=Back 4=Up 5=Down. Equirect keeps the status bar on Front regardless. |
r_cubemap_dome_hud_scale |
1.0 |
equirect | Status-bar size on the front face: 1 = full width, <1 shrinks it and centres it (uniform, no stretch) |
r_cubemap_dome_hud_crop |
0.275 |
equirect + domemaster | Trim each side (0 = full width, 0.49 = almost nothing). Equirect: clips the sides (no stretch); domemaster: crops the band source |
r_cubemap_dome_hud_arc |
45 |
domemaster | Arc width of the rim band, degrees |
r_cubemap_dome_hud_band |
0.035 |
domemaster | Radial thickness of the band (fraction of radius) |
r_cubemap_dome_hud_strip |
0.035 |
domemaster | Source strip height sampled from the HUD |
r_cubemap_dome_hud_offset |
0 |
domemaster | Manual angular offset from the forward view, degrees |
r_cubemap_dome_hud_flip_h |
false |
domemaster | Flip the HUD band horizontally |
r_cubemap_dome_hud_flip_v |
true |
domemaster | Flip the HUD band vertically |
NDI is compiled in when the NDI SDK headers are present at build time (CI
installs them); the libndi.so.6 runtime is loaded via dlopen at first use,
so the host running the AppImage must have the NDI 6 runtime installed
(https://ndi.video/download-ndi-sdk/ or the NDI Tools/Redist package).
DomeDoom sends per-source 3D positions to SpatGRIS over OSC UDP so the Satosphère's speaker array can do VBAP spatialization of each Doom sound object independently.
| CVar | Default | Description |
|---|---|---|
r_cubemap_spatgris |
false |
Enable SpatGRIS object-based audio |
r_cubemap_spatgris_ip |
"127.0.0.1" |
SpatGRIS host IP address |
r_cubemap_spatgris_port |
18032 |
SpatGRIS OSC UDP port |
r_cubemap_spatgris_stereo |
false |
Stereo-only mode — disables OSC, uses OpenAL mix |
r_cubemap_spatgris_sources |
32 |
Source pool size (max 128; set before launch) |
r_cubemap_spatgris_mute3d |
true |
Mute a 3D sound's stereo-mix copy once it plays on its own SpatGRIS channel — the stereo bed then carries only music + menu/UI sounds. Turn off to keep the full mix in stereo as well |
r_cubemap_spatgris_bed |
true |
Reserve SpatGRIS channels 1/2 for the music + menu/UI mix ("stereo bed"), positioned like a pair of stereo speakers: channel 1 at 45° left of the gun, channel 2 at 45° right, at the dome edge. World sounds then use IDs 3+. Restart to re-slot |
r_cubemap_spatgris_gun_dist |
0.5 |
SpatGRIS distance for the player's own sounds (gunfire, pickups): 0 = dome centre, 1 = dome edge. They play at the gun's azimuth at this radius |
Turning r_cubemap_spatgris off (or r_cubemap_spatgris_stereo on) restores
plain stereo output at any time — live muted sounds get their gains back —
so a normal stereo mix for screen recording is always one toggle away.
With r_cubemap_dome_lock_yaw the audio follows the dome behaviour: world
sounds keep their azimuth relative to the locked heading (fixed on the dome,
like the image) while the player's own sounds and the stereo-bed pair follow
the orbiting gun.
OSC message format (SpatGRIS dome mode, degrees):
/spat/serv "deg" <source_id> <azimuth_deg> <elevation_deg> <distance> 0.0 0.0
azimuth_deg: degrees clockwise from front (0 = ahead, 90 = right, −90 = left)elevation_deg: degrees above horizon (0 = equator, 90 = zenith)distance: normalized[0, 1]— 0 = center, 1 = dome edge (= 2048 Doom units)
Slots are allocated dynamically from a pool of IDs 1–N
(r_cubemap_spatgris_sources). A slot is claimed when a 3D sound starts and
released when it stops. IDs are reused in FIFO order.
| SpatGRIS ID | Doom sound | Notes |
|---|---|---|
| 1 | stereo bed LEFT (music + UI) | reserved while r_cubemap_spatgris_bed is on; 45° left of the gun |
| 2 | stereo bed RIGHT (music + UI) | reserved while r_cubemap_spatgris_bed is on; 45° right of the gun |
| 3 | first active 3D sound | reassigned each time a new sound claims an empty slot |
| … | … | |
| N | last active 3D sound | N = r_cubemap_spatgris_sources (default 32) |
With the bed disabled, 3D sounds start at ID 1.
2D sounds (UI, menus, HUD) use StartSound, not StartSound3D, and are never
sent to SpatGRIS — they remain in the OpenAL stereo mix.
SpatGRIS receives positions from DomeDoom via OSC. For audio, configure
OpenAL Soft to use the JACK backend (snd_aldevice in the console) and route
the JACK output ports into SpatGRIS's input channels, or use the Sh4lt audio
tap for a stereo bed. True per-source JACK audio (one port per active sound) is
a planned future enhancement.
DomeDoom can be driven in parallel over OSC/UDP — the normal keyboard/mouse/joystick input keeps working; OSC is folded into the same analog input path. Intended for a dome-side controller (touch surface, show control, etc.) that streams messages to the host running DomeDoom.
Enable it in-game under Options → OSC Control, or via CVars:
| CVar | Default | Description |
|---|---|---|
osc_control |
false |
Enable the OSC control receiver |
osc_control_port |
6666 |
UDP port to listen on (all interfaces) |
osc_control_debug |
false |
Log received messages to stderr |
Send OSC messages to the host's IP on that port. Every address lives under the
/domedoom/* namespace. The single argument is the magnitude in [0, 1]
(f or i); if omitted it defaults to 1.0. Send 0 to release.
| Address | Action |
|---|---|
/domedoom/front /domedoom/back |
Move forward / backward |
/domedoom/strafeleft /domedoom/straferight |
Strafe |
/domedoom/left /domedoom/right |
Turn (yaw) |
/domedoom/lookup /domedoom/lookdown |
Look (pitch) |
/domedoom/moveup /domedoom/movedown |
Fly / swim up / down |
/domedoom/attack /domedoom/altattack |
Fire |
/domedoom/use /domedoom/jump /domedoom/crouch |
Use / jump / crouch |
/domedoom/reload /domedoom/zoom /domedoom/run |
Reload / zoom / run modifier |
/domedoom/rotate <deg> |
Absolute view heading on the dome, 0–360° |
/domedoom/rotate turns DoomGuy to an absolute compass heading — the natural
way to point the action at a given spot on the dome. All other controls are
relative and mirror the joystick axes/buttons.
For the intended dome behaviour, enable r_cubemap_dome_lock_yaw (Options →
Fulldome Output → Lock dome heading). With it on, the scene stays fixed on the
dome while turning and /domedoom/rotate orbit the weapon around the dome to
show where the player is aiming (the output is counter-rotated by the player's
yaw so the world does not spin). With it off, turning yaws the entire dome
(normal Doom behaviour, useful on a flat window).
Axes and buttons auto-release ~250 ms after their last message, so a stalled sender never leaves DoomGuy running. A live controller streaming at ≥10 Hz holds the input; a single message acts as a brief pulse.
A dependency-free test sender lives at tools/osc_test_sender.py:
tools/osc_test_sender.py 127.0.0.1 6666 /domedoom/front 1.0
tools/osc_test_sender.py 127.0.0.1 6666 /domedoom/rotate 90
Recent changes — equirectangular output mode (r_cubemap_mode 2) with a
size/crop-controllable status bar baked onto the front face; the menu, console,
and title/intermission screens are baked onto the r_cubemap_hud_face face so
the stream is live from launch, not only in-level; parallel OSC/UDP game
control (/domedoom/*). Earlier fixes: sprites no longer split across cube-face
seams (billboards forced to face the camera); the GL dome pass fully
saves/restores GL state (fixes a GL freeze); clean shutdown no longer crashes
with "pure virtual method called"; FluidSynth "Not a RIFF file" soundfont spam
silenced.
AppImage / NDI runtime — NDI is compiled in when the SDK headers are
present at build time (CI installs them), but libndi.so.6 is not bundled;
it is loaded via dlopen at first use, so the host running the AppImage must
have the NDI 6 runtime installed
(https://ndi.video/download-ndi-sdk/ or the NDI Tools/Redist package).
OSC (SpatGRIS positions) uses raw UDP with no external library.
DomeDoom's engine is UZDoom, a modder-friendly OpenGL and Vulkan source port based on the DOOM engine and the community continuation of GZDoom.
Copyright (c) 1998-2025 ZDoom + GZDoom teams, and contributors Copyright (c) 2025-2026 UZDoom maintainers and contributors
Doom Source (c) 1997 id Software, Raven Software, and contributors
Please see license files for individual contributor licenses
- https://github.com/UZDoom/UZDoom - UZDoom (engine upstream)
- https://zdoom.org/ - ZDoom home page
- https://zdoom.org/wiki/ - Wiki (applies to UZDoom/GZDoom alike)
- https://forum.zdoom.org/ - Forum