From a1b0ee7ec2a5c5334907f3fd68c24f1b421d2511 Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 17:11:45 +0300 Subject: [PATCH 1/7] ApolloLib in toolchain/apollolib/, with its known bugs Describe ApolloLib (ApolloCrossDev Projects/ApolloLib, commit ce1fcbf, after the merge of the two versions in ApolloCrossDev #16 and #17): how a project uses it, sound on Arne, pictures, PIP and the pointer, input, memory and blit routines, system and timing, and the serial debug output. toolchain/pitfalls/apollolib-known-bugs.md lists what reading the code found, among them the 320-pixel fall-through in ApolloShowPicture, the VBlank server of ApolloTimer that is never removed, and the takeover helpers without restore. Co-Authored-By: Claude Opus 5.5 --- .github/CODEOWNERS | 8 ++ .../prefer-library-calls-for-saga-chips.md | 5 +- toolchain/apollolib/debug.md | 92 +++++++++++++ toolchain/apollolib/index.md | 120 +++++++++++++++++ toolchain/apollolib/input.md | 98 ++++++++++++++ toolchain/apollolib/memory-and-blit.md | 88 +++++++++++++ toolchain/apollolib/picture.md | 121 ++++++++++++++++++ toolchain/apollolib/sound.md | 106 +++++++++++++++ toolchain/apollolib/system-and-timing.md | 82 ++++++++++++ toolchain/index.md | 4 + toolchain/pitfalls/apollolib-known-bugs.md | 90 +++++++++++++ toolchain/project-makefile.md | 3 +- 12 files changed, 815 insertions(+), 2 deletions(-) create mode 100644 toolchain/apollolib/debug.md create mode 100644 toolchain/apollolib/index.md create mode 100644 toolchain/apollolib/input.md create mode 100644 toolchain/apollolib/memory-and-blit.md create mode 100644 toolchain/apollolib/picture.md create mode 100644 toolchain/apollolib/sound.md create mode 100644 toolchain/apollolib/system-and-timing.md create mode 100644 toolchain/pitfalls/apollolib-known-bugs.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8b66d1d..daa08c0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -186,7 +186,15 @@ /toolchain/project-makefile.md @WDrijver /toolchain/target-os-headers.md @WDrijver /toolchain/vasm-assembler.md @WDrijver +/toolchain/apollolib/debug.md @WDrijver +/toolchain/apollolib/index.md @WDrijver +/toolchain/apollolib/input.md @WDrijver +/toolchain/apollolib/memory-and-blit.md @WDrijver +/toolchain/apollolib/picture.md @WDrijver +/toolchain/apollolib/sound.md @WDrijver +/toolchain/apollolib/system-and-timing.md @WDrijver /toolchain/best-practices/gcc-code-generation-for-68080.md @TuKo1982 +/toolchain/pitfalls/apollolib-known-bugs.md @WDrijver /toolchain/pitfalls/calling-assembler-from-gcc.md @TuKo1982 /toolchain/pitfalls/float-settings-mismatch.md @TuKo1982 /toolchain/pitfalls/gcc-bbb-autoinc.md @TuKo1982 diff --git a/core/best-practices/prefer-library-calls-for-saga-chips.md b/core/best-practices/prefer-library-calls-for-saga-chips.md index b351dea..71cbff4 100644 --- a/core/best-practices/prefer-library-calls-for-saga-chips.md +++ b/core/best-practices/prefer-library-calls-for-saga-chips.md @@ -13,6 +13,7 @@ tags: [saga, arne, isabelle, maggie, library, apollolib, maggie-library, registe related: - core/saga-registers/index.md - os/libraries/maggie-library.md + - toolchain/apollolib/index.md - core/saga-audio-arne/index.md - core/saga-video-isabelle/index.md - core/saga-3d-maggie/index.md @@ -41,7 +42,9 @@ For the SAGA chips, prefer the library calls to direct register access - An official library for Arne and Isabelle is in development. Until it is released, ApolloLib is the best choice for library calls to Arne and - Isabelle. + Isabelle. It is described in + [toolchain/apollolib/](../../toolchain/apollolib/index.md); check its + [known bugs](../../toolchain/pitfalls/apollolib-known-bugs.md) first. - maggie.library is described in [os/libraries/maggie-library.md](../../os/libraries/maggie-library.md). When you need more than that file covers, the author's repository is the diff --git a/toolchain/apollolib/debug.md b/toolchain/apollolib/debug.md new file mode 100644 index 0000000..0b83fc8 --- /dev/null +++ b/toolchain/apollolib/debug.md @@ -0,0 +1,92 @@ +--- +title: ApolloLib serial debug output +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain] +applies_to: + core: "any" + os: [any] +tags: [apollolib, debug, serial, rawdofmt, logging] +related: + - toolchain/apollolib/index.md + - toolchain/project-makefile.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Debug.h, _Debug.c, _Defines.h), commit ce1fcbf (2026-11-04)" +--- + +## Summary + +ApolloLib writes debug text straight to the serial port at 115200 baud, +without OS calls, so it also works without a console and with the OS +taken over. Two macro sets use it: `AD()`/`ADX()` around `sprintf`, and +the levelled `Debug_Info()` … `Debug_Flag()` macros. + +## Details + +### Functions (ApolloCrossDev_Debug.c) + +| Function | What it does | +|---|---| +| `ApolloDebugInit()` | Sets DTR and RTS on CIA-B and SERPER to 115200 baud (PAL clock) | +| `ApolloDebugPutChar(c)` | Waits for TBE, writes one character to SERDAT; `\n` becomes `\r\n` | +| `ApolloDebugMayGetChar()` | Returns a received character, or −1 | +| `ApolloDebugPutStr(s)` | Writes a string | +| `ApolloDebugPutDec(label, v)`, `ApolloDebugPutHex(label, v)`, `ApolloDebugPutHexVal(v)` | Write `label: value` in decimal or hex | +| `WriteRegisterWord(reg, v)`, `ReadRegisterWord(reg)` | Write and read a 16-bit hardware register | + +`char ApolloDebugMessage[256]` is the shared buffer for `sprintf`. + +### Macros, set 1: AD and ADX (ApolloCrossDev_Defines.h) + +| Macro | Active with | +|---|---| +| `AD(x)` | `-DAPOLLO_DEBUG` | +| `ADX(x)` | `-DAPOLLO_DEBUG -DAPOLLO_DEBUGEXTRA` | +| `ADXX(x)` | Never (always empty) | + +```c +AD(sprintf(ApolloDebugMessage, "loaded %s\n", name);) +AD(ApolloDebugPutStr(ApolloDebugMessage);) +``` + +ApolloLib's own code uses this set. The example Makefiles build with +`-DAPOLLO_DEBUG -DAPOLLO_DEBUGEXTRA`. + +### Macros, set 2: levels (ApolloCrossDev_Debug.h) + +Set `DEBUG` to a sum of levels, for example `-DDEBUG=7`: + +| Level | Value | Macro | +|---|---|---| +| `DBG_INFO` | 1 | `Debug_Info(fmt, ...)` | +| `DBG_WARN` | 2 | `Debug_Warn(...)` (yellow) | +| `DBG_ERROR` | 4 | `Debug_Error(...)` (red) | +| `DBG_FLAG` | 8 | `Debug_Flag(...)` (blinking magenta) | +| `DBG_TRACE` | 16 | `Debug_Trace(...)` (grey) | +| `DBG_CMD` | 32 | `traceCommand(req)`: device drivers only, not in ApolloLib | +| `DBG_MEM` | 64 | `AllocMem`/`FreeMem` wrappers: device drivers only, not in ApolloLib | + +The macros print `[project][file][function][line] [LEVEL]` and the +message through exec `RawDoFmt` inside `Forbid()`/`Permit()`. They need +`PROJECT_NAME` and `BASE_FILE_NAME` defined, for example by the Makefile. +`RawDoFmt` formats: use `%ld` for 32-bit values. + +`BUG_RED`, `BUG_BOLD` … are ANSI colour codes for your own messages. + +## Differences from AmigaOS / AROS / 68060 + +- `kprintf`/debug.lib need the OS and go through exec; ApolloLib writes + SERDAT directly and works in takeover code and interrupts. +- The serial port is shared: don't use the debug output while serial.device + owns the port. + +## Do / Don't + +- DO call `ApolloDebugInit()` once before the first output. +- DO use `%ld` in `Debug_…` formats (`RawDoFmt`) and the C types in `AD()` + (`sprintf`). +- DON'T set `DBG_CMD` or `DBG_MEM` in an ApolloLib program: their code is + not in ApolloLib. diff --git a/toolchain/apollolib/index.md b/toolchain/apollolib/index.md new file mode 100644 index 0000000..b891269 --- /dev/null +++ b/toolchain/apollolib/index.md @@ -0,0 +1,120 @@ +--- +title: ApolloLib, the ApolloCrossDev library for SAGA and the 68080 +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, saga-audio-arne, saga-video-isabelle] +applies_to: + core: "any" + os: [any] +tags: [apollolib, library, saga, arne, isabelle, pip, pointer, debug] +related: + - core/best-practices/prefer-library-calls-for-saga-chips.md + - core/saga-registers/index.md + - toolchain/project-makefile.md + - toolchain/pitfalls/apollolib-known-bugs.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ce1fcbf (2026-11-04)" + - "WDrijver (maintainer), library calls for the SAGA chips, 2026-11-04" +--- + +## Summary + +ApolloLib is the C and assembly library that ships with ApolloCrossDev in +`Projects/ApolloLib`. It loads and plays sounds on Arne, loads and shows +pictures on the SAGA chunky plane, PIP and pointer sprite, reads mouse, +USB joypad and keyboard, and has fast copy, fill and blit routines and a +serial debug output. Until the official libraries for Arne and Isabelle +are released, ApolloLib is the preferred way to use them (see +[prefer-library-calls-for-saga-chips.md](../../core/best-practices/prefer-library-calls-for-saga-chips.md)). + +## Details + +### Using it in a project + +ApolloLib is not a link library: a project compiles its sources. The +example Makefiles (ApolloPointer, SAGAPiP) set +`APOLLOLIB = $(PWD)/../../Projects/ApolloLib`, add `-I$(APOLLOLIB)`, +and compile every `.c` and `.s` file there into the program (see +[project-makefile.md](../project-makefile.md)). + +```c +#include "ApolloCrossDev_Lib.h" /* includes _Defines.h and _Debug.h */ +``` + +| File | Content | +|---|---| +| `ApolloCrossDev_Lib.h` | Structures and prototypes | +| `ApolloCrossDev_Defines.h` | SAGA addresses, modes, formats, return codes, debug switches, Amiga includes | +| `ApolloCrossDev_LibC.c` | Files, sound, picture, PIP, pointer, input | +| `ApolloCrossDev_LibASM.s` | Copy, fill, blit, endian swap, timing, takeover (vasm, 68080 and AMMX) | +| `ApolloCrossDev_Debug.h`, `_Debug.c` | Serial debug output | + +The assembly functions take their arguments in registers, declared with +the macros `_A0(...)` … `_D7(...)` from `_Defines.h`. + +### Areas + +| Area | Functions | File | +|---|---|---| +| Sound on Arne | `ApolloLoadSound`, `ApolloPlaySound`, `ApolloStopSound`, `ApolloVolume…`, fades; the channel API `ApolloPlay`, `ApolloPlayFile`, `ApolloStartChannel`, `ApolloStopChannel` | [sound.md](sound.md) | +| Pictures, PIP, pointer | `ApolloLoadPicture`, `ApolloShowPicture`, `ApolloShowPiP1/2`, `ApolloHidePiP1/2`, `ApolloLoadPointer`, `ApolloShowPointer`, `ApolloShowFile` | [picture.md](picture.md) | +| Input | `ApolloMouse`, `ApolloJoypad`, `ApolloKeyboard`, `ApolloKeyboardToUnicode`, `ApolloGetMouseDelta` | [input.md](input.md) | +| Memory and blit | `ApolloCopyBlock`, `ApolloFillLongs`, `ApolloSwapLong`, `ApolloBlit…Loop` and others | [memory-and-blit.md](memory-and-blit.md) | +| System and timing | `ApolloWaitVBL`, `ApolloCPUTick`, `ApolloCPUDelay`, `ApolloTimer`, `ApolloTakeOver`, `ApolloForbid`, `ApolloDisable` | [system-and-timing.md](system-and-timing.md) | +| Debug | `ApolloDebugInit`, `ApolloDebugPutStr`, `AD()`, `Debug_Info()` … | [debug.md](debug.md) | +| Files | `ApolloLoadFile`, `ApolloCacheFile` | this file | + +### Files + +`ApolloLoadFile(struct ApolloFile *file)` reads `file->filename` from byte +`file->offset` to the end into `file->buffer`, which the caller allocates, +and sets `file->size`. `ApolloCacheFile(filename, &cache, &length, offset)` +does the same with plain arguments. Both use libnix `fopen`/`fread`. + +### Return codes + +| Value | Sound (`APOLLO_SOUND_…`) | Picture (`APOLLO_PICTURE_…`) | +|---|---|---| +| `$00` | OK | OK | +| `$01` | NOCHANNEL | W_ERROR (width has no SAGA mode) | +| `$02` | – | H_ERROR (height has no SAGA mode) | +| `$03` | ENDOFFILE | D_ERROR (depth not supported) | +| `$04` | LOADERROR | LOADERR | +| `$05` | NOHEADER | NOHEADER | +| `$06` | – | PLANEERR | +| `$07` | – | COMPRERR | +| `$09` | MEMERROR | MEMERROR | +| `$0A` | OPENERR | OPENERR | +| `$0B` | CLOSEERR | CLOSEERR | +| `$0C` | COMPRERR | – | + +`ApolloLoadFile` returns the sound codes. + +### Header mismatch + +`ApolloCrossDev_Lib.h` declares `ApolloShowPiP()` and `ApolloHidePiP()`, +which don't exist. The functions that exist, `ApolloShowPiP1/2` and +`ApolloHidePiP1/2`, are not declared: declare them yourself until the +header is fixed (see +[apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). + +## Differences from AmigaOS / AROS / 68060 + +- ApolloLib programs the SAGA registers directly, without graphics.library, + audio.device or input.device. Most functions are OS-takeover style: + under a running OS, `ApolloMouse` and `ApolloShowPicture` compete with + Intuition for the pointer and the screen. +- The assembly uses 68080 and AMMX instructions (`load`, `store`, `vperm`, + `dbra.l`, `move16`): it doesn't run on a 68060. + +## Do / Don't + +- DO use ApolloLib for Arne and Isabelle instead of programming the + registers yourself (a best practice, not a forced rule). +- DO check the known bugs before you rely on a function: + [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md). +- DO declare `ApolloShowPiP1/2` and `ApolloHidePiP1/2` yourself. +- DON'T call `ApolloShowPiP()` or `ApolloHidePiP()`: they don't exist. diff --git a/toolchain/apollolib/input.md b/toolchain/apollolib/input.md new file mode 100644 index 0000000..f283821 --- /dev/null +++ b/toolchain/apollolib/input.md @@ -0,0 +1,98 @@ +--- +title: ApolloLib input functions (mouse, USB joypad, keyboard) +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, general] +applies_to: + core: "any" + os: [any] + mode: [os-takeover] +tags: [apollolib, input, mouse, joypad, keyboard, pointer] +related: + - toolchain/apollolib/index.md + - core/saga-registers/input.md + - core/saga-registers/pointer.md + - toolchain/pitfalls/apollolib-known-bugs.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _LibASM.s, _Defines.h), commit ce1fcbf (2026-11-04)" +--- + +## Summary + +ApolloLib polls the mouse, the first USB joypad and the keyboard directly +from the hardware, and moves the SAGA pointer with the mouse. Call the +functions once per frame. They bypass input.device, so they belong in +OS-takeover code. + +## Details + +### Mouse: ApolloMouse(ApolloMouseState *m) + +| Source | Register | +|---|---| +| X, Y counters | `$DFF00B`, `$DFF00A` (JOY0DAT) | +| Left button | CIA-A `$BFE001` bit 6 (0 = pressed) | +| Right button | POTINP `$DFF016` bit 10 (0 = pressed) | +| "Middle" | Low byte of the wheel register `$DFF212` (see the known bugs) | + +Each call: + +1. computes the X/Y delta since the last call (with wrap-around of the + 8-bit counters); +2. moves `MouseX_Pointer`/`MouseY_Pointer`, clamped to 1 … + `MouseX_Pointer_Max`/`MouseY_Pointer_Max` (set these first); +3. writes the pointer position + 16 (X) and + 8 (Y) to `$DFF1D0`/`$DFF1D2`; +4. sets `Button_State` from the `APOLLOMOUSE_…` flags. + +| Flag | Value | Set when | +|---|---|---| +| `APOLLOMOUSE_LEFTCLICK` / `RIGHTCLICK` / `MIDDLECLICK` | `$0001` / `$0002` / `$0004` | Button released | +| `APOLLOMOUSE_LEFTDOUBLECLICK` / `RIGHTDOUBLECLICK` / `MIDDLEDOUBLECLICK` | `$0008` / `$0010` / `$0020` | Released again within 15 calls (`APOLLOMOUSE_DOUBLECLICKCOUNTER`) | +| `APOLLOMOUSE_LEFTDOWN` / `RIGHTDOWN` / `MIDDLEDOWN` | `$0040` / `$0080` / `$0100` | Button held | + +`ApolloGetMouseDelta(UWORD *old)` (assembly) is a smaller variant: it +returns the X and Y delta since the value in `*old`, and updates `*old`. + +### USB joypad: ApolloJoypad(ApolloJoypadState *j) + +Reads joypad 0 (`$DFF220`) into `Joypad_Value` and decodes it: + +| Field | Bits | +|---|---| +| `Joypad_LeftX_Delta` | 15 → +1 (right), 14 → −1 (left) | +| `Joypad_LeftY_Delta` | 13 → +1 (down), 12 → −1 (up) | +| `Joypad_RightX_Delta` | 2 → +1, 3 → −1 | +| `Joypad_RightY_Delta` | 1 → +1, 4 → −1 | +| `Joypad_Start`, `Joypad_Back` | 10, 9 | +| `Joypad_TR`, `TL`, `BR`, `BL` | 8, 7, 6, 5 | +| `Joypad_Y`, `X`, `B`, `A` | 4, 3, 2, 1 | +| `Joypad_Connect` | 0 | + +The right stick shares bits 1–4 with A, B, X and Y. Joypads 1–3 +(`$DFF222`–`$DFF226`) need your own read. + +### Keyboard: ApolloKeyboard(ApolloKeyBoardState *k) + +Reads the raw key code from CIA-A `$BFEC01` (`not` and `ror #1`). A new +key below 127 goes to `Current_Key` (and `Previous_Key`); otherwise +`Current_Key` is 127. `ApolloKeyboardToUnicode(code)` maps a raw Amiga key +code to a character. + +## Differences from AmigaOS / AROS / 68060 + +- input.device, Intuition and lowlevel.library are not used. Under a + running OS, Intuition also moves the pointer and reads the keyboard: the + two fight. +- The keyboard read doesn't handshake with the keyboard controller, as the + OS keyboard interrupt does. + +## Do / Don't + +- DO set `MouseX_Pointer_Max` and `MouseY_Pointer_Max` before the first + `ApolloMouse` call. +- DO call these functions once per frame, for example after `ApolloWaitVBL`. +- DON'T use them while Intuition owns the input. +- DON'T rely on the middle-button flags (see the known bugs). diff --git a/toolchain/apollolib/memory-and-blit.md b/toolchain/apollolib/memory-and-blit.md new file mode 100644 index 0000000..4419344 --- /dev/null +++ b/toolchain/apollolib/memory-and-blit.md @@ -0,0 +1,88 @@ +--- +title: ApolloLib memory, endian and blit routines +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, cpu-68080] +applies_to: + core: "any" + os: [any] +tags: [apollolib, memcpy, fill, endian, blit, alpha, ammx, move16] +related: + - toolchain/apollolib/index.md + - core/cpu-68080/measured-instruction-costs.md + - core/best-practices/align-copy-destination.md + - toolchain/pitfalls/apollolib-known-bugs.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibASM.s, _Lib.h), commit ce1fcbf (2026-11-04)" +--- + +## Summary + +ApolloLib's assembly file has copy, fill and endian-swap routines for +memory, and copy, fill and alpha-blit routines for pixel rectangles. They +use 68080 instructions (`dbra.l`, `move16`, AMMX `load`/`store`/`vperm`). +Most ignore a remainder: give sizes that are a multiple of the chunk size. + +## Details + +All take their arguments in registers; the prototypes in +`ApolloCrossDev_Lib.h` declare them with `_A0(...)` … `_D7(...)`. + +### Memory + +| Routine | Arguments | Chunk | Remainder | +|---|---|---|---| +| `ApolloCopyBlock(s, d, size)` | a0, a1, d3 = bytes | 16 bytes with `move16`, then bytes | Copied | +| `ApolloCopyBlock32(s, d, size)` | a0, a1, d3 = bytes | 32 bytes (2 × `move16`) | **Ignored** | +| `ApolloCopyLongs(s, d, size)` | a0, a1, d3 = bytes | 4 bytes | **Ignored** | +| `ApolloFillLongs(d, value, size)` | a1, d4, d3 = bytes | 4 bytes | **Ignored** | +| `ApolloFillBlock(d, count, value)` | a0, d3 = bytes, d4 | 32 bytes (4 × AMMX `store`), 32-byte aligned | **Ignored** | + +`move16` needs no alignment on the 68080, but is never faster than +aligned AMMX or quad moves (see +[measured-instruction-costs.md](../../core/cpu-68080/measured-instruction-costs.md)). + +### Endian swap + +| Routine | What it does | +|---|---| +| `ApolloSwapWord(w)`, `ApolloSwapLong(l)`, `ApolloSwapOcta(q)` | Return the value with its bytes reversed (d0) | +| `ApolloEndianSwapWordBuffer(s, length)` | Byte-swaps 16-bit words in place, 8 bytes per AMMX step; buffer ≥ 8 bytes, 2-byte aligned | +| `ApolloEndianSwapLongBuffer(s, length)` | Meant to byte-swap 32-bit longs in place; see the known bugs | +| `ApolloEndianSwap2Loop`, `ApolloEndianSwap8Loop` | Older swap loops, used by `ApolloShowFile` | + +### Pixel rectangles + +| Routine | What it does | +|---|---| +| `ApolloFillBitMap(dst, w, h, depth, dstmod, value)` | Fills a rectangle with a 32-bit value; modulo in pixels | +| `ApolloFillColor(dst, w, h, depth, dstmod, value)` | As above, value given byte-reversed | +| `ApolloCopyPicture(s, d, w, h, smod, dmod)` | Copies a rectangle; sizes and modulos in bytes | +| `ApolloCopyPicture32(...)` | As above, 32-byte aligned | +| `ApolloCopyLoop`, `ApolloCopy32Loop`, `ApolloBlitLoop` | Copy, aligned copy and copy with transparency; pitches in words | +| `ApolloFillLoop(d, w, h, dpitch, colour)` | Fills with an R5G6B5 colour | +| `ApolloBlitAlphaConstLoop(s, d, w, h, sp, dp, alpha)` | Blends with a constant alpha 0–255 (0 = transparent) | +| `ApolloBlitAlphaKeyTransLoop(..., alpha, key)` | Blends with alpha and skips the R5G6B5 key colour | +| `ApolloBlitAlphaHorizontalLineLoop(s, d, w, colour, alpha)` | Blends one line with an R8G8B8 colour | +| `ApolloBlitRed/Green/BlueAlphaBGLoop`, `ApolloBlitBlueAlphaUILoop` | Blend through a separate alpha map, one colour channel at a time | +| `ApolloUncompressLoop`, `ApolloUncompressVectorLoop` | Unpack sprites, with endian and colour swap (`$07C0` green becomes the key `$F81F`) | + +The blit routines work on R5G6B5 pixels (16-bit modes). + +## Differences from AmigaOS / AROS / 68060 + +- `CopyMem`/`CopyMemQuick` and the classic blitter are not used; these are + CPU loops for the 68080. +- AMMX (`load`, `store`, `vperm`) doesn't exist on the 68060: these + routines need a 68080. + +## Do / Don't + +- DO give sizes in multiples of 32 (`ApolloCopyBlock32`, `ApolloFillBlock`) + or 4 (`ApolloCopyLongs`, `ApolloFillLongs`). +- DO align the destination of large copies (see + [align-copy-destination.md](../../core/best-practices/align-copy-destination.md)). +- DON'T use `ApolloEndianSwapLongBuffer` until its known bug is fixed. diff --git a/toolchain/apollolib/picture.md b/toolchain/apollolib/picture.md new file mode 100644 index 0000000..6519b57 --- /dev/null +++ b/toolchain/apollolib/picture.md @@ -0,0 +1,121 @@ +--- +title: ApolloLib picture, PIP and pointer functions (Isabelle) +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, saga-video-isabelle] +applies_to: + core: "any" + os: [any] +tags: [apollolib, isabelle, picture, bmp, dds, pip, pointer, sprite] +related: + - toolchain/apollolib/index.md + - core/saga-registers/chunky-plane.md + - core/saga-registers/pip.md + - core/saga-registers/pointer.md + - toolchain/pitfalls/apollolib-known-bugs.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h, _Defines.h), commit ce1fcbf (2026-11-04)" + - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit ce1fcbf" + - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit ce1fcbf" +--- + +## Summary + +ApolloLib loads BMP and DDS files into a 32-byte aligned buffer +(`ApolloLoadPicture`) and shows them on the SAGA chunky plane by picking +the GFXMODE that matches width, height and depth (`ApolloShowPicture`). +It switches PIP 1 and 2 on and off and loads the 32x32 pointer sprite of +Core 12500 and later. + +## Details + +### struct ApolloPicture + +| Field | Set by | Meaning | +|---|---|---| +| `filename[256]`, `format` | Caller | `APOLLO_BMP_FORMAT` (3), `APOLLO_DDS_FORMAT` (2), or raw (0: the whole file) | +| `endian` | Caller | `true`: swap the byte order of each 16-, 24- or 32-bit pixel after loading | +| `buffer`, `position`, `size` | `ApolloLoadPicture` | Allocated buffer; pixels at `buffer + position` (32-byte aligned) | +| `width`, `height`, `depth` | `ApolloLoadPicture` | From the header | +| `palette[256]`, `palettesize` | `ApolloLoadPicture` | For depth ≤ 8 | +| `modulo` | Caller | Bytes skipped after each row (BPLHMOD) | +| `fullscreen`, `target` | Caller | `target`: 0 SAGA screen, 1 PIP 1, 2 PIP 2, 3 pointer sprite | + +### Functions + +| Function | What it does | +|---|---| +| `ApolloLoadPicture(pic)` | BMP: uncompressed (or BI_BITFIELDS), 1 plane; bottom-up rows are flipped. DDS: the data after the header as it is, without a compression check, so only uncompressed DDS works; depth = pitch / width × 8. Returns an `APOLLO_PICTURE_…` code. | +| `ApolloAllocPicture(pic)` | Allocates an aligned buffer of `width × height × depth / 8` bytes (or `size`) | +| `ApolloFreePicture(pic)` | `FreeVec` of the buffer | +| `ApolloShowPicture(pic)` | Picks the GFXMODE from width × height (see below) and depth 8, 15, 16, 24 or 32, loads the chunky palette for depth ≤ 8, writes GFXMODE (`$DFF1F4`), modulo (`$DFF1E6`) and pointer (`$DFF1EC`) | +| `ApolloShowPiP1(pic)` / `ApolloShowPiP2(pic)` | Writes the PIP's DMA row length (`width × depth / 8`), which switches it on. Set position, pointer, format and modulo first (see [pip.md](../../core/saga-registers/pip.md)). | +| `ApolloHidePiP1()` / `ApolloHidePiP2()` | Writes 0 to the DMA row length: the PIP goes off | +| `ApolloLoadPointer(ptr, pic)` | Converts a 32x32 BMP (8-bit with palette, or 32-bit ARGB) into pointer sprite data: colour index + transparency byte per pixel and a 256-entry palette | +| `ApolloShowPointer(ptr)` | Writes the palette (`$DFF3A8` index, `$DFF3AA` colour) and copies the image to `$DFD000` | +| `ApolloShowFile(filename, &draw, &live, length, offset, gfx_mode, modulo, endianswap)` | Reads a raw frame into `draw`, swaps `draw` and `live`, and shows `live`: double buffering for streamed frames | +| `ApolloShowPattern(buffer, w, h, depth)` | Fills a buffer with a test pattern | + +### Screen modes of ApolloShowPicture + +| Width | Heights | +|---|---| +| 304 | 224 | +| 320 | 200, 240, 256 | +| 480 | 270 | +| 640 | 200, 360, 400, 480, 512 | +| 720 | 405, 576 | +| 800, 848, 960, 1024 | 600, 480, 540, 768 (height not checked) | +| 1280 | 720, 800, 1024 | +| 1440, 1920 | 900, 1080 (height not checked) | + +Another width returns `APOLLO_PICTURE_W_ERROR`, another height of a +checked width `APOLLO_PICTURE_H_ERROR`. **The 320-pixel modes are broken** +in this version (see [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). + +### Pointer + +`ApolloLoadPointer` accepts a 32x32 BMP: + +- 8-bit: the BMP palette becomes the sprite palette; every pixel is + opaque. Colour index 0 is transparent (see + [pointer.md](../../core/saga-registers/pointer.md)). +- 32-bit ARGB: alpha 0 becomes index 0 (transparent), alpha `$FF` opaque, + any other alpha semi-transparent (50 %). Colours are gathered into a + palette of up to 255 entries. + +`ApolloMouse` (see [input.md](input.md)) moves the pointer. + +### Example + +```c +struct ApolloPicture pic = {0}; +strcpy(pic.filename, "title.dds"); +pic.format = APOLLO_DDS_FORMAT; /* 1280x720, 16-bit */ +if (ApolloLoadPicture(&pic) == APOLLO_PICTURE_OK) { + pic.modulo = 0; + ApolloShowPicture(&pic); + /* ... */ + ApolloFreePicture(&pic); /* after switching the mode back */ +} +``` + +## Differences from AmigaOS / AROS / 68060 + +- No graphics.library or cybergraphics: `ApolloShowPicture` writes + GFXMODE and the chunky pointer directly, behind Intuition's back. Save + the old mode and pointer at `$DFE1F4`/`$DFE1EC` and restore them on exit. +- BMP pixels are little-endian. Use `endian` or the swap routines when the + SAGA format needs big-endian pixels. + +## Do / Don't + +- DO set the PIP registers before `ApolloShowPiP1/2`: they only write the + DMA row length. +- DO restore GFXMODE and the chunky pointer before the program exits. +- DON'T free a picture that is still shown. +- DON'T use `ApolloShowPicture` for 320-pixel-wide or 8-bit pictures in + this version (see [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). diff --git a/toolchain/apollolib/sound.md b/toolchain/apollolib/sound.md new file mode 100644 index 0000000..3c89330 --- /dev/null +++ b/toolchain/apollolib/sound.md @@ -0,0 +1,106 @@ +--- +title: ApolloLib sound functions (Arne) +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, saga-audio-arne] +applies_to: + core: "any" + os: [any] +tags: [apollolib, arne, audio, wav, aiff, sound] +related: + - toolchain/apollolib/index.md + - core/saga-registers/arne-channels.md + - core/saga-registers/arne-dma-and-interrupts.md + - toolchain/pitfalls/apollolib-known-bugs.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h), commit ce1fcbf (2026-11-04)" +--- + +## Summary + +ApolloLib loads WAV and AIFF files into a 32-byte aligned buffer and plays +them on an Arne channel: `ApolloLoadSound`, then `ApolloPlaySound`. A +second, older API (`ApolloPlay`, `ApolloPlayFile`) takes a channel number +and a buffer instead of a `struct ApolloSound`. Both always play 16-bit +stereo. + +## Details + +### struct ApolloSound + +| Field | Set by | Meaning | +|---|---|---| +| `filename[256]`, `format` | Caller | File and format: `APOLLO_WAV_FORMAT` (4), `APOLLO_AIFF_FORMAT` (1), or raw (`APOLLO_RAW_FORMAT`, 0: the whole file) | +| `buffer`, `position` | `ApolloLoadSound` | Allocated buffer; the data starts at `buffer + position` (32-byte aligned) | +| `size` | `ApolloLoadSound` | Data size in bytes | +| `period` | `ApolloLoadSound` | `3546895 / sample rate` (PAL clock) | +| `stereo`, `datarate`, `bitspersample` | `ApolloLoadSound` (WAV) | Read from the header, but not used for playback | +| `channel` | Caller or `ApolloPlaySound` | Arne channel 0–15 | +| `staticchannel` | Caller | `true`: play on `channel`; `false`: `ApolloPlaySound` picks the first free channel from 1 to 15 | +| `loop`, `fadein`, `volume_left`, `volume_right` | Caller | Playback; volume `$00`–`$FF` per side | +| `fadeout`, `pan` | Caller | Not used by the library | + +### Functions + +| Function | What it does | +|---|---| +| `ApolloLoadSound(sound)` | Parses the WAV or AIFF header, allocates `buffer` (`AllocVec`, `MEMF_ANY`), reads the data. WAV: PCM only. Returns an `APOLLO_SOUND_…` code. | +| `ApolloAllocSound(sound)` | Allocates an aligned buffer of `sound->size` bytes for data you fill yourself | +| `ApolloFreeSound(sound)` | `FreeVec` of the buffer | +| `ApolloPlaySound(sound)` | Picks a channel if `staticchannel` is false, sets pointer, length (`size / 8`), volume 0, CTRL and period, starts DMA (DMACON for 0–3, DMACON2 for 4–15), then sets the volume or fades in | +| `ApolloStopSound(sound)` / `ApolloStartSound(sound)` | Clears or sets the channel's DMA bit | +| `ApolloVolumeSound(sound)` | Writes `volume_left << 8 \| volume_right` to AUDxVOL | +| `ApolloFadeInSound(sound)` / `ApolloFadeOutSound(sound)` | Steps the volume by 1 per VBlank (`ApolloWaitVBL`) to or from the average of left and right; fade-out then stops the channel. Both block the caller. | + +`ApolloPlaySound` writes CTRL `$0005` (16-bit, stereo, looping) when +`loop` is set and `$0007` (16-bit, stereo, one-shot) when not, whatever the +file contains. + +### Channel API (older, from the RHLOS project) + +| Function | What it does | +|---|---| +| `ApolloPlay(channel, vol_l, vol_r, loop, buffer, length, offset)` | Plays `buffer + offset`, length `(length - offset) / 8 - 32` pairs, at period 80 (44.1 kHz). Returns `false` if the channel is busy. | +| `ApolloPlayFile(filename, buffer, offset, channel, vol_l, vol_r, loop)` | Reads the file into `buffer` and plays it on `channel`, stopping the channel first if it runs | +| `ApolloCacheFile(filename, &cache, &length, offset)` | Reads a file into `cache` for `ApolloPlay` | +| `ApolloVolume(channel, vol_l, vol_r)` | Sets the volume | +| `ApolloStartChannel(channel)` / `ApolloStopChannel(channel)` | Starts or stops a channel's DMA | +| `ApolloFadeOut(channel, from, to)` | Steps the volume down per VBlank, then stops the channel | + +### Example + +```c +struct ApolloSound music = {0}; +strcpy(music.filename, "music.wav"); +music.format = APOLLO_WAV_FORMAT; /* 16-bit stereo PCM */ +if (ApolloLoadSound(&music) == APOLLO_SOUND_OK) { + music.volume_left = music.volume_right = 0x80; + music.loop = true; + if (ApolloPlaySound(&music) == APOLLO_SOUND_OK) { + /* ... */ + ApolloStopSound(&music); + } + ApolloFreeSound(&music); +} +``` + +## Differences from AmigaOS / AROS / 68060 + +- No audio.device or AHI: the functions write the Arne registers at + `$DFF400 + n × $10` and DMACON/DMACON2 directly. Don't use them on a + channel that audio.device or AHI also uses. +- Arne plays from any RAM, so `MEMF_ANY` buffers work; Paula needs Chip RAM. + +## Do / Don't + +- DO give `ApolloPlaySound` 16-bit stereo data: it always programs 16-bit + stereo. +- DO set `staticchannel` and `channel` if you need channel 0: the + automatic search starts at 1. +- DO call `ApolloFreeSound` when you are done. +- DON'T expect `stereo`, `bitspersample`, `fadeout` or `pan` to change + playback. +- DON'T use the channel API and audio.device on the same channel. diff --git a/toolchain/apollolib/system-and-timing.md b/toolchain/apollolib/system-and-timing.md new file mode 100644 index 0000000..a00fb62 --- /dev/null +++ b/toolchain/apollolib/system-and-timing.md @@ -0,0 +1,82 @@ +--- +title: ApolloLib system and timing routines (VBlank, CPU cycles, takeover) +type: reference +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, cpu-68080] +applies_to: + core: "any" + os: [any] +tags: [apollolib, vblank, timer, ccc, delay, takeover, forbid, disable] +related: + - toolchain/apollolib/index.md + - core/cpu-68080/performance-counters.md + - os/os-takeover.md + - toolchain/pitfalls/apollolib-known-bugs.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _LibASM.s), commit ce1fcbf (2026-11-04)" +--- + +## Summary + +ApolloLib waits for VBlank, reads the 68080 cycle counter and waits a +number of milliseconds, counts VBlanks in an interrupt server, and has +three takeover helpers. None of the takeover helpers has a counterpart +that undoes it: a program has to restore the system itself. + +## Details + +| Function | What it does | +|---|---| +| `ApolloWaitVBL()` | Polls INTREQR bit 5 (VERTB) until set, then clears it in INTREQ | +| `ApolloCPUTick()` | Returns the clock cycle counter CCC (`movec $809,d0`, see [performance-counters.md](../../core/cpu-68080/performance-counters.md)) | +| `ApolloCPUDelay(ms)` | Busy-waits `ms × 92000` cycles on CCC | +| `ApolloTimer()` | Adds a VBlank interrupt server (`AddIntServer`, `INTB_VERTB`) that increments `GlobalTimer` every frame | +| `ApolloTakeOver()` | Opens intuition.library, `CloseWorkBench()`, sets the task priority to 20 | +| `ApolloForbid()` | Calls exec `Forbid()` | +| `ApolloDisable()` | Calls exec `Disable()` | + +### Limits + +- **`ApolloWaitVBL` clears VERTB** itself. Under a running OS that takes + the event away from the OS's VBlank handling; use it in takeover code. +- **`ApolloCPUDelay` assumes 92 MHz** (92000 cycles per ms, also + `APOLLOCYCLES`). The V4 runs at 99.31 MHz (14 × 7.09379 MHz, see + [core-at-a-glance.md](../../core/cpu-68080/core-at-a-glance.md)), so a + delay is about 7 % short there. +- **No restore functions:** there is no Permit, Enable, `OpenWorkBench` + or priority reset, and no `RemIntServer` for `ApolloTimer`. See + [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md) for what + that means at exit. + +### Restore at exit (your code) + +| After | Call before exit | +|---|---| +| `ApolloForbid()` | `Permit()` | +| `ApolloDisable()` | `Enable()` | +| `ApolloTakeOver()` | `SetTaskPri(FindTask(NULL), old)`, `OpenWorkBench()`, `CloseLibrary()` on the intuition.library base it opened (you don't get it back) | +| `ApolloTimer()` | `RemIntServer(INTB_VERTB, …)`: the server node is internal to ApolloLib, so this isn't possible yet | + +This is what [os-takeover.md](../../os/os-takeover.md) asks of every +takeover program: restore multitasking and the Workbench screen on exit. + +## Differences from AmigaOS / AROS / 68060 + +- `ApolloCPUTick` reads a 68080 control register; there is no such + counter on the 68060. Use timer.device or the CIA timers for portable + timing. +- The takeover helpers wrap single exec and Intuition calls; they don't + save the display, the interrupt enables or the DMA state. + +## Do / Don't + +- DO pair `ApolloForbid` with `Permit()` and `ApolloDisable` with + `Enable()` in your exit code. +- DO reopen the Workbench after `ApolloTakeOver`. +- DO calibrate `ApolloCPUDelay` with the clock multiplier from `$DFF3FC` + if the delay must be exact. +- DON'T call `ApolloTimer` in a program that exits back to the OS (see the + known bugs). diff --git a/toolchain/index.md b/toolchain/index.md index 0b12317..4b3ec9e 100644 --- a/toolchain/index.md +++ b/toolchain/index.md @@ -15,6 +15,7 @@ related: - toolchain/vasm-assembler.md - toolchain/target-os-headers.md - toolchain/project-makefile.md + - toolchain/apollolib/index.md sources: - "github.com/WDrijver/ApolloCrossDev README.md" - "github.com/WDrijver/ApolloCrossDev GCC-6.50-Dev-Linux.sh" @@ -62,6 +63,9 @@ C and C++: `-O2 -m68080 -m68881 -ffast-math -fomit-frame-pointer`. Details in - DevPac includes for assembly, in `/m68k-amigaos/DevPac` - ApolloExplorer (`acp`) to upload files to an Apollo V4 over the network - bgdbserver, a GDB server that runs on the Apollo V4 for remote debugging +- ApolloLib in `Projects/ApolloLib`: sound, picture, PIP, pointer, input, + copy and blit routines and serial debug output for SAGA and the 68080, + see [toolchain/apollolib/](apollolib/index.md) ### Host requirements diff --git a/toolchain/pitfalls/apollolib-known-bugs.md b/toolchain/pitfalls/apollolib-known-bugs.md new file mode 100644 index 0000000..0c76998 --- /dev/null +++ b/toolchain/pitfalls/apollolib-known-bugs.md @@ -0,0 +1,90 @@ +--- +title: ApolloLib known bugs (commit ce1fcbf) +type: pitfall +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [toolchain, saga-audio-arne, saga-video-isabelle] +applies_to: + core: "any" + os: [any] +tags: [apollolib, bugs, showpicture, timer, takeover, endian, mouse] +related: + - toolchain/apollolib/index.md + - toolchain/apollolib/picture.md + - toolchain/apollolib/sound.md + - toolchain/apollolib/system-and-timing.md +sources: + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ce1fcbf (2026-11-04), read on 2026-11-04" +--- + +## Symptom + +A program built with ApolloLib shows a wrong screen mode or wrong colours, +crashes some time after it exits, never gets the Workbench back, or plays +a sound wrongly, while the calls look right. + +## Cause + +ApolloLib at commit ce1fcbf has bugs and gaps, found by reading the code +on 2026-11-04. "Confirmed" means the code shows it; "suspected" means the +code suggests it and a test on a V4 is still needed. None has been tested +on hardware. + +### Serious + +| Function | Problem | Status | +|---|---|---| +| `ApolloShowPicture` | `case 320:` has no `break` after its inner switch and falls into `case 480`, which ORs in `$0800`. 320x200 becomes `$0900` (304x224); 320x240 and 320x256 become `$0A00`/`$0B00` (1280x720, 640x360). | Confirmed | +| `ApolloTimer` | The VBlank interrupt server node lives in the program's code and is never removed. After the program exits and is unloaded, the next VBlank runs freed memory. | Confirmed (code); crash expected | +| `ApolloTakeOver`, `ApolloForbid`, `ApolloDisable` | No counterpart restores the Workbench, the task priority, `Permit()` or `Enable()`; the opened intuition.library is never closed | Confirmed | +| `ApolloShowPicture` | For depth ≤ 8 it reorders the bytes of each palette entry that `ApolloLoadPicture` already converted to `$IIRRGGBB`, which gives `$IIGGRRII` | Suspected: 8-bit pictures show wrong colours | + +### Others + +| Function | Problem | Status | +|---|---|---| +| `ApolloCrossDev_Lib.h` | Declares `ApolloShowPiP()`/`ApolloHidePiP()`, which don't exist; the real `ApolloShowPiP1/2`, `ApolloHidePiP1/2` are not declared | Confirmed | +| `ApolloMouse` | `Button_Middle` is the low byte of the wheel register `$DFF212` (the wheel counter), not a button | Suspected | +| `ApolloEndianSwapLongBuffer` | `vperm #$23016745` swaps the words of each long instead of reversing its bytes (`$32107654`); its remainder count (`and #5`) and that of `ApolloEndianSwapWordBuffer` (`and #3`) count bytes, not longs or words | Confirmed (code) | +| `ApolloCPUDelay` | Assumes 92 MHz (the V4 runs at 99.31 MHz); the 32-bit overflow check after `add.l` (`cmp.l #$FFFFFFFF`) can't detect a carry | Confirmed (code) | +| `ApolloPlaySound` | Always programs 16-bit stereo, LEN = size / 8, whatever the file contains; the free-channel search skips channel 0 | Confirmed | +| `ApolloPlayFile` | Applies `offset` twice (in `fseek` and again in pointer and length) | Confirmed | +| `ApolloPlay`, `ApolloPlayFile` | Subtract 32 from the length in sample pairs, without an explanation | Unclear | +| `ApolloLoadFile`, `ApolloLoadSound`, `ApolloLoadPicture` | Some error returns leave the file open (header checks, short reads) | Confirmed | +| `ApolloLoadPicture` | Reads the BMP palette from offset 54, right only for a 40-byte BITMAPINFOHEADER; uses `static` file state (not reentrant) | Confirmed | +| `ApolloShowPattern` | Writes only the first byte of each pixel for depths above 8 | Confirmed | + +## Wrong + +```c +pic.width = 320; pic.height = 200; /* 8-bit, 320x200 */ +ApolloShowPicture(&pic); /* sets GFXMODE $0901, not $0101 */ +ApolloTimer(); /* never removed */ +return 0; /* next VBlank jumps into freed code */ +``` + +## Right + +```c +/* Write the mode yourself for 320-pixel modes until the bug is fixed */ +*(volatile uint16_t *)0xDFF1F4 = APOLLO_SAGA_320_200 | APOLLO_SAGA_8_INDEX; +/* Count VBlanks in your own interrupt server, and remove it before exit */ +``` + +## Detection + +- Read GFXMODE back at `$DFE1F4` after `ApolloShowPicture`: the high byte + must match the picture's size (see + [chunky-plane.md](../../core/saga-registers/chunky-plane.md)). +- Search the program for `ApolloTimer`, `ApolloTakeOver`, `ApolloForbid` + and `ApolloDisable`, and check the exit path for the matching restore. + +## Evidence + +| Claim | Source | Core | Method | +|---|---|---|---| +| All bugs in the tables | (SRC) | any | Code reading of commit ce1fcbf, 2026-11-04 | +| The 14 earlier `-Wall` warnings, 5 of them bugs, are fixed | (SRC) | any | ApolloCrossDev pull request #17, merged 2026-11-04 | +| None of these is tested on V4 hardware | – | – | – | diff --git a/toolchain/project-makefile.md b/toolchain/project-makefile.md index e0a38e6..13d52a6 100644 --- a/toolchain/project-makefile.md +++ b/toolchain/project-makefile.md @@ -50,7 +50,8 @@ project folder must be two levels below the ApolloCrossDev root. | `C_LIBS_SDL`, `C_LIBS_MUI`, `C_LIBS_MATH` | off | Extra libraries, enable by removing `#` | The template only collects `.c` and `.s` files (`C_FILES`, `S_FILES`). It -also links every `.o` file in `Projects/ApolloLib`. +also links every `.o` file in `Projects/ApolloLib` (see +[apollolib/index.md](apollolib/index.md)). ### Targets From 38fef107e8a0a62f03ad695a7afb3dce80b93e52 Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 17:30:35 +0300 Subject: [PATCH 2/7] ApolloLib: describe commit 639e195, with the bug fixes of ApolloCrossDev #18 ApolloCrossDev #18 fixed the bugs listed in apollolib-known-bugs.md and added ApolloTimerStop, ApolloRelease, ApolloPermit and ApolloEnable. Describe that version: the pitfall now lists what was fixed and the limits that remain; sound, picture, input, memory and system files follow the fixes, and the restore table names the new functions. Co-Authored-By: Claude Opus 5.5 --- .../prefer-library-calls-for-saga-chips.md | 3 +- toolchain/apollolib/debug.md | 2 +- toolchain/apollolib/index.md | 23 ++-- toolchain/apollolib/input.md | 7 +- toolchain/apollolib/memory-and-blit.md | 7 +- toolchain/apollolib/picture.md | 15 +-- toolchain/apollolib/sound.md | 38 ++++--- toolchain/apollolib/system-and-timing.md | 55 +++++----- toolchain/pitfalls/apollolib-known-bugs.md | 101 ++++++++++-------- 9 files changed, 139 insertions(+), 112 deletions(-) diff --git a/core/best-practices/prefer-library-calls-for-saga-chips.md b/core/best-practices/prefer-library-calls-for-saga-chips.md index 71cbff4..070a5d1 100644 --- a/core/best-practices/prefer-library-calls-for-saga-chips.md +++ b/core/best-practices/prefer-library-calls-for-saga-chips.md @@ -44,7 +44,8 @@ For the SAGA chips, prefer the library calls to direct register access released, ApolloLib is the best choice for library calls to Arne and Isabelle. It is described in [toolchain/apollolib/](../../toolchain/apollolib/index.md); check its - [known bugs](../../toolchain/pitfalls/apollolib-known-bugs.md) first. + [fixed bugs and remaining limits](../../toolchain/pitfalls/apollolib-known-bugs.md) + first. - maggie.library is described in [os/libraries/maggie-library.md](../../os/libraries/maggie-library.md). When you need more than that file covers, the author's repository is the diff --git a/toolchain/apollolib/debug.md b/toolchain/apollolib/debug.md index 0b83fc8..cf9c963 100644 --- a/toolchain/apollolib/debug.md +++ b/toolchain/apollolib/debug.md @@ -14,7 +14,7 @@ related: - toolchain/apollolib/index.md - toolchain/project-makefile.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Debug.h, _Debug.c, _Defines.h), commit ce1fcbf (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Debug.h, _Debug.c, _Defines.h), commit 639e195 (2026-11-04)" --- ## Summary diff --git a/toolchain/apollolib/index.md b/toolchain/apollolib/index.md index b891269..698343a 100644 --- a/toolchain/apollolib/index.md +++ b/toolchain/apollolib/index.md @@ -16,7 +16,7 @@ related: - toolchain/project-makefile.md - toolchain/pitfalls/apollolib-known-bugs.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ce1fcbf (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit 639e195 (2026-11-04)" - "WDrijver (maintainer), library calls for the SAGA chips, 2026-11-04" --- @@ -63,7 +63,7 @@ the macros `_A0(...)` … `_D7(...)` from `_Defines.h`. | Pictures, PIP, pointer | `ApolloLoadPicture`, `ApolloShowPicture`, `ApolloShowPiP1/2`, `ApolloHidePiP1/2`, `ApolloLoadPointer`, `ApolloShowPointer`, `ApolloShowFile` | [picture.md](picture.md) | | Input | `ApolloMouse`, `ApolloJoypad`, `ApolloKeyboard`, `ApolloKeyboardToUnicode`, `ApolloGetMouseDelta` | [input.md](input.md) | | Memory and blit | `ApolloCopyBlock`, `ApolloFillLongs`, `ApolloSwapLong`, `ApolloBlit…Loop` and others | [memory-and-blit.md](memory-and-blit.md) | -| System and timing | `ApolloWaitVBL`, `ApolloCPUTick`, `ApolloCPUDelay`, `ApolloTimer`, `ApolloTakeOver`, `ApolloForbid`, `ApolloDisable` | [system-and-timing.md](system-and-timing.md) | +| System and timing | `ApolloWaitVBL`, `ApolloCPUTick`, `ApolloCPUDelay`, `ApolloTimer`/`ApolloTimerStop`, `ApolloTakeOver`/`ApolloRelease`, `ApolloForbid`/`ApolloPermit`, `ApolloDisable`/`ApolloEnable` | [system-and-timing.md](system-and-timing.md) | | Debug | `ApolloDebugInit`, `ApolloDebugPutStr`, `AD()`, `Debug_Info()` … | [debug.md](debug.md) | | Files | `ApolloLoadFile`, `ApolloCacheFile` | this file | @@ -93,13 +93,13 @@ does the same with plain arguments. Both use libnix `fopen`/`fread`. `ApolloLoadFile` returns the sound codes. -### Header mismatch +### Versions -`ApolloCrossDev_Lib.h` declares `ApolloShowPiP()` and `ApolloHidePiP()`, -which don't exist. The functions that exist, `ApolloShowPiP1/2` and -`ApolloHidePiP1/2`, are not declared: declare them yourself until the -header is fixed (see -[apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). +Many bugs were fixed on 2026-11-04 (commit 639e195): use that version or +later. An older copy has the bugs listed in +[apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md), among them +broken 320-pixel modes and no way to undo `ApolloTakeOver` or +`ApolloTimer`. ## Differences from AmigaOS / AROS / 68060 @@ -114,7 +114,8 @@ header is fixed (see - DO use ApolloLib for Arne and Isabelle instead of programming the registers yourself (a best practice, not a forced rule). -- DO check the known bugs before you rely on a function: +- DO build against ApolloLib 639e195 or later. +- DO check the limits that remain before you rely on a function: [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md). -- DO declare `ApolloShowPiP1/2` and `ApolloHidePiP1/2` yourself. -- DON'T call `ApolloShowPiP()` or `ApolloHidePiP()`: they don't exist. +- DON'T copy an old ApolloLib into a project: compile the one in + `Projects/ApolloLib`. diff --git a/toolchain/apollolib/input.md b/toolchain/apollolib/input.md index f283821..5dcc430 100644 --- a/toolchain/apollolib/input.md +++ b/toolchain/apollolib/input.md @@ -17,7 +17,7 @@ related: - core/saga-registers/pointer.md - toolchain/pitfalls/apollolib-known-bugs.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _LibASM.s, _Defines.h), commit ce1fcbf (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _LibASM.s, _Defines.h), commit 639e195 (2026-11-04)" --- ## Summary @@ -36,7 +36,7 @@ OS-takeover code. | X, Y counters | `$DFF00B`, `$DFF00A` (JOY0DAT) | | Left button | CIA-A `$BFE001` bit 6 (0 = pressed) | | Right button | POTINP `$DFF016` bit 10 (0 = pressed) | -| "Middle" | Low byte of the wheel register `$DFF212` (see the known bugs) | +| Middle button | POTINP `$DFF016` bit 8 (0 = pressed) | Each call: @@ -95,4 +95,5 @@ code to a character. `ApolloMouse` call. - DO call these functions once per frame, for example after `ApolloWaitVBL`. - DON'T use them while Intuition owns the input. -- DON'T rely on the middle-button flags (see the known bugs). +- DON'T rely on the middle-button flags with an ApolloLib older than + 639e195: there they followed the wheel counter. diff --git a/toolchain/apollolib/memory-and-blit.md b/toolchain/apollolib/memory-and-blit.md index 4419344..cd45aee 100644 --- a/toolchain/apollolib/memory-and-blit.md +++ b/toolchain/apollolib/memory-and-blit.md @@ -16,7 +16,7 @@ related: - core/best-practices/align-copy-destination.md - toolchain/pitfalls/apollolib-known-bugs.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibASM.s, _Lib.h), commit ce1fcbf (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibASM.s, _Lib.h), commit 639e195 (2026-11-04)" --- ## Summary @@ -51,7 +51,7 @@ aligned AMMX or quad moves (see |---|---| | `ApolloSwapWord(w)`, `ApolloSwapLong(l)`, `ApolloSwapOcta(q)` | Return the value with its bytes reversed (d0) | | `ApolloEndianSwapWordBuffer(s, length)` | Byte-swaps 16-bit words in place, 8 bytes per AMMX step; buffer ≥ 8 bytes, 2-byte aligned | -| `ApolloEndianSwapLongBuffer(s, length)` | Meant to byte-swap 32-bit longs in place; see the known bugs | +| `ApolloEndianSwapLongBuffer(s, length)` | Byte-swaps 32-bit longs in place, 8 bytes per AMMX step; buffer ≥ 8 bytes, 4-byte aligned. Before 639e195 it swapped words. | | `ApolloEndianSwap2Loop`, `ApolloEndianSwap8Loop` | Older swap loops, used by `ApolloShowFile` | ### Pixel rectangles @@ -85,4 +85,5 @@ The blit routines work on R5G6B5 pixels (16-bit modes). or 4 (`ApolloCopyLongs`, `ApolloFillLongs`). - DO align the destination of large copies (see [align-copy-destination.md](../../core/best-practices/align-copy-destination.md)). -- DON'T use `ApolloEndianSwapLongBuffer` until its known bug is fixed. +- DON'T use `ApolloEndianSwapLongBuffer` from an ApolloLib older than + 639e195: it swapped words, not bytes. diff --git a/toolchain/apollolib/picture.md b/toolchain/apollolib/picture.md index 6519b57..b23e765 100644 --- a/toolchain/apollolib/picture.md +++ b/toolchain/apollolib/picture.md @@ -17,9 +17,9 @@ related: - core/saga-registers/pointer.md - toolchain/pitfalls/apollolib-known-bugs.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h, _Defines.h), commit ce1fcbf (2026-11-04)" - - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit ce1fcbf" - - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit ce1fcbf" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h, _Defines.h), commit 639e195 (2026-11-04)" + - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit 639e195" + - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit 639e195" --- ## Summary @@ -73,8 +73,9 @@ Core 12500 and later. | 1440, 1920 | 900, 1080 (height not checked) | Another width returns `APOLLO_PICTURE_W_ERROR`, another height of a -checked width `APOLLO_PICTURE_H_ERROR`. **The 320-pixel modes are broken** -in this version (see [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). +checked width `APOLLO_PICTURE_H_ERROR`. Before ApolloLib 639e195 the +320-pixel modes were broken (see +[apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). ### Pointer @@ -117,5 +118,5 @@ if (ApolloLoadPicture(&pic) == APOLLO_PICTURE_OK) { DMA row length. - DO restore GFXMODE and the chunky pointer before the program exits. - DON'T free a picture that is still shown. -- DON'T use `ApolloShowPicture` for 320-pixel-wide or 8-bit pictures in - this version (see [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md)). +- DON'T use `ApolloShowPicture` for 320-pixel-wide or 8-bit pictures with + an ApolloLib older than 639e195. diff --git a/toolchain/apollolib/sound.md b/toolchain/apollolib/sound.md index 3c89330..07564ce 100644 --- a/toolchain/apollolib/sound.md +++ b/toolchain/apollolib/sound.md @@ -16,16 +16,17 @@ related: - core/saga-registers/arne-dma-and-interrupts.md - toolchain/pitfalls/apollolib-known-bugs.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h), commit ce1fcbf (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h), commit 639e195 (2026-11-04)" --- ## Summary ApolloLib loads WAV and AIFF files into a 32-byte aligned buffer and plays -them on an Arne channel: `ApolloLoadSound`, then `ApolloPlaySound`. A +them on an Arne channel: `ApolloLoadSound`, then `ApolloPlaySound`, which +programs 16-bit stereo, 16-bit mono or 8-bit mono to match the file. A second, older API (`ApolloPlay`, `ApolloPlayFile`) takes a channel number -and a buffer instead of a `struct ApolloSound`. Both always play 16-bit -stereo. +and a buffer instead of a `struct ApolloSound`, and always plays 16-bit +stereo at 44.1 kHz. ## Details @@ -37,7 +38,8 @@ stereo. | `buffer`, `position` | `ApolloLoadSound` | Allocated buffer; the data starts at `buffer + position` (32-byte aligned) | | `size` | `ApolloLoadSound` | Data size in bytes | | `period` | `ApolloLoadSound` | `3546895 / sample rate` (PAL clock) | -| `stereo`, `datarate`, `bitspersample` | `ApolloLoadSound` (WAV) | Read from the header, but not used for playback | +| `stereo`, `bitspersample` | `ApolloLoadSound` (WAV, AIFF) or caller | Select the format in `ApolloPlaySound`; `bitspersample` 0 means 16-bit stereo | +| `datarate` | `ApolloLoadSound` (WAV) | Not used for playback | | `channel` | Caller or `ApolloPlaySound` | Arne channel 0–15 | | `staticchannel` | Caller | `true`: play on `channel`; `false`: `ApolloPlaySound` picks the first free channel from 1 to 15 | | `loop`, `fadein`, `volume_left`, `volume_right` | Caller | Playback; volume `$00`–`$FF` per side | @@ -50,21 +52,29 @@ stereo. | `ApolloLoadSound(sound)` | Parses the WAV or AIFF header, allocates `buffer` (`AllocVec`, `MEMF_ANY`), reads the data. WAV: PCM only. Returns an `APOLLO_SOUND_…` code. | | `ApolloAllocSound(sound)` | Allocates an aligned buffer of `sound->size` bytes for data you fill yourself | | `ApolloFreeSound(sound)` | `FreeVec` of the buffer | -| `ApolloPlaySound(sound)` | Picks a channel if `staticchannel` is false, sets pointer, length (`size / 8`), volume 0, CTRL and period, starts DMA (DMACON for 0–3, DMACON2 for 4–15), then sets the volume or fades in | +| `ApolloPlaySound(sound)` | Picks a channel if `staticchannel` is false, sets pointer, length, volume 0, CTRL and period, starts DMA (DMACON for 0–3, DMACON2 for 4–15), then sets the volume or fades in | | `ApolloStopSound(sound)` / `ApolloStartSound(sound)` | Clears or sets the channel's DMA bit | | `ApolloVolumeSound(sound)` | Writes `volume_left << 8 \| volume_right` to AUDxVOL | | `ApolloFadeInSound(sound)` / `ApolloFadeOutSound(sound)` | Steps the volume by 1 per VBlank (`ApolloWaitVBL`) to or from the average of left and right; fade-out then stops the channel. Both block the caller. | -`ApolloPlaySound` writes CTRL `$0005` (16-bit, stereo, looping) when -`loop` is set and `$0007` (16-bit, stereo, one-shot) when not, whatever the -file contains. +`ApolloPlaySound` picks CTRL and the LEN unit from the format, and adds +the one-shot bit (`$0002`) when `loop` is not set: + +| Format | CTRL | LEN = size / | +|---|---|---| +| 16-bit stereo, or unknown (`bitspersample` 0) | `$0005` | 8 | +| 16-bit mono | `$0001` | 4 | +| 8-bit mono | `$0000` | 2 | + +8-bit stereo can't play: Arne's stereo mode is 16-bit. 8-bit WAV samples +are unsigned and are not converted to the signed format Arne expects. ### Channel API (older, from the RHLOS project) | Function | What it does | |---|---| | `ApolloPlay(channel, vol_l, vol_r, loop, buffer, length, offset)` | Plays `buffer + offset`, length `(length - offset) / 8 - 32` pairs, at period 80 (44.1 kHz). Returns `false` if the channel is busy. | -| `ApolloPlayFile(filename, buffer, offset, channel, vol_l, vol_r, loop)` | Reads the file into `buffer` and plays it on `channel`, stopping the channel first if it runs | +| `ApolloPlayFile(filename, buffer, offset, channel, vol_l, vol_r, loop)` | Reads the file from `offset` into `buffer` and plays it on `channel`, stopping the channel first if it runs | | `ApolloCacheFile(filename, &cache, &length, offset)` | Reads a file into `cache` for `ApolloPlay` | | `ApolloVolume(channel, vol_l, vol_r)` | Sets the volume | | `ApolloStartChannel(channel)` / `ApolloStopChannel(channel)` | Starts or stops a channel's DMA | @@ -96,11 +106,11 @@ if (ApolloLoadSound(&music) == APOLLO_SOUND_OK) { ## Do / Don't -- DO give `ApolloPlaySound` 16-bit stereo data: it always programs 16-bit - stereo. +- DO set `stereo` and `bitspersample` yourself for raw data that isn't + 16-bit stereo. - DO set `staticchannel` and `channel` if you need channel 0: the automatic search starts at 1. - DO call `ApolloFreeSound` when you are done. -- DON'T expect `stereo`, `bitspersample`, `fadeout` or `pan` to change - playback. +- DON'T play 8-bit WAV files without converting them to signed samples. +- DON'T expect `fadeout` or `pan` to change playback. - DON'T use the channel API and audio.device on the same channel. diff --git a/toolchain/apollolib/system-and-timing.md b/toolchain/apollolib/system-and-timing.md index a00fb62..7d1406f 100644 --- a/toolchain/apollolib/system-and-timing.md +++ b/toolchain/apollolib/system-and-timing.md @@ -16,15 +16,15 @@ related: - os/os-takeover.md - toolchain/pitfalls/apollolib-known-bugs.md sources: - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _LibASM.s), commit ce1fcbf (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _LibASM.s), commit 639e195 (2026-11-04)" --- ## Summary ApolloLib waits for VBlank, reads the 68080 cycle counter and waits a number of milliseconds, counts VBlanks in an interrupt server, and has -three takeover helpers. None of the takeover helpers has a counterpart -that undoes it: a program has to restore the system itself. +three takeover helpers. From commit 639e195 every one of them has a +counterpart that undoes it; call it before the program exits. ## Details @@ -32,33 +32,36 @@ that undoes it: a program has to restore the system itself. |---|---| | `ApolloWaitVBL()` | Polls INTREQR bit 5 (VERTB) until set, then clears it in INTREQ | | `ApolloCPUTick()` | Returns the clock cycle counter CCC (`movec $809,d0`, see [performance-counters.md](../../core/cpu-68080/performance-counters.md)) | -| `ApolloCPUDelay(ms)` | Busy-waits `ms × 92000` cycles on CCC | +| `ApolloCPUDelay(ms)` | Busy-waits `ms × multiplier × 7094` cycles on CCC; the multiplier is the low byte of `$DFF3FC` (14 if 0) | | `ApolloTimer()` | Adds a VBlank interrupt server (`AddIntServer`, `INTB_VERTB`) that increments `GlobalTimer` every frame | -| `ApolloTakeOver()` | Opens intuition.library, `CloseWorkBench()`, sets the task priority to 20 | -| `ApolloForbid()` | Calls exec `Forbid()` | -| `ApolloDisable()` | Calls exec `Disable()` | +| `ApolloTimerStop()` | Removes that server (`RemIntServer`) | +| `ApolloTakeOver()` | Opens intuition.library, `CloseWorkBench()`, sets the task priority to 20, and remembers the library base and the old priority | +| `ApolloRelease()` | Restores the old priority, `OpenWorkBench()`, closes intuition.library | +| `ApolloForbid()` / `ApolloPermit()` | Call exec `Forbid()` / `Permit()` | +| `ApolloDisable()` / `ApolloEnable()` | Call exec `Disable()` / `Enable()` | + +`ApolloTimerStop`, `ApolloRelease`, `ApolloPermit` and `ApolloEnable` +exist from ApolloLib commit 639e195. ### Limits - **`ApolloWaitVBL` clears VERTB** itself. Under a running OS that takes the event away from the OS's VBlank handling; use it in takeover code. -- **`ApolloCPUDelay` assumes 92 MHz** (92000 cycles per ms, also - `APOLLOCYCLES`). The V4 runs at 99.31 MHz (14 × 7.09379 MHz, see - [core-at-a-glance.md](../../core/cpu-68080/core-at-a-glance.md)), so a - delay is about 7 % short there. -- **No restore functions:** there is no Permit, Enable, `OpenWorkBench` - or priority reset, and no `RemIntServer` for `ApolloTimer`. See - [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md) for what - that means at exit. +- **`ApolloCPUDelay`** counts `multiplier × 7.094` MHz cycles per ms, so + it follows the clock the board reports at `$DFF3FC` (see + [system.md](../../core/saga-registers/system.md)). Before 639e195 it + assumed 92 MHz, about 7 % short on a 99.31 MHz V4. +- **`ApolloRelease`** restores the priority that `ApolloTakeOver` saved. + Call it only after `ApolloTakeOver`; otherwise it sets priority 0. -### Restore at exit (your code) +### Restore at exit | After | Call before exit | |---|---| -| `ApolloForbid()` | `Permit()` | -| `ApolloDisable()` | `Enable()` | -| `ApolloTakeOver()` | `SetTaskPri(FindTask(NULL), old)`, `OpenWorkBench()`, `CloseLibrary()` on the intuition.library base it opened (you don't get it back) | -| `ApolloTimer()` | `RemIntServer(INTB_VERTB, …)`: the server node is internal to ApolloLib, so this isn't possible yet | +| `ApolloTimer()` | `ApolloTimerStop()`: the server node lives in the program, so a server left behind runs freed memory | +| `ApolloTakeOver()` | `ApolloRelease()` | +| `ApolloForbid()` | `ApolloPermit()` | +| `ApolloDisable()` | `ApolloEnable()` | This is what [os-takeover.md](../../os/os-takeover.md) asks of every takeover program: restore multitasking and the Workbench screen on exit. @@ -73,10 +76,8 @@ takeover program: restore multitasking and the Workbench screen on exit. ## Do / Don't -- DO pair `ApolloForbid` with `Permit()` and `ApolloDisable` with - `Enable()` in your exit code. -- DO reopen the Workbench after `ApolloTakeOver`. -- DO calibrate `ApolloCPUDelay` with the clock multiplier from `$DFF3FC` - if the delay must be exact. -- DON'T call `ApolloTimer` in a program that exits back to the OS (see the - known bugs). +- DO call `ApolloTimerStop`, `ApolloRelease`, `ApolloPermit` and + `ApolloEnable` in your exit code, in the reverse order of their + counterparts. +- DON'T use `ApolloTimer` with an ApolloLib older than 639e195 in a + program that exits back to the OS: there is no way to remove the server. diff --git a/toolchain/pitfalls/apollolib-known-bugs.md b/toolchain/pitfalls/apollolib-known-bugs.md index 0c76998..c1f8b75 100644 --- a/toolchain/pitfalls/apollolib-known-bugs.md +++ b/toolchain/pitfalls/apollolib-known-bugs.md @@ -1,5 +1,5 @@ --- -title: ApolloLib known bugs (commit ce1fcbf) +title: ApolloLib bugs fixed in 2026-11 and the limits that remain type: pitfall owner: WDrijver status: draft @@ -9,7 +9,7 @@ components: [toolchain, saga-audio-arne, saga-video-isabelle] applies_to: core: "any" os: [any] -tags: [apollolib, bugs, showpicture, timer, takeover, endian, mouse] +tags: [apollolib, bugs, showpicture, timer, takeover, endian, mouse, sound] related: - toolchain/apollolib/index.md - toolchain/apollolib/picture.md @@ -17,74 +17,85 @@ related: - toolchain/apollolib/system-and-timing.md sources: - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ce1fcbf (2026-11-04), read on 2026-11-04" + - "ApolloCrossDev pull requests #17 and #18 (merged 2026-11-04), fixed in commit 639e195" --- ## Symptom -A program built with ApolloLib shows a wrong screen mode or wrong colours, -crashes some time after it exits, never gets the Workbench back, or plays -a sound wrongly, while the calls look right. +A program shows a 320-pixel picture in the wrong mode or an 8-bit picture +in wrong colours, crashes some time after it exits, never gets the +Workbench back, or plays a mono sound too fast. It was built against +ApolloLib from before 2026-11-04. ## Cause -ApolloLib at commit ce1fcbf has bugs and gaps, found by reading the code -on 2026-11-04. "Confirmed" means the code shows it; "suspected" means the -code suggests it and a test on a V4 is still needed. None has been tested -on hardware. - -### Serious - -| Function | Problem | Status | -|---|---|---| -| `ApolloShowPicture` | `case 320:` has no `break` after its inner switch and falls into `case 480`, which ORs in `$0800`. 320x200 becomes `$0900` (304x224); 320x240 and 320x256 become `$0A00`/`$0B00` (1280x720, 640x360). | Confirmed | -| `ApolloTimer` | The VBlank interrupt server node lives in the program's code and is never removed. After the program exits and is unloaded, the next VBlank runs freed memory. | Confirmed (code); crash expected | -| `ApolloTakeOver`, `ApolloForbid`, `ApolloDisable` | No counterpart restores the Workbench, the task priority, `Permit()` or `Enable()`; the opened intuition.library is never closed | Confirmed | -| `ApolloShowPicture` | For depth ≤ 8 it reorders the bytes of each palette entry that `ApolloLoadPicture` already converted to `$IIRRGGBB`, which gives `$IIGGRRII` | Suspected: 8-bit pictures show wrong colours | - -### Others - -| Function | Problem | Status | -|---|---|---| -| `ApolloCrossDev_Lib.h` | Declares `ApolloShowPiP()`/`ApolloHidePiP()`, which don't exist; the real `ApolloShowPiP1/2`, `ApolloHidePiP1/2` are not declared | Confirmed | -| `ApolloMouse` | `Button_Middle` is the low byte of the wheel register `$DFF212` (the wheel counter), not a button | Suspected | -| `ApolloEndianSwapLongBuffer` | `vperm #$23016745` swaps the words of each long instead of reversing its bytes (`$32107654`); its remainder count (`and #5`) and that of `ApolloEndianSwapWordBuffer` (`and #3`) count bytes, not longs or words | Confirmed (code) | -| `ApolloCPUDelay` | Assumes 92 MHz (the V4 runs at 99.31 MHz); the 32-bit overflow check after `add.l` (`cmp.l #$FFFFFFFF`) can't detect a carry | Confirmed (code) | -| `ApolloPlaySound` | Always programs 16-bit stereo, LEN = size / 8, whatever the file contains; the free-channel search skips channel 0 | Confirmed | -| `ApolloPlayFile` | Applies `offset` twice (in `fseek` and again in pointer and length) | Confirmed | -| `ApolloPlay`, `ApolloPlayFile` | Subtract 32 from the length in sample pairs, without an explanation | Unclear | -| `ApolloLoadFile`, `ApolloLoadSound`, `ApolloLoadPicture` | Some error returns leave the file open (header checks, short reads) | Confirmed | -| `ApolloLoadPicture` | Reads the BMP palette from offset 54, right only for a 40-byte BITMAPINFOHEADER; uses `static` file state (not reentrant) | Confirmed | -| `ApolloShowPattern` | Writes only the first byte of each pixel for depths above 8 | Confirmed | +ApolloLib up to commit ce1fcbf had these bugs. They were found by reading +the code while documenting the library, and fixed in commit 639e195 +(ApolloCrossDev pull request #18). None of the fixes is tested on V4 +hardware yet. + +### Fixed in 639e195 + +| Function | Bug before 639e195 | +|---|---| +| `ApolloShowPicture` | `case 320` fell through into `case 480`: 320x200 became 304x224, 320x240/256 became 1280x720/640x360 | +| `ApolloShowPicture` | The 8-bit palette was reordered a second time: wrong colours | +| `ApolloTimer` | The VBlank server was never removed; its node lives in the program, so the system crashed after the program exited. Now: `ApolloTimerStop()` | +| `ApolloTakeOver`, `ApolloForbid`, `ApolloDisable` | No way back. Now: `ApolloRelease()`, `ApolloPermit()`, `ApolloEnable()` | +| `ApolloCPUDelay` | Fixed 92 MHz; the overflow check could not work | +| `ApolloEndianSwapLongBuffer` | Swapped words instead of bytes; both buffer swaps counted their remainder wrongly | +| `ApolloPlaySound` | Always 16-bit stereo, whatever the file contained | +| `ApolloPlayFile` | Applied its offset twice | +| `ApolloMouse` | The "middle button" was the wheel counter | +| `ApolloLoadFile`/`Sound`/`Picture` | Left the file open on errors; BMP palette read from a fixed offset | +| `ApolloShowPattern` | Wrote only the first byte of each pixel | +| `ApolloCrossDev_Lib.h` | Declared `ApolloShowPiP`/`ApolloHidePiP`, which don't exist, and not `ApolloShowPiP1/2`/`ApolloHidePiP1/2` | +| Five functions (#17) | Wrong DMACON2R bit for channels 4–15, a write through an uninitialized pointer, missing return values, an always-on keyboard print | + +### Limits that remain + +| Function | Limit | +|---|---| +| `ApolloPlaySound` | 8-bit stereo can't play (Arne's stereo mode is 16-bit); 8-bit WAV samples are unsigned and are not converted to the signed format Arne expects | +| `ApolloPlaySound` | A sound without format information (`bitspersample` 0, for example RAW) plays as 16-bit stereo | +| `ApolloPlaySound` | The free-channel search starts at channel 1 | +| `ApolloPlay`, `ApolloPlayFile` | Subtract 32 sample pairs from the length, without an explanation | +| `ApolloLoadPicture` | Keeps its file state in `static` variables: not reentrant | +| `ApolloRelease` | Restores the priority saved by `ApolloTakeOver`; called without it, it sets priority 0 | ## Wrong ```c -pic.width = 320; pic.height = 200; /* 8-bit, 320x200 */ -ApolloShowPicture(&pic); /* sets GFXMODE $0901, not $0101 */ -ApolloTimer(); /* never removed */ -return 0; /* next VBlank jumps into freed code */ +ApolloTakeOver(); +ApolloTimer(); +/* ... */ +return 0; /* Workbench stays closed; next VBlank jumps into freed code */ ``` ## Right ```c -/* Write the mode yourself for 320-pixel modes until the bug is fixed */ -*(volatile uint16_t *)0xDFF1F4 = APOLLO_SAGA_320_200 | APOLLO_SAGA_8_INDEX; -/* Count VBlanks in your own interrupt server, and remove it before exit */ +ApolloTakeOver(); +ApolloTimer(); +/* ... */ +ApolloTimerStop(); /* ApolloLib 639e195 or later */ +ApolloRelease(); +return 0; ``` ## Detection +- Check which ApolloLib a project builds against: `ApolloRelease` and + `ApolloTimerStop` exist only from 639e195. A build that fails on them + uses an older copy. - Read GFXMODE back at `$DFE1F4` after `ApolloShowPicture`: the high byte must match the picture's size (see [chunky-plane.md](../../core/saga-registers/chunky-plane.md)). -- Search the program for `ApolloTimer`, `ApolloTakeOver`, `ApolloForbid` - and `ApolloDisable`, and check the exit path for the matching restore. ## Evidence | Claim | Source | Core | Method | |---|---|---|---| -| All bugs in the tables | (SRC) | any | Code reading of commit ce1fcbf, 2026-11-04 | -| The 14 earlier `-Wall` warnings, 5 of them bugs, are fixed | (SRC) | any | ApolloCrossDev pull request #17, merged 2026-11-04 | -| None of these is tested on V4 hardware | – | – | – | +| The bugs before 639e195 | (SRC) | any | Code reading of commit ce1fcbf, 2026-11-04 | +| Fixed in 639e195; builds without warnings | (SRC) | any | ApolloCrossDev #18: ApolloPointer, SAGAPiP and a link test build | +| None of the fixes is tested on V4 hardware | – | – | – | From c78657454eaa0c9d6a7a5367147d93c38cd0f45b Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 17:43:02 +0300 Subject: [PATCH 3/7] First example: SAGA PIP in overlay and chroma-key mode examples/saga-pip-overlay-and-chroma-key.md takes the PIP part of ApolloCrossDev's SAGAPiP example (commit 5ad4035) as three compiling functions on ApolloLib, and lists the problems of the full example: no PIP palette, a window check that can't fail, BMB_ bits used as BMF_ flags, a hard-coded Workbench mode, and leaks on the error paths. CI: compile-snippets.sh adds -I$APOLLOLIB when set; the compile-examples job fetches ApolloCrossDev Projects/ApolloLib (sparse, same commit as the toolchain) so snippets can include ApolloCrossDev_Lib.h. ApolloLib files: ApolloShowPiP1/2 don't load an 8-bit PIP's palette (a regression from the older ApolloLib). Co-Authored-By: Claude Opus 5.5 --- .github/CODEOWNERS | 1 + .github/workflows/kb-checks.yml | 11 ++ examples/saga-pip-overlay-and-chroma-key.md | 138 ++++++++++++++++++++ toolchain/apollolib/picture.md | 4 +- toolchain/pitfalls/apollolib-known-bugs.md | 1 + tools/compile-snippets.sh | 5 +- 6 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 examples/saga-pip-overlay-and-chroma-key.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index daa08c0..d1970f5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -169,6 +169,7 @@ /core/saga-registers/sprite-collision.md @WDrijver /core/saga-registers/system.md @WDrijver /core/saga-video-isabelle/index.md @WDrijver +/examples/saga-pip-overlay-and-chroma-key.md @WDrijver /hardware/index.md @WDrijver /os/apolloos-and-amigaos-on-v4.md @WDrijver /os/apolloos-specific-on-v4.md @WDrijver diff --git a/.github/workflows/kb-checks.yml b/.github/workflows/kb-checks.yml index 7bdc267..9b2d5dd 100644 --- a/.github/workflows/kb-checks.yml +++ b/.github/workflows/kb-checks.yml @@ -44,6 +44,7 @@ jobs: env: ACD_DIR: /home/runner/ApolloCrossDev PREFIX: /home/runner/ApolloCrossDev/Compilers/GCC-6.50-Dev + APOLLOLIB: /home/runner/acd-src/Projects/ApolloLib steps: - uses: actions/checkout@v4 @@ -52,6 +53,16 @@ jobs: - name: Find ApolloCrossDev commit id: acd run: echo "sha=$(git ls-remote https://github.com/WDrijver/ApolloCrossDev refs/heads/main | cut -f1)" >> "$GITHUB_OUTPUT" + # Snippets may include ApolloLib's headers: fetch only Projects/ApolloLib + # of the same commit (a few hundred KB), also when the toolchain is cached. + - name: Fetch ApolloLib + run: | + git init -q /home/runner/acd-src + cd /home/runner/acd-src + git sparse-checkout set --no-cone Projects/ApolloLib + git fetch -q --depth 1 --filter=blob:none https://github.com/WDrijver/ApolloCrossDev ${{ steps.acd.outputs.sha }} + git checkout -q FETCH_HEAD + test -f "$APOLLOLIB/ApolloCrossDev_Lib.h" - name: Restore ApolloCrossDev id: cache uses: actions/cache/restore@v4 diff --git a/examples/saga-pip-overlay-and-chroma-key.md b/examples/saga-pip-overlay-and-chroma-key.md new file mode 100644 index 0000000..89fe104 --- /dev/null +++ b/examples/saga-pip-overlay-and-chroma-key.md @@ -0,0 +1,138 @@ +--- +title: SAGA PIP in overlay and chroma-key mode (from the SAGAPiP example) +type: example +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [saga-video-isabelle, toolchain] +applies_to: + core: "Core 12000+" + os: [any] +tags: [saga, isabelle, pip, overlay, chroma-key, apollolib, example] +related: + - core/saga-registers/pip.md + - core/saga-registers/colour-ports.md + - toolchain/apollolib/picture.md + - core/best-practices/prefer-library-calls-for-saga-chips.md +sources: + - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit 5ad4035 (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit 639e195 (2026-11-04)" +--- + +## Purpose + +Show a picture-in-picture (PIP) window on top of the screen (overlay mode) +and behind it (chroma-key mode), with ApolloLib. The code below is the PIP +part of ApolloCrossDev's SAGAPiP example, in three functions: + +- `ShowPiP1Overlay`: an 8-bit PIP in front of the screen; palette index + `$FF` is transparent. +- `ShowPiP1Behind`: a 16-bit PIP behind the screen, visible where the + screen is purple (the chroma key). +- `HidePiP1`: PIP 1 off. + +The full SAGAPiP program does more: it plays a soundtrack, shows a +1280x720 background, opens a Workbench window, lays PIP 1 and PIP 2 over +it and follows the window when it moves (see "The full example" below). + +## Build + +The functions compile as a library unit. In a project that uses ApolloLib +(see [toolchain/apollolib/index.md](../toolchain/apollolib/index.md)): + +```sh +m68k-amigaos-gcc -noixemul -O2 -m68080 -m68881 -ffast-math -fomit-frame-pointer -Wall \ + -I$APOLLOLIB -c pip.c -o pip.o +``` + +The full example builds in ApolloCrossDev with `make` in +`Projects/SAGAPiP` (which also uploads it to the Apollo V4), or +`make cleanbefore build` to only build. + +## Code + +```c +// kb:compile +/* SAGA PIP with ApolloLib: overlay and chroma-key mode, after the SAGAPiP + example in ApolloCrossDev Projects/SAGAPiP. Needs ApolloLib 639e195+. */ +#include "ApolloCrossDev_Lib.h" + +#define PIP1_REG(a) (*(volatile UWORD *)(a)) + +/* Load the palette of an 8-bit picture into PIP 1's colour port. */ +static void LoadPiP1Palette(const struct ApolloPicture *pic) +{ + for (ULONG i = 0; i < pic->palettesize; i++) + *(volatile ULONG *)APOLLO_SAGA_PIP1CHK_COL = (i << 24) | (pic->palette[i] & 0xFFFFFF); +} + +/* Overlay mode: PIP 1 lies in front of the screen at (left, top). With the + transparency bit, palette index $FF is transparent. */ +void ShowPiP1Overlay(struct ApolloPicture *pic, WORD left, WORD top) +{ + LoadPiP1Palette(pic); + *(volatile ULONG *)APOLLO_SAGA_PIP1_POINTER = (ULONG)(pic->buffer + pic->position); + PIP1_REG(APOLLO_SAGA_PIP1_X_START) = left + 16; /* +16 and -1: as in SAGAPiP */ + PIP1_REG(APOLLO_SAGA_PIP1_X_STOP) = left + pic->width + 16; + PIP1_REG(APOLLO_SAGA_PIP1_Y_START) = top - 1; + PIP1_REG(APOLLO_SAGA_PIP1_Y_STOP) = top + pic->height - 1; + PIP1_REG(APOLLO_SAGA_PIP1_GFXMODE) = APOLLO_SAGA_PIP_TRANSON | APOLLO_SAGA_8_INDEX; + PIP1_REG(APOLLO_SAGA_PIP1_MODULO) = 0; + PIP1_REG(APOLLO_SAGA_PIP1_CLRKEY) = 0x0000; /* 0 = overlay mode */ + ApolloShowPiP1(pic); /* DMA row length last: PIP on */ +} + +/* Chroma-key mode: PIP 1 lies behind the screen and shows only where the + screen has the key colour, here purple ($F0F in RGB444). */ +void ShowPiP1Behind(struct ApolloPicture *pic16, WORD width, WORD height) +{ + *(volatile ULONG *)APOLLO_SAGA_PIP1_POINTER = (ULONG)(pic16->buffer + pic16->position); + PIP1_REG(APOLLO_SAGA_PIP1_X_START) = 16; + PIP1_REG(APOLLO_SAGA_PIP1_X_STOP) = width + 16; + PIP1_REG(APOLLO_SAGA_PIP1_Y_START) = 0; + PIP1_REG(APOLLO_SAGA_PIP1_Y_STOP) = height; + PIP1_REG(APOLLO_SAGA_PIP1_GFXMODE) = APOLLO_SAGA_16_R5G6B5; + PIP1_REG(APOLLO_SAGA_PIP1_MODULO) = 0; + PIP1_REG(APOLLO_SAGA_PIP1_CLRKEY) = 0x8F0F; /* bit 15 = on, R=$F G=$0 B=$F */ + ApolloShowPiP1(pic16); +} + +/* Switch PIP 1 off: DMA row length 0. */ +void HidePiP1(void) +{ + ApolloHidePiP1(); +} +``` + +Load the pictures with `ApolloLoadPicture` first: an 8-bit BMP for the +overlay, a 16-bit DDS (R5G6B5) for the background. + +## Expected result + +- **Overlay:** after `ShowPiP1Overlay(&pic, left, top)` the 8-bit picture + appears over the screen, its top-left corner at (`left`, `top`) in + screen pixels. Pixels with palette index `$FF` show the screen behind. +- **Chroma key:** after `ShowPiP1Behind(&bg, 1280, 720)` the screen looks + unchanged, except where it is purple (`$F0F`): there the PIP shows + through. SAGAPiP fills a Workbench window with purple to look through it. +- **Off:** after `HidePiP1()` the PIP is gone. + +## The full example + +`Projects/SAGAPiP/SAGAPiP.c` (ApolloCrossDev commit 5ad4035) shows more than +the code above, but has these problems. Keep them out of your own code: + +| Problem | Effect | Do instead | +|---|---|---| +| It never loads the PIP palettes | The 8-bit overlays show whatever palette was in the PIP colour ports. ApolloLib's older version did this in `ApolloLoadPicture`; the current one only loads the chunky palette, in `ApolloShowPicture`. | Write the palette to `$DFF38C` (PIP 1) or `$DFF37C` (PIP 2), as `LoadPiP1Palette` does | +| `if (!&pipwindow)` | Tests the address of the variable, which is never NULL: a failed `OpenWindowTags` goes unnoticed | `if (!pipwindow)` | +| `AllocBitMap(..., BMF_DISPLAYABLE \| BMB_CLEAR \| BMB_HIJACKED \| ...)` | `BMB_…` are bit numbers (0, 7, 2, 9), not flags: ORed with `BMF_DISPLAYABLE` (2) they give `$F` = CLEAR, DISPLAYABLE, INTERLEAVED and STANDARD, not the HIJACKED and RTGCHECK the code names | Use only `BMF_…` flags | +| Restores the Workbench as GFXMODE `$0A04` | Works only on a 1280x720, 24-bit Workbench | Save GFXMODE and the pointer from `$DFE1F4`/`$DFE1EC` first, restore those | +| `struct ApolloSound soundtrack;` not cleared | `fadeout` is read uninitialized at exit | `struct ApolloSound soundtrack = {0};` | +| `goto exit4` from early errors | Frees picture buffers that were never loaded | Clear the structures first, or free only what was loaded | +| cybergraphics.library and the "Workbench" screen lock are never released | Resource leak | `CloseLibrary`, `UnlockPubScreen` | +| `ApolloCPUDelay(2000)` in a Workbench program | Busy-waits two seconds | `Delay()` from dos.library | + +The +16 (X) and −1 (Y) corrections place the PIP exactly over a Workbench +window in SAGAPiP; they are not documented elsewhere. diff --git a/toolchain/apollolib/picture.md b/toolchain/apollolib/picture.md index b23e765..e6ad1b2 100644 --- a/toolchain/apollolib/picture.md +++ b/toolchain/apollolib/picture.md @@ -52,7 +52,7 @@ Core 12500 and later. | `ApolloAllocPicture(pic)` | Allocates an aligned buffer of `width × height × depth / 8` bytes (or `size`) | | `ApolloFreePicture(pic)` | `FreeVec` of the buffer | | `ApolloShowPicture(pic)` | Picks the GFXMODE from width × height (see below) and depth 8, 15, 16, 24 or 32, loads the chunky palette for depth ≤ 8, writes GFXMODE (`$DFF1F4`), modulo (`$DFF1E6`) and pointer (`$DFF1EC`) | -| `ApolloShowPiP1(pic)` / `ApolloShowPiP2(pic)` | Writes the PIP's DMA row length (`width × depth / 8`), which switches it on. Set position, pointer, format and modulo first (see [pip.md](../../core/saga-registers/pip.md)). | +| `ApolloShowPiP1(pic)` / `ApolloShowPiP2(pic)` | Writes the PIP's DMA row length (`width × depth / 8`), which switches it on. Set position, pointer, format and modulo first (see [pip.md](../../core/saga-registers/pip.md)). It doesn't load the palette of an 8-bit picture: write `pic->palette` to the PIP colour port yourself (see [the example](../../examples/saga-pip-overlay-and-chroma-key.md)). | | `ApolloHidePiP1()` / `ApolloHidePiP2()` | Writes 0 to the DMA row length: the PIP goes off | | `ApolloLoadPointer(ptr, pic)` | Converts a 32x32 BMP (8-bit with palette, or 32-bit ARGB) into pointer sprite data: colour index + transparency byte per pixel and a 256-entry palette | | `ApolloShowPointer(ptr)` | Writes the palette (`$DFF3A8` index, `$DFF3AA` colour) and copies the image to `$DFD000` | @@ -116,6 +116,8 @@ if (ApolloLoadPicture(&pic) == APOLLO_PICTURE_OK) { - DO set the PIP registers before `ApolloShowPiP1/2`: they only write the DMA row length. +- DO load an 8-bit PIP's palette into its colour port (`$DFF38C` for PIP 1, + `$DFF37C` for PIP 2) yourself. - DO restore GFXMODE and the chunky pointer before the program exits. - DON'T free a picture that is still shown. - DON'T use `ApolloShowPicture` for 320-pixel-wide or 8-bit pictures with diff --git a/toolchain/pitfalls/apollolib-known-bugs.md b/toolchain/pitfalls/apollolib-known-bugs.md index c1f8b75..d5512e8 100644 --- a/toolchain/pitfalls/apollolib-known-bugs.md +++ b/toolchain/pitfalls/apollolib-known-bugs.md @@ -61,6 +61,7 @@ hardware yet. | `ApolloPlaySound` | The free-channel search starts at channel 1 | | `ApolloPlay`, `ApolloPlayFile` | Subtract 32 sample pairs from the length, without an explanation | | `ApolloLoadPicture` | Keeps its file state in `static` variables: not reentrant | +| `ApolloShowPiP1/2` | Don't load the palette of an 8-bit picture into the PIP colour port. The older ApolloLib did it in `ApolloLoadPicture`, chosen by `picture->pip`; the field is now `target`, and nothing reads it. | | `ApolloRelease` | Restores the priority saved by `ApolloTakeOver`; called without it, it sets priority 0 | ## Wrong diff --git a/tools/compile-snippets.sh b/tools/compile-snippets.sh index 70ff048..c44e254 100755 --- a/tools/compile-snippets.sh +++ b/tools/compile-snippets.sh @@ -4,7 +4,9 @@ # Snippets are compiled to object files, not linked, so each one must be a # complete translation unit. Flags follow the ApolloCrossDev Makefile template # (Projects/_makefiles/Makefile). PREFIX is the ApolloCrossDev compiler folder, -# e.g. ~/ApolloCrossDev/Compilers/GCC-6.50-Dev. +# e.g. ~/ApolloCrossDev/Compilers/GCC-6.50-Dev. When APOLLOLIB is set to an +# ApolloLib folder (ApolloCrossDev Projects/ApolloLib), snippets can include +# "ApolloCrossDev_Lib.h". # # compile-snippets.sh [dir] compile all .c/.cpp/.s files in dir # (default: build/snippets) @@ -19,6 +21,7 @@ AS="${AS:-$PREFIX/bin/vasmm68k_mot}" OPTS="-O2 -m68080 -m68881 -ffast-math -fomit-frame-pointer" INCL="-I$PREFIX/$TARGET/libnix/include -I$PREFIX/$TARGET/sys-include -I$PREFIX/$TARGET/ndk32-include/Include_H" +[ -n "$APOLLOLIB" ] && INCL="$INCL -I$APOLLOLIB" CFLAGS="${CFLAGS:--noixemul $OPTS -Wall $INCL}" CXXFLAGS="${CXXFLAGS:--noixemul $OPTS -Wall $INCL}" ASFLAGS="${ASFLAGS:--quiet -Fhunk -m68080 -m68881 -opt-speed -I$PREFIX/$TARGET/DevPac/Include}" From 9395d426a19fcc976312bf13255e062cb17a4b9b Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 17:46:11 +0300 Subject: [PATCH 4/7] ApolloLib PIP palette: dropped in commit 789cbcc, not via picture->pip Correct the history: ApolloLoadPicture wrote the palette into the colour port chosen by picture->target until ApolloCrossDev commit 789cbcc (2026-06-16), which made palette an array for the pointer. SAGAPiP (2026-06-03) still relies on the old behaviour. Co-Authored-By: Claude Opus 5.5 --- examples/saga-pip-overlay-and-chroma-key.md | 2 +- toolchain/pitfalls/apollolib-known-bugs.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/saga-pip-overlay-and-chroma-key.md b/examples/saga-pip-overlay-and-chroma-key.md index 89fe104..38899e3 100644 --- a/examples/saga-pip-overlay-and-chroma-key.md +++ b/examples/saga-pip-overlay-and-chroma-key.md @@ -125,7 +125,7 @@ the code above, but has these problems. Keep them out of your own code: | Problem | Effect | Do instead | |---|---|---| -| It never loads the PIP palettes | The 8-bit overlays show whatever palette was in the PIP colour ports. ApolloLib's older version did this in `ApolloLoadPicture`; the current one only loads the chunky palette, in `ApolloShowPicture`. | Write the palette to `$DFF38C` (PIP 1) or `$DFF37C` (PIP 2), as `LoadPiP1Palette` does | +| It never loads the PIP palettes | The 8-bit overlays show whatever palette was in the PIP colour ports. The example dates from 2026-06-03, when `ApolloLoadPicture` still wrote the palette into the colour port chosen by `picture->target`; ApolloLib commit 789cbcc (2026-06-16) dropped that. | Write the palette to `$DFF38C` (PIP 1) or `$DFF37C` (PIP 2), as `LoadPiP1Palette` does | | `if (!&pipwindow)` | Tests the address of the variable, which is never NULL: a failed `OpenWindowTags` goes unnoticed | `if (!pipwindow)` | | `AllocBitMap(..., BMF_DISPLAYABLE \| BMB_CLEAR \| BMB_HIJACKED \| ...)` | `BMB_…` are bit numbers (0, 7, 2, 9), not flags: ORed with `BMF_DISPLAYABLE` (2) they give `$F` = CLEAR, DISPLAYABLE, INTERLEAVED and STANDARD, not the HIJACKED and RTGCHECK the code names | Use only `BMF_…` flags | | Restores the Workbench as GFXMODE `$0A04` | Works only on a 1280x720, 24-bit Workbench | Save GFXMODE and the pointer from `$DFE1F4`/`$DFE1EC` first, restore those | diff --git a/toolchain/pitfalls/apollolib-known-bugs.md b/toolchain/pitfalls/apollolib-known-bugs.md index d5512e8..efa3bf3 100644 --- a/toolchain/pitfalls/apollolib-known-bugs.md +++ b/toolchain/pitfalls/apollolib-known-bugs.md @@ -61,7 +61,7 @@ hardware yet. | `ApolloPlaySound` | The free-channel search starts at channel 1 | | `ApolloPlay`, `ApolloPlayFile` | Subtract 32 sample pairs from the length, without an explanation | | `ApolloLoadPicture` | Keeps its file state in `static` variables: not reentrant | -| `ApolloShowPiP1/2` | Don't load the palette of an 8-bit picture into the PIP colour port. The older ApolloLib did it in `ApolloLoadPicture`, chosen by `picture->pip`; the field is now `target`, and nothing reads it. | +| `ApolloShowPiP1/2` | Don't load the palette of an 8-bit picture into the PIP colour port. Until commit 789cbcc (2026-06-16) `ApolloLoadPicture` wrote it straight into the chunky, PIP 1, PIP 2 or sprite colour port, chosen by `picture->target`. Since then it only fills `picture->palette`: `ApolloShowPicture` loads the chunky palette from it and `ApolloShowPointer` the sprite palette, but nothing loads a PIP palette, and `target` is not read. | | `ApolloRelease` | Restores the priority saved by `ApolloTakeOver`; called without it, it sets priority 0 | ## Wrong From c061b0b12f3c694d1c0c0ad7590ab357e2ecd2c9 Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 17:54:56 +0300 Subject: [PATCH 5/7] SAGAPiP example and ApolloLib PIP palette: ApolloCrossDev #19 (a9a994f) ApolloCrossDev #19 made ApolloShowPiP1/2 load the 8-bit palette and fixed the SAGAPiP example. The example page now calls ApolloShowPiP1 without its own palette loop, and its "full example" section lists what #19 changed. The PIP palette moves from the remaining limits to the fixed bugs. Co-Authored-By: Claude Opus 5.5 --- examples/saga-pip-overlay-and-chroma-key.md | 44 ++++++++++----------- toolchain/apollolib/index.md | 3 +- toolchain/apollolib/picture.md | 6 +-- toolchain/pitfalls/apollolib-known-bugs.md | 3 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/examples/saga-pip-overlay-and-chroma-key.md b/examples/saga-pip-overlay-and-chroma-key.md index 38899e3..a240bbb 100644 --- a/examples/saga-pip-overlay-and-chroma-key.md +++ b/examples/saga-pip-overlay-and-chroma-key.md @@ -16,8 +16,8 @@ related: - toolchain/apollolib/picture.md - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit 5ad4035 (2026-11-04)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit 639e195 (2026-11-04)" + - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit a9a994f (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit a9a994f (2026-11-04)" --- ## Purpose @@ -55,23 +55,16 @@ The full example builds in ApolloCrossDev with `make` in ```c // kb:compile /* SAGA PIP with ApolloLib: overlay and chroma-key mode, after the SAGAPiP - example in ApolloCrossDev Projects/SAGAPiP. Needs ApolloLib 639e195+. */ + example in ApolloCrossDev Projects/SAGAPiP. Needs ApolloLib a9a994f+: + from there ApolloShowPiP1 also loads the palette of an 8-bit picture. */ #include "ApolloCrossDev_Lib.h" #define PIP1_REG(a) (*(volatile UWORD *)(a)) -/* Load the palette of an 8-bit picture into PIP 1's colour port. */ -static void LoadPiP1Palette(const struct ApolloPicture *pic) -{ - for (ULONG i = 0; i < pic->palettesize; i++) - *(volatile ULONG *)APOLLO_SAGA_PIP1CHK_COL = (i << 24) | (pic->palette[i] & 0xFFFFFF); -} - /* Overlay mode: PIP 1 lies in front of the screen at (left, top). With the transparency bit, palette index $FF is transparent. */ void ShowPiP1Overlay(struct ApolloPicture *pic, WORD left, WORD top) { - LoadPiP1Palette(pic); *(volatile ULONG *)APOLLO_SAGA_PIP1_POINTER = (ULONG)(pic->buffer + pic->position); PIP1_REG(APOLLO_SAGA_PIP1_X_START) = left + 16; /* +16 and -1: as in SAGAPiP */ PIP1_REG(APOLLO_SAGA_PIP1_X_STOP) = left + pic->width + 16; @@ -80,7 +73,7 @@ void ShowPiP1Overlay(struct ApolloPicture *pic, WORD left, WORD top) PIP1_REG(APOLLO_SAGA_PIP1_GFXMODE) = APOLLO_SAGA_PIP_TRANSON | APOLLO_SAGA_8_INDEX; PIP1_REG(APOLLO_SAGA_PIP1_MODULO) = 0; PIP1_REG(APOLLO_SAGA_PIP1_CLRKEY) = 0x0000; /* 0 = overlay mode */ - ApolloShowPiP1(pic); /* DMA row length last: PIP on */ + ApolloShowPiP1(pic); /* palette, then DMA row length: PIP on */ } /* Chroma-key mode: PIP 1 lies behind the screen and shows only where the @@ -120,19 +113,24 @@ overlay, a 16-bit DDS (R5G6B5) for the background. ## The full example -`Projects/SAGAPiP/SAGAPiP.c` (ApolloCrossDev commit 5ad4035) shows more than -the code above, but has these problems. Keep them out of your own code: +`Projects/SAGAPiP/SAGAPiP.c` (ApolloCrossDev commit a9a994f) does more than +the code above: soundtrack, background picture, a Workbench window with +PIP 1 and PIP 2 on it that follow the window, and a click that punches a +transparent square into the overlay. + +ApolloCrossDev pull request #19 (2026-11-04) fixed these problems of the +earlier version. Keep them out of your own code: -| Problem | Effect | Do instead | +| Earlier problem | Effect | Now | |---|---|---| -| It never loads the PIP palettes | The 8-bit overlays show whatever palette was in the PIP colour ports. The example dates from 2026-06-03, when `ApolloLoadPicture` still wrote the palette into the colour port chosen by `picture->target`; ApolloLib commit 789cbcc (2026-06-16) dropped that. | Write the palette to `$DFF38C` (PIP 1) or `$DFF37C` (PIP 2), as `LoadPiP1Palette` does | -| `if (!&pipwindow)` | Tests the address of the variable, which is never NULL: a failed `OpenWindowTags` goes unnoticed | `if (!pipwindow)` | -| `AllocBitMap(..., BMF_DISPLAYABLE \| BMB_CLEAR \| BMB_HIJACKED \| ...)` | `BMB_…` are bit numbers (0, 7, 2, 9), not flags: ORed with `BMF_DISPLAYABLE` (2) they give `$F` = CLEAR, DISPLAYABLE, INTERLEAVED and STANDARD, not the HIJACKED and RTGCHECK the code names | Use only `BMF_…` flags | -| Restores the Workbench as GFXMODE `$0A04` | Works only on a 1280x720, 24-bit Workbench | Save GFXMODE and the pointer from `$DFE1F4`/`$DFE1EC` first, restore those | -| `struct ApolloSound soundtrack;` not cleared | `fadeout` is read uninitialized at exit | `struct ApolloSound soundtrack = {0};` | -| `goto exit4` from early errors | Frees picture buffers that were never loaded | Clear the structures first, or free only what was loaded | -| cybergraphics.library and the "Workbench" screen lock are never released | Resource leak | `CloseLibrary`, `UnlockPubScreen` | -| `ApolloCPUDelay(2000)` in a Workbench program | Busy-waits two seconds | `Delay()` from dos.library | +| No PIP palette loaded | 8-bit overlays in the wrong colours, since ApolloLib 789cbcc (2026-06-16) dropped the palette preload in `ApolloLoadPicture` | `ApolloShowPiP1/2` load it | +| Positional initializers for `struct ApolloPicture` | Since `palette` is an array, the last value went into `palette[2]`, not `target` | Fields set by name | +| Workbench restored as GFXMODE `$0A04` | Worked only on a 1280x720, 24-bit Workbench | Mode, pointer and modulo saved from `$DFE1F4`/`$DFE1EC`/`$DFE1E6` and restored | +| `if (!&pipwindow)` | A failed `OpenWindowTags` went unnoticed | `if (!pipwindow)` | +| `BMF_DISPLAYABLE \| BMB_CLEAR \| BMB_HIJACKED \| …` | `BMB_` are bit numbers: the flags became `$F` | `BMF_DISPLAYABLE \| BMF_CLEAR \| BMF_INTERLEAVED` | +| Five exit labels, uninitialized structures | Freed buffers that were never loaded; read uninitialized fields | Everything starts cleared; one exit path frees only what was acquired | +| cybergraphics.library never closed | Leak | Closed | +| `ApolloCPUDelay` under Workbench | Busy waits | `Delay()` | The +16 (X) and −1 (Y) corrections place the PIP exactly over a Workbench window in SAGAPiP; they are not documented elsewhere. diff --git a/toolchain/apollolib/index.md b/toolchain/apollolib/index.md index 698343a..496950f 100644 --- a/toolchain/apollolib/index.md +++ b/toolchain/apollolib/index.md @@ -99,7 +99,8 @@ Many bugs were fixed on 2026-11-04 (commit 639e195): use that version or later. An older copy has the bugs listed in [apollolib-known-bugs.md](../pitfalls/apollolib-known-bugs.md), among them broken 320-pixel modes and no way to undo `ApolloTakeOver` or -`ApolloTimer`. +`ApolloTimer`. From commit a9a994f, `ApolloShowPiP1/2` also load the +palette of an 8-bit picture. ## Differences from AmigaOS / AROS / 68060 diff --git a/toolchain/apollolib/picture.md b/toolchain/apollolib/picture.md index e6ad1b2..d6cce8d 100644 --- a/toolchain/apollolib/picture.md +++ b/toolchain/apollolib/picture.md @@ -52,7 +52,7 @@ Core 12500 and later. | `ApolloAllocPicture(pic)` | Allocates an aligned buffer of `width × height × depth / 8` bytes (or `size`) | | `ApolloFreePicture(pic)` | `FreeVec` of the buffer | | `ApolloShowPicture(pic)` | Picks the GFXMODE from width × height (see below) and depth 8, 15, 16, 24 or 32, loads the chunky palette for depth ≤ 8, writes GFXMODE (`$DFF1F4`), modulo (`$DFF1E6`) and pointer (`$DFF1EC`) | -| `ApolloShowPiP1(pic)` / `ApolloShowPiP2(pic)` | Writes the PIP's DMA row length (`width × depth / 8`), which switches it on. Set position, pointer, format and modulo first (see [pip.md](../../core/saga-registers/pip.md)). It doesn't load the palette of an 8-bit picture: write `pic->palette` to the PIP colour port yourself (see [the example](../../examples/saga-pip-overlay-and-chroma-key.md)). | +| `ApolloShowPiP1(pic)` / `ApolloShowPiP2(pic)` | Writes the PIP's DMA row length (`width × depth / 8`), which switches it on. Set position, pointer, format and modulo first (see [pip.md](../../core/saga-registers/pip.md)). For an 8-bit picture it first loads `pic->palette` into the PIP colour port (from ApolloLib a9a994f). See [the example](../../examples/saga-pip-overlay-and-chroma-key.md). | | `ApolloHidePiP1()` / `ApolloHidePiP2()` | Writes 0 to the DMA row length: the PIP goes off | | `ApolloLoadPointer(ptr, pic)` | Converts a 32x32 BMP (8-bit with palette, or 32-bit ARGB) into pointer sprite data: colour index + transparency byte per pixel and a 256-entry palette | | `ApolloShowPointer(ptr)` | Writes the palette (`$DFF3A8` index, `$DFF3AA` colour) and copies the image to `$DFD000` | @@ -116,8 +116,8 @@ if (ApolloLoadPicture(&pic) == APOLLO_PICTURE_OK) { - DO set the PIP registers before `ApolloShowPiP1/2`: they only write the DMA row length. -- DO load an 8-bit PIP's palette into its colour port (`$DFF38C` for PIP 1, - `$DFF37C` for PIP 2) yourself. +- DO load an 8-bit PIP's palette yourself (`$DFF38C` for PIP 1, `$DFF37C` + for PIP 2) with an ApolloLib older than a9a994f. - DO restore GFXMODE and the chunky pointer before the program exits. - DON'T free a picture that is still shown. - DON'T use `ApolloShowPicture` for 320-pixel-wide or 8-bit pictures with diff --git a/toolchain/pitfalls/apollolib-known-bugs.md b/toolchain/pitfalls/apollolib-known-bugs.md index efa3bf3..4b6586a 100644 --- a/toolchain/pitfalls/apollolib-known-bugs.md +++ b/toolchain/pitfalls/apollolib-known-bugs.md @@ -18,6 +18,7 @@ related: sources: - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ce1fcbf (2026-11-04), read on 2026-11-04" - "ApolloCrossDev pull requests #17 and #18 (merged 2026-11-04), fixed in commit 639e195" + - "ApolloCrossDev pull request #19 (merged 2026-11-04), PiP palette, commit a9a994f" --- ## Symptom @@ -50,6 +51,7 @@ hardware yet. | `ApolloLoadFile`/`Sound`/`Picture` | Left the file open on errors; BMP palette read from a fixed offset | | `ApolloShowPattern` | Wrote only the first byte of each pixel | | `ApolloCrossDev_Lib.h` | Declared `ApolloShowPiP`/`ApolloHidePiP`, which don't exist, and not `ApolloShowPiP1/2`/`ApolloHidePiP1/2` | +| PiP palette (a9a994f, #19) | From 789cbcc (2026-06-16) to a9a994f nothing loaded an 8-bit PiP palette: `ApolloLoadPicture` had stopped writing it into the port chosen by `picture->target`. Now `ApolloShowPiP1/2` load it. | | Five functions (#17) | Wrong DMACON2R bit for channels 4–15, a write through an uninitialized pointer, missing return values, an always-on keyboard print | ### Limits that remain @@ -61,7 +63,6 @@ hardware yet. | `ApolloPlaySound` | The free-channel search starts at channel 1 | | `ApolloPlay`, `ApolloPlayFile` | Subtract 32 sample pairs from the length, without an explanation | | `ApolloLoadPicture` | Keeps its file state in `static` variables: not reentrant | -| `ApolloShowPiP1/2` | Don't load the palette of an 8-bit picture into the PIP colour port. Until commit 789cbcc (2026-06-16) `ApolloLoadPicture` wrote it straight into the chunky, PIP 1, PIP 2 or sprite colour port, chosen by `picture->target`. Since then it only fills `picture->palette`: `ApolloShowPicture` loads the chunky palette from it and `ApolloShowPointer` the sprite palette, but nothing loads a PIP palette, and `target` is not read. | | `ApolloRelease` | Restores the priority saved by `ApolloTakeOver`; called without it, it sets priority 0 | ## Wrong From 85f9c4d79a7ccd93af5fdf1e20304c4c60eeb365 Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 18:00:50 +0300 Subject: [PATCH 6/7] Second example: SAGA hardware pointer from a 32x32 BMP examples/saga-pointer-sprite.md takes ApolloCrossDev's ApolloPointer example: SetPointerFromBMP and MovePointer on ApolloLib, compiling. It records the points of ApolloLoadPointer that the code works around: no return value and bLoaded never set, 24-bit BMPs accepted but not converted, the loaded BMP not freed, alpha read correctly only with endian set. Co-Authored-By: Claude Opus 5.5 --- .github/CODEOWNERS | 1 + examples/saga-pointer-sprite.md | 123 ++++++++++++++++++++++++++++++++ toolchain/apollolib/picture.md | 5 +- 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 examples/saga-pointer-sprite.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d1970f5..1faa0e3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -170,6 +170,7 @@ /core/saga-registers/system.md @WDrijver /core/saga-video-isabelle/index.md @WDrijver /examples/saga-pip-overlay-and-chroma-key.md @WDrijver +/examples/saga-pointer-sprite.md @WDrijver /hardware/index.md @WDrijver /os/apolloos-and-amigaos-on-v4.md @WDrijver /os/apolloos-specific-on-v4.md @WDrijver diff --git a/examples/saga-pointer-sprite.md b/examples/saga-pointer-sprite.md new file mode 100644 index 0000000..2f9d26e --- /dev/null +++ b/examples/saga-pointer-sprite.md @@ -0,0 +1,123 @@ +--- +title: SAGA hardware pointer from a 32x32 BMP (from the ApolloPointer example) +type: example +owner: WDrijver +status: draft +origin: ai-assisted +basis: [source-code] +components: [saga-video-isabelle, toolchain] +applies_to: + core: "Core 12500+" + os: [any] +tags: [saga, isabelle, pointer, sprite, bmp, apollolib, example] +related: + - core/saga-registers/pointer.md + - toolchain/apollolib/picture.md + - toolchain/apollolib/input.md + - core/best-practices/prefer-library-calls-for-saga-chips.md +sources: + - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit 9e35355 (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloLoadPointer, ApolloShowPointer), commit a9a994f (2026-11-04)" +--- + +## Purpose + +Replace the SAGA hardware pointer (the 32x32 sprite of Core 12500 and +later) with an image from a BMP file, and move it, with ApolloLib. +ApolloCrossDev's ApolloPointer example does the first part as a command: +`ApolloPointer sprite.bmp`. + +## Build + +The functions compile as a library unit. In a project that uses ApolloLib +(see [toolchain/apollolib/index.md](../toolchain/apollolib/index.md)): + +```sh +m68k-amigaos-gcc -noixemul -O2 -m68080 -m68881 -ffast-math -fomit-frame-pointer -Wall \ + -I$APOLLOLIB -c pointer.c -o pointer.o +``` + +The full example builds in ApolloCrossDev with `make` in +`Projects/ApolloPointer` (which also uploads it to the Apollo V4), or +`make cleanbefore build` to only build. Its folder has seven 32x32 8-bit +pointer images (`ApolloPointer_*_32x32x8.bmp`). + +## Code + +```c +// kb:compile +/* Replace the SAGA hardware pointer with a 32x32 BMP, after the ApolloPointer + example in ApolloCrossDev Projects/ApolloPointer. Core 12500+ (32x32 + sprite), ApolloLib a9a994f+. */ +#include "ApolloCrossDev_Lib.h" + +static struct ApolloPointer pointer; /* over 5 KB: keep it off the stack */ + +/* Load a 32x32 BMP (8-bit with palette, or 32-bit ARGB) and show it as the + pointer. Returns false if the file is not a usable pointer image. */ +bool SetPointerFromBMP(const char *filename) +{ + struct ApolloPicture bmp = {0}; + bool usable; + + strncpy(bmp.filename, filename, sizeof(bmp.filename) - 1); + bmp.format = APOLLO_BMP_FORMAT; + bmp.endian = true; /* 32-bit: ARGB byte order for ApolloLoadPointer */ + bmp.target = APOLLO_PIC_TARGET_SPRITE; + + ApolloLoadPointer(&pointer, &bmp); /* loads the BMP, converts it into pointer */ + + /* ApolloLoadPointer returns nothing: check the picture it loaded */ + usable = bmp.buffer && bmp.width == 32 && bmp.height == 32 && + (bmp.depth == 8 || bmp.depth == 32); + ApolloFreePicture(&bmp); /* pointer has its own copy */ + if (!usable) + return false; + + ApolloShowPointer(&pointer); /* palette to $DFF3A8/$DFF3AA, image to $DFD000 */ + return true; +} + +/* Move the pointer to screen position (x, y), with the offsets ApolloMouse uses. */ +void MovePointer(WORD x, WORD y) +{ + *(volatile UWORD *)APOLLO_POINTER_SET_X = x + 16; + *(volatile UWORD *)APOLLO_POINTER_SET_Y = y + 8; +} +``` + +## Expected result + +- `SetPointerFromBMP("ApolloPointer_Gold_32x32x8.bmp")` returns true, and + the pointer on screen changes to the image at once, at its current + position. +- Pixels with colour index 0 are transparent. In an 8-bit BMP every other + pixel is opaque. In a 32-bit ARGB BMP, alpha 0 is transparent, alpha + `$FF` opaque, and any other alpha 50 % semi-transparent (see + [pointer.md](../core/saga-registers/pointer.md)). +- A BMP that isn't 32x32, or has another depth, returns false and leaves + the pointer unchanged. +- `MovePointer(x, y)` places it; `ApolloMouse` (see + [input.md](../toolchain/apollolib/input.md)) does the same from the + mouse every frame. + +## The full example and ApolloLib's pointer functions + +`Projects/ApolloPointer/ApolloPointer.c` (ApolloCrossDev commit 9e35355) +calls `ApolloLoadPointer` and `ApolloShowPointer` on the file named on the +command line, and exits; the new pointer stays. These points are why the +code above looks the way it does: + +| Point | Effect | In the code above | +|---|---|---| +| `ApolloLoadPointer` returns `void` and never sets `pointer.bLoaded` | A caller can't see whether loading worked; `ApolloShowPointer` shows whatever is in `pointer` | Checks the loaded picture's size and depth | +| `ApolloLoadPointer` accepts 24-bit BMPs, but has no 24-bit conversion | The pointer becomes empty (all index 0, transparent) | Accepts only 8 and 32 bits | +| `ApolloLoadPointer` loads the BMP through `ApolloLoadPicture` and doesn't free it; nor does the example | About 1–4 KB stays allocated | `ApolloFreePicture` | +| The 32-bit path reads `pixel >> 24` as alpha | Right only when the picture is loaded with `endian = true` | Sets `endian` | +| `hotspotX`/`hotspotY` in `struct ApolloPointer` | Not used by ApolloLib | – | +| `struct ApolloPointer` holds 4096 bytes of image data for a 2048-byte sprite | Harmless; `ApolloShowPointer` copies 2048 bytes | Static, off the stack | + +The example's usage text says "Color #0 is transparent and Color #1 is +semi-transparent". For an 8-bit BMP, `ApolloLoadPointer` marks every +pixel opaque; whether colour index 1 is semi-transparent in the hardware +is not documented in the knowledge base. diff --git a/toolchain/apollolib/picture.md b/toolchain/apollolib/picture.md index d6cce8d..f33ba42 100644 --- a/toolchain/apollolib/picture.md +++ b/toolchain/apollolib/picture.md @@ -88,7 +88,10 @@ checked width `APOLLO_PICTURE_H_ERROR`. Before ApolloLib 639e195 the any other alpha semi-transparent (50 %). Colours are gathered into a palette of up to 255 entries. -`ApolloMouse` (see [input.md](input.md)) moves the pointer. +`ApolloMouse` (see [input.md](input.md)) moves the pointer. A complete, +checked use of both functions is in +[the pointer example](../../examples/saga-pointer-sprite.md): +`ApolloLoadPointer` returns nothing and doesn't free the BMP it loads. ### Example From fd39dacef79656e8a36b54a32dde3ea9b9a8c2fd Mon Sep 17 00:00:00 2001 From: Willem Drijver Date: Wed, 4 Nov 2026 18:44:23 +0300 Subject: [PATCH 7/7] Examples and ApolloLib: ApolloPiP rename and ApolloLoadPointer fixes ApolloCrossDev #20 renamed SAGAPiP to ApolloPiP, #21 fixed ApolloLoadPointer (result code, 24-bit, BMP freed). Cite commit ad7f0b4, rename the example everywhere (also in the SAGA register converter, and regenerate), simplify the pointer example to use the result code, and add the ApolloLoadPointer fixes to the ApolloLib files. Co-Authored-By: Claude Opus 5.5 --- core/saga-registers/arne-channels.md | 2 +- core/saga-registers/chunky-plane.md | 2 +- core/saga-registers/colour-ports.md | 2 +- core/saga-registers/index.md | 2 +- core/saga-registers/input.md | 2 +- core/saga-registers/pip.md | 10 +-- core/saga-registers/pointer.md | 4 +- examples/saga-pip-overlay-and-chroma-key.md | 24 +++--- examples/saga-pointer-sprite.md | 85 ++++++++++----------- toolchain/apollolib/index.md | 2 +- toolchain/apollolib/picture.md | 18 +++-- toolchain/pitfalls/apollolib-known-bugs.md | 4 +- tools/intake/sagaregs.py | 14 ++-- 13 files changed, 85 insertions(+), 86 deletions(-) diff --git a/core/saga-registers/arne-channels.md b/core/saga-registers/arne-channels.md index 1e07fb7..79b3ca5 100644 --- a/core/saga-registers/arne-channels.md +++ b/core/saga-registers/arne-channels.md @@ -17,7 +17,7 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" - "WDrijver (maintainer), answers on the SAGA register documentation, 2026-11-04" - "Gunnar (core developer, BigGun), Apollo chat, 2026-11-04 (relayed by WDrijver)" generated_from: sources/official/saga-registers/index.htm diff --git a/core/saga-registers/chunky-plane.md b/core/saga-registers/chunky-plane.md index 3818d96..104ef6c 100644 --- a/core/saga-registers/chunky-plane.md +++ b/core/saga-registers/chunky-plane.md @@ -15,7 +15,7 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" - "maggie.library samples, see os/libraries/maggie-library.md" generated_from: sources/official/saga-registers/index.htm --- diff --git a/core/saga-registers/colour-ports.md b/core/saga-registers/colour-ports.md index 9faf194..941b970 100644 --- a/core/saga-registers/colour-ports.md +++ b/core/saga-registers/colour-ports.md @@ -15,7 +15,7 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" - "Gunnar (core developer, BigGun), Apollo chat, 2026-11-04 (relayed by WDrijver)" generated_from: sources/official/saga-registers/index.htm --- diff --git a/core/saga-registers/index.md b/core/saga-registers/index.md index b8c1067..52605fb 100644 --- a/core/saga-registers/index.md +++ b/core/saga-registers/index.md @@ -14,7 +14,7 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" - "WDrijver (maintainer), answers on the SAGA register documentation, 2026-11-04" - "Gunnar (core developer, BigGun), Apollo chat, 2026-11-04 (relayed by WDrijver)" generated_from: sources/official/saga-registers/index.htm diff --git a/core/saga-registers/input.md b/core/saga-registers/input.md index 9e15f92..fc4aa2f 100644 --- a/core/saga-registers/input.md +++ b/core/saga-registers/input.md @@ -15,7 +15,7 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" generated_from: sources/official/saga-registers/index.htm --- diff --git a/core/saga-registers/pip.md b/core/saga-registers/pip.md index 925ab33..dc17d63 100644 --- a/core/saga-registers/pip.md +++ b/core/saga-registers/pip.md @@ -17,8 +17,8 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" - - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" + - "Example ApolloPiP.c (formerly SAGAPiP), github.com/WDrijver/ApolloCrossDev Projects/ApolloPiP, commit ad7f0b4 (2026-11-04)" - "WDrijver (maintainer), answers on the SAGA register documentation, 2026-11-04" generated_from: sources/official/saga-registers/index.htm --- @@ -45,7 +45,7 @@ SAGA overlays up to two picture-in-picture windows (PIP) on the main screen, eac | `$DFF3E0` | PipColorKey | W | ColorKey 1000,RRRR,GGGG,BBBB | | `$DFF3E2` | PipDMARowLen | W | PipDMARowLen | -The original links these rows to a page that doesn't exist; the facts below come from ApolloLib and the SAGAPiP example (SRC). +The original links these rows to a page that doesn't exist; the facts below come from ApolloLib and the ApolloPiP example (SRC). ### PIP 2 registers (SRC, not in the original) @@ -63,7 +63,7 @@ PIP 2 has the same layout as PIP 1, `$20` lower (ApolloLib): | `$DFF3C0` | PIP2 colour key | `$DFF3E0` | | `$DFF3C2` | PIP2 DMA row length | `$DFF3E2` | -Its colour port is `$DFF37C` (see [colour-ports.md](colour-ports.md)). The SAGAPiP example says two PIPs exist from Core 11900 (SRC). +Its colour port is `$DFF37C` (see [colour-ports.md](colour-ports.md)). The ApolloPiP example says two PIPs exist from Core 11900 (SRC). ### Register meanings (SRC) @@ -90,7 +90,7 @@ Its colour port is `$DFF37C` (see [colour-ports.md](colour-ports.md)). The SAGAP Formats and transparent colours: WDrijver, 2026-11-04. The transparent colour is fixed per format. ApolloLib still marks YUV as not supported on PIP; that is an error in ApolloLib, not in the hardware. -### Two modes (SAGAPiP example, SRC) +### Two modes (ApolloPiP example, SRC) - **Overlay:** colour key 0. The PIP is in front of the main screen; with format bit 8 set, pixels in the transparent colour of the format are transparent. - **Chroma key:** colour key with bit 15 set, for example `$8F0F` (purple). The PIP is behind the main screen and shows only where the main screen has the key colour. diff --git a/core/saga-registers/pointer.md b/core/saga-registers/pointer.md index d9332e0..ca9d10b 100644 --- a/core/saga-registers/pointer.md +++ b/core/saga-registers/pointer.md @@ -15,9 +15,9 @@ related: - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - "SAGA register documentation, sources/official/saga-registers/ (HTML)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)" - "ApolloLib, older copy in ApolloClaude docs/toolchain/_ApolloLib (ApolloCrossDev_Defines.h), 2026-11-04" - - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit 2dfaa5c (2026-09-02)" + - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit ad7f0b4 (2026-11-04)" - "WDrijver (maintainer), answers on the SAGA register documentation, 2026-11-04" generated_from: sources/official/saga-registers/index.htm --- diff --git a/examples/saga-pip-overlay-and-chroma-key.md b/examples/saga-pip-overlay-and-chroma-key.md index a240bbb..ad287c0 100644 --- a/examples/saga-pip-overlay-and-chroma-key.md +++ b/examples/saga-pip-overlay-and-chroma-key.md @@ -1,5 +1,5 @@ --- -title: SAGA PIP in overlay and chroma-key mode (from the SAGAPiP example) +title: SAGA PIP in overlay and chroma-key mode (from the ApolloPiP example) type: example owner: WDrijver status: draft @@ -16,15 +16,15 @@ related: - toolchain/apollolib/picture.md - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit a9a994f (2026-11-04)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit a9a994f (2026-11-04)" + - "Example ApolloPiP.c (formerly SAGAPiP), github.com/WDrijver/ApolloCrossDev Projects/ApolloPiP, commit ad7f0b4 (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ad7f0b4 (2026-11-04)" --- ## Purpose Show a picture-in-picture (PIP) window on top of the screen (overlay mode) and behind it (chroma-key mode), with ApolloLib. The code below is the PIP -part of ApolloCrossDev's SAGAPiP example, in three functions: +part of ApolloCrossDev's ApolloPiP example, in three functions: - `ShowPiP1Overlay`: an 8-bit PIP in front of the screen; palette index `$FF` is transparent. @@ -32,7 +32,7 @@ part of ApolloCrossDev's SAGAPiP example, in three functions: screen is purple (the chroma key). - `HidePiP1`: PIP 1 off. -The full SAGAPiP program does more: it plays a soundtrack, shows a +The full ApolloPiP program does more: it plays a soundtrack, shows a 1280x720 background, opens a Workbench window, lays PIP 1 and PIP 2 over it and follows the window when it moves (see "The full example" below). @@ -47,15 +47,15 @@ m68k-amigaos-gcc -noixemul -O2 -m68080 -m68881 -ffast-math -fomit-frame-pointer ``` The full example builds in ApolloCrossDev with `make` in -`Projects/SAGAPiP` (which also uploads it to the Apollo V4), or +`Projects/ApolloPiP` (which also uploads it to the Apollo V4), or `make cleanbefore build` to only build. ## Code ```c // kb:compile -/* SAGA PIP with ApolloLib: overlay and chroma-key mode, after the SAGAPiP - example in ApolloCrossDev Projects/SAGAPiP. Needs ApolloLib a9a994f+: +/* SAGA PIP with ApolloLib: overlay and chroma-key mode, after the ApolloPiP + example in ApolloCrossDev Projects/ApolloPiP. Needs ApolloLib a9a994f+: from there ApolloShowPiP1 also loads the palette of an 8-bit picture. */ #include "ApolloCrossDev_Lib.h" @@ -66,7 +66,7 @@ The full example builds in ApolloCrossDev with `make` in void ShowPiP1Overlay(struct ApolloPicture *pic, WORD left, WORD top) { *(volatile ULONG *)APOLLO_SAGA_PIP1_POINTER = (ULONG)(pic->buffer + pic->position); - PIP1_REG(APOLLO_SAGA_PIP1_X_START) = left + 16; /* +16 and -1: as in SAGAPiP */ + PIP1_REG(APOLLO_SAGA_PIP1_X_START) = left + 16; /* +16 and -1: as in ApolloPiP */ PIP1_REG(APOLLO_SAGA_PIP1_X_STOP) = left + pic->width + 16; PIP1_REG(APOLLO_SAGA_PIP1_Y_START) = top - 1; PIP1_REG(APOLLO_SAGA_PIP1_Y_STOP) = top + pic->height - 1; @@ -108,12 +108,12 @@ overlay, a 16-bit DDS (R5G6B5) for the background. screen pixels. Pixels with palette index `$FF` show the screen behind. - **Chroma key:** after `ShowPiP1Behind(&bg, 1280, 720)` the screen looks unchanged, except where it is purple (`$F0F`): there the PIP shows - through. SAGAPiP fills a Workbench window with purple to look through it. + through. ApolloPiP fills a Workbench window with purple to look through it. - **Off:** after `HidePiP1()` the PIP is gone. ## The full example -`Projects/SAGAPiP/SAGAPiP.c` (ApolloCrossDev commit a9a994f) does more than +`Projects/ApolloPiP/ApolloPiP.c` (ApolloCrossDev commit ad7f0b4; called SAGAPiP before 2026-11-04) does more than the code above: soundtrack, background picture, a Workbench window with PIP 1 and PIP 2 on it that follow the window, and a click that punches a transparent square into the overlay. @@ -133,4 +133,4 @@ earlier version. Keep them out of your own code: | `ApolloCPUDelay` under Workbench | Busy waits | `Delay()` | The +16 (X) and −1 (Y) corrections place the PIP exactly over a Workbench -window in SAGAPiP; they are not documented elsewhere. +window in ApolloPiP; they are not documented elsewhere. diff --git a/examples/saga-pointer-sprite.md b/examples/saga-pointer-sprite.md index 2f9d26e..c84a232 100644 --- a/examples/saga-pointer-sprite.md +++ b/examples/saga-pointer-sprite.md @@ -16,8 +16,8 @@ related: - toolchain/apollolib/input.md - core/best-practices/prefer-library-calls-for-saga-chips.md sources: - - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit 9e35355 (2026-11-04)" - - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloLoadPointer, ApolloShowPointer), commit a9a994f (2026-11-04)" + - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit ad7f0b4 (2026-11-04)" + - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloLoadPointer, ApolloShowPointer), commit ad7f0b4 (2026-11-04)" --- ## Purpose @@ -48,34 +48,26 @@ pointer images (`ApolloPointer_*_32x32x8.bmp`). // kb:compile /* Replace the SAGA hardware pointer with a 32x32 BMP, after the ApolloPointer example in ApolloCrossDev Projects/ApolloPointer. Core 12500+ (32x32 - sprite), ApolloLib a9a994f+. */ + sprite), ApolloLib b9891d9+ (ApolloLoadPointer returns a result code). */ #include "ApolloCrossDev_Lib.h" static struct ApolloPointer pointer; /* over 5 KB: keep it off the stack */ -/* Load a 32x32 BMP (8-bit with palette, or 32-bit ARGB) and show it as the - pointer. Returns false if the file is not a usable pointer image. */ -bool SetPointerFromBMP(const char *filename) +/* Load a 32x32 BMP (8-bit with palette, 24-bit, or 32-bit ARGB) and show it + as the pointer. Returns APOLLO_PICTURE_OK or the error code. */ +uint8_t SetPointerFromBMP(const char *filename) { struct ApolloPicture bmp = {0}; - bool usable; + uint8_t result; strncpy(bmp.filename, filename, sizeof(bmp.filename) - 1); bmp.format = APOLLO_BMP_FORMAT; - bmp.endian = true; /* 32-bit: ARGB byte order for ApolloLoadPointer */ bmp.target = APOLLO_PIC_TARGET_SPRITE; - ApolloLoadPointer(&pointer, &bmp); /* loads the BMP, converts it into pointer */ - - /* ApolloLoadPointer returns nothing: check the picture it loaded */ - usable = bmp.buffer && bmp.width == 32 && bmp.height == 32 && - (bmp.depth == 8 || bmp.depth == 32); - ApolloFreePicture(&bmp); /* pointer has its own copy */ - if (!usable) - return false; - - ApolloShowPointer(&pointer); /* palette to $DFF3A8/$DFF3AA, image to $DFD000 */ - return true; + result = ApolloLoadPointer(&pointer, &bmp); /* loads, converts and frees the BMP */ + if (result == APOLLO_PICTURE_OK) + ApolloShowPointer(&pointer); /* palette to $DFF3A8/$DFF3AA, image to $DFD000 */ + return result; } /* Move the pointer to screen position (x, y), with the offsets ApolloMouse uses. */ @@ -88,36 +80,37 @@ void MovePointer(WORD x, WORD y) ## Expected result -- `SetPointerFromBMP("ApolloPointer_Gold_32x32x8.bmp")` returns true, and - the pointer on screen changes to the image at once, at its current - position. -- Pixels with colour index 0 are transparent. In an 8-bit BMP every other - pixel is opaque. In a 32-bit ARGB BMP, alpha 0 is transparent, alpha - `$FF` opaque, and any other alpha 50 % semi-transparent (see - [pointer.md](../core/saga-registers/pointer.md)). -- A BMP that isn't 32x32, or has another depth, returns false and leaves - the pointer unchanged. +- `SetPointerFromBMP("ApolloPointer_Gold_32x32x8.bmp")` returns + `APOLLO_PICTURE_OK`, and the pointer on screen changes to the image at + once, at its current position. +- Pixels with colour index 0 are transparent. In an 8-bit or 24-bit BMP + every other pixel is opaque. In a 32-bit ARGB BMP, alpha 0 is + transparent, alpha `$FF` opaque, and any other alpha 50 % + semi-transparent (see [pointer.md](../core/saga-registers/pointer.md)). +- A BMP that isn't 32x32 returns `APOLLO_PICTURE_W_ERROR` or `H_ERROR`, + another depth `D_ERROR`, a missing file the load error; the pointer + stays as it was. - `MovePointer(x, y)` places it; `ApolloMouse` (see [input.md](../toolchain/apollolib/input.md)) does the same from the mouse every frame. -## The full example and ApolloLib's pointer functions +## The full example -`Projects/ApolloPointer/ApolloPointer.c` (ApolloCrossDev commit 9e35355) +`Projects/ApolloPointer/ApolloPointer.c` (ApolloCrossDev commit ad7f0b4) calls `ApolloLoadPointer` and `ApolloShowPointer` on the file named on the -command line, and exits; the new pointer stays. These points are why the -code above looks the way it does: - -| Point | Effect | In the code above | -|---|---|---| -| `ApolloLoadPointer` returns `void` and never sets `pointer.bLoaded` | A caller can't see whether loading worked; `ApolloShowPointer` shows whatever is in `pointer` | Checks the loaded picture's size and depth | -| `ApolloLoadPointer` accepts 24-bit BMPs, but has no 24-bit conversion | The pointer becomes empty (all index 0, transparent) | Accepts only 8 and 32 bits | -| `ApolloLoadPointer` loads the BMP through `ApolloLoadPicture` and doesn't free it; nor does the example | About 1–4 KB stays allocated | `ApolloFreePicture` | -| The 32-bit path reads `pixel >> 24` as alpha | Right only when the picture is loaded with `endian = true` | Sets `endian` | -| `hotspotX`/`hotspotY` in `struct ApolloPointer` | Not used by ApolloLib | – | -| `struct ApolloPointer` holds 4096 bytes of image data for a 2048-byte sprite | Harmless; `ApolloShowPointer` copies 2048 bytes | Static, off the stack | - -The example's usage text says "Color #0 is transparent and Color #1 is -semi-transparent". For an 8-bit BMP, `ApolloLoadPointer` marks every -pixel opaque; whether colour index 1 is semi-transparent in the hardware -is not documented in the knowledge base. +command line, prints an error and returns 10 if the file can't be used, +and exits; the new pointer stays. + +ApolloCrossDev pull request #21 (2026-11-04) fixed `ApolloLoadPointer`. +With an older ApolloLib, keep these in mind: + +| Before b9891d9 | Effect | +|---|---| +| `ApolloLoadPointer` returned `void` and never set `bLoaded` | A caller couldn't see whether loading worked; `ApolloShowPointer` showed whatever was in the struct | +| 24-bit BMPs were accepted but not converted | An empty (transparent) pointer | +| The loaded BMP was not freed | About 1–4 KB stayed allocated | +| 32-bit alpha read correctly only with `endian = true` | Wrong transparency otherwise | +| The example's text said colour 1 was semi-transparent | Not true: only the transparency byte decides (WDrijver, 2026-11-04) | + +`hotspotX`/`hotspotY` in `struct ApolloPointer` are still not used by +ApolloLib. diff --git a/toolchain/apollolib/index.md b/toolchain/apollolib/index.md index 496950f..9361cd8 100644 --- a/toolchain/apollolib/index.md +++ b/toolchain/apollolib/index.md @@ -35,7 +35,7 @@ are released, ApolloLib is the preferred way to use them (see ### Using it in a project ApolloLib is not a link library: a project compiles its sources. The -example Makefiles (ApolloPointer, SAGAPiP) set +example Makefiles (ApolloPointer, ApolloPiP) set `APOLLOLIB = $(PWD)/../../Projects/ApolloLib`, add `-I$(APOLLOLIB)`, and compile every `.c` and `.s` file there into the program (see [project-makefile.md](../project-makefile.md)). diff --git a/toolchain/apollolib/picture.md b/toolchain/apollolib/picture.md index f33ba42..9512e71 100644 --- a/toolchain/apollolib/picture.md +++ b/toolchain/apollolib/picture.md @@ -18,7 +18,7 @@ related: - toolchain/pitfalls/apollolib-known-bugs.md sources: - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib (ApolloCrossDev_LibC.c, _Lib.h, _Defines.h), commit 639e195 (2026-11-04)" - - "Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, commit 639e195" + - "Example ApolloPiP.c (formerly SAGAPiP), github.com/WDrijver/ApolloCrossDev Projects/ApolloPiP, commit ad7f0b4" - "Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev Projects/ApolloPointer, commit 639e195" --- @@ -54,8 +54,8 @@ Core 12500 and later. | `ApolloShowPicture(pic)` | Picks the GFXMODE from width × height (see below) and depth 8, 15, 16, 24 or 32, loads the chunky palette for depth ≤ 8, writes GFXMODE (`$DFF1F4`), modulo (`$DFF1E6`) and pointer (`$DFF1EC`) | | `ApolloShowPiP1(pic)` / `ApolloShowPiP2(pic)` | Writes the PIP's DMA row length (`width × depth / 8`), which switches it on. Set position, pointer, format and modulo first (see [pip.md](../../core/saga-registers/pip.md)). For an 8-bit picture it first loads `pic->palette` into the PIP colour port (from ApolloLib a9a994f). See [the example](../../examples/saga-pip-overlay-and-chroma-key.md). | | `ApolloHidePiP1()` / `ApolloHidePiP2()` | Writes 0 to the DMA row length: the PIP goes off | -| `ApolloLoadPointer(ptr, pic)` | Converts a 32x32 BMP (8-bit with palette, or 32-bit ARGB) into pointer sprite data: colour index + transparency byte per pixel and a 256-entry palette | -| `ApolloShowPointer(ptr)` | Writes the palette (`$DFF3A8` index, `$DFF3AA` colour) and copies the image to `$DFD000` | +| `uint8_t ApolloLoadPointer(ptr, pic)` | Loads a 32x32 BMP (8-bit with palette, 24-bit or 32-bit ARGB) and converts it into pointer sprite data: colour index + transparency byte per pixel and a 256-entry palette. Frees the BMP again. Returns `APOLLO_PICTURE_OK`, a load error, `W_ERROR`/`H_ERROR` (not 32x32) or `D_ERROR`; sets `ptr->bLoaded`. | +| `ApolloShowPointer(ptr)` | If `ptr->bLoaded`: writes the palette (`$DFF3A8` index, `$DFF3AA` colour) and copies the 2048-byte image to `$DFD000` | | `ApolloShowFile(filename, &draw, &live, length, offset, gfx_mode, modulo, endianswap)` | Reads a raw frame into `draw`, swaps `draw` and `live`, and shows `live`: double buffering for streamed frames | | `ApolloShowPattern(buffer, w, h, depth)` | Fills a buffer with a test pattern | @@ -79,19 +79,23 @@ checked width `APOLLO_PICTURE_H_ERROR`. Before ApolloLib 639e195 the ### Pointer -`ApolloLoadPointer` accepts a 32x32 BMP: +`ApolloLoadPointer` accepts a 32x32 BMP (from ApolloLib b9891d9; before, +it returned nothing, ignored 24-bit BMPs and kept the BMP allocated): - 8-bit: the BMP palette becomes the sprite palette; every pixel is opaque. Colour index 0 is transparent (see [pointer.md](../../core/saga-registers/pointer.md)). +- 24-bit: every pixel opaque; colours are gathered into a palette. - 32-bit ARGB: alpha 0 becomes index 0 (transparent), alpha `$FF` opaque, any other alpha semi-transparent (50 %). Colours are gathered into a - palette of up to 255 entries. + palette of up to 255 entries; further colours use entry 255. + +It loads the BMP with `endian` off and reads the bytes in BMP order, so the +caller doesn't set `endian`. `ApolloMouse` (see [input.md](input.md)) moves the pointer. A complete, checked use of both functions is in -[the pointer example](../../examples/saga-pointer-sprite.md): -`ApolloLoadPointer` returns nothing and doesn't free the BMP it loads. +[the pointer example](../../examples/saga-pointer-sprite.md). ### Example diff --git a/toolchain/pitfalls/apollolib-known-bugs.md b/toolchain/pitfalls/apollolib-known-bugs.md index 4b6586a..940e07e 100644 --- a/toolchain/pitfalls/apollolib-known-bugs.md +++ b/toolchain/pitfalls/apollolib-known-bugs.md @@ -19,6 +19,7 @@ sources: - "ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib, commit ce1fcbf (2026-11-04), read on 2026-11-04" - "ApolloCrossDev pull requests #17 and #18 (merged 2026-11-04), fixed in commit 639e195" - "ApolloCrossDev pull request #19 (merged 2026-11-04), PiP palette, commit a9a994f" + - "ApolloCrossDev pull request #21 (merged 2026-11-04), ApolloLoadPointer, commit b9891d9" --- ## Symptom @@ -51,6 +52,7 @@ hardware yet. | `ApolloLoadFile`/`Sound`/`Picture` | Left the file open on errors; BMP palette read from a fixed offset | | `ApolloShowPattern` | Wrote only the first byte of each pixel | | `ApolloCrossDev_Lib.h` | Declared `ApolloShowPiP`/`ApolloHidePiP`, which don't exist, and not `ApolloShowPiP1/2`/`ApolloHidePiP1/2` | +| `ApolloLoadPointer` (b9891d9, #21) | Returned nothing and never set `bLoaded`; accepted 24-bit BMPs without converting them (empty pointer); read 32-bit alpha correctly only with `endian` set; overwrote palette entry 255 when full; never freed the BMP it loaded. Now it returns an `APOLLO_PICTURE_…` code, converts 24-bit, reads BMP byte order, frees the BMP, and `ApolloShowPointer` does nothing without a loaded pointer. | | PiP palette (a9a994f, #19) | From 789cbcc (2026-06-16) to a9a994f nothing loaded an 8-bit PiP palette: `ApolloLoadPicture` had stopped writing it into the port chosen by `picture->target`. Now `ApolloShowPiP1/2` load it. | | Five functions (#17) | Wrong DMACON2R bit for channels 4–15, a write through an uninitialized pointer, missing return values, an always-on keyboard print | @@ -99,5 +101,5 @@ return 0; | Claim | Source | Core | Method | |---|---|---|---| | The bugs before 639e195 | (SRC) | any | Code reading of commit ce1fcbf, 2026-11-04 | -| Fixed in 639e195; builds without warnings | (SRC) | any | ApolloCrossDev #18: ApolloPointer, SAGAPiP and a link test build | +| Fixed in 639e195; builds without warnings | (SRC) | any | ApolloCrossDev #18: ApolloPointer, ApolloPiP (then SAGAPiP) and a link test build | | None of the fixes is tested on V4 hardware | – | – | – | diff --git a/tools/intake/sagaregs.py b/tools/intake/sagaregs.py index 4c9148a..c1df1b4 100644 --- a/tools/intake/sagaregs.py +++ b/tools/intake/sagaregs.py @@ -33,13 +33,13 @@ BIGGUN = "Gunnar (core developer, BigGun), Apollo chat, 2026-11-04 (relayed by WDrijver)" BGS = "G, 2026-11-04" APOLLOLIB = ("ApolloLib, github.com/WDrijver/ApolloCrossDev Projects/ApolloLib " - "(ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit 2dfaa5c (2026-09-02)") + "(ApolloCrossDev_Defines.h, ApolloCrossDev_LibC.c), commit ad7f0b4 (2026-11-04)") APOLLOLIB_OLD = ("ApolloLib, older copy in ApolloClaude docs/toolchain/_ApolloLib " "(ApolloCrossDev_Defines.h), 2026-11-04") -EX_PIP = ("Example SAGAPiP.c, github.com/WDrijver/ApolloCrossDev Projects/SAGAPiP, " - "commit 2dfaa5c (2026-09-02)") +EX_PIP = ("Example ApolloPiP.c (formerly SAGAPiP), github.com/WDrijver/ApolloCrossDev " + "Projects/ApolloPiP, commit ad7f0b4 (2026-11-04)") EX_POINTER = ("Example ApolloPointer.c, github.com/WDrijver/ApolloCrossDev " - "Projects/ApolloPointer, commit 2dfaa5c (2026-09-02)") + "Projects/ApolloPointer, commit ad7f0b4 (2026-11-04)") APOLLOOS = ("ApolloOS source, github.com/ApolloTeam-dev/ApolloOS.R10 " "arch/m68k-amiga/hidd/sagagfx/sagagfx_hw.h, commit ca5ef46 (2025-06-10)") MAGGIE = "maggie.library samples, see os/libraries/maggie-library.md" @@ -448,13 +448,13 @@ def pip(rows): p1 = pick(rows, 0xDFF3D0, 0xDFF3E2) details = ["### PIP 1 registers (original)", ""] + reg_table(p1) details += ["", "The original links these rows to a page that doesn't exist; the " - "facts below come from ApolloLib and the SAGAPiP example (SRC).", "", + "facts below come from ApolloLib and the ApolloPiP example (SRC).", "", "### PIP 2 registers (SRC, not in the original)", "", "PIP 2 has the same layout as PIP 1, `$20` lower (ApolloLib):", "", "| Address | Register | Same as PIP 1 |", "|---|---|---|"] details += [f"| `${a}` | {n} | `${addr(a) + 0x20:06X}` |" for a, n in PIP2] details += ["", "Its colour port is `$DFF37C` (see [colour-ports.md](colour-ports.md)). " - "The SAGAPiP example says two PIPs exist from Core 11900 (SRC).", "", + "The ApolloPiP example says two PIPs exist from Core 11900 (SRC).", "", "### Register meanings (SRC)", "", "| Register | Meaning |", "|---|---|", "| X/Y start, X/Y stop | Window position in screen pixels. The example " @@ -480,7 +480,7 @@ def pip(rows): f"Formats and transparent colours: {WDS}. The transparent colour is " "fixed per format. ApolloLib still marks YUV as not supported on PIP; " "that is an error in ApolloLib, not in the hardware.", "", - "### Two modes (SAGAPiP example, SRC)", "", + "### Two modes (ApolloPiP example, SRC)", "", "- **Overlay:** colour key 0. The PIP is in front of the main screen; " "with format bit 8 set, pixels in the transparent colour of the " "format are transparent.",