Building for consoles
Guide how to build, deploy and run Jazz² Resurrection on game consoles.
Beside the desktop and mobile platforms the game runs on six consoles. Each of them is cross-compiled with its own SDK, most of them have a bespoke window/input backend instead of SDL2** or GLFW, and four of them have no programmable shaders at all — so they are driven by one of three rendering backends written for their fixed-function graphics hardware. This page covers the whole path for each one: installing the toolchain, configuring the build, packaging the result, copying it to the device and getting a log back out of it. For everything that is not console-specific see Building the project.
| Console | Toolchain (CMake toolchain file) | Rendering backend | Window backend | Build artifact |
|---|---|---|---|---|
| Sega Dreamcast | KallistiOS (kallistios.toolchain.cmake) | PVR — fixed-function | Dc | jazz2.cdi (bootable disc image) |
| PlayStation Portable | pspdev (pspdev.cmake) | GU — fixed-function | Psp | EBOOT.PBP (in a staged ms0/ tree) |
| Nintendo Wii | devkitPPC + libogc (Wii.cmake) | GX — fixed-function | Ogc | boot.dol (in a staged sd/ tree) |
| Nintendo GameCube | devkitPPC + libogc (GameCube.cmake) | GX — fixed-function | Ogc | Jazz2.dol (in a staged sd/ tree) |
| PlayStation Vita | VitaSDK (vita.toolchain.cmake) | GXM (native sceGxm, the default), OpenGL via vitaGL (ES 2.0 profile) or Software | SDL2 | jazz2.vpk |
| Nintendo Switch | devkitA64 (Switch.cmake) | OpenGL | SDL2 | jazz2.nro |
The rendering backend is not a choice on the first four — NCINE_PREFERRED_RHI is pinned to the one backend the console has and any other value is a configure error. The PS Vita and the Switch are ordinary shader platforms that happen to be consoles, so most of what follows applies to them only in the deployment part.
What every console build shares
Three properties separate a console build from a desktop one, and they explain most of the extra steps below.
- The host tools are not built. ShaderCompiler and AssetPacker run on the build machine, so they are skipped for every cross-compiled target. Everything they produce is either committed to the repository (the generated shader headers) or prepared by hand ahead of time (the game content).
- The game data has to be converted in advance on the Dreamcast, the PSP, the Wii and the GameCube. Those four skip the first-run conversion entirely and mark their content as verified, because none of them can rewrite a cache of its own; a build without a prepared content tree reaches the main menu and finds no episode to play. The Vita and the Switch have a writable filesystem and convert on first run like a desktop build does.
- Four of them have no shaders. The
PVR,GXandGUbackends (the Wii and the GameCube shareGX) implement each effect as a short list of fixed-function hardware passes, transpiled from the very same.shaderfiles by ShaderCompiler — see console fixed-function blocks. Because those backends expose no shader capability, the post-processing chain (and with it the rescale filters) does not exist there; the level viewport is aspect-fitted into the console's native output instead.
Preparing the game content
The content tree is prepared with AssetPacker, built as part of an ordinary desktop build. All four data-prepared consoles consume the same tree, so it is built once:
# Build the tool first (part of the regular desktop build) cmake -B build -D CMAKE_BUILD_TYPE=Release make -j $(nproc) -C build # Convert an installed copy of the original game into a console content tree ./build/Sources/Utilities/AssetPacker/AssetPacker <path to the game installation> ./build/ConsoleContent --target=console
Point the console build at that directory with NCINE_CONTENT_DIR; the build stages it into the package it creates, under the name "Content", at the place the console looks for it:
| Console | Content directory the game reads | Cache / save data |
|---|---|---|
| Dreamcast | "/cd/Content/" (inside the disc image) | "/cd/Cache/" — read-only, so nothing is ever written |
| PlayStation Portable | "ms0:/PSP/GAME/Jazz2/Content/" | "ms0:/PSP/GAME/Jazz2/Cache/" |
| Wii | "sd:/apps/Jazz2/Content/" | "sd:/apps/Jazz2/Cache/" |
| GameCube | "carda:/Jazz2/Content/" | "carda:/Jazz2/Cache/" |
| PS Vita | "app0:/Content/" (inside the VPK) | "ux0:/data/jazz2/Cache/" |
| Switch | "romfs:/" (embedded in the .nro) | "sdmc:/Games/Jazz2/Cache/" |
What is available on which console
Everything below is decided at configure time from the platform, not from a build parameter. The values are what a default Release configuration of each console produces.
| Feature | Dreamcast | PSP | Wii | GameCube | Vita | Switch |
|---|---|---|---|---|---|---|
Threads (NCINE_WITH_THREADS) | yes | no | yes | yes | yes | yes |
Asynchronous tracing (DEATH_TRACE_ASYNC) | no | no | no | no | no | no |
| Audio backend | AICA (KallistiOS sound driver) | OpenAL (pspdev's OpenAL Soft) | ASND (libogc DSP mixer) | ASND (libogc DSP mixer) | OpenAL if the SDK has one | OpenAL if the SDK has one |
| Sound effects | yes | yes | yes | yes | SDK-dependent | SDK-dependent |
| Module music (libopenmpt) | yes | off on purpose (see Limits and known issues) | yes | yes | SDK-dependent | SDK-dependent |
| Underwater low-pass filter | no — the driver leaves it off | no — no efx.h in pspdev | no — no filter stage on the DSP | no — no filter stage on the DSP | SDK-dependent | yes |
| Local splitscreen | yes | no — needs threads | yes | yes | yes | yes |
| Online multiplayer | no | no | no | no | yes | yes |
| Post-processing and rescale filters | no — fixed-function | no — fixed-function | no — fixed-function | no — fixed-function | yes | yes |
External .shader files | no | no | no | no | yes | yes |
| Converts the game data itself | no | no | no | no | yes | yes |
Audio backends
Sound is structured the way rendering is: WITH_AUDIO says the audio subsystem is compiled in at all, and exactly one audio backend is compiled with it, chosen at configure time from the platform. The backend implements nCine::
Each backend lives in "nCine/Audio/Backends/<backend>/", the way the rendering ones live under "nCine/Graphics/RHI/":
| Backend | WITH_* | What it drives |
|---|---|---|
| AL | WITH_OPENAL | The system OpenAL on desktop, and the SDK's own on the PSP, Switch and Vita |
| ASND | WITH_ASND | libogc's DSP mixer, the only sound API devkitPro ships for PowerPC. Comes with the toolchain, so there is nothing to install |
| AICA | WITH_AICA | The Dreamcast sound processor through KallistiOS, using its wavetable channels for sound effects and its snd_stream driver for music |
| none | — | The silent fallback when no backend is available, or when the game is started with audio turned off |
Nothing has to be installed by hand for any of them — each console's own SDK provides what its backend needs. NCINE_WITH_AUDIO is left at ON everywhere and turns the whole subsystem off when cleared.
Getting a log out of a console
Every console writes the same trace output the desktop build does, but each one needs a different receiver. This is the single most useful thing to set up before debugging anything.
| Console | Where the trace goes |
|---|---|
| Dreamcast | dbgio — the framebuffer console during startup (visible on screen), then the SCIF serial port once the renderer takes over. Emulators print it to their own log, dc-tool shows it in its console |
| PlayStation Portable | stdout through sceIoWrite on fd 1 — PPSSPP prints it into its log, psplink into its console; plus the debug screen on the display until the GU session takes the framebuffer over |
| Wii / GameCube | A USB Gecko on EXI channel 1 (memory-card slot B), probed once at startup; without the adapter nothing is written and the game runs normally |
| PS Vita | sceClibPrintf — picked up by the usual host-side console tools (psp2shell, VitaCompanion) |
| Switch | svcOutputDebugString — picked up by a debug logger on the host; writing the trace to a file on the SD card is forced on this platform as well |
DEATH_TRACE_LOG_PATH additionally forces the trace into a file, which is worth using on the platforms with writable storage (not the Dreamcast, whose only medium is a read-only disc). Verbose I/O lines use the deferred trace level and are only flushed when an error follows them, so a sparse log is normal and does not mean files are not being opened.
Sega Dreamcast
The oldest and by far the tightest target: a 200 MHz SH-4 with 16 MB of main memory, 8 MB of video memory and a PowerVR2 (CLX2) that has no programmable shading, no hardware scissor and reads textures in 16-bit formats only. The port renders through the PVR backend ("Sources/nCine/Graphics/RHI/PVR") on top of KallistiOS**, presented by the Dc window backend in 640x480 RGB565.
Toolchain
KallistiOS is built from source together with its sh-elf cross-compiler; there is no prebuilt package.
git clone https://github.com/KallistiOS/KallistiOS.git kos cd kos # Build the sh-elf (SH-4) cross-compiler make -C utils/kos-chain # The sample configuration matches the paths the toolchain just used cp doc/environ.sh.sample environ.sh source environ.sh make -j $(nproc) # zlib is required for the compressed game content and comes from kos-ports git clone https://github.com/KallistiOS/kos-ports.git ../kos-ports make -C ../kos-ports/zlib install
Edit "utils/kos-chain/Makefile.cfg" first if the compilers should not be installed into the default prefix, and "environ.sh" afterwards so KOS_BASE, KOS_CC_BASE and KOS_PORTS point at the tree that was just built.
To get a bootable disc image, mkdcdisc has to be on PATH as well:
git clone https://gitlab.com/simulant/mkdcdisc.git cd mkdcdisc meson setup build && ninja -C build sudo cp ./build/mkdcdisc /usr/local/bin/
Building
source <path to KOS>/environ.sh cmake -B ./build/dreamcast/ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_TOOLCHAIN_FILE=${KOS_BASE}/utils/cmake/kallistios.toolchain.cmake \ -D NCINE_CONTENT_DIR=$PWD/build/ConsoleContent make -j $(nproc) -C ./build/dreamcast/
"BuildDreamcast.sh" in the repository root wraps both commands (it sources environ.sh itself; override the SDK location with the KOS_BASE environment variable). It leaves NCINE_CONTENT_DIR at its default, which is the repository's own "Content" — enough to reach the main menu, but pass the parameter on the first configuration of the build directory to get a playable disc. Only "BuildPsp.sh" defaults to the staged tree on its own. Link-time optimization is force-disabled for this platform because it makes the sh-elf GCC 15.2 abort, and Release compiles with -O2 rather than -Ofast — fast-math reordering is untested on the SH-4 and code size matters here.
The build produces "build/dreamcast/jazz2.elf" and, if mkdcdisc was found, the bootable "build/dreamcast/jazz2.cdi" with the content tree included as "/cd/Content".
Deploying and running
- Emulator —
"jazz2.cdi"boots directly in Flycast, lxdream or redream**, no further preparation. - Real hardware — burn the CDI image with a tool that understands the Dreamcast's multi-session layout, or serve it from an optical-drive emulator such as GDEMU/MODE.
dc-toolover serial or a Broadband Adapter uploads"jazz2.elf"for a fast edit-run cycle, but the game reads its content from"/cd/Content/"— so a disc with the content still has to be present.
To read the log in Flycast, set Debug.SerialConsoleEnabled = yes in "flycast/emu.cfg" while the emulator is not running — it rewrites that file on exit and can silently revert the setting, which looks exactly like a game that died before main(). Startup messages go to the on-screen framebuffer console, not to serial, so a crash during initialization has to be read off the screen; the guest's serial output is block-buffered, so a crash also loses everything written since the last flush.
Limits and known issues
- Main memory is the binding constraint. The heap window between the loaded ELF and the top of RAM is roughly 13.8 MB. Exhaustion is not a clean error — it appears in the log as
Out of memory. Requested sbrk_base …followed bystd::bad_allocand an abort, which the emulator reports only as a CPU exception. Video memory has its own message,Out of PVR memory allocating …, and the render-to-texture variant of it names the target size. - The engine keeps several console-only budgets for this reason: live debris particles are capped (bursts are coarsened rather than truncated, so an explosion still looks like one), the outgoing level's assets are released before the incoming level loads, and the host copies of PVR textures are kept RLE-compressed.
- Audio costs almost nothing in main memory here, which is worth knowing on a console whose heap is the binding constraint: the AICA has its own 2 MB of sound RAM and every fully loaded sound effect lives there, not on the 13.8 MB heap. What main memory does hold is the module decoder and the three 16 KB buffers a stream is refilled through.
- The two kinds of player use two different parts of the sound processor, see nCine::
AicaAudioDevice. A sound effect is one AICA wavetable channel (two for a stereo sample, since a channel is mono — the sample is de-interleaved into one block per channel when it is uploaded), and whether it has finished is read back from the hardware rather than predicted from a timer. Music goes through KallistiOS's snd_streamdriver, of which there are only four. - A channel addresses at most 65534 samples from wherever it is pointed at, which a long sound effect can exceed (about 3 seconds of 22 kHz audio). Rather than cutting one off, nCine::
AicaAudioDevice:: uploadBuffer halves its sample rate until it fits and says so in the log — the hardware resamples every channel anyway, so the sound still plays to its end at the right pitch and only loses some high end. A sound long enough to need this more than once is really a stream. - Panning and distance attenuation are computed on the SH4 and folded into the channel volume and pan, because the hardware has no notion of a listener. Each channel does have a low-pass filter, but the KallistiOS driver switches it off and exposes no command to reach it, so nothing is muffled underwater.
- Module music is rendered at 22050 Hz rather than at the output rate: the AICA resamples every channel in hardware for free, so a higher rate would only cost the SH4 more time in the module mixer — the one place this console has no headroom to spare.
- A stream is resampled at the rate it was started with and the driver cannot change it afterwards, so pitch changes apply to sound effects only.
- Textures are 16-bit (ARGB4444, or palettized with the hardware's palette banks), the PVR has no hardware scissor (clipping is geometric), and there is no post-processing tier, so the rescale filters and the lightmap's shader path are unavailable — the lightmap is combined on the CPU instead.
- Online multiplayer is disabled; local splitscreen works.
PlayStation Portable
A 222 MHz (raised to 333 MHz at startup) MIPS Allegrex with 32 MB of memory (24 MB usable, 64 MB on the 2000/3000 models) and a Graphics Engine driven through sceGu. The port renders through the GU backend ("Sources/nCine/Graphics/RHI/GU"), presented by the Psp window backend in the console's native 480x272.
Toolchain
pspdev** ships prebuilt for Linux, macOS and Windows, can be built from source with its build-all.sh, and is also published as the pspdev/pspdev container image (which is what the CI workflow uses):
# Prebuilt SDK (see https://github.com/pspdev/pspdev/releases) tar -xJf pspdev-<platform>.tar.xz -C ~/sdk export PSPDEV=~/sdk/pspdev export PATH=$PSPDEV/bin:$PATH
Everything the game needs is in the SDK itself — zlib, libvorbis and an OpenAL Soft whose output backend drives sceAudio. Further ports are installed with psp-pacman. The CMake toolchain file is "$PSPDEV/psp/share/pspdev.cmake"; the psp-cmake wrapper passes it automatically, and $PSPDEV/bin must stay on PATH because the packaging step invokes psp-fixup-imports, mksfoex and pack-pbp from there.
Building
export PSPDEV=~/sdk/pspdev export PATH=$PSPDEV/bin:$PATH cmake -B ./build/psp/ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake \ -D NCINE_CONTENT_DIR=$PWD/build/ConsoleContent make -j $(nproc) -C ./build/psp/
"BuildPsp.sh" does this and defaults NCINE_CONTENT_DIR to "build/ConsoleContent", warning and falling back to the repository's own "Content" if that tree has not been prepared. Release compiles with -O2 for the same reasons as the other consoles — the Allegrex FPU is single-precision only.
The build produces "build/psp/jazz2.elf" and stages the complete memory-stick layout under "build/psp/ms0/":
{.shell-session} build/psp/ms0/PSP/GAME/Jazz2/EBOOT.PBP build/psp/ms0/PSP/GAME/Jazz2/Content/…
The EBOOT.PBP is packed by create_pbp_file() from the stripped ELF with MEMSIZE=1 in its SFO, which asks the firmware for the extra memory of the 2000/3000 models; a PSP-1000 keeps its 24 MB.
Deploying and running
- Real hardware — copy
"build/psp/ms0/PSP/GAME/Jazz2"onto the memory stick at exactly that path. Requires custom firmware or another way to start unsigned homebrew. - PPSSPP — copy the same directory into the emulator's configured memory stick, then start it from the game list.
The application raises the CPU to 333 MHz itself and installs its HOME-button callback before anything else can fail, so the console always has a way out even if initialization goes wrong.
Limits and known issues
- Module music is disabled. The game's soundtrack is tracker modules, and while libopenmpt does cross-compile, play correctly and cost only ~0.2 FPS in steady state, the first module a process loads costs a fixed ~29 seconds inside the library on this CPU (every later load takes ~1.8 s) — most likely because the Allegrex has no double-precision unit. There is nowhere to hide a half-minute freeze on a handheld, so
NCINE_WITH_OPENMPTis forced off; sound effects work normally. - Threads are off, so multiplayer (including local splitscreen) is compiled out and asset loading is synchronous. pspdev does ship a pthreads implementation, but the engine's threading layer also wants thread names, priorities and affinity, which do not map onto
sceKernelCreateThreadas they stand. - The underwater and pause low-pass filters are absent, because pspdev's OpenAL has no
efx.h. - Textures are limited to 512 pixels per axis, so larger atlases are split into pages with per-primitive page selection; palettized textures use the hardware CLUT, of which only one is resident at a time, so the palette is part of the batch key.
- The GE has no post-texture additive term and no combiner output scale, which is why an
offset_colorpass expands into a modulated draw plus an additive silhouette, and why theMODULATE_X2/MODULATE_X4presets are rejected for this target.
Nintendo Wii
A 729 MHz PowerPC (Broadway) with 24 MB of fast MEM1 and 64 MB of MEM2, and the Hollywood GPU — a fixed-function design with a 16-stage TEV combiner. The port renders through the GX backend ("Sources/nCine/Graphics/RHI/GX") on libogc, presented by the Ogc window backend, which adopts whatever video mode the console prefers (PAL 50 Hz or NTSC/EDTV 60 Hz, interlaced or progressive).
Toolchain
# Install devkitPro's pacman, then the Wii group and zlib sudo dkp-pacman -S --needed wii-dev ppc-zlib export DEVKITPRO=/opt/devkitpro
ppc-zlib is not part of the wii-dev group but is required for the compressed game content. The toolchain file is "$DEVKITPRO/cmake/Wii.cmake".
Building
cmake -B ./build/wii/ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/Wii.cmake \ -D NCINE_CONTENT_DIR=$PWD/build/ConsoleContent make -j $(nproc) -C ./build/wii/
"BuildWii.sh" wraps this (override the SDK location with the DEVKITPRO environment variable), with the same NCINE_CONTENT_DIR caveat as the Dreamcast script above. Beside "build/wii/jazz2.dol" the build stages the SD card layout the Homebrew Channel expects, so its contents can be copied to a card as they are:
{.shell-session} build/wii/sd/apps/Jazz2/boot.dol build/wii/sd/apps/Jazz2/Content/…
Deploying and running
- Real hardware — copy the contents of
"build/wii/sd/"to the root of an SD card or USB storage device and launch Jazz² Resurrection from the Homebrew Channel. An"apps/Jazz2/meta.xml"is optional; without it the entry is listed under its directory name. - Dolphin — build a FAT32 image from the same staging tree and enable the virtual SD card:
truncate -s 512M sd.raw && mkfs.vfat -F 32 sd.raw mcopy -i sd.raw -s ./build/wii/sd/apps ::/ # Dolphin.ini: [Core] WiiSDCard = True, WiiSDCardPath = <path to sd.raw> flatpak run org.DolphinEmu.dolphin-emu -e ./build/wii/jazz2.dol -b
For the log, set a USB Gecko in memory-card slot B (SlotB = 7 in Dolphin.ini) and read the TCP socket Dolphin opens for it on port 55020. The adapter is probed once at startup, so a build without one behaves normally and simply writes nothing. Frame dumps are the practical way to check rendering: DumpXFBTarget = True in "GFX.ini" writes every displayed frame as a PNG.
Limits and known issues
- Sound goes through libogc's ASND, not OpenAL — devkitPro ships no OpenAL for PowerPC. The DSP mixes up to 16 voices at 48 kHz and resamples each one, so the voice count rather than CPU time is the limit; that is also the source count the engine advertises, well below the 64 it uses on desktop. Panning and distance attenuation are computed on the CPU and folded into the per-voice volume, because ASND has no notion of a listener — see nCine::AsndAudioDevice::computeVolume.
- ASND holds only two buffers per voice (one playing, one queued) where a streaming player expects an OpenAL-style queue, so the queue itself is kept in the backend and fed into the voice one buffer at a time from nCine::
AsndAudioDevice:: updatePlayers. - Sample data is read by the DSP straight out of main memory by DMA, so every buffer is 32-byte aligned and padded. Unlike the Dreamcast, there is no separate sound RAM — a level's sound effects come out of the same heap as everything else, which is worth remembering on the GameCube in particular.
- There is no filter stage on the DSP, so nothing is muffled underwater.
- Online multiplayer is disabled; local splitscreen works.
- No post-processing tier, so no rescale filters; the lightmap is combined on the CPU.
- Textures are converted to the GX formats (including CI8 with a hardware TLUT for palettized ones) and the effect passes are expressed as TEV stages with swap tables and a KONST colour, all generated from the
.shaderfiles. - Palette data is byte-swapped for the big-endian CPU on load — worth knowing when comparing a palette dump with the desktop build.
Nintendo GameCube
The Wii's predecessor and the same code path: a 486 MHz Gekko and the Flipper GPU, driven by the same GX backend and Ogc window backend. The one difference that matters in practice is memory — 24 MB of main RAM and no MEM2, which makes it the tighter of the two PowerPC targets — and that its storage is an SD card in a memory-card slot.
Toolchain
sudo dkp-pacman -S --needed gamecube-dev ppc-zlib export DEVKITPRO=/opt/devkitpro
As on the Wii, ppc-zlib is not part of the console's package group. The toolchain file is "$DEVKITPRO/cmake/GameCube.cmake".
Building
cmake -B ./build/gamecube/ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/GameCube.cmake \ -D NCINE_CONTENT_DIR=$PWD/build/ConsoleContent make -j $(nproc) -C ./build/gamecube/
"BuildGameCube.sh" wraps this, again without NCINE_CONTENT_DIR. The staged layout differs from the Wii's, because the GameCube has no Homebrew Channel and reads the card directly:
{.shell-session} build/gamecube/sd/Jazz2/Jazz2.dol build/gamecube/sd/Jazz2/Content/…
Deploying and running
- Real hardware — copy the contents of
"build/gamecube/sd/"to an SD card in an SD Gecko** (memory-card slot A) or an SD2SP2 adapter, and start"Jazz2/Jazz2.dol"from Swiss. The game reads its content from"carda:/Jazz2/Content/", which is slot A by definition. - Dolphin — attach an SD adapter to slot A and point it at a FAT32 image built exactly like the Wii one above. Keep slot B free for the USB Gecko if the log is needed; the game writes its trace to EXI channel 1, which is slot B.
Limits and known issues
Everything in Limits and known issues applies, with two additions:
- Memory is much tighter than on the Wii — 24 MB total, with no second pool to fall back on. The console budgets described for the Dreamcast are active here as well, and audio is one more claim on that pool here: the ASND backend keeps every decoded sound effect in main memory for the DSP to read by DMA.
- There is no Bluetooth, so only GameCube controllers are read (the Wii build additionally links
wiiuseandbtefor Wii Remotes).
PS Vita
The Vita is a shader platform, and the only console here with a choice of two hardware backends. Its GPU is a PowerVR SGX543MP4+ driven by sceGxm, the console's own graphics API; the window and input come from SDL2, so the build differs from a desktop one mostly in its packaging.
NCINE_PREFERRED_RHI | What it drives |
|---|---|
GXM (the default) | sceGxm directly. A full-pipeline backend — unlike the fixed-function consoles above, the SGX is a unified-shader part, so the whole bloom / lighting / combine chain runs. What it removes compared to the OpenGL path on the same console is the translation layer between the engine and sceGxm, nothing else |
OpenGL | vitaGL, an OpenGL|ES 2.0 implementation layered on that very same sceGxm. Force-enables the OpenGL|ES path and the strict ES 2.0 profile |
Software | The CPU rasterizer, for comparison and bring-up |
Toolchain
Install VitaSDK with vdpm (see vitasdk.org) and export VITASDK, then use "$VITASDK/share/vita.toolchain.cmake".
Building
export VITASDK=/usr/local/vitasdk cmake -B ./build/vita/ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake make -j $(nproc) -C ./build/vita/
The native GXM backend is what this builds; add "-D NCINE_PREFERRED_RHI=OpenGL" for the vitaGL one instead. Either way the console needs "libshacccg.suprx", see The console needs a firmware module.
"BuildVita.sh" currently configures a Debug build with synchronous tracing enabled, which is what a bring-up build wants; edit it or use the command above for a release build. The result is "build/vita/jazz2.vpk".
Deploying and running
Install the VPK with VitaShell (or over FTP). It carries the "Content" directory with it, which the firmware unpacks into the application's own read-only directory ("ux0:/app/jazz20000/", mounted as "app0:"), so nothing else has to be copied for the game to start.
Unlike the four consoles above, the Vita converts the original game data itself on first run, so copy an original installation to "ux0:/data/jazz2/Source/" and let it build "ux0:/data/jazz2/Cache/" — or prepare the tree with AssetPacker anyway to skip the wait. Both of those live on "ux0:" because "app0:" cannot be written to; reinstalling the VPK replaces the content but leaves the cache, the converted source data and the settings alone.
The console needs a firmware module
Shaders are compiled on the console, whichever backend is selected, because the VitaSDK ships no offline shader compiler for the platform: the only one is "libshacccg.suprx", part of the console's own firmware. Extract it with VitaShaRK's instructions and place it in "ur0:/data/".
GXMconsumes GXP binaries, so it ships its shaders as Cg source (the generated"CgGeneratedShaders.h", see The Cg dialect (PS Vita)) and compiles them through vitaShaRK when a program links. Without the module the backend refuses to start and says exactly that in the log.OpenGLhands GLSL toglCompileShader(), and vitaGL compiles it through the very same SceShaccCg. That is also what lets external".shader"files and the rescale filters work on this platform, unlike the fixed-function consoles above.
Limits and known issues
- The ES 2.0 profile has no uniform buffer objects and no
gl_VertexID, so its shaders come from the ESSL 100 lowering that ShaderCompiler bakes into the same generated headers, see Platform notes. TheGXMbackend has the same missing vertex-ID input and reuses the very same rewrite, so both read the quad corner from a vertex attribute instead. - Under Vita3K the
GXMbackend draws a one-pixel seam where a repeating texture wraps — most visibly along the scrolling textured background. The wrap is set up correctly for the hardware (a power-of-two, tiled colour-surface texture whose control words read back withSCE_GXM_TEXTURE_ADDR_REPEATon both axes) and the emulator translates sceGxm onto desktop OpenGL, where such a texture repeats without any restriction, so the join is expected to be absent on a console. No workaround is applied for it: emulating one in the shared shader would cost every other backend and could not blend across the join anyway. - Online multiplayer is unavailable: ENet has no Vita arm (it needs
"sys/ioctl.h", which the SDK does not provide), soWITH_ONLINE_MULTIPLAYERis forced off. Local (split-screen) multiplayer is unaffected. - Asynchronous tracing is disabled; threads are available.
- Sound depends on the SDK providing an OpenAL implementation — without one, audio is compiled out exactly as on the fixed-function consoles.
Nintendo Switch
Also a shader platform, built with devkitA64 and using SDL2 and desktop OpenGL. Install the switch-dev group and use "$DEVKITPRO/cmake/Switch.cmake":
sudo dkp-pacman -S --needed switch-dev cmake -B ./build/switch/ -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/Switch.cmake \ -D DEATH_TRACE_ASYNC=OFF make -j $(nproc) -C ./build/switch/
DEATH_TRACE_ASYNC has to be off — it crashes on startup on this platform. The build produces "build/switch/jazz2.nro" with the shipped game content embedded in its RomFS, so the application is self-contained: copy the .nro to "sd:/switch/" and start it from hbmenu. Like the Vita, the Switch converts the original game data itself — put an original installation into "sdmc:/Games/Jazz2/Source/" and the converted cache and save data appear next to it in "sdmc:/Games/Jazz2/".
Libretro core
Not a console target in itself, but the other way to reach a console-like device: with NCINE_BUILD_LIBRETRO=ON the game builds as a libretro core ("jazz2_libretro.so", sources in "Sources/nCine/Backends/Libretro") that RetroArch drives, with no window backend of its own. NCINE_PREFERRED_RHI accepts Software (the CPU rasterizer's framebuffer is handed to retro_video_refresh, which works on every frontend) or OpenGL (rendering into the frontend's FBO through SET_HW_RENDER, targeting OpenGL|ES 3.0 as the common denominator of RetroArch's GPU platforms).
cmake -B ./build/libretro/ -D CMAKE_BUILD_TYPE=Release \ -D NCINE_BUILD_LIBRETRO=ON -D NCINE_PREFERRED_RHI=Software make -j $(nproc) -C ./build/libretro/
Continuous integration
Every console is built on each push by its own workflow in ".github/workflows", all of them inside the SDK's container image, and each uploads a ready-to-deploy package as an artifact:
| Workflow | Container image | Artifact |
|---|---|---|
dreamcast.yml | pcercuei/dreamcast-toolchain (compilers only — KOS, kos-ports and mkdcdisc are built in the job and cached) | Jazz2.cdi + Jazz2.elf |
psp.yml | pspdev/pspdev | the ms0/ memory-stick tree with EBOOT.PBP |
wii.yml | devkitpro/devkitppc | the sd/ card tree with apps/Jazz2/boot.dol |
gamecube.yml | devkitpro/devkitppc | the sd/ card tree with Jazz2/Jazz2.dol |
vita.yml | vitasdk/vitasdk | Jazz2.vpk |
switch.yml | devkitpro/devkita64 | Jazz2.nro |
The KallistiOS, kos-ports and mkdcdisc revisions in dreamcast.yml are pinned to specific commits and their build output is cached against those commits: an unpinned clone of KOS master can otherwise break the workflow — or silently change the runtime — without any change in this repository. The workflows build against the repository's own "Content" (they cannot ship converted original game data), so their artifacts boot into the main menu and need a content tree prepared as described in Preparing the game content to actually play a level.
Troubleshooting
- The game starts, but no episode can be played. The content tree is missing. The four data-prepared consoles mark their content as verified without checking it, so this is what a missing or misplaced
"Content"directory looks like; check the path for the console in Preparing the game content. - Text is missing, tilesets look wrong, or a level refuses to load. Almost always a stale content tree — regenerate it with an AssetPacker built from the current sources. On the Wii and GameCube also make sure the card really was rewritten; an emulator's SD image and a physical card both keep an old copy convincingly.
- No CDI image after a Dreamcast build.
mkdcdiscwas not onPATHwhen CMake configured the build directory, see Building. - An empty log. Each console needs its receiver enabled — the serial console in Flycast's configuration, a USB Gecko in slot B on the PowerPC consoles, PPSSPP's stdout. On the Dreamcast and the PSP the startup messages go to the screen rather than to the log, which is exactly the window in which a crash would otherwise be invisible.
std::bad_alloc, ansbrkmessage, or a CPU exception with no explanation on the Dreamcast or GameCube is main memory running out;Out of PVR memory allocating …on the Dreamcast is video memory, see Limits and known issues.- PPSSPP runs an old build. It boots from its own configured memory stick, see Deploying and running.
- CMake cannot find the compiler on the Dreamcast.
"environ.sh"was not sourced in that shell, see Toolchain. - A fully loaded sound is cut short on the Dreamcast. An AICA channel addresses 65534 samples at most, see Limits and known issues.
- Sound effects are noise on the Wii or GameCube. The buffer formats are native-endian, so a reader that emits little-endian samples on a big-endian console produces exactly this, see Audio backends.