Tilemap stuff
This commit is contained in:
@@ -8,6 +8,143 @@ For the planned render-queue refactor (required for Saturn), see [display-refact
|
||||
|
||||
---
|
||||
|
||||
## Render / ROP system (`display/render/`)
|
||||
|
||||
The ROP (Render OPcode) system is the low-level, backend-agnostic drawing API. All game drawing goes through this layer; backends (`rendergl.c`, `renderpsp.c`, `renderdolphin.c`) execute the commands at display flush time.
|
||||
|
||||
### API (`display/render/render.h`)
|
||||
|
||||
```c
|
||||
/* Clear the framebuffer */
|
||||
void renderClear(color_t color);
|
||||
|
||||
/* 2D textured quad at pixel coordinates */
|
||||
void renderSprite(
|
||||
int16_t x, int16_t y, int16_t w, int16_t h,
|
||||
int16_t depth, /* 0=front … 32767=back */
|
||||
rtexture_t texture, color_t tint
|
||||
);
|
||||
|
||||
/* Set perspective projection for subsequent 3D draws */
|
||||
void renderSetProjection(
|
||||
fixed_t fovY, fixed_t aspect, fixed_t nearZ, fixed_t farZ
|
||||
);
|
||||
|
||||
/* Set camera position/target for subsequent 3D draws */
|
||||
void renderSetView(
|
||||
int16_t eyeX, int16_t eyeY, int16_t eyeZ,
|
||||
int16_t tgtX, int16_t tgtY, int16_t tgtZ
|
||||
);
|
||||
|
||||
/* World-space quad: center point + right half-extent + up half-extent */
|
||||
void renderQuad3D(
|
||||
int16_t cx, int16_t cy, int16_t cz,
|
||||
int16_t rx, int16_t ry, int16_t rz,
|
||||
int16_t ux, int16_t uy, int16_t uz,
|
||||
int16_t depth,
|
||||
rtexture_t texture, color_t tint
|
||||
);
|
||||
|
||||
/* Create / dispose an 8-bit indexed palette texture */
|
||||
rtexture_t renderTextureCreate(
|
||||
uint16_t w, uint16_t h,
|
||||
const uint8_t *indices, /* w×h pixel indices (0-255) */
|
||||
const color_t *palette /* 256 RGBA colour entries */
|
||||
);
|
||||
void renderTextureDispose(rtexture_t tex);
|
||||
|
||||
/* Mutable pointers to the texture's CPU-side data.
|
||||
* Write directly to these; the next draw call picks up the changes.
|
||||
* GL: dirty flag set on getter call; glTexSubImage2D at next bind.
|
||||
* PSP: re-pads indices and converts palette → ABGR at bind time.
|
||||
* Dolphin: re-tiles CI8 and converts palette → RGB5A3 at bind time. */
|
||||
color_t *renderTextureGetPalette(rtexture_t tex); /* color_t[256] */
|
||||
uint8_t *renderTextureGetIndices(rtexture_t tex); /* uint8_t[w*h] */
|
||||
```
|
||||
|
||||
### Coordinate conventions
|
||||
|
||||
| Domain | Type | Scale | Notes |
|
||||
|---|---|---|---|
|
||||
| 3D world positions | `int16_t` | 1 unit = 1 cm | Matches PS1 GTE / N64 RSP native format |
|
||||
| Camera/projection params | `fixed_t` | Q24.8 | `FIXED(x)` for literals |
|
||||
| 2D screen positions | `int16_t` | pixels | Origin top-left |
|
||||
| UV coords | `uint8_t` | 0–255 → 0.0–1.0 | Stored in ROP structs |
|
||||
|
||||
### Palettized textures
|
||||
|
||||
All textures are 8-bit indexed. `renderTextureCreate` takes:
|
||||
- `indices`: one `uint8_t` per pixel (0–255), row-major
|
||||
- `palette`: exactly **256** `color_t` RGBA entries
|
||||
|
||||
**Per-platform storage:**
|
||||
|
||||
| Platform | CPU source of truth | GPU/native format | When derived |
|
||||
|---|---|---|---|
|
||||
| GL (Linux/Vita) | `color_t palette[256]` + `uint8_t *cpuIndices` in slot | `GL_R8` index tex + `GL_RGBA` 256×1 palette tex | Lazy: dirty flag set by getter, `glTexSubImage2D` at next bind |
|
||||
| PSP | `color_t palette[256]` + unpadded `uint8_t *cpuIndices` | Stride-padded indices (POT ≥ 8) + ABGR8888 CLUT in shared `pspAbgrBuf` | Every `bindTexture` call; dcache-flushed before GU reads |
|
||||
| Dolphin/GC/Wii | `color_t palette[256]` + unpadded `uint8_t *cpuIndices` | CI8 tiled (8×4 tiles, 32 B/tile) + RGB5A3 TLUT in `tlutData` | Every `bindTexture` call; `DCFlushRange` before GX load |
|
||||
|
||||
**GL palette shader detail**: The fragment shader samples the R8 index texture, converts the normalised float back to an exact texel centre with `raw*(255/256) + 0.5/256`, then looks up the 256×1 palette texture. This gives pixel-exact results for all 256 index values and allows independent real-time updates to indices or palette.
|
||||
|
||||
**Dolphin RGB5A3 encoding**:
|
||||
- Opaque (`a == 255`): bit 15 = 1, RGB555
|
||||
- Transparent: bit 15 = 0, A3RGB4 (alpha quantised to 3 bits — dithered transparency is planned for a future pass)
|
||||
|
||||
### ROP buffer (`display/render/ropbuffer.h` / `rop.h`)
|
||||
|
||||
Commands are written into `ROPBUFFER` (a static byte array) then replayed by the backend at flush time. All ops are fixed-size aligned structs:
|
||||
|
||||
| Op | Struct | Size |
|
||||
|---|---|---|
|
||||
| `ROP_CLEAR` | `ropclear_t` | 32 bytes |
|
||||
| `ROP_DRAW_SPRITE` | `ropsprite_t` | 32 bytes |
|
||||
| `ROP_SET_PROJECTION` | `ropprojection_t` | 32 bytes |
|
||||
| `ROP_SET_VIEW` | `ropview_t` | 32 bytes |
|
||||
| `ROP_DRAW_QUAD_3D` | `ropquad3d_t` | 64 bytes |
|
||||
| `ROP_DRAW_TILEMAP_CHUNK` | `roptilemapc_t` | 32 bytes |
|
||||
|
||||
`ropOpSize(op)` returns the byte size for any op. Backends iterate with `offset += ropOpSize(op)`.
|
||||
|
||||
### Texture handles (`display/render/rtexture.h`)
|
||||
|
||||
`rtexture_t` is a `uint16_t` index into the platform's texture table. `RTEXTURE_NONE` (0 or a sentinel) means "white fallback". Tables are platform-static; handles are valid until `renderTextureDispose` is called.
|
||||
|
||||
### Tilemap chunk handles (`display/render/rtilemapchunk.h`)
|
||||
|
||||
`rtilemapchunk_t` is a `uint16_t` index into the platform's chunk table. `RTILEMAPCHUNK_INVALID` (0) means no-op. Chunks are pre-built at map load time; each backend constructs its native draw structure once (VAO+VBO on GL, display list on PSP/GX/N64) and the ROP entry costs only a handle lookup + single native draw call per frame.
|
||||
|
||||
```c
|
||||
/* Build once at map load */
|
||||
rtilemapchunk_t chunk = renderTilemapChunkCreate(
|
||||
chunkW, chunkH, /* size in tiles */
|
||||
tileW, tileH, /* pixels per tile */
|
||||
tileset, /* rtexture_t of the packed tileset */
|
||||
tileIndices /* uint8_t[chunkW*chunkH], row-major tile indices */
|
||||
);
|
||||
|
||||
/* Each frame for visible chunks */
|
||||
renderTilemapChunk(screenX, screenY, depth, chunk);
|
||||
|
||||
/* At map unload */
|
||||
renderTilemapChunkDispose(chunk);
|
||||
```
|
||||
|
||||
Animated tiles should be drawn on top as separate `renderSprite()` calls; the chunk itself is treated as static geometry and never rebuilt at runtime.
|
||||
|
||||
**Per-platform build:**
|
||||
|
||||
| Platform | What's built at create time | Draw cost per frame |
|
||||
|---|---|---|
|
||||
| GL (Linux/Vita) | VAO + VBO (`GL_STATIC_DRAW`), `uOffset` uniform translates to screen pos | 1 `glDrawArrays` |
|
||||
| PSP | GU display list in uncached EDRAM | 1 `sceGuCallList` |
|
||||
| GC/Wii | Compiled GX display list | 1 `GX_CallDispList` |
|
||||
| PS1 | Pre-linked POLY_FT4/SPRT chain | Linked into OT at one slot |
|
||||
| N64 | RDP display list with pre-scheduled `LOAD_TILE` batches (TMEM-aware) | 1 `gSPDisplayList` |
|
||||
| Saturn | VDP2 plane config + VRAM tilemap data | Scroll register write only |
|
||||
|
||||
---
|
||||
|
||||
## Initialization order
|
||||
|
||||
Within the display system, init must follow this order (enforced in `engine.c`):
|
||||
|
||||
Reference in New Issue
Block a user