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