317 lines
10 KiB
Text
317 lines
10 KiB
Text
== Surfaces and Presenting
|
|
|
|
Declared in `<joey/surface.h>` and `<joey/present.h>`.
|
|
|
|
A surface is a 320x200 16-colour image with its own 200-entry SCB table and its
|
|
own 16x16 palette set. Storage is target-native -- chunky 4bpp on the IIgs,
|
|
DOS, DVX, and the hosted ports, planar on the Amiga, ST, and X68000 -- but every
|
|
entry point speaks in colour indices, so your code never sees the difference.
|
|
A new surface starts with the 16-colour EGA palette in all 16 of its palettes.
|
|
|
|
=== Constants
|
|
|
|
[cols="3,1,4", options="header"]
|
|
|===
|
|
|Name |Value |Meaning
|
|
|
|
|`JL_SURFACE_WIDTH` |320 |Surface width in pixels.
|
|
|`JL_SURFACE_HEIGHT` |200 |Surface height in pixels.
|
|
|`JL_SURFACE_BYTES_PER_ROW` |160 |Bytes per row in chunky 4bpp form.
|
|
|`JL_SURFACE_PIXELS_SIZE` |32000 |Total pixel bytes.
|
|
|`JL_SURFACE_PALETTE_COUNT` |16 |Palettes per surface.
|
|
|`JL_SURFACE_COLORS_PER_PALETTE` |16 |Colours per palette.
|
|
|`JL_SURFACE_PALETTE_ENTRIES` |256 |Total palette entries per surface.
|
|
|===
|
|
|
|
=== jlSurfaceT
|
|
|
|
[source,c]
|
|
----
|
|
typedef struct jlSurfaceT jlSurfaceT;
|
|
----
|
|
|
|
Opaque. Obtain one from `jlSurfaceCreate`, `jlStageGet`, or a loader.
|
|
|
|
=== jlSurfaceCreate
|
|
|
|
[source,c]
|
|
----
|
|
jlSurfaceT *jlSurfaceCreate(void);
|
|
----
|
|
|
|
Allocates a new offscreen surface. Each one costs about 32 KB. On the Amiga
|
|
that comes from chip RAM, where the blitter can work on its planes; once chip
|
|
RAM is full, a surface comes from Fast RAM instead, and its fills and copies
|
|
run on the CPU, more slowly, so make the surfaces drawn on most first.
|
|
|
|
*Parameters:* none.
|
|
|
|
*Returns:* the surface, or `NULL` on failure (`jlLastError()` explains why).
|
|
|
|
=== jlSurfaceDestroy
|
|
|
|
[source,c]
|
|
----
|
|
void jlSurfaceDestroy(jlSurfaceT *s);
|
|
----
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`s` |`jlSurfaceT *` |Surface to release. `NULL` is a no-op, and passing the
|
|
stage is also a no-op -- the library owns it.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlStageGet
|
|
|
|
[source,c]
|
|
----
|
|
jlSurfaceT *jlStageGet(void);
|
|
----
|
|
|
|
Returns the library-owned stage: the back buffer that `jlStagePresent` flips to
|
|
the display. Always valid between `jlInit` and `jlShutdown`.
|
|
|
|
On the IIgs the stage's pixel buffer is pinned to bank `$01` Super Hi-Res space
|
|
with shadowing inhibited, so writes run at full speed and stay isolated from
|
|
the displayed framebuffer until the next present.
|
|
|
|
*Parameters:* none. *Returns:* the stage surface.
|
|
|
|
=== jlSurfaceCopy
|
|
|
|
[source,c]
|
|
----
|
|
void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
|
----
|
|
|
|
Copies pixels, SCBs, and palettes from `src` into `dst`.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination. Must be valid.
|
|
|`src` |`const jlSurfaceT *` |Source. Must be valid.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlSurfaceCopyRect
|
|
|
|
[source,c]
|
|
----
|
|
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src,
|
|
int16_t x, int16_t y, uint16_t w, uint16_t h);
|
|
----
|
|
|
|
Copies a pixel rectangle from `src` to `dst` *at the same position* -- there is
|
|
no repositioning, so the planar ports never have to shift bits. This is the
|
|
erase half of the clean-buffer pattern: keep a pristine copy of the scene, and
|
|
restore rectangles from it before redrawing sprites.
|
|
|
|
Copies pixels only. SCBs and palettes are untouched.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`src` |`const jlSurfaceT *` |Source surface.
|
|
|`x`, `y` |`int16_t` |Top-left of the rectangle, clipped to the surface.
|
|
|`w`, `h` |`uint16_t` |Size in pixels.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
[WARNING]
|
|
====
|
|
The rectangle is clipped to the surface and then *widened outward to the
|
|
16-pixel group grid on every port*: `x` rounds down and `x + w` rounds up to
|
|
multiples of 16. Two consequences you own:
|
|
|
|
* up to 15 extra pixels each side are copied, so a snapped erase can clobber
|
|
on-screen content outside the rectangle you asked for;
|
|
* it silently invalidates any live `jlSpriteBackupT` whose recorded region it
|
|
touches.
|
|
|
|
Erase *all* regions, then draw *all* sprites. Never mix this with per-sprite
|
|
save and restore in the same region.
|
|
====
|
|
|
|
=== jlSurfacePasteIndexed
|
|
|
|
[source,c]
|
|
----
|
|
void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
|
----
|
|
|
|
Writes a rectangle of colour indices from your memory onto a surface: one byte
|
|
a pixel, whatever the port stores pixels as. Row `r` of the source starts at
|
|
`src + r * srcStride`. Each byte's low nibble is the colour index; the high
|
|
nibble is ignored, so a byte array of 0..15 with flags in the top bits pastes
|
|
as it is.
|
|
|
|
With `doubleWide` each byte fills two pixels side by side, so `w` bytes cover
|
|
`2 * w` pixels: a 160-wide picture fills the 320-wide surface, the way Sierra's
|
|
AGI draws.
|
|
|
|
The rectangle is clipped to the surface pixel by pixel (a doubled byte cut by
|
|
an edge shows its one pixel there). Only the pasted pixels change and are
|
|
marked for the next `jlStagePresent`.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
|
|`x`, `y` |`int16_t` |Top-left pixel on the surface.
|
|
|`w`, `h` |`uint16_t` |Bytes a row and rows.
|
|
|`src` |`const uint8_t *` |The colour indices, row by row.
|
|
|`srcStride` |`uint16_t` |Bytes from one source row to the next.
|
|
|`doubleWide` |`bool` |Each byte fills two pixels.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
[source,c]
|
|
----
|
|
// A 160x168 AGI picture, doubled to 320 wide, below an 8-row status line.
|
|
jlSurfacePasteIndexed(jlStageGet(), 0, 8, 160, 168, picture, 160, true);
|
|
----
|
|
|
|
Every port has a fast path: asm on the IIgs (two pixel pairs a 16-bit
|
|
operation) and on the planar ports (table-driven chunky-to-planar, eight
|
|
pixels a step), four bytes a step in C on the chunky ports.
|
|
|
|
=== jlSurfaceSnapIndexed
|
|
|
|
[source,c]
|
|
----
|
|
void jlSurfaceSnapIndexed(const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
|
----
|
|
|
|
The reverse of `jlSurfacePasteIndexed`: reads a rectangle of a surface into your
|
|
memory as colour indices, one byte a pixel (0..15), row `r` at
|
|
`dst + r * dstStride`. With `doubleWide` the rectangle is `2 * w` pixels wide
|
|
and each byte takes the left pixel of its pair. A byte whose pixel is off the
|
|
surface reads 0, as `jlSamplePixel` does. Bytes between `w` and `dstStride` are
|
|
left alone.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`src` |`const jlSurfaceT *` |Source surface.
|
|
|`x`, `y` |`int16_t` |Top-left pixel on the surface.
|
|
|`w`, `h` |`uint16_t` |Bytes a row and rows.
|
|
|`dst` |`uint8_t *` |Where the colour indices go, row by row.
|
|
|`dstStride` |`uint16_t` |Bytes from one destination row to the next.
|
|
|`doubleWide` |`bool` |Read every other pixel.
|
|
|===
|
|
|
|
*Returns:* nothing.
|
|
|
|
=== jlSurfaceSaveFile
|
|
|
|
[source,c]
|
|
----
|
|
bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path);
|
|
----
|
|
|
|
Writes a surface to disk in target-native form: 32000 pixel bytes, then 200 SCB
|
|
bytes, then 512 bytes of `$0RGB` palette data.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`src` |`const jlSurfaceT *` |Surface to write.
|
|
|`path` |`const char *` |Destination path.
|
|
|===
|
|
|
|
*Returns:* `true` on success; `false` if the file could not be opened or the
|
|
write failed.
|
|
|
|
[IMPORTANT]
|
|
====
|
|
The format is *not portable*. No endianness or layout conversion is performed,
|
|
so the same platform that wrote a file must be the one that reads it. Use this
|
|
for runtime caches, not for shipped assets.
|
|
====
|
|
|
|
=== jlSurfaceLoadFile
|
|
|
|
[source,c]
|
|
----
|
|
bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path);
|
|
----
|
|
|
|
Reads a surface from disk, overwriting `dst`. The surface keeps its identity --
|
|
nothing is reallocated.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`dst` |`jlSurfaceT *` |Surface to overwrite.
|
|
|`path` |`const char *` |Source path.
|
|
|===
|
|
|
|
*Returns:* `true` on success; `false` if the file is missing, the wrong size,
|
|
has an SCB byte above 15, or unreadable. A read that fails part way can leave
|
|
the pixels partly overwritten; the SCBs and palettes are then untouched. Colour
|
|
0 of every palette loaded is set to black, as `jlPaletteSet` does. Neither file
|
|
call sets `jlLastError`.
|
|
|
|
=== jlSurfaceHash
|
|
|
|
[source,c]
|
|
----
|
|
uint32_t jlSurfaceHash(const jlSurfaceT *s);
|
|
----
|
|
|
|
Computes a 32-bit content hash over the surface: the packed chunky pixel byte
|
|
stream, then the 200 SCB bytes, then the 256 palette entries.
|
|
|
|
The important property is that *the same logical pixels hash the same on every
|
|
port*, whatever the internal storage format. That makes it the tool for proving
|
|
two platforms render identically -- which is exactly how the library's own
|
|
cross-port validation works.
|
|
|
|
[cols="1,1,4", options="header"]
|
|
|===
|
|
|Parameter |Type |Description
|
|
|`s` |`const jlSurfaceT *` |Surface to hash.
|
|
|===
|
|
|
|
*Returns:* the hash value.
|
|
|
|
=== jlStagePresent
|
|
|
|
[source,c]
|
|
----
|
|
void jlStagePresent(void);
|
|
----
|
|
|
|
Flips the dirty regions of the stage to the display and clears the dirty state.
|
|
Where the stage already has the display's layout this is a copy (the IIgs's
|
|
4bpp pixels; the planes of the Amiga, ST, and X68000); DOS, DVX, and the hosted
|
|
ports expand each 4-bit pixel to their display's 8-bit or RGB pixels. Palette
|
|
and SCB changes reach the display here too.
|
|
|
|
Drawing primitives mark per-row dirty ranges as a side effect, so you only need
|
|
to call this at the end of a frame and everything you drew appears. Rows
|
|
nothing touched since the last present are skipped.
|
|
|
|
The Amiga, the ST, DOS, and the X68000 draw into a second screen page and flip
|
|
to it at the next vertical blank, so the picture never tears. A present that comes before
|
|
that blank, right after another one, waits for it: the page it would draw into
|
|
is still on the screen. Present once a frame, after `jlWaitVBL`, and it never
|
|
waits. On DOS, a present that changes colours also waits: VGA loads them in
|
|
the blank that shows the new page, and the `jlWaitVBL` after it returns at
|
|
once, so the loop keeps its pace.
|
|
|
|
*Parameters:* none. *Returns:* nothing.
|
|
|
|
[NOTE]
|
|
====
|
|
Every call that changes the stage's pixels marks the rows it touched, and the
|
|
API has no other way to write them, so there is nothing to mark by hand.
|
|
====
|