Tilemap stuff

This commit is contained in:
2026-06-19 13:17:20 -05:00
parent 57b2cdb9d1
commit 4e491d8332
52 changed files with 2372 additions and 362 deletions
+137
View File
@@ -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` | 0255 → 0.01.0 | Stored in ROP structs |
### Palettized textures
All textures are 8-bit indexed. `renderTextureCreate` takes:
- `indices`: one `uint8_t` per pixel (0255), 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 ## Initialization order
Within the display system, init must follow this order (enforced in `engine.c`): Within the display system, init must follow this order (enforced in `engine.c`):
+21
View File
@@ -227,6 +227,27 @@ errorret_t spriteBatchFlush(void);
--- ---
## Unused parameters
Do **not** use `(void)param;` casts to suppress unused-parameter warnings. They are
redundant noise. If a callback signature is fixed by a function-pointer type and the
parameter is genuinely unused, just leave it — do not suppress:
```c
// correct — parameter unused, no cast
errorret_t sceneTestUpdate(scenedata_t *data) {
errorOk();
}
// wrong
errorret_t sceneTestUpdate(scenedata_t *data) {
(void)data;
errorOk();
}
```
---
## Global subsystem state ## Global subsystem state
Each subsystem exposes a single global instance declared `extern` in the header and defined (once) in the `.c` file: Each subsystem exposes a single global instance declared `extern` in the header and defined (once) in the `.c` file:
+1 -4
View File
@@ -9,12 +9,10 @@ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES} ${SDL2_LIBRARIES}
SDL2 SDL2
pthread pthread
OpenGL::GL
zip zip
bz2 bz2
z z
@@ -28,6 +26,7 @@ target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
pspge pspge
pspctrl pspctrl
pspgu pspgu
pspgum
pspaudio pspaudio
pspaudiolib pspaudiolib
psputility psputility
@@ -47,11 +46,9 @@ target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_SDL2 DUSK_SDL2
DUSK_OPENGL
DUSK_PSP DUSK_PSP
DUSK_INPUT_GAMEPAD DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480 DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272 DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD DUSK_THREAD_PTHREAD
-1
View File
@@ -13,7 +13,6 @@ if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp") elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(duskpsp) add_subdirectory(duskpsp)
add_subdirectory(dusksdl2) add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "vita") elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(duskvita) add_subdirectory(duskvita)
-1
View File
@@ -58,7 +58,6 @@ add_subdirectory(assert)
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(console) add_subdirectory(console)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(render)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(engine) add_subdirectory(engine)
add_subdirectory(error) add_subdirectory(error)
+1
View File
@@ -15,3 +15,4 @@ dusk_run_python(
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h --output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h
) )
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_color_defs) add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_color_defs)
add_subdirectory(render)
+1 -1
View File
@@ -6,7 +6,7 @@
*/ */
#include "display/display.h" #include "display/display.h"
#include "render/ropbuffer.h" #include "display/render/ropbuffer.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "util/memory.h" #include "util/memory.h"
+134
View File
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/render.h"
#include "display/render/renderplatform.h"
void renderClear(color_t color) {
ropclear_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_CLEAR);
cmd->color = color;
}
void renderSprite(
int16_t x, int16_t y, int16_t w, int16_t h,
int16_t depth,
rtexture_t texture, color_t tint
) {
ropsprite_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_DRAW_SPRITE);
cmd->header.depth = depth;
cmd->x = x;
cmd->y = y;
cmd->w = w;
cmd->h = h;
cmd->uvX = 0;
cmd->uvY = 0;
cmd->uvW = 255;
cmd->uvH = 255;
cmd->tint = tint;
cmd->texture = texture;
}
void renderSetProjection(fixed_t fovY, fixed_t aspect, fixed_t nearZ, fixed_t farZ) {
ropprojection_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_SET_PROJECTION);
cmd->fovY = fovY;
cmd->aspect = aspect;
cmd->nearZ = nearZ;
cmd->farZ = farZ;
}
void renderSetView(
int16_t eyeX, int16_t eyeY, int16_t eyeZ,
int16_t tgtX, int16_t tgtY, int16_t tgtZ
) {
ropview_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_SET_VIEW);
cmd->eyeX = eyeX; cmd->eyeY = eyeY; cmd->eyeZ = eyeZ;
cmd->tgtX = tgtX; cmd->tgtY = tgtY; cmd->tgtZ = tgtZ;
}
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
) {
ropquad3d_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_DRAW_QUAD_3D);
cmd->header.depth = depth;
cmd->cx = cx; cmd->cy = cy; cmd->cz = cz;
cmd->rx = rx; cmd->ry = ry; cmd->rz = rz;
cmd->ux = ux; cmd->uy = uy; cmd->uz = uz;
cmd->uvX = 0; cmd->uvY = 0;
cmd->uvW = 255; cmd->uvH = 255;
cmd->tint = tint;
cmd->texture = texture;
}
rtexture_t renderTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
) {
#ifdef renderPlatformTextureCreate
return renderPlatformTextureCreate(w, h, indices, palette);
#else
return RTEXTURE_NONE;
#endif
}
void renderTextureDispose(rtexture_t tex) {
#ifdef renderPlatformTextureDispose
renderPlatformTextureDispose(tex);
#endif
}
color_t *renderTextureGetPalette(rtexture_t tex) {
#ifdef renderPlatformTextureGetPalette
return renderPlatformTextureGetPalette(tex);
#else
return NULL;
#endif
}
uint8_t *renderTextureGetIndices(rtexture_t tex) {
#ifdef renderPlatformTextureGetIndices
return renderPlatformTextureGetIndices(tex);
#else
return NULL;
#endif
}
rtilemapchunk_t renderTilemapChunkCreate(
uint16_t chunkW, uint16_t chunkH,
uint16_t tileW, uint16_t tileH,
rtexture_t tileset,
const uint8_t *tileIndices
) {
#ifdef renderPlatformTilemapChunkCreate
return renderPlatformTilemapChunkCreate(
chunkW, chunkH, tileW, tileH, tileset, tileIndices
);
#else
return RTILEMAPCHUNK_INVALID;
#endif
}
void renderTilemapChunkDispose(rtilemapchunk_t chunk) {
#ifdef renderPlatformTilemapChunkDispose
renderPlatformTilemapChunkDispose(chunk);
#endif
}
void renderTilemapChunk(
int16_t x, int16_t y,
int16_t depth,
rtilemapchunk_t chunk
) {
roptilemapc_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_DRAW_TILEMAP_CHUNK);
cmd->header.depth = depth;
cmd->x = x;
cmd->y = y;
cmd->chunk = chunk;
}
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/render/rop.h"
#include "display/render/ropbuffer.h"
#include "display/render/rtexture.h"
#include "display/render/rtilemapchunk.h"
/* ---- 2D ------------------------------------------------------------------ */
void renderClear(color_t color);
void renderSprite(
int16_t x, int16_t y, int16_t w, int16_t h,
int16_t depth,
rtexture_t texture, color_t tint
);
/* ---- 3D ------------------------------------------------------------------ */
void renderSetProjection(
fixed_t fovY, fixed_t aspect, fixed_t nearZ, fixed_t farZ
);
void renderSetView(
int16_t eyeX, int16_t eyeY, int16_t eyeZ,
int16_t tgtX, int16_t tgtY, int16_t tgtZ
);
/* World-space quad: center + 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
);
/* ---- Textures ------------------------------------------------------------ */
/* 8-bit indexed palette texture. palette must contain exactly 256 entries. */
rtexture_t renderTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
);
void renderTextureDispose(rtexture_t tex);
/* Mutable access to the CPU-side palette and index arrays.
* Write to these pointers directly; the next draw call picks up the changes.
* GL re-uploads to the GPU texture lazily at bind (dirty flag); PSP/Dolphin
* re-convert their native format (ABGR/CI8) at bind time from these arrays. */
color_t *renderTextureGetPalette(rtexture_t tex); /* color_t[256] */
uint8_t *renderTextureGetIndices(rtexture_t tex); /* uint8_t[w*h], row-major */
/* ---- Tilemap chunks ------------------------------------------------------- */
/* Build a pre-baked chunk from a tile-index array. The backend constructs
* its native draw structure (VAO+VBO on GL, display list on PSP/GX/N64) once
* at create time; subsequent renderTilemapChunk() calls cost only one ROP
* entry and one native draw call per chunk.
*
* chunkW/chunkH — chunk size in tiles
* tileW/tileH — pixel dimensions of one tile in the tileset texture
* tileset — palettized texture containing all tiles packed in a grid
* tileIndices — chunkW*chunkH uint8_t values; each is a tile index into the
* tileset grid (row-major, tile 0 = top-left of tileset) */
rtilemapchunk_t renderTilemapChunkCreate(
uint16_t chunkW, uint16_t chunkH,
uint16_t tileW, uint16_t tileH,
rtexture_t tileset,
const uint8_t *tileIndices
);
void renderTilemapChunkDispose(rtilemapchunk_t chunk);
/* Emit a ROP_DRAW_TILEMAP_CHUNK entry. x/y are the screen-space pixel position
* of the chunk's top-left corner. */
void renderTilemapChunk(
int16_t x, int16_t y,
int16_t depth,
rtilemapchunk_t chunk
);
+120
View File
@@ -0,0 +1,120 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/color.h"
#include "display/render/rtexture.h"
#include "display/render/rtilemapchunk.h"
#include "util/fixed.h"
/* Fixed size for all 2D/state opcodes. 3D quads use ROP_SIZE_3D. */
#define ROP_SIZE 32
#define ROP_SIZE_3D 64
typedef enum {
ROP_NOP = 0,
ROP_CLEAR,
ROP_DRAW_SPRITE, /* 2D screen-space quad ROP_SIZE */
ROP_SET_PROJECTION, /* perspective / ortho params ROP_SIZE */
ROP_SET_VIEW, /* camera eye + target ROP_SIZE */
ROP_DRAW_QUAD_3D, /* world-space textured quad ROP_SIZE_3D */
ROP_DRAW_TILEMAP_CHUNK, /* pre-built tile chunk handle ROP_SIZE */
ROP_COUNT
} ropop_t;
#define ROP_FLAG_BLEND ((uint8_t)(1 << 0))
/* Returns the byte size of a given opcode. */
static inline uint32_t ropOpSize(ropop_t op) {
if(op == ROP_DRAW_QUAD_3D) return ROP_SIZE_3D;
return ROP_SIZE;
}
/* 4-byte header that starts every opcode. */
typedef struct {
uint8_t op;
uint8_t flags;
int16_t depth;
} ropheader_t;
_Static_assert(sizeof(ropheader_t) == 4, "ropheader_t must be 4 bytes");
/* ROP_CLEAR — 32 bytes */
typedef struct {
ropheader_t header; /* 4 */
color_t color; /* 4 */
uint8_t pad[24];/* 24 */
} ropclear_t;
_Static_assert(sizeof(ropclear_t) == ROP_SIZE, "ropclear_t must be ROP_SIZE bytes");
/* ROP_DRAW_SPRITE — 32 bytes, screen-space pixel coordinates */
typedef struct {
ropheader_t header; /* 4 */
int16_t x, y; /* 4 */
int16_t w, h; /* 4 */
uint8_t uvX, uvY; /* 2 */
uint8_t uvW, uvH; /* 2 */
color_t tint; /* 4 */
uint16_t texture; /* 2 handle, 0 = white */
uint16_t palette; /* 2 */
uint8_t pad[8]; /* 8 */
} ropsprite_t;
_Static_assert(sizeof(ropsprite_t) == ROP_SIZE, "ropsprite_t must be ROP_SIZE bytes");
/* ROP_SET_PROJECTION — 32 bytes. fovY==0 means orthographic. */
typedef struct {
ropheader_t header; /* 4 */
fixed_t fovY; /* 4 radians; 0 = ortho */
fixed_t aspect; /* 4 w/h */
fixed_t nearZ; /* 4 */
fixed_t farZ; /* 4 */
uint8_t pad[12]; /* 12 */
} ropprojection_t;
_Static_assert(sizeof(ropprojection_t) == ROP_SIZE, "ropprojection_t must be ROP_SIZE bytes");
/* ROP_SET_VIEW — 32 bytes. Camera eye position and look-at target. */
typedef struct {
ropheader_t header; /* 4 */
int16_t eyeX, eyeY, eyeZ; /* 6 */
int16_t tgtX, tgtY, tgtZ; /* 6 */
uint8_t pad[16]; /* 16 */
} ropview_t;
_Static_assert(sizeof(ropview_t) == ROP_SIZE, "ropview_t must be ROP_SIZE bytes");
/* ROP_DRAW_QUAD_3D — 64 bytes.
* Quad defined by center + two half-extent vectors.
* Corners: center ± right ± up
*/
typedef struct {
ropheader_t header; /* 4 */
int16_t cx, cy, cz; /* 6 center */
int16_t rx, ry, rz; /* 6 right half-ext */
int16_t ux, uy, uz; /* 6 up half-ext */
uint8_t uvX, uvY; /* 2 */
uint8_t uvW, uvH; /* 2 */
color_t tint; /* 4 */
uint16_t texture; /* 2 handle, 0=white */
uint8_t pad[32]; /* 32 */
} ropquad3d_t;
_Static_assert(sizeof(ropquad3d_t) == ROP_SIZE_3D, "ropquad3d_t must be ROP_SIZE_3D bytes");
/* ROP_DRAW_TILEMAP_CHUNK — 32 bytes.
* References a pre-built backend chunk by handle; x/y are the screen-space
* pixel offset applied at draw time. */
typedef struct {
ropheader_t header; /* 4 */
int16_t x, y; /* 4 screen-space pixel offset */
rtilemapchunk_t chunk; /* 2 handle, RTILEMAPCHUNK_INVALID = no-op */
uint8_t pad[22]; /* 22 */
} roptilemapc_t;
_Static_assert(sizeof(roptilemapc_t) == ROP_SIZE, "roptilemapc_t must be ROP_SIZE bytes");
@@ -5,21 +5,27 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#include "render/ropbuffer.h" #include "display/render/ropbuffer.h"
#include "util/memory.h" #include "util/memory.h"
#include "assert/assert.h" #include "assert/assert.h"
ropbuffer_t ROPBUFFER; ropbuffer_t ROPBUFFER;
void ropBufferReset(ropbuffer_t *buf) { void ropBufferReset(ropbuffer_t *buf) {
buf->byteCount = 0;
buf->count = 0; buf->count = 0;
} }
void *ropBufferAlloc(ropbuffer_t *buf, ropop_t op) { void *ropBufferAlloc(ropbuffer_t *buf, ropop_t op) {
assertTrue(buf->count < ROPBUFFER_MAX_COMMANDS, "ROP buffer is full"); uint32_t size = ropOpSize(op);
uint8_t *ptr = buf->data + (buf->count * ROP_SIZE); assertTrue(
memoryZero(ptr, ROP_SIZE); buf->byteCount + size <= ROPBUFFER_BYTE_SIZE,
"ROP buffer is full"
);
uint8_t *ptr = buf->data + buf->byteCount;
memoryZero(ptr, size);
((ropheader_t *)ptr)->op = (uint8_t)op; ((ropheader_t *)ptr)->op = (uint8_t)op;
buf->byteCount += size;
buf->count++; buf->count++;
return ptr; return ptr;
} }
@@ -6,13 +6,15 @@
*/ */
#pragma once #pragma once
#include "render/rop.h" #include "display/render/rop.h"
#define ROPBUFFER_MAX_COMMANDS 4096 /* 256 KB pool — enough for ~8192 2D sprites or ~4096 3D quads mixed. */
#define ROPBUFFER_BYTE_SIZE (256 * 1024)
typedef struct { typedef struct {
uint8_t data[ROPBUFFER_MAX_COMMANDS * ROP_SIZE]; uint8_t data[ROPBUFFER_BYTE_SIZE];
uint32_t count; uint32_t byteCount; /* bytes consumed this frame */
uint32_t count; /* number of commands */
} ropbuffer_t; } ropbuffer_t;
extern ropbuffer_t ROPBUFFER; extern ropbuffer_t ROPBUFFER;
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
typedef uint16_t rtexture_t;
#define RTEXTURE_NONE ((rtexture_t)0)
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdint.h>
typedef uint16_t rtilemapchunk_t;
#define RTILEMAPCHUNK_INVALID ((rtilemapchunk_t)0)
-30
View File
@@ -1,30 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "render/render.h"
void renderClear(color_t color) {
ropclear_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_CLEAR);
cmd->color = color;
}
void renderSprite(
int16_t x, int16_t y,
int16_t w, int16_t h,
color_t tint
) {
ropsprite_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_DRAW_SPRITE);
cmd->x = x;
cmd->y = y;
cmd->w = w;
cmd->h = h;
cmd->uvX = 0;
cmd->uvY = 0;
cmd->uvW = 255;
cmd->uvH = 255;
cmd->tint = tint;
}
-18
View File
@@ -1,18 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "render/rop.h"
#include "render/ropbuffer.h"
void renderClear(color_t color);
void renderSprite(
int16_t x, int16_t y,
int16_t w, int16_t h,
color_t tint
);
-53
View File
@@ -1,53 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/color.h"
#define ROP_SIZE 32
typedef enum {
ROP_NOP = 0,
ROP_CLEAR,
ROP_DRAW_SPRITE,
ROP_COUNT
} ropop_t;
#define ROP_FLAG_BLEND ((uint8_t)(1 << 0))
/* 4 bytes, every opcode starts with this */
typedef struct {
uint8_t op;
uint8_t flags;
int16_t depth;
} ropheader_t;
_Static_assert(sizeof(ropheader_t) == 4, "ropheader_t must be 4 bytes");
/* ROP_CLEAR — 32 bytes */
typedef struct {
ropheader_t header; /* 4 */
color_t color; /* 4 */
uint8_t pad[24];/* 24 */
} ropclear_t;
_Static_assert(sizeof(ropclear_t) == ROP_SIZE, "ropclear_t must be ROP_SIZE bytes");
/* ROP_DRAW_SPRITE — 32 bytes, screen-space pixel coordinates */
typedef struct {
ropheader_t header; /* 4 */
int16_t x, y; /* 4 */
int16_t w, h; /* 4 */
uint8_t uvX, uvY; /* 2 */
uint8_t uvW, uvH; /* 2 */
color_t tint; /* 4 */
uint16_t texture; /* 2 handle, 0 = white */
uint16_t palette; /* 2 */
uint8_t pad[8]; /* 8 */
} ropsprite_t;
_Static_assert(sizeof(ropsprite_t) == ROP_SIZE, "ropsprite_t must be ROP_SIZE bytes");
+191 -7
View File
@@ -6,27 +6,211 @@
*/ */
#include "scene/test/scenetest.h" #include "scene/test/scenetest.h"
#include "render/render.h" #include "assert/assert.h"
#include "display/render/render.h"
#include "display/color.h" #include "display/color.h"
#include "time/time.h"
#include "util/fixed.h"
#include "util/memory.h"
/* Initial data for the 3×3 test texture. After create, we write directly
* to the texture's CPU buffers via renderTextureGet* — these statics
* are only referenced in sceneTestInit. */
static const uint8_t initIndices[3 * 3] = {
0, 1, 2,
3, 4, 5,
6, 7, 8,
};
static const color_t initPalette[256] = {
[0] = { 255, 0, 0, 255 }, /* red */
[1] = { 0, 255, 0, 255 }, /* green */
[2] = { 0, 0, 255, 255 }, /* blue */
[3] = { 255, 255, 0, 255 }, /* yellow */
[4] = { 255, 0, 255, 255 }, /* magenta */
[5] = { 0, 255, 255, 255 }, /* cyan */
[6] = { 255, 165, 0, 255 }, /* orange */
[7] = { 128, 0, 255, 255 }, /* purple */
[8] = { 255, 255, 255, 255 }, /* white */
};
/* Rainbow hue cycle using 120°-offset cosines. */
static color_t hueColor(float t) {
uint8_t r = (uint8_t)((cosf(t) * 0.5f + 0.5f) * 255.0f);
uint8_t g = (uint8_t)((cosf(t - 2.094f) * 0.5f + 0.5f) * 255.0f);
uint8_t b = (uint8_t)((cosf(t - 4.189f) * 0.5f + 0.5f) * 255.0f);
return color(r, g, b, 255);
}
static rtexture_t testTex;
/* ---- aspect ratio for 3D tests (matches linux default 640×480) ---------- */
#define ASPECT FIXED(640.0f / 480.0f)
/* fovY ≈ 60° in radians */
#define FOV_Y FIXED(1.0472f)
/* ---- Tilemap test -------------------------------------------------------- */
/* Two 16×16 tiles packed side-by-side in a 32×16 tileset texture.
* The chunk is 64px wider than the 640px window; the scroll range is exactly
* 4 tile widths = 2 checkerboard periods, so the wrap is seamless. */
#define TILEMAP_TILE_W 16
#define TILEMAP_TILE_H 16
#define TILEMAP_CHUNK_W 44 /* tiles; 44*16=704px */
#define TILEMAP_CHUNK_H 13 /* tiles; 13*16=208px */
#define TILEMAP_Y 262 /* screen-space y: just below 2D tests */
#define TILEMAP_SCROLL_SPEED 40.0f /* px / second */
#define TILEMAP_SCROLL_RANGE 64.0f /* px; = 4 tile widths = 2 chk periods */
static rtexture_t tilemapTileset;
static rtilemapchunk_t tilemapChunk;
/* =========================================================================
* Test 1 — single 2D textured quad
* top-left quadrant (x: 50240, y: 30190)
* ======================================================================= */
static void renderTest2DQuad(void) {
renderSprite(50, 30, 192, 160, 0, testTex, COLOR_WHITE);
}
/* =========================================================================
* Test 2 — three 2D quads with Z-ordering
* top-right quadrant (x: 340620, y: 30200)
* Depth: 0 = frontmost, 32767 = backmost.
* Back quad (depth 24000) tinted blue-grey, drawn first visually behind.
* Mid quad (depth 12000) tinted orange.
* Front quad (depth 0) full texture, drawn on top.
* ======================================================================= */
static void renderTest2DZOrder(void) {
renderSprite(340, 30, 160, 160, 24000, testTex, color(100, 100, 220, 255));
renderSprite(380, 60, 160, 160, 12000, testTex, color(220, 140, 60, 255));
renderSprite(420, 90, 160, 160, 0, testTex, COLOR_WHITE);
}
/* =========================================================================
* Test 3 — single 3D textured quad spinning around Y axis
* World position: left side, x=-180 cm.
* ======================================================================= */
static void renderTest3DQuad(void) {
float angle = fixedToFloat(TIME.time);
renderQuad3D(
-180, 0, 0,
(int16_t)(cosf(angle) * 60.0f), 0, (int16_t)(sinf(angle) * 60.0f),
0, 60, 0,
0, testTex, COLOR_WHITE
);
}
/* =========================================================================
* Test 4 — two 3D quads overlapping (depth test)
* Right side of world space.
* Quad A (behind, z=-60 cm): blue tint.
* Quad B (in front, z=60 cm): white/texture.
* Both quads share screen-space centre so depth buffer decides ordering.
* ======================================================================= */
static void renderTest3DOverlap(void) {
/* behind */
renderQuad3D(
150, 0, -60,
60, 0, 0,
0, 60, 0,
0, testTex, color(100, 100, 220, 255)
);
/* in front */
renderQuad3D(
190, 0, 60,
60, 0, 0,
0, 60, 0,
0, testTex, COLOR_WHITE
);
}
/* =========================================================================
* Test 5 — scrolling tilemap (bottom of screen)
* Checkerboard of two hue-cycling tiles.
* x offset scrolls left by TILEMAP_SCROLL_SPEED px/s, wraps seamlessly.
* ======================================================================= */
static void renderTestTilemap(void) {
float t = fixedToFloat(TIME.time);
float scrollPx = fmodf(t * TILEMAP_SCROLL_SPEED, TILEMAP_SCROLL_RANGE);
int16_t x = -(int16_t)(int)scrollPx;
renderTilemapChunk(x, TILEMAP_Y, 8000, tilemapChunk);
}
/* ---- Lifecycle ----------------------------------------------------------- */
errorret_t sceneTestInit(scenedata_t *data) { errorret_t sceneTestInit(scenedata_t *data) {
(void)data; testTex = renderTextureCreate(3, 3, initIndices, initPalette);
assertTrue(testTex != RTEXTURE_NONE, "Failed to create test texture");
/* Tileset: 32×16, two 16×16 tiles — left half = index 0, right = index 1 */
uint8_t tsIdx[32 * 16];
color_t tsPal[256];
memoryZero(tsPal, sizeof(tsPal));
for(int y = 0; y < 16; y++)
for(int x = 0; x < 32; x++)
tsIdx[y * 32 + x] = (x < 16) ? 0 : 1;
tsPal[0] = color(210, 180, 100, 255); /* warm sand — overridden each frame */
tsPal[1] = color( 60, 130, 60, 255); /* cool grass — overridden each frame */
tilemapTileset = renderTextureCreate(32, 16, tsIdx, tsPal);
assertTrue(tilemapTileset != RTEXTURE_NONE, "Failed to create tilemap tileset");
/* Chunk: checkerboard — (col+row)%2 selects tile 0 or tile 1 */
uint8_t chunkIdx[TILEMAP_CHUNK_W * TILEMAP_CHUNK_H];
for(int row = 0; row < TILEMAP_CHUNK_H; row++)
for(int col = 0; col < TILEMAP_CHUNK_W; col++)
chunkIdx[row * TILEMAP_CHUNK_W + col] = (uint8_t)((col + row) % 2);
tilemapChunk = renderTilemapChunkCreate(
TILEMAP_CHUNK_W, TILEMAP_CHUNK_H,
TILEMAP_TILE_W, TILEMAP_TILE_H,
tilemapTileset,
chunkIdx
);
/* RTILEMAPCHUNK_INVALID is valid on platforms that don't implement chunk
* creation yet; renderTilemapChunk() and renderTilemapChunkDispose() both
* handle it as a no-op. */
errorOk(); errorOk();
} }
errorret_t sceneTestUpdate(scenedata_t *data) { errorret_t sceneTestUpdate(scenedata_t *data) {
(void)data; float t = fixedToFloat(TIME.time);
/* Palette demo: entry 8 (bottom-right pixel) cycles through hue */
renderTextureGetPalette(testTex)[8] = hueColor(t * 2.0f);
/* Index demo: center pixel (1,1) steps through entries 0-8 once per second */
renderTextureGetIndices(testTex)[1 * 3 + 1] = (uint8_t)((uint32_t)t % 9u);
/* Tilemap palette animation: complementary hues so tiles always contrast */
color_t *tilePal = renderTextureGetPalette(tilemapTileset);
tilePal[0] = hueColor(t * 0.7f);
tilePal[1] = hueColor(t * 0.7f + 3.14159f);
errorOk(); errorOk();
} }
errorret_t sceneTestRender(scenedata_t *data) { errorret_t sceneTestRender(scenedata_t *data) {
(void)data; renderClear(color(24, 24, 40, 255));
renderClear(color(32, 32, 48, 255));
renderSprite(100, 100, 32, 32, COLOR_WHITE); /* 2D tests — no camera needed */
renderTest2DQuad();
renderTest2DZOrder();
/* 3D tests — shared camera looking slightly down from above */
renderSetProjection(FOV_Y, ASPECT, FIXED(10), FIXED(10000));
renderSetView(0, 150, 400, 0, 0, 0);
renderTest3DQuad();
renderTest3DOverlap();
/* Tilemap test — full-width scrolling strip below the 2D tests */
renderTestTilemap();
errorOk(); errorOk();
} }
errorret_t sceneTestDispose(scenedata_t *data) { errorret_t sceneTestDispose(scenedata_t *data) {
(void)data; renderTilemapChunkDispose(tilemapChunk);
renderTextureDispose(tilemapTileset);
renderTextureDispose(testTex);
errorOk(); errorOk();
} }
+1 -6
View File
@@ -7,11 +7,6 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
displaydolphin.c displaydolphin.c
# debug.c
) )
# Subdirs add_subdirectory(render)
add_subdirectory(framebuffer)
add_subdirectory(mesh)
add_subdirectory(texture)
add_subdirectory(shader)
+18 -40
View File
@@ -6,8 +6,10 @@
*/ */
#include "display/display.h" #include "display/display.h"
#include "display/render/renderdolphin.h"
#include "util/memory.h" #include "util/memory.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "assert/assert.h"
errorret_t displayInitDolphin(void) { errorret_t displayInitDolphin(void) {
VIDEO_Init(); VIDEO_Init();
@@ -79,52 +81,28 @@ errorret_t displayInitDolphin(void) {
GX_SetColorUpdate(GX_TRUE); GX_SetColorUpdate(GX_TRUE);
// Describe mesh vertex format. // Describe mesh vertex format.
GX_ClearVtxDesc(); /* Vertex format is configured per-draw by the render backend */
GX_SetVtxDesc(GX_VA_POS, GX_INDEX16);
#if MESH_ENABLE_COLOR
GX_SetVtxDesc(GX_VA_CLR0, GX_INDEX16);
#endif
GX_SetVtxDesc(GX_VA_TEX0, GX_INDEX16);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
#if MESH_ENABLE_COLOR
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
#endif
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
errorOk();
}
errorret_t displayUpdateDolphin(void) {
ENGINE.running = SYS_MainLoop();
errorOk();
}
errorret_t displaySetStateDolphin(displaystate_t state) {
if(state.flags & DISPLAY_STATE_FLAG_CULL) {
GX_SetCullMode(GX_CULL_FRONT);
} else {
GX_SetCullMode(GX_CULL_NONE); GX_SetCullMode(GX_CULL_NONE);
}
if(state.flags & DISPLAY_STATE_FLAG_DEPTH_TEST) {
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
} else { GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE); GX_SetColorUpdate(GX_TRUE);
} GX_SetAlphaUpdate(GX_TRUE);
if(state.flags & DISPLAY_STATE_FLAG_BLEND) {
GX_SetBlendMode(
GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR
);
} else {
GX_SetBlendMode(
GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR
);
}
errorChain(renderDolphinInit());
errorOk(); errorOk();
} }
errorret_t displayFlushDolphin(ropbuffer_t *buf) {
assertNotNull(buf, "Dolphin flush: null ropbuffer");
ENGINE.running = SYS_MainLoop();
errorChain(renderDolphinFlush(buf));
errorOk();
}
void displayDisposeDolphin(void) {
renderDolphinDispose();
}
errorret_t displaySwapDolphin(void) { errorret_t displaySwapDolphin(void) {
GX_DrawDone(); GX_DrawDone();
+3 -14
View File
@@ -8,6 +8,7 @@
#pragma once #pragma once
#include "error/error.h" #include "error/error.h"
#include "display/displaystate.h" #include "display/displaystate.h"
#include "display/render/ropbuffer.h"
#define DISPLAY_DOLPHIN_FIFO_SIZE (256*1024) #define DISPLAY_DOLPHIN_FIFO_SIZE (256*1024)
@@ -22,18 +23,6 @@ typedef struct {
* Initializes the display system on Dolphin. * Initializes the display system on Dolphin.
*/ */
errorret_t displayInitDolphin(void); errorret_t displayInitDolphin(void);
errorret_t displayFlushDolphin(ropbuffer_t *buf);
/**
* Tells the display system to actually draw the frame on Dolphin.
*/
errorret_t displayUpdateDolphin(void);
/**
* Swaps the display buffers on Dolphin.
*/
errorret_t displaySwapDolphin(void); errorret_t displaySwapDolphin(void);
void displayDisposeDolphin(void);
/**
* Sets the display state on Dolphin.
*/
errorret_t displaySetStateDolphin(displaystate_t state);
+6 -6
View File
@@ -6,11 +6,11 @@
*/ */
#pragma once #pragma once
#include "displaydolphin.h" #include "display/displaydolphin.h"
#define displayPlatformInit displayInitDolphin
#define displayPlatformUpdate displayUpdateDolphin
#define displayPlatformSwap displaySwapDolphin
#define displayPlatformSetState displaySetStateDolphin
typedef displaydolphin_t displayplatform_t; typedef displaydolphin_t displayplatform_t;
#define displayPlatformInit displayInitDolphin
#define displayPlatformFlush displayFlushDolphin
#define displayPlatformSwap displaySwapDolphin
#define displayPlatformDispose displayDisposeDolphin
@@ -0,0 +1,9 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
renderdolphin.c
)
@@ -0,0 +1,373 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/renderdolphin.h"
#include "display/render/rop.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <gccore.h>
#include <math.h>
/* ---- Texture table ------------------------------------------------------- */
#define DOLPHIN_RTEXTURE_MAX 256
/* GX CI8 (8-bit indexed) textures.
* cpuIndices : w*h row-major bytes, user-writable source of truth.
* tiledData : CI8 8×4-texel tiles (32 bytes/tile), re-derived at bind.
* palette : 256 RGBA entries, user-writable source of truth.
* tlutData : 256 × uint16_t RGB5A3, re-derived at bind.
* All textures share GX_TLUT0 (loaded per bind). */
typedef struct {
GXTexObj obj;
GXTlutObj tlut;
void *tiledData; /* CI8 tiled, memalign(32) */
void *tlutData; /* 256 × uint16_t RGB5A3, memalign(32) */
uint8_t *cpuIndices;
color_t palette[256];
uint16_t w, h;
} dolphintexentry_t;
static dolphintexentry_t dolphinTexTable[DOLPHIN_RTEXTURE_MAX];
static uint16_t dolphinTexNext = 1; /* 0 = white fallback */
/* ---- Camera state -------------------------------------------------------- */
static Mtx44 dolphinProj;
static Mtx dolphinView;
static int dolphinIs3D = 0;
/* ---- Helpers ------------------------------------------------------------- */
/* Convert color_t RGBA → GX RGB5A3 16-bit big-endian. */
static uint16_t toRGB5A3(color_t c) {
if(c.a == 0xFF) {
/* Opaque: RGB555 with bit15=1 */
return (uint16_t)(0x8000u
| ((uint16_t)(c.r >> 3) << 10)
| ((uint16_t)(c.g >> 3) << 5)
| ((uint16_t)(c.b >> 3)));
}
/* Transparent: A3RGB4 with bit15=0 */
return (uint16_t)(((uint16_t)(c.a >> 5) << 12)
| ((uint16_t)(c.r >> 4) << 8)
| ((uint16_t)(c.g >> 4) << 4)
| ((uint16_t)(c.b >> 4)));
}
/* Convert linear row-major indices to GX CI8 tiled layout (8×4 tiles). */
static void toCI8Tiled(
uint8_t *dst, const uint8_t *src, uint16_t w, uint16_t h
) {
uint16_t tW = (uint16_t)((w + 7) / 8);
uint16_t tH = (uint16_t)((h + 3) / 4);
for(uint16_t ty = 0; ty < tH; ty++) {
for(uint16_t tx = 0; tx < tW; tx++) {
uint8_t *tile = dst + ((uint32_t)(ty * tW + tx)) * 32;
for(uint16_t row = 0; row < 4; row++) {
for(uint16_t col = 0; col < 8; col++) {
uint16_t px = (uint16_t)(tx * 8 + col);
uint16_t py = (uint16_t)(ty * 4 + row);
tile[row * 8 + col] = (px < w && py < h) ? src[py * w + px] : 0;
}
}
}
}
}
/* ---- Init ---------------------------------------------------------------- */
errorret_t renderDolphinInit(void) {
/* White 1×1 fallback: CI8 single tile (32 bytes), palette[0]=white */
dolphintexentry_t *e = &dolphinTexTable[0];
e->cpuIndices = (uint8_t *)memalign(32, 1);
assertNotNull(e->cpuIndices, "Dolphin: failed to allocate fallback cpu indices");
e->cpuIndices[0] = 0;
e->tiledData = (uint8_t *)memalign(32, 32);
assertNotNull(e->tiledData, "Dolphin: failed to allocate fallback tile data");
memset(e->tiledData, 0, 32);
DCFlushRange(e->tiledData, 32);
e->tlutData = (uint16_t *)memalign(32, 256 * sizeof(uint16_t));
assertNotNull(e->tlutData, "Dolphin: failed to allocate fallback TLUT");
memset(e->tlutData, 0, 256 * sizeof(uint16_t));
memset(e->palette, 0, sizeof(e->palette));
e->palette[0] = COLOR_WHITE;
((uint16_t *)e->tlutData)[0] = 0xFFFF; /* RGB5A3 white */
DCFlushRange(e->tlutData, 256 * sizeof(uint16_t));
e->w = 1; e->h = 1;
GX_InitTlutObj(&e->tlut, e->tlutData, GX_TL_RGB5A3, 256);
GX_InitTexObjCI(&e->obj, e->tiledData, 1, 1, GX_TF_CI8, GX_CLAMP, GX_CLAMP, GX_FALSE, GX_TLUT0);
GX_InitTexObjFilterMode(&e->obj, GX_NEAR, GX_NEAR);
guMtxIdentity(dolphinView);
guPerspective(dolphinProj, 60.0f, 4.0f/3.0f, 0.1f, 100.0f);
errorOk();
}
/* ---- Texture ------------------------------------------------------------- */
rtexture_t renderDolphinTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
) {
assertTrue(dolphinTexNext < DOLPHIN_RTEXTURE_MAX, "Dolphin texture table full");
rtexture_t handle = (rtexture_t)dolphinTexNext++;
dolphintexentry_t *e = &dolphinTexTable[handle];
uint32_t cpuBytes = (uint32_t)w * h;
e->cpuIndices = (uint8_t *)memalign(32, cpuBytes);
assertNotNull(e->cpuIndices, "Dolphin: failed to allocate cpu index buffer");
memcpy(e->cpuIndices, indices, cpuBytes);
uint16_t tW = (uint16_t)((w + 7) / 8);
uint16_t tH = (uint16_t)((h + 3) / 4);
uint32_t tileBytes = (uint32_t)tW * tH * 32;
e->tiledData = (uint8_t *)memalign(32, tileBytes);
assertNotNull(e->tiledData, "Dolphin: failed to allocate tile data");
toCI8Tiled((uint8_t *)e->tiledData, indices, w, h);
DCFlushRange(e->tiledData, tileBytes);
e->tlutData = (uint16_t *)memalign(32, 256 * sizeof(uint16_t));
assertNotNull(e->tlutData, "Dolphin: failed to allocate TLUT data");
memcpy(e->palette, palette, 256 * sizeof(color_t));
uint16_t *tlut = (uint16_t *)e->tlutData;
for(int i = 0; i < 256; i++) tlut[i] = toRGB5A3(palette[i]);
DCFlushRange(e->tlutData, 256 * sizeof(uint16_t));
e->w = w; e->h = h;
GX_InitTlutObj(&e->tlut, e->tlutData, GX_TL_RGB5A3, 256);
GX_InitTexObjCI(&e->obj, e->tiledData, w, h, GX_TF_CI8, GX_CLAMP, GX_CLAMP, GX_FALSE, GX_TLUT0);
GX_InitTexObjFilterMode(&e->obj, GX_NEAR, GX_NEAR);
return handle;
}
void renderDolphinTextureDispose(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= DOLPHIN_RTEXTURE_MAX) return;
dolphintexentry_t *e = &dolphinTexTable[tex];
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
if(e->tiledData) { free(e->tiledData); e->tiledData = NULL; }
if(e->tlutData) { free(e->tlutData); e->tlutData = NULL; }
}
color_t *renderDolphinTextureGetPalette(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= DOLPHIN_RTEXTURE_MAX) return NULL;
return dolphinTexTable[tex].palette;
}
uint8_t *renderDolphinTextureGetIndices(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= DOLPHIN_RTEXTURE_MAX) return NULL;
return dolphinTexTable[tex].cpuIndices;
}
static void bindTexture(rtexture_t tex) {
dolphintexentry_t *e =
(tex < DOLPHIN_RTEXTURE_MAX && dolphinTexTable[tex].cpuIndices)
? &dolphinTexTable[tex]
: &dolphinTexTable[0];
/* Re-tile cpuIndices → tiledData and flush for GX DMA. */
toCI8Tiled((uint8_t *)e->tiledData, e->cpuIndices, e->w, e->h);
uint16_t tW = (uint16_t)((e->w + 7) / 8);
uint16_t tH = (uint16_t)((e->h + 3) / 4);
DCFlushRange(e->tiledData, (uint32_t)tW * tH * 32);
/* Re-convert palette color_t RGBA → RGB5A3 and flush. */
uint16_t *tlut = (uint16_t *)e->tlutData;
for(int i = 0; i < 256; i++) tlut[i] = toRGB5A3(e->palette[i]);
DCFlushRange(e->tlutData, 256 * sizeof(uint16_t));
GX_LoadTlut(&e->tlut, GX_TLUT0);
GX_LoadTexObj(&e->obj, GX_TEXMAP0);
}
/* ---- 2D vertex format setup ---------------------------------------------- */
static void setup2D(void) {
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
Mtx44 ortho;
guOrtho(ortho, 0, DUSK_DISPLAY_HEIGHT, 0, DUSK_DISPLAY_WIDTH, -1.0f, 1.0f);
GX_LoadProjectionMtx(ortho, GX_ORTHOGRAPHIC);
Mtx identity;
guMtxIdentity(identity);
GX_LoadPosMtxImm(identity, GX_PNMTX0);
GX_SetCurrentMtx(GX_PNMTX0);
dolphinIs3D = 0;
}
/* ---- 3D vertex format setup ---------------------------------------------- */
static void setup3D(void) {
GX_ClearVtxDesc();
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
GX_LoadProjectionMtx(dolphinProj, GX_PERSPECTIVE);
GX_LoadPosMtxImm(dolphinView, GX_PNMTX0);
GX_SetCurrentMtx(GX_PNMTX0);
dolphinIs3D = 1;
}
/* ---- Tint channel -------------------------------------------------------- */
static void setTintChannel(color_t tint) {
GX_SetTevColor(GX_TEVREG0, (GXColor){tint.r, tint.g, tint.b, tint.a});
GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C0, GX_CC_ZERO);
GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_A0, GX_CA_ZERO);
GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
}
/* ---- 2D sprite ----------------------------------------------------------- */
static void draw2DSprite(const ropsprite_t *s) {
if(dolphinIs3D) setup2D();
bindTexture(s->texture);
setTintChannel(s->tint);
float u0 = (s->uvX / 255.0f);
float v0 = (s->uvY / 255.0f);
float u1 = ((s->uvX + s->uvW) / 255.0f);
float v1 = ((s->uvY + s->uvH) / 255.0f);
int16_t x0 = s->x, y0 = s->y;
int16_t x1 = (int16_t)(s->x + s->w), y1 = (int16_t)(s->y + s->h);
GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
GX_Position2s16(x0, y0); GX_Color4u8(s->tint.r,s->tint.g,s->tint.b,s->tint.a); GX_TexCoord2f32(u0,v0);
GX_Position2s16(x1, y0); GX_Color4u8(s->tint.r,s->tint.g,s->tint.b,s->tint.a); GX_TexCoord2f32(u1,v0);
GX_Position2s16(x1, y1); GX_Color4u8(s->tint.r,s->tint.g,s->tint.b,s->tint.a); GX_TexCoord2f32(u1,v1);
GX_Position2s16(x0, y1); GX_Color4u8(s->tint.r,s->tint.g,s->tint.b,s->tint.a); GX_TexCoord2f32(u0,v1);
GX_End();
}
/* ---- 3D quad ------------------------------------------------------------- */
static void draw3DQuad(const ropquad3d_t *q) {
if(!dolphinIs3D) setup3D();
bindTexture(q->texture);
setTintChannel(q->tint);
float u0 = q->uvX/255.0f, v0 = q->uvY/255.0f;
float u1 = (q->uvX+q->uvW)/255.0f, v1 = (q->uvY+q->uvH)/255.0f;
float cx = (float)q->cx, cy = (float)q->cy, cz = (float)q->cz;
float rx = (float)q->rx, ry = (float)q->ry, rz = (float)q->rz;
float ux = (float)q->ux, uy = (float)q->uy, uz = (float)q->uz;
float tlx = cx-rx+ux, tly = cy-ry+uy, tlz = cz-rz+uz;
float trx = cx+rx+ux, try_= cy+ry+uy, trz = cz+rz+uz;
float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz;
float brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
uint8_t r=q->tint.r, g=q->tint.g, b=q->tint.b, a=q->tint.a;
GX_Begin(GX_QUADS, GX_VTXFMT0, 4);
GX_Position3f32(tlx,tly,tlz); GX_Color4u8(r,g,b,a); GX_TexCoord2f32(u0,v0);
GX_Position3f32(trx,try_,trz); GX_Color4u8(r,g,b,a); GX_TexCoord2f32(u1,v0);
GX_Position3f32(brx,bry,brz); GX_Color4u8(r,g,b,a); GX_TexCoord2f32(u1,v1);
GX_Position3f32(blx,bly,blz); GX_Color4u8(r,g,b,a); GX_TexCoord2f32(u0,v1);
GX_End();
}
/* ---- Flush --------------------------------------------------------------- */
errorret_t renderDolphinFlush(ropbuffer_t *buf) {
assertNotNull(buf, "Dolphin flush: null ropbuffer");
setup2D();
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
GX_SetAlphaUpdate(GX_TRUE);
GX_SetColorUpdate(GX_TRUE);
uint32_t offset = 0;
while(offset < buf->byteCount) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
ropop_t op = (ropop_t)hdr->op;
switch(op) {
case ROP_CLEAR: {
const ropclear_t *c = (const ropclear_t *)hdr;
GXColor bg = {c->color.r, c->color.g, c->color.b, c->color.a};
GX_SetCopyClear(bg, GX_MAX_Z24);
break;
}
case ROP_DRAW_SPRITE:
draw2DSprite((const ropsprite_t *)hdr);
break;
case ROP_SET_PROJECTION: {
const ropprojection_t *p = (const ropprojection_t *)hdr;
if(p->fovY > FIXED_ZERO) {
float fovDeg = fixedToFloat(p->fovY) * (180.0f / 3.14159f);
float aspect = fixedToFloat(p->aspect);
guPerspective(dolphinProj,
fovDeg, aspect,
fixedToFloat(p->nearZ), fixedToFloat(p->farZ));
} else {
float aspect = fixedToFloat(p->aspect);
guOrtho(dolphinProj,
-1.0f, 1.0f, -aspect, aspect,
fixedToFloat(p->nearZ), fixedToFloat(p->farZ));
}
break;
}
case ROP_SET_VIEW: {
const ropview_t *v = (const ropview_t *)hdr;
guVector eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ};
guVector target = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ};
guVector up = {0.0f, 1.0f, 0.0f};
guLookAt(dolphinView, &eye, &up, &target);
if(dolphinIs3D) {
GX_LoadPosMtxImm(dolphinView, GX_PNMTX0);
}
break;
}
case ROP_DRAW_QUAD_3D:
draw3DQuad((const ropquad3d_t *)hdr);
break;
default:
break;
}
offset += ropOpSize(op);
}
errorOk();
}
/* ---- Dispose ------------------------------------------------------------- */
void renderDolphinDispose(void) {
for(uint16_t i = 0; i < dolphinTexNext; i++) {
dolphintexentry_t *e = &dolphinTexTable[i];
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
if(e->tiledData) { free(e->tiledData); e->tiledData = NULL; }
if(e->tlutData) { free(e->tlutData); e->tlutData = NULL; }
}
dolphinTexNext = 1;
}
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "display/render/ropbuffer.h"
#include "display/render/rtexture.h"
#include "display/color.h"
errorret_t renderDolphinInit(void);
errorret_t renderDolphinFlush(ropbuffer_t *buf);
void renderDolphinDispose(void);
rtexture_t renderDolphinTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
);
void renderDolphinTextureDispose(rtexture_t tex);
color_t *renderDolphinTextureGetPalette(rtexture_t tex);
uint8_t *renderDolphinTextureGetIndices(rtexture_t tex);
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/render/renderdolphin.h"
#define renderPlatformTextureCreate renderDolphinTextureCreate
#define renderPlatformTextureDispose renderDolphinTextureDispose
#define renderPlatformTextureGetPalette renderDolphinTextureGetPalette
#define renderPlatformTextureGetIndices renderDolphinTextureGetIndices
+1 -1
View File
@@ -9,4 +9,4 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
) )
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(render) add_subdirectory(display)
+6
View File
@@ -0,0 +1,6 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(render)
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
rendergl.c
rendertexturegl.c
rendertilemapcgl.c
)
+313
View File
@@ -0,0 +1,313 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/rendergl.h"
#include "display/render/rendertexturegl.h"
#include "display/render/rendertilemapcgl.h"
#include "error/errorgl.h"
#include "display/render/rop.h"
#include <cglm/cglm.h>
/* ---- 2D shader: xy rgba uv, depth from uniform -------------------------- */
static const char *VERT2D =
"#version 330 core\n"
"layout(location=0) in vec2 aPos;\n"
"layout(location=1) in vec4 aColor;\n"
"layout(location=2) in vec2 aUV;\n"
"uniform vec2 uRes;\n"
"uniform vec2 uOffset;\n"
"uniform float uDepth;\n"
"out vec4 vColor; out vec2 vUV;\n"
"void main() {\n"
" vec2 clip = ((aPos + uOffset) / uRes) * 2.0 - 1.0;\n"
" clip.y = -clip.y;\n"
" gl_Position = vec4(clip, uDepth, 1.0);\n"
" vColor = aColor; vUV = aUV;\n"
"}\n";
/* ---- 3D shader: xyz rgba uv, full MVP ----------------------------------- */
static const char *VERT3D =
"#version 330 core\n"
"layout(location=0) in vec3 aPos;\n"
"layout(location=1) in vec4 aColor;\n"
"layout(location=2) in vec2 aUV;\n"
"uniform mat4 uMVP;\n"
"out vec4 vColor; out vec2 vUV;\n"
"void main() {\n"
" gl_Position = uMVP * vec4(aPos, 1.0);\n"
" vColor = aColor; vUV = aUV;\n"
"}\n";
/* Palette shader: uTexIndices is a GL_R8 W×H texture (one byte per pixel);
* uTexPalette is a 256×1 RGBA texture (the colour table).
* The index value (0-255) stored as a normalised float in R8 is converted back
* to an exact texel centre using (raw*(255/256) + 0.5/256) before sampling,
* giving pixel-exact palette lookups for all 256 possible indices. */
static const char *FRAG =
"#version 330 core\n"
"in vec4 vColor; in vec2 vUV;\n"
"uniform sampler2D uTexIndices;\n"
"uniform sampler2D uTexPalette;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" float raw = texture(uTexIndices, vUV).r;\n"
" float u = raw * (255.0/256.0) + (0.5/256.0);\n"
" fragColor = texture(uTexPalette, vec2(u, 0.5)) * vColor;\n"
"}\n";
/* ---- State --------------------------------------------------------------- */
typedef struct {
GLuint prog, vao, vbo;
GLint uRes, uOffset, uDepth, uTexIndices, uTexPalette;
} rgl2d_t;
typedef struct {
GLuint prog, vao, vbo;
GLint uMVP, uTexIndices, uTexPalette;
mat4 proj, view;
} rgl3d_t;
static rgl2d_t gl2d;
static rgl3d_t gl3d;
/* ---- Helpers ------------------------------------------------------------- */
static GLuint buildProgram(const char *vsrc, const char *fsrc) {
GLuint v = glCreateShader(GL_VERTEX_SHADER);
GLuint f = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(v, 1, &vsrc, NULL); glCompileShader(v);
glShaderSource(f, 1, &fsrc, NULL); glCompileShader(f);
GLuint p = glCreateProgram();
glAttachShader(p, v); glAttachShader(p, f);
glLinkProgram(p);
glDeleteShader(v); glDeleteShader(f);
return p;
}
/* ---- Init ---------------------------------------------------------------- */
errorret_t renderGLInit(void) {
/* 2D — 8 floats/vert: x y r g b a u v */
gl2d.prog = buildProgram(VERT2D, FRAG);
gl2d.uRes = glGetUniformLocation(gl2d.prog, "uRes");
gl2d.uOffset = glGetUniformLocation(gl2d.prog, "uOffset");
gl2d.uDepth = glGetUniformLocation(gl2d.prog, "uDepth");
gl2d.uTexIndices = glGetUniformLocation(gl2d.prog, "uTexIndices");
gl2d.uTexPalette = glGetUniformLocation(gl2d.prog, "uTexPalette");
glGenVertexArrays(1, &gl2d.vao); glGenBuffers(1, &gl2d.vbo);
glBindVertexArray(gl2d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl2d.vbo);
glBufferData(GL_ARRAY_BUFFER, 6*8*sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(6*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
errorChain(errorGLCheck());
/* 3D — 9 floats/vert: x y z r g b a u v */
gl3d.prog = buildProgram(VERT3D, FRAG);
gl3d.uMVP = glGetUniformLocation(gl3d.prog, "uMVP");
gl3d.uTexIndices = glGetUniformLocation(gl3d.prog, "uTexIndices");
gl3d.uTexPalette = glGetUniformLocation(gl3d.prog, "uTexPalette");
glm_mat4_identity(gl3d.proj);
glm_mat4_identity(gl3d.view);
glGenVertexArrays(1, &gl3d.vao); glGenBuffers(1, &gl3d.vbo);
glBindVertexArray(gl3d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl3d.vbo);
glBufferData(GL_ARRAY_BUFFER, 6*9*sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), (void*)(7*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
errorChain(errorGLCheck());
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
renderGLTextureInit();
errorChain(errorGLCheck());
errorOk();
}
/* ---- 2D draw ------------------------------------------------------------- */
static void draw2DSprite(const ropsprite_t *s, float rW, float rH) {
float r = s->tint.r/255.0f, g = s->tint.g/255.0f;
float b = s->tint.b/255.0f, a = s->tint.a/255.0f;
float x0 = (float)s->x, y0 = (float)s->y;
float x1 = x0+(float)s->w, y1 = y0+(float)s->h;
float u0 = s->uvX/255.0f, v0 = s->uvY/255.0f;
float u1 = (s->uvX+s->uvW)/255.0f, v1 = (s->uvY+s->uvH)/255.0f;
/* int16 depth: 0=front, 32767=back mapped to [0,1] NDC Z */
float depth = (float)s->header.depth / 32767.0f;
GLfloat verts[6][8] = {
{x0,y1, r,g,b,a, u0,v1},
{x0,y0, r,g,b,a, u0,v0},
{x1,y0, r,g,b,a, u1,v0},
{x0,y1, r,g,b,a, u0,v1},
{x1,y0, r,g,b,a, u1,v0},
{x1,y1, r,g,b,a, u1,v1},
};
glUseProgram(gl2d.prog);
glUniform2f(gl2d.uRes, rW, rH);
glUniform2f(gl2d.uOffset, 0.0f, 0.0f);
glUniform1f(gl2d.uDepth, depth);
glUniform1i(gl2d.uTexIndices, 0);
glUniform1i(gl2d.uTexPalette, 1);
renderGLTextureBind(s->texture);
glBindVertexArray(gl2d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl2d.vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
static void draw2DTilemapChunk(const roptilemapc_t *t, float rW, float rH) {
const glchunkentry_t *e = renderGLTilemapChunkGet(t->chunk);
if(!e) return;
float depth = (float)t->header.depth / 32767.0f;
glUseProgram(gl2d.prog);
glUniform2f(gl2d.uRes, rW, rH);
glUniform2f(gl2d.uOffset, (float)t->x, (float)t->y);
glUniform1f(gl2d.uDepth, depth);
glUniform1i(gl2d.uTexIndices, 0);
glUniform1i(gl2d.uTexPalette, 1);
renderGLTextureBind(e->tileset);
glBindVertexArray(e->vao);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)e->vertCount);
}
/* ---- 3D draw ------------------------------------------------------------- */
static void draw3DQuad(const ropquad3d_t *q) {
float r = q->tint.r/255.0f, g = q->tint.g/255.0f;
float b = q->tint.b/255.0f, a = q->tint.a/255.0f;
float u0 = q->uvX/255.0f, v0 = q->uvY/255.0f;
float u1 = (q->uvX+q->uvW)/255.0f, v1 = (q->uvY+q->uvH)/255.0f;
float cx = (float)q->cx, cy = (float)q->cy, cz = (float)q->cz;
float rx = (float)q->rx, ry = (float)q->ry, rz = (float)q->rz;
float ux = (float)q->ux, uy = (float)q->uy, uz = (float)q->uz;
float tlx = cx-rx+ux, tly = cy-ry+uy, tlz = cz-rz+uz;
float trx = cx+rx+ux, tr_y= cy+ry+uy, trz = cz+rz+uz;
float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz;
float brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
GLfloat verts[6][9] = {
{tlx,tly,tlz, r,g,b,a, u0,v0},
{blx,bly,blz, r,g,b,a, u0,v1},
{brx,bry,brz, r,g,b,a, u1,v1},
{tlx,tly,tlz, r,g,b,a, u0,v0},
{brx,bry,brz, r,g,b,a, u1,v1},
{trx,tr_y,trz, r,g,b,a, u1,v0},
};
mat4 mvp;
glm_mat4_mul(gl3d.proj, gl3d.view, mvp);
glUseProgram(gl3d.prog);
glUniformMatrix4fv(gl3d.uMVP, 1, GL_FALSE, (GLfloat *)mvp);
glUniform1i(gl3d.uTexIndices, 0);
glUniform1i(gl3d.uTexPalette, 1);
renderGLTextureBind(q->texture);
glBindVertexArray(gl3d.vao);
glBindBuffer(GL_ARRAY_BUFFER, gl3d.vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
/* ---- Flush --------------------------------------------------------------- */
errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH) {
glViewport(0, 0, winW, winH);
errorChain(errorGLCheck());
uint32_t offset = 0;
while(offset < buf->byteCount) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
ropop_t op = (ropop_t)hdr->op;
switch(op) {
case ROP_CLEAR: {
const ropclear_t *c = (const ropclear_t *)hdr;
glClearColor(
c->color.r/255.0f, c->color.g/255.0f,
c->color.b/255.0f, c->color.a/255.0f
);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
errorChain(errorGLCheck());
break;
}
case ROP_DRAW_SPRITE: {
draw2DSprite((const ropsprite_t *)hdr, (float)winW, (float)winH);
errorChain(errorGLCheck());
break;
}
case ROP_SET_PROJECTION: {
const ropprojection_t *p = (const ropprojection_t *)hdr;
if(p->fovY > FIXED_ZERO)
glm_perspective(
fixedToFloat(p->fovY), fixedToFloat(p->aspect),
fixedToFloat(p->nearZ), fixedToFloat(p->farZ),
gl3d.proj
);
else
glm_mat4_identity(gl3d.proj);
break;
}
case ROP_SET_VIEW: {
const ropview_t *v = (const ropview_t *)hdr;
vec3 eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ};
vec3 tgt = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ};
vec3 up = {0.0f, 1.0f, 0.0f};
glm_lookat(eye, tgt, up, gl3d.view);
break;
}
case ROP_DRAW_QUAD_3D: {
draw3DQuad((const ropquad3d_t *)hdr);
errorChain(errorGLCheck());
break;
}
case ROP_DRAW_TILEMAP_CHUNK: {
draw2DTilemapChunk((const roptilemapc_t *)hdr, (float)winW, (float)winH);
errorChain(errorGLCheck());
break;
}
default:
break;
}
offset += ropOpSize(op);
}
glUseProgram(0);
glBindVertexArray(0);
errorOk();
}
/* ---- Dispose ------------------------------------------------------------- */
void renderGLDispose(void) {
renderGLTilemapChunkTableDispose();
renderGLTextureTableDispose();
if(gl2d.vbo) { glDeleteBuffers(1, &gl2d.vbo); gl2d.vbo = 0; }
if(gl2d.vao) { glDeleteVertexArrays(1, &gl2d.vao); gl2d.vao = 0; }
if(gl2d.prog) { glDeleteProgram(gl2d.prog); gl2d.prog = 0; }
if(gl3d.vbo) { glDeleteBuffers(1, &gl3d.vbo); gl3d.vbo = 0; }
if(gl3d.vao) { glDeleteVertexArrays(1, &gl3d.vao); gl3d.vao = 0; }
if(gl3d.prog) { glDeleteProgram(gl3d.prog); gl3d.prog = 0; }
}
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include "duskgl.h" #include "duskgl.h"
#include "error/error.h" #include "error/error.h"
#include "render/ropbuffer.h" #include "display/render/ropbuffer.h"
errorret_t renderGLInit(void); errorret_t renderGLInit(void);
errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH); errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH);
+150
View File
@@ -0,0 +1,150 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/rendertexturegl.h"
#include "display/color.h"
#include "assert/assert.h"
#include <stdlib.h>
/* Each slot stores a CPU-side copy of the palette and index data.
* Dirty flags are set by the getters; renderGLTextureBind re-uploads
* to the GPU textures lazily before binding. */
typedef struct {
GLuint idxTex;
GLuint palTex;
uint8_t *cpuIndices; /* heap-allocated w*h */
color_t palette[256];
uint16_t w, h;
uint8_t idxDirty;
uint8_t palDirty;
} gltexentry_t;
static gltexentry_t glTexTable[RTEXTURE_MAX];
static uint16_t glTexNext = 1;
static void initIndexTex(GLuint id, uint16_t w, uint16_t h, const uint8_t *data) {
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
static void initPaletteTex(GLuint id, const color_t *palette) {
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, palette);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void renderGLTextureInit(void) {
static const uint8_t whiteIdx = 0;
static color_t whitePal[256];
whitePal[0] = COLOR_WHITE;
gltexentry_t *e = &glTexTable[0];
e->cpuIndices = (uint8_t *)malloc(1);
assertNotNull(e->cpuIndices, "GL: failed to allocate fallback index buffer");
e->cpuIndices[0] = 0;
e->palette[0] = COLOR_WHITE;
e->w = e->h = 1;
e->idxDirty = e->palDirty = 0;
glGenTextures(1, &e->idxTex);
glGenTextures(1, &e->palTex);
initIndexTex(e->idxTex, 1, 1, &whiteIdx);
initPaletteTex(e->palTex, whitePal);
}
void renderGLTextureTableDispose(void) {
for(uint16_t i = 0; i < glTexNext; i++) {
gltexentry_t *e = &glTexTable[i];
if(e->idxTex) { glDeleteTextures(1, &e->idxTex); e->idxTex = 0; }
if(e->palTex) { glDeleteTextures(1, &e->palTex); e->palTex = 0; }
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
}
glTexNext = 1;
}
rtexture_t renderGLTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
) {
assertTrue(glTexNext < RTEXTURE_MAX, "Texture table full");
rtexture_t handle = (rtexture_t)glTexNext++;
gltexentry_t *e = &glTexTable[handle];
uint32_t pixels = (uint32_t)w * h;
e->cpuIndices = (uint8_t *)malloc(pixels);
assertNotNull(e->cpuIndices, "GL: failed to allocate index buffer");
memcpy(e->cpuIndices, indices, pixels);
memcpy(e->palette, palette, 256 * sizeof(color_t));
e->w = w; e->h = h;
e->idxDirty = e->palDirty = 0;
glGenTextures(1, &e->idxTex);
glGenTextures(1, &e->palTex);
initIndexTex(e->idxTex, w, h, indices);
initPaletteTex(e->palTex, palette);
return handle;
}
void renderGLTextureDispose(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX) return;
gltexentry_t *e = &glTexTable[tex];
if(e->idxTex) { glDeleteTextures(1, &e->idxTex); e->idxTex = 0; }
if(e->palTex) { glDeleteTextures(1, &e->palTex); e->palTex = 0; }
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
}
color_t *renderGLTextureGetPalette(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX) return NULL;
glTexTable[tex].palDirty = 1;
return glTexTable[tex].palette;
}
uint8_t *renderGLTextureGetIndices(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX) return NULL;
glTexTable[tex].idxDirty = 1;
return glTexTable[tex].cpuIndices;
}
void renderGLTextureGetSize(rtexture_t tex, uint16_t *w, uint16_t *h) {
if(tex == RTEXTURE_NONE || tex >= RTEXTURE_MAX || !glTexTable[tex].idxTex) {
*w = 1; *h = 1;
return;
}
*w = glTexTable[tex].w;
*h = glTexTable[tex].h;
}
/* Flush any dirty CPU data to GPU, then bind both texture units. */
void renderGLTextureBind(rtexture_t tex) {
gltexentry_t *e = (tex < RTEXTURE_MAX && glTexTable[tex].idxTex)
? &glTexTable[tex]
: &glTexTable[0];
if(e->palDirty) {
glBindTexture(GL_TEXTURE_2D, e->palTex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_RGBA, GL_UNSIGNED_BYTE, e->palette);
e->palDirty = 0;
}
if(e->idxDirty) {
glBindTexture(GL_TEXTURE_2D, e->idxTex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, e->w, e->h, GL_RED, GL_UNSIGNED_BYTE, e->cpuIndices);
e->idxDirty = 0;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, e->idxTex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, e->palTex);
}
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskgl.h"
#include "display/render/rtexture.h"
#include "display/color.h"
#define RTEXTURE_MAX 256
void renderGLTextureInit(void);
void renderGLTextureTableDispose(void);
rtexture_t renderGLTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
);
void renderGLTextureDispose(rtexture_t tex);
void renderGLTextureBind(rtexture_t tex);
color_t *renderGLTextureGetPalette(rtexture_t tex);
uint8_t *renderGLTextureGetIndices(rtexture_t tex);
void renderGLTextureGetSize(rtexture_t tex, uint16_t *w, uint16_t *h);
@@ -0,0 +1,120 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/rendertilemapcgl.h"
#include "display/render/rendertexturegl.h"
#include "assert/assert.h"
#include <stdlib.h>
static glchunkentry_t glChunkTable[RTILEMAPCHUNK_MAX];
static uint16_t glChunkNext = 1; /* 0 reserved for RTILEMAPCHUNK_INVALID */
void renderGLTilemapChunkTableDispose(void) {
for(uint16_t i = 1; i < glChunkNext; i++) {
glchunkentry_t *e = &glChunkTable[i];
if(e->vbo) { glDeleteBuffers(1, &e->vbo); e->vbo = 0; }
if(e->vao) { glDeleteVertexArrays(1, &e->vao); e->vao = 0; }
}
glChunkNext = 1;
}
rtilemapchunk_t renderGLTilemapChunkCreate(
uint16_t chunkW, uint16_t chunkH,
uint16_t tileW, uint16_t tileH,
rtexture_t tileset,
const uint8_t *tileIndices
) {
assertTrue(glChunkNext < RTILEMAPCHUNK_MAX, "Tilemap chunk table full");
assertTrue(tileW > 0 && tileH > 0, "Tile dimensions must be > 0");
uint16_t texW, texH;
renderGLTextureGetSize(tileset, &texW, &texH);
assertTrue(texW >= tileW && texH >= tileH, "Tileset smaller than one tile");
uint16_t tilesPerRow = texW / tileW;
float fTexW = (float)texW;
float fTexH = (float)texH;
float fTileW = (float)tileW;
float fTileH = (float)tileH;
rtilemapchunk_t handle = (rtilemapchunk_t)glChunkNext++;
glchunkentry_t *e = &glChunkTable[handle];
e->tileset = tileset;
uint32_t tileCount = (uint32_t)chunkW * chunkH;
e->vertCount = tileCount * 6;
/* Temporary CPU buffer: 6 verts × 8 floats (x y r g b a u v) per tile */
GLfloat *verts = (GLfloat *)malloc(e->vertCount * 8 * sizeof(GLfloat));
assertNotNull(verts, "GL: failed to allocate tilemap chunk vertex buffer");
uint32_t v = 0;
for(uint32_t ci = 0; ci < tileCount; ci++) {
uint8_t idx = tileIndices[ci];
uint16_t tileCol = idx % tilesPerRow;
uint16_t tileRow = idx / tilesPerRow;
float px0 = (float)((ci % chunkW) * tileW);
float py0 = (float)((ci / chunkW) * tileH);
float px1 = px0 + fTileW;
float py1 = py0 + fTileH;
float u0 = (float)(tileCol * tileW) / fTexW;
float v0 = (float)(tileRow * tileH) / fTexH;
float u1 = u0 + fTileW / fTexW;
float v1 = v0 + fTileH / fTexH;
#define V(px,py,uu,vv) \
verts[v*8+0]=(px); verts[v*8+1]=(py); \
verts[v*8+2]=1.0f; verts[v*8+3]=1.0f; \
verts[v*8+4]=1.0f; verts[v*8+5]=1.0f; \
verts[v*8+6]=(uu); verts[v*8+7]=(vv); v++
V(px0, py1, u0, v1);
V(px0, py0, u0, v0);
V(px1, py0, u1, v0);
V(px0, py1, u0, v1);
V(px1, py0, u1, v0);
V(px1, py1, u1, v1);
#undef V
}
glGenVertexArrays(1, &e->vao);
glGenBuffers(1, &e->vbo);
glBindVertexArray(e->vao);
glBindBuffer(GL_ARRAY_BUFFER, e->vbo);
glBufferData(
GL_ARRAY_BUFFER,
(GLsizeiptr)(e->vertCount * 8 * sizeof(GLfloat)),
verts,
GL_STATIC_DRAW
);
/* layout: aPos(2) aColor(4) aUV(2) — matches the 2D shader */
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (void*)(6*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
free(verts);
return handle;
}
void renderGLTilemapChunkDispose(rtilemapchunk_t chunk) {
if(chunk == RTILEMAPCHUNK_INVALID || chunk >= RTILEMAPCHUNK_MAX) return;
glchunkentry_t *e = &glChunkTable[chunk];
if(e->vbo) { glDeleteBuffers(1, &e->vbo); e->vbo = 0; }
if(e->vao) { glDeleteVertexArrays(1, &e->vao); e->vao = 0; }
}
const glchunkentry_t *renderGLTilemapChunkGet(rtilemapchunk_t chunk) {
if(chunk == RTILEMAPCHUNK_INVALID || chunk >= RTILEMAPCHUNK_MAX) return NULL;
if(!glChunkTable[chunk].vao) return NULL;
return &glChunkTable[chunk];
}
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "duskgl.h"
#include "display/render/rtilemapchunk.h"
#include "display/render/rtexture.h"
#define RTILEMAPCHUNK_MAX 256
/* Opaque GL state for one pre-built chunk. */
typedef struct {
GLuint vao, vbo;
uint32_t vertCount;
rtexture_t tileset;
} glchunkentry_t;
void renderGLTilemapChunkTableDispose(void);
rtilemapchunk_t renderGLTilemapChunkCreate(
uint16_t chunkW, uint16_t chunkH,
uint16_t tileW, uint16_t tileH,
rtexture_t tileset,
const uint8_t *tileIndices
);
void renderGLTilemapChunkDispose(rtilemapchunk_t chunk);
const glchunkentry_t *renderGLTilemapChunkGet(rtilemapchunk_t chunk);
-153
View File
@@ -1,153 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "render/rendergl.h"
#include "error/errorgl.h"
#include "render/rop.h"
static const char *VERT_SRC =
"#version 330 core\n"
"layout(location=0) in vec2 aPos;\n"
"layout(location=1) in vec4 aColor;\n"
"uniform vec2 uRes;\n"
"out vec4 vColor;\n"
"void main() {\n"
" vec2 clip = (aPos / uRes) * 2.0 - 1.0;\n"
" clip.y = -clip.y;\n"
" gl_Position = vec4(clip, 0.0, 1.0);\n"
" vColor = aColor;\n"
"}\n";
static const char *FRAG_SRC =
"#version 330 core\n"
"in vec4 vColor;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vColor;\n"
"}\n";
typedef struct {
GLuint prog;
GLuint vao;
GLuint vbo;
GLint uRes;
} rendergl_t;
static rendergl_t renderGL;
static GLuint compileShader(GLenum type, const char *src) {
GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, NULL);
glCompileShader(s);
return s;
}
errorret_t renderGLInit(void) {
GLuint vert = compileShader(GL_VERTEX_SHADER, VERT_SRC);
GLuint frag = compileShader(GL_FRAGMENT_SHADER, FRAG_SRC);
renderGL.prog = glCreateProgram();
glAttachShader(renderGL.prog, vert);
glAttachShader(renderGL.prog, frag);
glLinkProgram(renderGL.prog);
glDeleteShader(vert);
glDeleteShader(frag);
errorChain(errorGLCheck());
renderGL.uRes = glGetUniformLocation(renderGL.prog, "uRes");
glGenVertexArrays(1, &renderGL.vao);
glGenBuffers(1, &renderGL.vbo);
glBindVertexArray(renderGL.vao);
glBindBuffer(GL_ARRAY_BUFFER, renderGL.vbo);
/* 6 verts * (2 pos + 4 color) floats — enough for one sprite */
glBufferData(GL_ARRAY_BUFFER, 6 * 6 * sizeof(GLfloat), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
errorChain(errorGLCheck());
errorOk();
}
static void drawSprite(const ropsprite_t *s, float rW, float rH) {
float r = s->tint.r / 255.0f;
float g = s->tint.g / 255.0f;
float b = s->tint.b / 255.0f;
float a = s->tint.a / 255.0f;
float x0 = (float)s->x;
float y0 = (float)s->y;
float x1 = x0 + (float)s->w;
float y1 = y0 + (float)s->h;
GLfloat verts[6][6] = {
{ x0, y1, r,g,b,a },
{ x0, y0, r,g,b,a },
{ x1, y0, r,g,b,a },
{ x0, y1, r,g,b,a },
{ x1, y0, r,g,b,a },
{ x1, y1, r,g,b,a },
};
glBindVertexArray(renderGL.vao);
glBindBuffer(GL_ARRAY_BUFFER, renderGL.vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_TRIANGLES, 0, 6);
(void)rW; (void)rH;
}
errorret_t renderGLFlush(ropbuffer_t *buf, int winW, int winH) {
glViewport(0, 0, winW, winH);
errorChain(errorGLCheck());
glUseProgram(renderGL.prog);
glUniform2f(renderGL.uRes, (GLfloat)winW, (GLfloat)winH);
errorChain(errorGLCheck());
for(uint32_t i = 0; i < buf->count; i++) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + i * ROP_SIZE);
switch(hdr->op) {
case ROP_CLEAR: {
const ropclear_t *c = (const ropclear_t *)hdr;
glClearColor(
c->color.r / 255.0f,
c->color.g / 255.0f,
c->color.b / 255.0f,
c->color.a / 255.0f
);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
errorChain(errorGLCheck());
break;
}
case ROP_DRAW_SPRITE: {
const ropsprite_t *s = (const ropsprite_t *)hdr;
drawSprite(s, (float)winW, (float)winH);
errorChain(errorGLCheck());
break;
}
default:
break;
}
}
glUseProgram(0);
glBindVertexArray(0);
errorOk();
}
void renderGLDispose(void) {
if(renderGL.vbo) { glDeleteBuffers(1, &renderGL.vbo); renderGL.vbo = 0; }
if(renderGL.vao) { glDeleteVertexArrays(1, &renderGL.vao); renderGL.vao = 0; }
if(renderGL.prog) { glDeleteProgram(renderGL.prog); renderGL.prog = 0; }
}
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/render/rendertexturegl.h"
#include "display/render/rendertilemapcgl.h"
#define renderPlatformTextureCreate renderGLTextureCreate
#define renderPlatformTextureDispose renderGLTextureDispose
#define renderPlatformTextureGetPalette renderGLTextureGetPalette
#define renderPlatformTextureGetIndices renderGLTextureGetIndices
#define renderPlatformTilemapChunkCreate renderGLTilemapChunkCreate
#define renderPlatformTilemapChunkDispose renderGLTilemapChunkDispose
+1
View File
@@ -16,6 +16,7 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(display)
add_subdirectory(input) add_subdirectory(input)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(network) add_subdirectory(network)
+11
View File
@@ -0,0 +1,11 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
displaypsp.c
)
add_subdirectory(render)
+6 -7
View File
@@ -6,12 +6,11 @@
*/ */
#pragma once #pragma once
#include "display/displaysdl2.h" #include "display/displaypsp.h"
typedef displaysdl2_t displayplatform_t; typedef displaypsp_t displayplatform_t;
#define displayPlatformInit displaySDL2Init #define displayPlatformInit displayPSPInit
#define displayPlatformUpdate displaySDL2Update #define displayPlatformFlush displayPSPFlush
#define displayPlatformSwap displaySDL2Swap #define displayPlatformSwap displayPSPSwap
#define displayPlatformDispose displaySDL2Dispose #define displayPlatformDispose displayPSPDispose
#define displayPlatformSetState displaySDL2SetState
+75
View File
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/displaypsp.h"
#include "display/render/renderpsp.h"
#include "display/display.h"
#include "assert/assert.h"
#include <pspgu.h>
#include <pspgum.h>
#include <pspdisplay.h>
#include <pspge.h>
#define FRAME_SIZE (PSP_SCREEN_W * PSP_SCREEN_H * 4)
#define VRAM_ADDR(offset) ((void *)((uintptr_t)sceGeEdramGetAddr() + (offset)))
static uint32_t __attribute__((aligned(64))) displayList[0x10000];
errorret_t displayPSPInit(void) {
DISPLAY.whichBuffer = 0;
sceGuInit();
sceGuStart(GU_DIRECT, displayList);
/* Draw buffer: frame 0 at offset 0, frame 1 at FRAME_SIZE */
sceGuDrawBuffer(GU_PSM_8888, VRAM_ADDR(0), PSP_SCREEN_W);
sceGuDispBuffer(PSP_SCREEN_W, PSP_SCREEN_H, VRAM_ADDR(FRAME_SIZE), PSP_SCREEN_W);
sceGuDepthBuffer(VRAM_ADDR(FRAME_SIZE * 2), PSP_SCREEN_W);
sceGuOffset(2048 - PSP_SCREEN_W / 2, 2048 - PSP_SCREEN_H / 2);
sceGuViewport(2048, 2048, PSP_SCREEN_W, PSP_SCREEN_H);
sceGuDepthRange(65535, 0); /* PSP uses reversed depth */
sceGuScissor(0, 0, PSP_SCREEN_W, PSP_SCREEN_H);
sceGuEnable(GU_SCISSOR_TEST);
sceGuEnable(GU_ALPHA_TEST);
sceGuAlphaFunc(GU_GREATER, 0, 0xFF);
sceGuEnable(GU_DEPTH_TEST);
sceGuDepthFunc(GU_GEQUAL);
sceGuFrontFace(GU_CW);
sceGuEnable(GU_TEXTURE_2D);
sceGuEnable(GU_CLIP_PLANES);
sceGuFinish();
sceGuSync(0, 0);
sceDisplaySetMode(0, PSP_SCREEN_W, PSP_SCREEN_H);
sceGuDisplay(GU_TRUE);
errorChain(renderPSPInit());
errorOk();
}
errorret_t displayPSPFlush(ropbuffer_t *buf) {
assertNotNull(buf, "PSP flush: null ropbuffer");
errorChain(renderPSPFlush(buf));
errorOk();
}
errorret_t displayPSPSwap(void) {
sceGuSwapBuffers();
errorOk();
}
void displayPSPDispose(void) {
renderPSPDispose();
sceGuDisplay(GU_FALSE);
sceGuTerm();
}
+23
View File
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "display/displaystate.h"
#include "display/render/ropbuffer.h"
#define PSP_SCREEN_W 480
#define PSP_SCREEN_H 272
typedef struct {
int_t whichBuffer;
} displaypsp_t;
errorret_t displayPSPInit(void);
errorret_t displayPSPFlush(ropbuffer_t *buf);
errorret_t displayPSPSwap(void);
void displayPSPDispose(void);
@@ -5,5 +5,5 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
rendergl.c renderpsp.c
) )
@@ -0,0 +1,14 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/render/renderpsp.h"
#define renderPlatformTextureCreate renderPSPTextureCreate
#define renderPlatformTextureDispose renderPSPTextureDispose
#define renderPlatformTextureGetPalette renderPSPTextureGetPalette
#define renderPlatformTextureGetIndices renderPSPTextureGetIndices
+318
View File
@@ -0,0 +1,318 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/render/renderpsp.h"
#include "display/render/rop.h"
#include "display/color.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <malloc.h>
#include <psputils.h> /* sceKernelDcacheWritebackRange */
#include <pspgu.h>
#include <pspgum.h>
#include <pspdisplay.h>
/* ---- Display list -------------------------------------------------------- */
#define DISPLAY_LIST_SIZE (256 * 1024)
static uint32_t __attribute__((aligned(64))) displayList[DISPLAY_LIST_SIZE / 4];
/* ---- Texture table ------------------------------------------------------- */
#define PSP_RTEXTURE_MAX 256
/* GU T8 (8-bit indexed) textures.
* cpuIndices : w*h row-major bytes, user-writable source of truth.
* gpuIndices : tbw*h padded for sceGuTexImage, re-derived at bind.
* palette : 256 RGBA entries, user-writable source of truth.
* Converted to ABGR on-the-fly at bind into pspAbgrBuf.
* tbw : power-of-two stride ≥ 8 required by the GU. */
typedef struct {
uint8_t *cpuIndices;
uint8_t *gpuIndices;
color_t palette[256];
uint16_t w, h, tbw;
} psptexentry_t;
/* Shared 16-byte-aligned ABGR buffer used during every CLUT load. */
static uint32_t __attribute__((aligned(16))) pspAbgrBuf[256];
static psptexentry_t pspTexTable[PSP_RTEXTURE_MAX];
static uint16_t pspTexNext = 1; /* 0 = white fallback */
/* ---- Vertex types -------------------------------------------------------- */
/* 2D sprite: two corner vertices define the rect (GU_SPRITES uses 2 verts). */
typedef struct {
uint16_t u, v; /* texel coords (integer, not normalised) */
uint32_t color; /* ABGR */
int16_t x, y, z;
} __attribute__((packed)) GuVert2D;
/* 3D triangle vertex */
typedef struct {
float u, v;
uint32_t color; /* ABGR */
float x, y, z;
} __attribute__((packed)) GuVert3D;
/* ---- Helpers ------------------------------------------------------------- */
static uint32_t toABGR(color_t c) {
return ((uint32_t)c.a << 24) |
((uint32_t)c.b << 16) |
((uint32_t)c.g << 8) |
((uint32_t)c.r);
}
/* Smallest power-of-two ≥ n and ≥ 8 (PSP GU minimum stride for T8). */
static uint16_t texturePow2(uint16_t n) {
uint16_t p = 8;
while(p < n) p = (uint16_t)(p << 1);
return p;
}
/* ---- Init ---------------------------------------------------------------- */
errorret_t renderPSPInit(void) {
/* White 1×1 fallback: index 0 → palette[0] = white */
psptexentry_t *e = &pspTexTable[0];
e->cpuIndices = (uint8_t *)memalign(16, 1);
assertNotNull(e->cpuIndices, "PSP: failed to allocate fallback cpu index buffer");
e->cpuIndices[0] = 0;
e->gpuIndices = (uint8_t *)memalign(16, 8); /* tbw=8 minimum */
assertNotNull(e->gpuIndices, "PSP: failed to allocate fallback gpu index buffer");
memoryZero(e->gpuIndices, 8);
memoryZero(e->palette, 256 * sizeof(color_t));
e->palette[0] = COLOR_WHITE;
e->w = 1; e->h = 1; e->tbw = 8;
errorOk();
}
/* ---- Texture ------------------------------------------------------------- */
rtexture_t renderPSPTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
) {
assertTrue(pspTexNext < PSP_RTEXTURE_MAX, "PSP texture table full");
uint16_t tbw = texturePow2(w);
rtexture_t handle = (rtexture_t)pspTexNext++;
psptexentry_t *e = &pspTexTable[handle];
uint32_t cpuBytes = (uint32_t)w * h;
e->cpuIndices = (uint8_t *)memalign(16, cpuBytes);
assertNotNull(e->cpuIndices, "PSP: failed to allocate cpu index buffer");
memoryCopy(e->cpuIndices, indices, cpuBytes);
uint32_t gpuBytes = (uint32_t)tbw * h;
e->gpuIndices = (uint8_t *)memalign(16, gpuBytes);
assertNotNull(e->gpuIndices, "PSP: failed to allocate gpu index buffer");
memoryZero(e->gpuIndices, gpuBytes);
for(uint16_t row = 0; row < h; row++)
memoryCopy(e->gpuIndices + row * tbw, indices + row * w, w);
memoryCopy(e->palette, palette, 256 * sizeof(color_t));
e->w = w; e->h = h; e->tbw = tbw;
return handle;
}
void renderPSPTextureDispose(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= PSP_RTEXTURE_MAX) return;
psptexentry_t *e = &pspTexTable[tex];
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
if(e->gpuIndices) { free(e->gpuIndices); e->gpuIndices = NULL; }
}
color_t *renderPSPTextureGetPalette(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= PSP_RTEXTURE_MAX) return NULL;
return pspTexTable[tex].palette;
}
uint8_t *renderPSPTextureGetIndices(rtexture_t tex) {
if(tex == RTEXTURE_NONE || tex >= PSP_RTEXTURE_MAX) return NULL;
return pspTexTable[tex].cpuIndices;
}
static void bindTexture(rtexture_t tex) {
psptexentry_t *e = (tex < PSP_RTEXTURE_MAX && pspTexTable[tex].cpuIndices)
? &pspTexTable[tex]
: &pspTexTable[0];
/* Re-pad cpuIndices → gpuIndices (stride tbw, rows w wide). */
memoryZero(e->gpuIndices, (uint32_t)e->tbw * e->h);
for(uint16_t row = 0; row < e->h; row++)
memoryCopy(e->gpuIndices + row * e->tbw, e->cpuIndices + row * e->w, e->w);
sceKernelDcacheWritebackRange(e->gpuIndices, (uint32_t)e->tbw * e->h);
/* Convert palette color_t RGBA → ABGR into the shared aligned buffer. */
for(int i = 0; i < 256; i++) pspAbgrBuf[i] = toABGR(e->palette[i]);
sceKernelDcacheWritebackRange(pspAbgrBuf, 256 * sizeof(uint32_t));
sceGuTexMode(GU_PSM_T8, 0, 0, GU_FALSE);
sceGuClutMode(GU_PSM_8888, 0, 0xFF, 0);
sceGuClutLoad(32, pspAbgrBuf); /* 32 × 8 entries = 256 */
sceGuTexImage(0, e->tbw, e->h, e->tbw, e->gpuIndices);
sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA);
sceGuTexFilter(GU_NEAREST, GU_NEAREST);
sceGuTexScale(1.0f, 1.0f);
sceGuTexOffset(0.0f, 0.0f);
}
/* ---- Draw helpers -------------------------------------------------------- */
static void draw2DSprite(const ropsprite_t *s) {
psptexentry_t *e = (s->texture < PSP_RTEXTURE_MAX && pspTexTable[s->texture].cpuIndices)
? &pspTexTable[s->texture]
: &pspTexTable[0];
uint32_t abgr = toABGR(s->tint);
float u0 = (s->uvX / 255.0f) * (float)e->w;
float v0 = (s->uvY / 255.0f) * (float)e->h;
float u1 = ((s->uvX + s->uvW) / 255.0f) * (float)e->w;
float v1 = ((s->uvY + s->uvH) / 255.0f) * (float)e->h;
bindTexture(s->texture);
GuVert2D *verts = (GuVert2D *)sceGuGetMemory(2 * sizeof(GuVert2D));
assertNotNull(verts, "PSP: failed to allocate sprite vertices");
verts[0].u = (uint16_t)u0; verts[0].v = (uint16_t)v0;
verts[0].color = abgr;
verts[0].x = s->x; verts[0].y = s->y; verts[0].z = 0;
verts[1].u = (uint16_t)u1; verts[1].v = (uint16_t)v1;
verts[1].color = abgr;
verts[1].x = (int16_t)(s->x + s->w);
verts[1].y = (int16_t)(s->y + s->h);
verts[1].z = 0;
sceGuDrawArray(
GU_SPRITES,
GU_TEXTURE_16BIT | GU_COLOR_8888 | GU_VERTEX_16BIT | GU_TRANSFORM_2D,
2, 0, verts
);
}
static void draw3DQuad(const ropquad3d_t *q) {
uint32_t abgr = toABGR(q->tint);
float u0 = q->uvX / 255.0f, v0 = q->uvY / 255.0f;
float u1 = (q->uvX + q->uvW) / 255.0f;
float v1 = (q->uvY + q->uvH) / 255.0f;
float cx = (float)q->cx, cy = (float)q->cy, cz = (float)q->cz;
float rx = (float)q->rx, ry = (float)q->ry, rz = (float)q->rz;
float ux = (float)q->ux, uy = (float)q->uy, uz = (float)q->uz;
float tlx = cx-rx+ux, tly = cy-ry+uy, tlz = cz-rz+uz;
float trx = cx+rx+ux, try_= cy+ry+uy, trz = cz+rz+uz;
float blx = cx-rx-ux, bly = cy-ry-uy, blz = cz-rz-uz;
float brx = cx+rx-ux, bry = cy+ry-uy, brz = cz+rz-uz;
bindTexture(q->texture);
GuVert3D *verts = (GuVert3D *)sceGuGetMemory(6 * sizeof(GuVert3D));
assertNotNull(verts, "PSP: failed to allocate 3D quad vertices");
verts[0] = (GuVert3D){u0,v0, abgr, tlx,tly,tlz};
verts[1] = (GuVert3D){u0,v1, abgr, blx,bly,blz};
verts[2] = (GuVert3D){u1,v1, abgr, brx,bry,brz};
verts[3] = (GuVert3D){u0,v0, abgr, tlx,tly,tlz};
verts[4] = (GuVert3D){u1,v1, abgr, brx,bry,brz};
verts[5] = (GuVert3D){u1,v0, abgr, trx,try_,trz};
sceGuDrawArray(
GU_TRIANGLES,
GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D,
6, 0, verts
);
}
/* ---- Flush --------------------------------------------------------------- */
errorret_t renderPSPFlush(ropbuffer_t *buf) {
sceGuStart(GU_DIRECT, displayList);
sceGuEnable(GU_TEXTURE_2D);
sceGuEnable(GU_DEPTH_TEST);
sceGuDepthFunc(GU_GEQUAL); /* PSP uses reversed depth */
uint32_t offset = 0;
while(offset < buf->byteCount) {
const ropheader_t *hdr = (const ropheader_t *)(buf->data + offset);
ropop_t op = (ropop_t)hdr->op;
switch(op) {
case ROP_CLEAR: {
const ropclear_t *c = (const ropclear_t *)hdr;
uint32_t abgr = toABGR(c->color);
sceGuClearColor(abgr);
sceGuClearDepth(0);
sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT);
break;
}
case ROP_DRAW_SPRITE:
draw2DSprite((const ropsprite_t *)hdr);
break;
case ROP_SET_PROJECTION: {
const ropprojection_t *p = (const ropprojection_t *)hdr;
sceGumMatrixMode(GU_PROJECTION);
sceGumLoadIdentity();
if(p->fovY > FIXED_ZERO) {
float fovDeg = fixedToFloat(p->fovY) * (180.0f / 3.14159f);
sceGumPerspective(
fovDeg, fixedToFloat(p->aspect),
fixedToFloat(p->nearZ), fixedToFloat(p->farZ)
);
} else {
float aspect = fixedToFloat(p->aspect);
sceGumOrtho(-aspect, aspect, -1.0f, 1.0f,
fixedToFloat(p->nearZ), fixedToFloat(p->farZ));
}
break;
}
case ROP_SET_VIEW: {
const ropview_t *v = (const ropview_t *)hdr;
ScePspFVector3 eye = {(float)v->eyeX, (float)v->eyeY, (float)v->eyeZ};
ScePspFVector3 target = {(float)v->tgtX, (float)v->tgtY, (float)v->tgtZ};
ScePspFVector3 up = {0.0f, 1.0f, 0.0f};
sceGumMatrixMode(GU_VIEW);
sceGumLoadIdentity();
sceGumLookAt(&eye, &target, &up);
sceGumMatrixMode(GU_MODEL);
sceGumLoadIdentity();
break;
}
case ROP_DRAW_QUAD_3D:
draw3DQuad((const ropquad3d_t *)hdr);
break;
default:
break;
}
offset += ropOpSize(op);
}
sceGuFinish();
sceGuSync(0, 0);
errorOk();
}
/* ---- Dispose ------------------------------------------------------------- */
void renderPSPDispose(void) {
for(uint16_t i = 0; i < pspTexNext; i++) {
psptexentry_t *e = &pspTexTable[i];
if(e->cpuIndices) { free(e->cpuIndices); e->cpuIndices = NULL; }
if(e->gpuIndices) { free(e->gpuIndices); e->gpuIndices = NULL; }
}
pspTexNext = 1;
}
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "display/render/ropbuffer.h"
#include "display/render/rtexture.h"
#include "display/color.h"
errorret_t renderPSPInit(void);
errorret_t renderPSPFlush(ropbuffer_t *buf);
void renderPSPDispose(void);
rtexture_t renderPSPTextureCreate(
uint16_t w, uint16_t h,
const uint8_t *indices, const color_t *palette
);
void renderPSPTextureDispose(rtexture_t tex);
color_t *renderPSPTextureGetPalette(rtexture_t tex);
uint8_t *renderPSPTextureGetIndices(rtexture_t tex);
+3 -1
View File
@@ -3,10 +3,12 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Sources # Sources — PSP uses its own native GU display; SDL2 display not compiled there
if(NOT DUSK_TARGET_SYSTEM STREQUAL "psp")
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
displaysdl2.c displaysdl2.c
) )
endif()
# Subdirs # Subdirs
+1 -1
View File
@@ -7,7 +7,7 @@
#include "display/display.h" #include "display/display.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "render/rendergl.h" #include "display/render/rendergl.h"
#include "error/errorgl.h" #include "error/errorgl.h"
errorret_t displaySDL2Init(void) { errorret_t displaySDL2Init(void) {
+1 -1
View File
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include "error/error.h" #include "error/error.h"
#include "display/displaystate.h" #include "display/displaystate.h"
#include "render/ropbuffer.h" #include "display/render/ropbuffer.h"
typedef struct { typedef struct {
SDL_Window *window; SDL_Window *window;
+2
View File
@@ -7,4 +7,6 @@
#pragma once #pragma once
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#ifdef DUSK_OPENGL
#include "duskgl.h" #include "duskgl.h"
#endif
+31 -3
View File
@@ -8,11 +8,41 @@
#include "input/input.h" #include "input/input.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "display/display.h" #include "display/display.h"
#include "engine/engine.h"
void inputUpdateSDL2(void) { void inputUpdateSDL2(void) {
#ifdef DUSK_INPUT_POINTER
INPUT.platform.scrollX = 0.0f;
INPUT.platform.scrollY = 0.0f;
#endif
SDL_Event e;
while(SDL_PollEvent(&e)) {
switch(e.type) {
case SDL_QUIT:
ENGINE.running = false;
break;
#ifdef DUSK_INPUT_POINTER
case SDL_MOUSEWHEEL:
INPUT.platform.scrollX = (float_t)e.wheel.x;
INPUT.platform.scrollY = (float_t)e.wheel.y;
break;
#endif
#ifdef DUSK_INPUT_GAMEPAD
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
break;
#endif
default:
break;
}
}
#ifdef DUSK_INPUT_GAMEPAD #ifdef DUSK_INPUT_GAMEPAD
INPUT.platform.controller = NULL; INPUT.platform.controller = NULL;
for(int32_t i = 0; i < SDL_NumJoysticks(); i++) { for(int32_t i = 0; i < SDL_NumJoysticks(); i++) {
if(!SDL_IsGameController(i)) continue; if(!SDL_IsGameController(i)) continue;
INPUT.platform.controller = SDL_GameControllerOpen(i); INPUT.platform.controller = SDL_GameControllerOpen(i);
@@ -33,8 +63,6 @@ void inputUpdateSDL2(void) {
INPUT.platform.mouseX = (float_t)pointerX / (float_t)windowWidth; INPUT.platform.mouseX = (float_t)pointerX / (float_t)windowWidth;
INPUT.platform.mouseY = (float_t)pointerY / (float_t)windowHeight; INPUT.platform.mouseY = (float_t)pointerY / (float_t)windowHeight;
INPUT.platform.scrollX = 0.0f;
INPUT.platform.scrollY = 0.0f;
#endif #endif
} }