Building the project for Amiga systems
Guide how to build, deploy and run Jazz² Resurrection on AmigaOS 3.x, AmigaOS 4.1 and MorphOS.
None of the three supported Amiga systems has graphics hardware the game's shader pipeline can use. Classic machines render with the CPU Software rasterizer. The two PowerPC systems use the fixed-function LegacyGL backend (TinyGL on MorphOS, MiniGL over Warp3D on AmigaOS 4, see Hardware rendering through TinyGL) by default, with Software still selectable for a card without a 3D driver.
| System | Toolchain (CMake toolchain file) | Renderer | Window/input | Audio | Details |
|---|---|---|---|---|---|
| AmigaOS 3.x (m68k) | AmigaPorts' m68k-amigaos-gcc (amiga.cmake) | Software | own backend (Intuition + RTG) | AHI | AmigaOS 3.x (m68k) |
| AmigaOS 4.1 (PowerPC) | adtools (os4.cmake) | LegacyGL (MiniGL) | SDL2 | OpenAL or SDL | AmigaOS 4.1 (PowerPC) |
| MorphOS (PowerPC) | MorphOS SDK (morphos.cmake) | LegacyGL (TinyGL) | SDL2 | SDL (OpenAL if built) | MorphOS (PowerPC) |
The design document, including the hardware research behind the classic port, is Docs/AmigaPortDesign.md.
AmigaOS 3.x (m68k)
One executable covers PiStorm/Emu68, Apollo Vampire and 68060 machines with an RTG card. It presents into a Picasso96/CyberGraphX chunky framebuffer. The amount of work is chosen at startup by measuring the machine, see Performance presets and autodetection.
| Property | Value |
|---|---|
| Toolchain | AmigaPorts' m68k-amigaos-gcc (GCC 16.2 prebuilt, cmake/toolchains/amiga.cmake) |
| C runtime | libnix (-mcrt=nix20) |
| Rendering backend | Software, 16-bit surfaces (any other value is a configure error) |
| Window/input backend | Amiga (Sources/nCine/Backends/Amiga/): Intuition screen, IDCMP input, lowlevel.library gamepads |
| Audio backend | AHI (Sources/nCine/Audio/Backends/Amiga/): software mixer into ahi.device |
| Music | libxmp (NCINE_WITH_XMP, downloaded and built by CMake configure), reads .j2b directly |
| Build artifact | build/amiga/dist/: Jazz2 executable + Content/ |
What it runs on
| Machine | Verdict |
|---|---|
| PiStorm / Emu68 (Pi3A+, CM4, Pi4) | Primary target. |
| Apollo Vampire V4 / V1200 (68080 + SAGA) | Supported, AMMX kernels enabled automatically. Expect reduced settings. |
| 68060 + RTG card (Picasso IV, ZZ9000, …) | The floor: Low preset, experimental. |
| 68030/68040, or no FPU | Not supported: refuses to start without an FPU. |
| Stock AGA (no RTG) | Not supported. |
An RTG screen mode is required. Plan for at least 8 MB of fast RAM.
Toolchain
Use the prebuilt, relocatable AmigaPorts/
# The archive's directory is the prefix; unpack it anywhere curl -fsSLO https://github.com/AmigaPorts/m68k-amigaos-gcc/releases/download/v16.2-rc10/m68k-amigaos-gcc-16.2-rc10-x86_64-linux.tar.xz tar xJf m68k-amigaos-gcc-16.2-rc10-x86_64-linux.tar.xz -C /opt export AMIGA_INST=/opt/m68k-amigaos-gcc-16.2
The release already contains everything needed: NDK 3.2, the CyberGraphX and AHI headers, vasm (vasmm68k_mot, needed only for NCINE_WITH_AMMX) and zlib 1.3.2 for every multilib.
Flags in the toolchain file that must stay
-mcpu=68060with-m68881: the CPU flag alone means soft-float, which makes all float math libgcc calls and makes the hard-float libnixlibm(libm020/libm881/libm.a) return garbage fromfminf(),fmaxf(),rint(),nearbyint().-print-multi-directoryprinting.for this combination is harmless.-fno-optimize-sibling-callsis required for correctness: binutils resolves tail calls to weak symbols to the wrong address.-fno-weakis not an alternative, it breaks linking of template statics such asVector2f::Zero.-std=gnu++17is pinned because GCC 16 defaults to C++20, where<math.h>bringsstd::lerpinto the global namespace and clashes withnCine::lerp.
It also force-includes cmake/toolchains/amiga-libstdc++-c99.h (as on PS3), because this libstdc++ lacks _GLIBCXX_USE_C99_MATH_TR1 and <cmath> declares no C99 math in std::.
Preparing the content tree
Source and Cache sit next to the executable, so an original Jazz Jackrabbit 2 copied into Source is converted on first start, but that is very slow on a 68060. Normally point the build at a tree prebaked on the host with AssetPacker:
cmake -B ./build/amiga/ -D CMAKE_TOOLCHAIN_FILE=./cmake/toolchains/amiga.cmake \ -D CMAKE_BUILD_TYPE=Release -D NCINE_CONTENT_DIR=./build/ConsoleContent
With NCINE_CONTENT_DIR pointing at the repository's Content instead, only base assets are included and the game shows "content missing" until an original installation is placed in Source.
Building
export AMIGA_INST=/opt/amiga export PATH="$AMIGA_INST/bin:$PATH" cmake -B ./build/amiga/ -D CMAKE_TOOLCHAIN_FILE=./cmake/toolchains/amiga.cmake \ -D CMAKE_BUILD_TYPE=Release -D NCINE_CONTENT_DIR=./build/ConsoleContent cmake --build ./build/amiga/ --parallel $(nproc)
AMIGA_INST is the toolchain prefix. Configure fails unless $AMIGA_INST/bin/m68k-amigaos-gcc, the NDK headers and zlib are there (see Toolchain). The AmigaOS 3.x job in .github/workflows/amiga.yml runs the same recipe unattended.
Copy build/amiga/dist/ to the Amiga. Jazz2.config is written into the same directory.
- Link-time optimization is off (
NCINE_LINKTIME_OPTIMIZATION) and Release uses-O2, not-O3. - Threads are off (no
std::threadon m68k-amigaos): everything, including audio decoding, runs on the main thread.
Deploying and running
Copy dist/ anywhere on a hard drive and run it from a Shell or its icon. It opens its own screen. The 1 MB stack comes from the executable's __stack cookie, so Stack isn't needed (Stack 1048576 in a script does no harm).
For development, use FS-UAE with AROS 68k (ships CyberGraphX and AHI):
# jazz2.fs-uae amiga_model = A4000 cpu = 68040 jit_compiler = 0 kickstart_file = /path/to/aros/boot/amiga/aros-rom.bin hard_drive_0 = /path/to/aros/dh0 hard_drive_1 = /path/to/jazz2/build/amiga/dist
The log goes to stdout unbuffered, so redirect it to keep a full trace across crashes. A -DDEATH_DEBUG=ON build also accepts /level <episode>/<level> to boot straight into a level:
Jazz2:Jazz2 >Jazz2:jazz2-log.txt Jazz2:Jazz2 /level prince/castle1 >Jazz2:jazz2-log.txt
Performance presets and autodetection
At startup AmigaPlatform::RunPerformanceProbe() times a 16-bit blend loop and an FPU chain and picks a preset:
| Preset | Pixel budget | Mixing rate |
|---|---|---|
Ultra | 640×512 | 44100 Hz |
High | 640×360 | 44100 Hz |
Medium | 384×300 | 22050 Hz |
Low | 320×220 | 22050 Hz |
The RTG mode chosen is the one with the largest logical resolution (720×405-clamped aspect fit) within the budget, so 640×400 can win over 640×480. The probe prints … ns/composited pixel, … ns/FPU multiply-add to the log on every boot. Under emulation use it only to compare two builds, never as an absolute figure.
| Variable | Effect |
|---|---|
NCINE_PRESET | low, medium, high or ultra: forces the preset. |
NCINE_NO_AMMX | Any value: use the scalar kernels on an Apollo 68080 (diagnostic only, output is bit-identical). |
Limits and known issues
- FPU required. Without one the game exits with a message. On a 68040/68060
Libs:68040.library(resp.68060.library) must be installed forSetPatch. Bare Workbench 3.1 lacks it and fails withRecoverable Alert 0006 8881. The 68080 and Emu68 need nothing. - RTG only, no chunky-to-planar path.
- No threads (
WITH_THREADSoff): audio streaming decodes on the main thread and multiplayer client/server aren't built. - Music is libxmp (
cmake/Findlibxmp.cmake): it can't read MO3, so the four.mo3tracks are silent (as on PSP, see Limits and known issues). ahi.devicequeueing differs between implementations (AROS 68k never playsahir_Linkrequests and stalls a drained queue). The backend detects linking at startup and restarts a queue idle for a second. TheAudio device initialized: …log line shows the mode.- libnix gaps:
exp2,exp2f,log2f,strnlen,wcstoul,flock,fchmod,getpwuid,realpath,gethostnameand__xpg_strerror_rare provided bySources/nCine/Backends/Amiga/AmigaLibcCompat.c. Don't add replacements forround,trunc,fmin,fmax,rintornearbyint, because libnix's are correct with hard-float (see Flags in the toolchain file that must stay).
AmigaOS 4.1 (PowerPC)
Window and input use the desktop SDL2 backend. Audio is OpenAL Soft (AHI backend, linked statically, and the SDK's libopenal.so is ignored) when the toolchain image carries it (images from October 2026 on), otherwise the SDL backend (Sources/nCine/Audio/Backends/SDL/, software mixer into SDL_QueueAudio()). -D CMAKE_DISABLE_FIND_PACKAGE_OpenAL=ON forces the SDL fallback.
Hardware rendering through MiniGL
The renderer is LegacyGL on MiniGL (libGL.a + minigl.library), the same backend as MorphOS (see Hardware rendering through TinyGL), with two differences:
- MiniGL has
GL_EXT_texture_env_combinebut no framebuffer objects, so the copy-back render-target path is compiled in and FBOs compiled out (RHI_LEGACYGL_HAS_FBOinLegacyGlApi.h). - No mirrored repeat: that wrap mode degrades to plain repeat.
Warp3D Nova's ogles2.library would fit the ES2 profile (NCINE_RHI_GL_PROFILE=ES2), but the SDK has no GLES2 headers or link library for it. Use -D NCINE_PREFERRED_RHI=Software on a machine with no 3D driver.
Toolchain and building
adtools (ppc-amigaos-gcc 11.5) and the AmigaOS 4.1 SDK come in one container image. Mount the repository at its own path:
docker run --rm -v "$PWD:$PWD" -w "$PWD" docker.io/walkero/amigagccondocker:os4-gcc11 bash -lc ' cmake -B ./build/os4/ -D CMAKE_TOOLCHAIN_FILE=./cmake/toolchains/os4.cmake \ -D CMAKE_BUILD_TYPE=Release -D NCINE_CONTENT_DIR=./build/ConsoleContent cmake --build ./build/os4/ --parallel $(nproc)'
- Podman takes the same arguments. Add
--security-opt label=disableunder enforcing SELinux. - The adtools prefix defaults to
/opt/ppc-amigaos. SetOS4_INSTto use a host installation. - Output:
build/os4/dist/(Jazz2+Content/, stripped). - CI: the container job in
.github/workflows/amiga.yml.
What differs from a desktop build
- No online multiplayer: the SDK's bsdsocket has no IPv6 (ENet needs it) and no
<poll.h>(IXWebSocket needs it). Local splitscreen works. - Threads are on, using
-athread=native+ the SDK'slibpthread(-athread=pthreadcan't be used:gthr-amigaos-pthread.oisn't shipped). Thread affinity, names, barriers andpthread_kill()are compiled out. libatomicis linked explicitly for 64-bit atomics on 32-bit PowerPC.- Keep
-Uamigain the machine flags: the predefined lowercaseamigamacro breaks identifiers such as libopenmpt'scommon_encoding::amiga.
MorphOS (PowerPC)
Same setup as AmigaOS 4: SDL2 window/input, SDL audio and LegacyGL on TinyGL (Hardware rendering through TinyGL), with a different toolchain and its own quirks.
Toolchain and building
The MorphOS SDK (ppc-morphos-gcc 11.3, at /gg) is a container image too. It ships no zlib, which the game requires, so build it first:
docker run --rm -v "$PWD:$PWD" -w "$PWD" -e MORPHOS_DEPS=/tmp/morphos-deps \ docker.io/walkero/amigagccondocker:mos-gcc-amd64 bash -lc ' curl -fsSL https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz | tar xz -C /tmp cd /tmp/zlib-1.3.1 CC=ppc-morphos-gcc CFLAGS="-O2 -noixemul" ./configure --prefix=$MORPHOS_DEPS --static make -j $(nproc) libz.a && make install cd '"$PWD"' cmake -B ./build/morphos/ -D CMAKE_TOOLCHAIN_FILE=./cmake/toolchains/morphos.cmake \ -D CMAKE_BUILD_TYPE=Release -D NCINE_CONTENT_DIR=./build/ConsoleContent cmake --build ./build/morphos/ --parallel $(nproc)'
MORPHOS_DEPS is where the toolchain file finds cross-compiled dependencies. MORPHOS_SDK points at an SDK outside /gg. Output goes to build/morphos/dist/. CI runs the same steps in .github/workflows/amiga.yml.
For OpenAL audio, cross-compile BSzili's openal-soft port (from its sources on MorphOS Storage, not the prebuilt library, which targets an older C runtime) into MORPHOS_DEPS before configuring. Otherwise the SDL backend is used.
Running it
Install sdl2.library into LIBS: ("SDL 2.32.11 Libraries" on MorphOS Storage). The SDK's libSDL2.a is only a stub and without the library the loader stops the game before main(). No TCP/IP stack is needed.
Without hardware, QEMU's Pegasos II boots the MorphOS ISO:
# boot.img is extracted from the top level of the MorphOS ISO qemu-system-ppc -machine pegasos2 -m 1024 -rtc base=localtime -device ati-vga,romfile="" -cdrom morphos-3.20.iso -kernel boot.img -serial stdio
Hardware rendering through TinyGL
TinyGL (tinygl.library, on Radeon, Voodoo and Permedia cards) is OpenGL 1.x with 1.3 combiners, FBOs and vertex arrays but no shaders. LegacyGL (Sources/nCine/Graphics/RHI/LegacyGL/) is the default on both PowerPC Amigas. For a card without a 3D driver use the CPU rasterizer:
cmake -B ./build/morphos/ -D CMAKE_TOOLCHAIN_FILE=./cmake/toolchains/morphos.cmake \ -D CMAKE_BUILD_TYPE=Release -D NCINE_PREFERRED_RHI=Software
Like the Dreamcast and PSP backends, it uses the transpiled fixed_function effects (Shaders/Generated/LegacyGlGeneratedEffects.h) as GL_COMBINE programs and runs the direct tier: the scene goes straight to the display at logical resolution with the CPU lightmap composited by the device.
- Indexed textures are baked through their palette row into RGBA8 before upload (no
GL_EXT_paletted_texture). - Power-of-two padding is used where NPOT isn't supported. NPOT support and
GL_MAX_TEXTURE_SIZE(capped at 2048) are probed at startup. Tileset atlases are 15 tiles wide in 512-texel chunks. Larger prebaked textures are split into pages.NCINE_NO_NPOTforces padding. - Render targets use an FBO if one works at startup, otherwise draw into the back buffer and copy with
glCopyTexSubImage2D().NCINE_NO_FBOforces the copy path.
What differs from a desktop build
The SDK's old Amiga headers clash with C++. The workarounds live in cmake/toolchains/morphos.cmake and cmake/toolchains/morphos-compat.h:
_NO_PPCINLINEis defined because theppcinlinemacros (e.g.bind) rewritestd::bindin libstdc++.morphos-compat.hdeclares what that leaves out (e.g.bzeroforFD_ZERO).proto/exec.handproto/usergroup.hare skipped via their include guards, since their globalstruct Taskmakes the engine'sTask<...>ambiguous.FindTask()(forgetpid()) is declared by hand with an opaque return type.- Threads are off:
<pthread.h>bringsstruct Taskback, so the port is single-threaded. __STDC_LIMIT_MACROSis defined to getINT32_MINand friends.realpath(),fsync()andflock()are compiled out, whilewcstouland__throw_bad_array_new_lengthare provided inSources/nCine/Backends/MorphOS/MorphOSLibcCompat.cpp.- TinyGL globals
TinyGLBaseand__tglContextmust live in the program: the link forces-u _CSTP_init_TinyGLBase, andSources/nCine/Backends/MorphOS/MorphOSTinyGl.cpppoints__tglContextat theSDL_GL_CreateContext()context. - No
gethostname(),getpwuid()or libcurl: any reference tobsdsocket.libraryorusergroup.librarywould stop the game from starting without a TCP/IP stack. So there is no update check, and no online multiplayer.