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.

SystemToolchain (CMake toolchain file)RendererWindow/inputAudioDetails
AmigaOS 3.x (m68k)AmigaPorts' m68k-amigaos-gcc (amiga.cmake)Softwareown backend (Intuition + RTG)AHIAmigaOS 3.x (m68k)
AmigaOS 4.1 (PowerPC)adtools (os4.cmake)LegacyGL (MiniGL)SDL2OpenAL or SDLAmigaOS 4.1 (PowerPC)
MorphOS (PowerPC)MorphOS SDK (morphos.cmake)LegacyGL (TinyGL)SDL2SDL (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.

PropertyValue
ToolchainAmigaPorts' m68k-amigaos-gcc (GCC 16.2 prebuilt, cmake/toolchains/amiga.cmake)
C runtimelibnix (-mcrt=nix20)
Rendering backendSoftware, 16-bit surfaces (any other value is a configure error)
Window/input backendAmiga (Sources/nCine/Backends/Amiga/): Intuition screen, IDCMP input, lowlevel.library gamepads
Audio backendAHI (Sources/nCine/Audio/Backends/Amiga/): software mixer into ahi.device
Musiclibxmp (NCINE_WITH_XMP, downloaded and built by CMake configure), reads .j2b directly
Build artifactbuild/amiga/dist/: Jazz2 executable + Content/

What it runs on

MachineVerdict
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 FPUNot 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/m68k-amigaos-gcc release (Linux x86_64, macOS arm64). GCC 13 is the minimum.

# 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=68060 with -m68881: the CPU flag alone means soft-float, which makes all float math libgcc calls and makes the hard-float libnix libm (libm020/libm881/libm.a) return garbage from fminf(), fmaxf(), rint(), nearbyint(). -print-multi-directory printing . for this combination is harmless.
  • -fno-optimize-sibling-calls is required for correctness: binutils resolves tail calls to weak symbols to the wrong address. -fno-weak is not an alternative, it breaks linking of template statics such as Vector2f::Zero.
  • -std=gnu++17 is pinned because GCC 16 defaults to C++20, where <math.h> brings std::lerp into the global namespace and clashes with nCine::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::thread on 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:

PresetPixel budgetMixing rate
Ultra640×51244100 Hz
High640×36044100 Hz
Medium384×30022050 Hz
Low320×22022050 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.

VariableEffect
NCINE_PRESETlow, medium, high or ultra: forces the preset.
NCINE_NO_AMMXAny 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 for SetPatch. Bare Workbench 3.1 lacks it and fails with Recoverable Alert 0006 8881. The 68080 and Emu68 need nothing.
  • RTG only, no chunky-to-planar path.
  • No threads (WITH_THREADS off): 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 .mo3 tracks are silent (as on PSP, see Limits and known issues).
  • ahi.device queueing differs between implementations (AROS 68k never plays ahir_Link requests and stalls a drained queue). The backend detects linking at startup and restarts a queue idle for a second. The Audio device initialized: … log line shows the mode.
  • libnix gaps: exp2, exp2f, log2f, strnlen, wcstoul, flock, fchmod, getpwuid, realpath, gethostname and __xpg_strerror_r are provided by Sources/nCine/Backends/Amiga/AmigaLibcCompat.c. Don't add replacements for round, trunc, fmin, fmax, rint or nearbyint, 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_combine but no framebuffer objects, so the copy-back render-target path is compiled in and FBOs compiled out (RHI_LEGACYGL_HAS_FBO in LegacyGlApi.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=disable under enforcing SELinux.
  • The adtools prefix defaults to /opt/ppc-amigaos. Set OS4_INST to 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's libpthread (-athread=pthread can't be used: gthr-amigaos-pthread.o isn't shipped). Thread affinity, names, barriers and pthread_kill() are compiled out.
  • libatomic is linked explicitly for 64-bit atomics on 32-bit PowerPC.
  • Keep -Uamiga in the machine flags: the predefined lowercase amiga macro breaks identifiers such as libopenmpt's common_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_NPOT forces padding.
  • Render targets use an FBO if one works at startup, otherwise draw into the back buffer and copy with glCopyTexSubImage2D(). NCINE_NO_FBO forces 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_PPCINLINE is defined because the ppcinline macros (e.g. bind) rewrite std::bind in libstdc++. morphos-compat.h declares what that leaves out (e.g. bzero for FD_ZERO).
  • proto/exec.h and proto/usergroup.h are skipped via their include guards, since their global struct Task makes the engine's Task<...> ambiguous. FindTask() (for getpid()) is declared by hand with an opaque return type.
  • Threads are off: <pthread.h> brings struct Task back, so the port is single-threaded.
  • __STDC_LIMIT_MACROS is defined to get INT32_MIN and friends.
  • realpath(), fsync() and flock() are compiled out, while wcstoul and __throw_bad_array_new_length are provided in Sources/nCine/Backends/MorphOS/MorphOSLibcCompat.cpp.
  • TinyGL globals TinyGLBase and __tglContext must live in the program: the link forces -u _CSTP_init_TinyGLBase, and Sources/nCine/Backends/MorphOS/MorphOSTinyGl.cpp points __tglContext at the SDL_GL_CreateContext() context.
  • No gethostname(), getpwuid() or libcurl: any reference to bsdsocket.library or usergroup.library would stop the game from starting without a TCP/IP stack. So there is no update check, and no online multiplayer.