joeylib2/docs/ApiSurface.adoc

237 lines
6.8 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, because the blitter works on the planes there, so on
a 512 KB machine, beside the display's own pages, only a few fit.
*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.
====
=== 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.
*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.
====