This commit is contained in:
2026-06-18 20:25:54 -05:00
parent 730a5b2b10
commit 57b2cdb9d1
111 changed files with 865 additions and 3328 deletions
+92
View File
@@ -0,0 +1,92 @@
# Display Refactor Progress
## Immediate Goal
Render a 32x32 white square through the new render opcode stack on Linux.
## Architecture (summary)
See `.claude/display-refactor.md` for the full design.
- `src/dusk/render/` -- opcode format + buffer + submission API (the *contract*).
- Platform backends (e.g. `src/duskgl/`) consume the buffer and translate to native API calls.
- `src/dusk/display/` -- orchestration shell only: `displayInit`, `displayUpdate`, `displayDispose`.
- Scenes call `renderSprite(...)`, `renderClear(...)`. The backend executes the intent.
## Opcode format (32 bytes)
Every command starts with a 4-byte `ropheader_t` (opcode, flags, depth). Two commands defined:
- `ROP_CLEAR` (32 bytes) -- clear with a color.
- `ROP_DRAW_SPRITE` (32 bytes) -- screen-space int16 x/y/w/h + tint color.
## Milestone 1 -- Archive + strip existing display deps ✓
- [x] Old `src/dusk/display/` archived (now deleted from working tree via git).
- [x] Old `src/duskgl/display/` removed (new GL renderer replaces it).
- [x] `engine.c` stripped to minimal subsystems, set to `SCENE_TYPE_TEST`.
- [x] `scene.c` stripped of old display/shader/screen references.
- [x] `console.c` stripped of display deps.
- [x] `ui/CMakeLists.txt` gutted (re-implementation deferred).
- [x] `asset/loader/CMakeLists.txt` -- display loaders disabled.
- [x] `asset/loader/assetloader.h` -- display loader types removed.
- [x] `rpg/overworld/chunk.h` -- mesh_t / meshvertex_t removed.
- [x] `rpg/overworld/map.c` -- mesh/spritebatch calls removed.
- [x] `scene/overworld/sceneoverworld.c` -- stubbed to empty callbacks.
- [x] Test suite display tests disabled.
## Milestone 2 -- Render opcode system ✓
- [x] `src/dusk/render/rop.h` -- `ropheader_t`, `ropclear_t`, `ropsprite_t`.
- [x] `src/dusk/render/ropbuffer.h/.c` -- `ROPBUFFER` global, reset, alloc.
- [x] `src/dusk/render/render.h/.c` -- `renderClear()`, `renderSprite()`.
- [x] `src/dusk/render/CMakeLists.txt`.
## Milestone 3 -- New minimal display shell ✓
- [x] `src/dusk/display/display.h/.c` -- init/update/dispose, calls platform hooks.
- [x] `src/dusk/display/displaystate.h` -- cull/depth/blend flags.
- [x] `src/dusk/display/color.csv` + `CMakeLists.txt` -- color generation kept.
## Milestone 4 -- GL backend ✓
- [x] `src/duskgl/render/rendergl.h/.c`:
- GL 3.3 core shader (ortho projection, solid color, no texture yet).
- `renderGLInit` -- creates VAO/VBO/shader.
- `renderGLFlush(buf, w, h)` -- walks ROPBUFFER, GL calls per opcode.
- `ROP_CLEAR``glClearColor` + `glClear`.
- `ROP_DRAW_SPRITE` → 6-vertex quad, `glDrawArrays`.
- [x] `src/duskgl/error/errorgl.h/.c` -- `errorGLCheck`.
- [x] `src/duskgl/CMakeLists.txt`.
- [x] `src/dusksdl2/display/displaysdl2.h/.c` updated:
- `displaySDL2Init` -- SDL2 window + GL 3.3 context + `renderGLInit`.
- `displaySDL2Flush(ropbuffer_t *)` -- MakeCurrent + `renderGLFlush`.
- `displaySDL2Swap` -- SDL_GL_SwapWindow.
- [x] `src/dusklinux/display/displayplatform.h` updated with new macros.
## Milestone 5 -- Test scene ✓
- [x] `SCENE_TYPE_TEST` added to `scenetype.h/.c`.
- [x] `src/dusk/scene/test/scenetest.h/.c`:
- `renderClear(color(32, 32, 48, 255))` -- dark blue-grey background.
- `renderSprite(100, 100, 32, 32, COLOR_WHITE)` -- 32x32 white square.
- [x] `engine.c` starts with `SCENE_TYPE_TEST`.
## Milestone 6 -- Verified ✓
- [x] Build succeeds with no errors (2026-06-18).
- [x] Engine initializes: SDL window + GL context + shader + test scene.
- [x] No crashes running for 5+ seconds.
- [ ] 32x32 white square visually confirmed on screen.
---
## Status: BUILD PASSING -- awaiting visual confirmation
---
## Decisions log
**2026-06-18** -- `color_t = color4b_t` (from generated `display/color.h`). The color generation pipeline (color.csv + Python tool) is kept in the new minimal `src/dusk/display/CMakeLists.txt`.
**2026-06-18** -- `ROP_SIZE = 32`. All opcodes fixed 32 bytes. 3D quads will be 64 bytes when added later.
**2026-06-18** -- Depth sort deferred. Buffer stores unsorted commands; painter platforms sort on flush. GL uses Z-buffer.
**2026-06-18** -- Texture system not yet wired into the opcode pipeline. `ROP_DRAW_SPRITE` with `texture=0` uses solid tint color only (no sampler). Texture handle system comes next.
**2026-06-18** -- GL backend uses GL 3.3 Core profile. Shader takes screen-space pixel coordinates and converts to clip space using window size queried from SDL each frame.
**2026-06-18** -- `ROPBUFFER` is a global (4096 slots × 32 bytes = 128 KB). Reset at start of each frame in `displayUpdate`.
**2026-06-18** -- `ui/`, `rpg/overworld` display code, asset display loaders all temporarily stubbed/disabled. Will be rewritten against the new render API.
-714
View File
@@ -1,714 +0,0 @@
# Display Layer Refactor
## Vision
The goal is to remove the implicit assumption that all platforms render
through a GL-like API, and replace it with a system where each platform
owns its rendering stack completely. The scene describes *what* to draw
in platform-neutral terms; the platform decides *how* to draw it.
This unlocks:
- Saturn (VDP1/VDP2 command-list, no Z-buffer, affine-only)
- PlayStation 1 (ordering table, affine textures, GTE fixed-point, CMake SDK)
- Nintendo 64 (RSP display list, hardware Z-buffer, perspective-correct,
real FPU -- closer to modern GL than to Saturn)
- SNES (PPU tile engine, Mode7 for overworld, no real 3D)
- Vulkan (explicit, modern, no legacy GL baggage)
- Native PSP GU (drop PSPGL which is just a compatibility shim)
- Legacy fixed-function GL as its own standalone target
- A real first-class 2D UI system not bolted onto 3D space
---
## Why
### The current abstraction assumes GPU-style rendering
The current display layer was designed around a GL-like mental model:
vertex buffers, shaders, Z-buffered triangle rasterization, and texture
objects. `duskgl` implements this with real OpenGL. `duskdolphin` does its
own GX thing but still matches the same interface (mesh, shader, texture,
framebuffer). PSP uses PSPGL -- a library that *emulates* GL on top of
the PSP's native GE/GU hardware, which is entirely different underneath.
Problems this creates:
**PSPGL is a lie.** The PSP has a native graphics engine (GE/GU) with its
own command list, its own vertex formats, and its own display list model.
PSPGL translates GL calls into GU calls, but imperfectly -- and we end up
paying the abstraction cost without getting GL correctness. Writing directly
to GU gives better performance, access to native formats, and correct
behavior on edge cases that PSPGL gets wrong.
**Legacy GL should not share code with modern GL.** The fixed-function
pipeline (no shaders, matrix stacks via glMatrixMode, glTexEnv) is
meaningfully different from modern GL (VAO/VBO, GLSL, explicit uniform
locations). Treating them as "the same thing with a flag" creates a tangle
of `#ifdef DUSK_OPENGL_LEGACY` guards throughout the rendering code.
They are separate targets and should be separate platform directories.
**Saturn cannot fit the model at all.** VDP1 is a command-list processor:
you write 32-byte command structs (sprites, quads, lines) into VRAM, then
poke a register to trigger execution. There are no vertex buffers, no
shaders, no Z-buffer. Depth is pure painter's algorithm -- command order
IS the depth. VDP2 composites up to 6 background planes at scanline time;
these are tile maps and rotation parameter tables, not meshes. Nothing
about the current API maps onto this hardware.
**SNES is even further removed.** The PPU renders tiles. VRAM holds 8x8
or 16x16 pixel tiles and tile maps; the PPU references these during
scanline rendering. There are no draw calls. Mode7 is an affine transform
applied to a single background layer (the basis for the overworld map and
road perspective effects). Sprites are entries in OAM (Object Attribute
Memory). The 65816 CPU writes to memory-mapped registers and VRAM; the
PPU does the rest. The concept of "mesh" or "shader" is meaningless here.
**Textures loaded as RGBA waste memory and exclude platforms.** Loading
every texture as 32-bit RGBA and converting at runtime is expensive on
memory-constrained platforms (Saturn has ~1 MB total RAM; SNES has 64 KB
VRAM) and simply wrong for platforms that have native formats incompatible
with RGBA (e.g., PSP's ABGR8888 / BGR5650, Saturn's RGB555 / CI4 / CI8,
SNES's 2bpp/4bpp/8bpp indexed). The asset pipeline must compile textures
to platform-native formats at build time.
**UI in 3D space is wasteful and limiting.** Currently UI elements are
rendered as geometry projected into screen space, going through the full
3D pipeline. On platforms with dedicated 2D hardware (Saturn VDP2,
SNES BG layers), this is actively wrong -- UI should map to a hardware
plane, not a 3D draw call. On modern platforms it should be a clean
screen-space pass that never touches the 3D depth buffer.
---
## Current Model (Summary)
```
Scene
-> shaderBind(shader)
-> textureBind(texture)
-> meshDraw(mesh) <-- immediate draw call per object
-> meshDraw(mesh)
-> ...
Platform receives each draw call immediately.
Depth is handled by Z-buffer hardware.
All textures live in GPU memory as RGBA (or Dolphin's tiled RGBA).
UI is rendered as 3D geometry with an orthographic projection.
```
Key current concepts:
- `mesh_t` -- vertex array (triangles/quads), in GPU VBO (GL) or CPU
memory (Dolphin)
- `shader_t` -- GLSL program (modern GL), GL fixed-function state
(legacy GL), or GX matrix + TEV config (Dolphin)
- `texture_t` -- GPU texture handle (GL) or tiled CPU buffer (Dolphin);
always RGBA at the engine level
- `framebuffer_t` -- FBO (GL) or fixed hardware XFB (Dolphin)
- `spritebatch_t` -- accumulates 2D quads and flushes in batches of 32;
the only existing deferred-submission system in the engine
The spritebatch hints at the right model. Everything needs to work this way.
---
## The Core Shift: Platform-Native Rendering
### Before
```
src/dusk/ Core engine + GL-like rendering API definition
src/duskgl/ OpenGL implementation
src/dusksdl2/ SDL2 window/input (shared)
src/duskpsp/ PSP via PSPGL (shim over GU)
src/duskvita/ Vita via GL ES (similar path to duskgl)
src/duskdolphin/ GameCube/Wii via GX (already custom)
src/dusklinux/ Linux (uses dusksdl2 + duskgl)
```
### After
```
src/dusk/ Core engine logic + render intent API ONLY
src/dusksdl2/ SDL2 window/input (unchanged)
src/duskgl/ Modern OpenGL (Linux, Vita modern path)
src/duskgllegacy/ Fixed-function OpenGL (older hardware, PSP with PSPGL
as a last resort)
src/duskvulkan/ Vulkan (Linux modern, future)
src/duskpsp/ PSP native GU (no PSPGL, direct command lists)
src/duskvita/ Vita native GXM (TBD)
src/duskdolphin/ GameCube/Wii GX (already custom, mostly kept)
src/dusksaturn/ Saturn VDP1/VDP2 (new)
src/duskps1/ PlayStation 1 ordering table + GTE (new)
src/duskn64/ Nintendo 64 RSP/RDP display list (new)
src/dusksnes/ SNES PPU/Mode7 (new, extremely constrained)
```
`src/dusk/` no longer knows about meshes, shaders, or framebuffers.
It defines the *render intent* system: what the scene wants to draw.
Each platform directory is entirely self-contained and responsible for
translating intents to its native API.
---
## Render Intent System (new)
Instead of the scene calling `meshDraw()` or `shaderBind()`, it submits
render intents into a `renderqueue_t`. An intent describes what should
appear on screen without prescribing how to draw it.
### Primitive intents (3D world)
```
RENDER_INTENT_QUAD -- textured quad, 4 vertices or transform + size
RENDER_INTENT_POLYGON -- filled polygon (convex, up to N vertices)
RENDER_INTENT_LINE -- line segment or polyline
RENDER_INTENT_SPRITE -- 2D billboard (always faces camera)
RENDER_INTENT_MESH -- arbitrary vertex array (GL/GX only; degraded
on command-list platforms)
```
Each intent carries: texture reference, color/tint, depth hint (for
painter's algorithm sorting), blend mode, and cull flags.
### Background plane intents (2D layers)
```
RENDER_INTENT_BGPLANE -- configure a background/tilemap layer
```
Carries: layer index, tile map data reference, scroll offset, palette,
and transform (for Mode7-style affine).
### UI intents (screen space)
```
RENDER_INTENT_UI_RECT -- solid colored rectangle
RENDER_INTENT_UI_SPRITE -- textured rectangle (UI image)
RENDER_INTENT_UI_TEXT -- text string at screen position
```
UI intents are always screen-space. They are never mixed into the 3D
world queue. See UI System section below.
### Platform translation
| Intent | Modern GL | PSP GU | Saturn VDP1 | PS1 OT | N64 RSP | SNES PPU |
|---|---|---|---|---|---|---|
| QUAD | VAO + glDraw | GU display list | distorted-sprite cmd | GPU quad packet | RSP display list | OAM + BG tile |
| POLYGON | VAO + glDraw | GU display list | polygon cmd | GPU poly packet | RSP display list | OAM |
| BGPLANE | fullscreen quad | fullscreen quad | VDP2 config | fullscreen quad | fullscreen quad | BG layer config |
| UI_SPRITE | 2D ortho quad | 2D GU quad | VDP2 BG plane | GPU rect packet | RDP rectangle | BG layer tile |
| MESH | VAO/VBO | GU buffers | (degrade: quads) | (degrade: tris/quads) | RSP display list | (not supported) |
Note: N64 supports both triangles and axis-aligned rectangles natively via
RDP. PS1 supports triangles and quads (4-vertex) natively, so neither needs
the dead-vertex trick that Saturn requires.
---
## Asset Pipeline: Platform-Native Formats
### The problem
All textures currently enter the engine as RGBA and are converted at
runtime by each platform (Dolphin retiles to 4x4 blocks; GL uploads as-is).
This wastes memory and CPU time, and is impossible for platforms where RGBA
is not a valid intermediate format at all.
### The solution
The asset compiler (offline, run at build time) produces platform-specific
binary bundles. A texture asset has one source (PNG or similar) but N
compiled outputs, one per target.
### Texture formats by platform
| Platform | Native Formats | Notes |
|---|---|---|
| Modern GL | RGBA8, RGB8, BC1-BC7 (compressed) | Upload directly, GPU handles |
| Legacy GL | RGBA8, RGB8, CI8 (palette via extension) | No compressed formats |
| Vulkan | VkFormat variants (RGBA8, BC, ASTC) | Chosen at compile time |
| PSP GU | ABGR8888, BGR5650, ABGR1555, ABGR4444, CI4, CI8 | Native swizzled format |
| Saturn VDP1/VDP2 | RGB555, CI4, CI8 (15-bit palette in CRAM) | Big-endian, packed |
| PlayStation 1 | RGB555 / CI4 / CI8 (CLUT in VRAM) | Little-endian; VRAM flat; CLUT at coord |
| Nintendo 64 | RGBA16, RGBA32, IA4-IA16, I4-I8, CI4, CI8 | 4 KB TMEM; tiles must fit in TMEM banks |
| GameCube/Wii GX | I4, I8, IA4, IA8, RGB565, RGB5A3, RGBA8, CMPR | 4x4 tiled, big-endian |
| SNES PPU | 2bpp, 4bpp, 8bpp indexed (CGRAM palette) | Tile-packed, no direct access |
### Asset bundle structure
The `.dsk` bundle gains a platform tag. The loader picks the right section
at runtime (or the build produces a single-platform bundle for constrained
targets like SNES/Saturn where there is no spare storage for unused data).
---
## UI System (first-class)
### Current problem
UI elements go through the 3D pipeline: they are meshes with an orthographic
shader, rendered in the same pass as the world. This means:
- UI competes for Z-buffer depth with world geometry
- On Saturn/SNES, UI cannot use dedicated hardware planes
- Text rendering is tied to the sprite batch which is tied to the 3D pass
- No separation between "draw the world" and "draw the HUD"
### New model
UI is a completely separate rendering context. The world renders first,
then the UI renders on top. They share no state.
UI coordinates are always in screen space (pixels or a logical resolution
that the platform scales to its native display size). No camera matrix,
no projection, no depth buffer involvement.
### Platform mapping
| Platform | UI implementation |
|---|---|
| Modern GL | Separate 2D ortho pass, screen-space quads, no depth test |
| Legacy GL | Same, using fixed-function |
| PSP GU | Separate GU display list, 2D mode |
| Saturn | VDP2 background plane(s) dedicated to UI |
| PlayStation 1 | Separate GPU packet chain, no Z; ordered after world OT |
| Nintendo 64 | RDP rectangle commands in a separate display list segment |
| GameCube/Wii | GX 2D mode or dedicated GX pass |
| SNES | Dedicated BG layer(s) for HUD tiles |
On Saturn, the UI occupying VDP2 planes is a genuine hardware win -- the
PPU composites it for free at scanline time, costing zero VDP1 commands.
On SNES, the HUD must live in a BG layer because there is no alternative.
### UI API (proposed)
```c
uiBegin();
uiDrawRect(x, y, w, h, color);
uiDrawSprite(x, y, w, h, texture, uvMin, uvMax);
uiDrawText(x, y, font, string);
uiEnd(); // platform flushes UI to hardware
```
The `uiBegin`/`uiEnd` block collects intents; the platform submits them
at frame end in whatever way is appropriate.
---
## SNES / Mode7
SNES is the most constrained platform the engine will ever support and
needs its own section because it breaks assumptions that even Saturn keeps.
### Hardware
- **CPU**: 65816 @ ~3.58 MHz (16-bit, no FPU, no cache)
- **PPU**: Tile-based scanline renderer. VRAM holds tile graphics and
tile maps. BG layers reference tiles by index.
- **Mode7**: A single BG layer with a 2D affine matrix applied per
scanline. Used for overworld maps, road perspective (F-Zero), rotation
effects. The matrix is set via HDMA (scanline DMA) for per-scanline
variation, enabling horizon-perspective effects.
- **Sprites/OAM**: Up to 128 sprites (8x8, 16x16, 32x32, 64x64 pixels),
4bpp indexed, up to 8 per scanline.
- **Palette**: CGRAM holds 256 entries of 15-bit RGB (512 bytes total).
BG layers use sub-palettes of 4/16/256 colors depending on bit depth.
- **VRAM**: 64 KB (tiles + tile maps)
- **WRAM**: 128 KB work RAM + usually 8 KB SRAM on cart for saves
- **No frame buffer.** The PPU renders scanlines directly. You cannot
read back what was drawn.
- **No general-purpose draw calls.** You configure registers and VRAM
before the frame and the PPU does the rest.
### What "3D" means on SNES
True 3D is not possible. What can be approximated:
- **Overworld map**: Mode7 with a flat texture and HDMA scroll gives a
top-down perspective with a horizon line (the classic JRPG overworld).
- **Depth illusion**: Mode7 matrix manipulation can simulate a moving
camera over flat terrain. Objects are sprites placed at screen positions
calculated by software perspective projection.
- **Sprite scaling**: Software-scaled sprites using pre-rendered frames
or the RSP-style tricks used in Super FX games (Star Fox). Super FX
is a co-processor on the cartridge -- base SNES cannot do this.
- **Basic 3D effects**: Some games use HDMA color gradient + Mode7 floor
with overlaid sprites to create a pseudo-3D look.
The engine plan for SNES: Mode7 overworld (confirmed), sprite-based world
objects, BG layer UI. "Basic 3D effects" (pseudo-perspective with sprites)
is aspirational -- implementation complexity TBD.
### SNES constraints on the engine
- **No dynamic allocation.** With 128 KB WRAM, a general-purpose allocator
is risky. The engine memory system may need a static pool mode for SNES.
- **No floating point.** `float_t` must resolve to integer or fixed-point.
- **No scripting (JerryScript).** The JS engine requires far more than
128 KB RAM. SNES scenes must be compiled C.
- **Asset data in ROM, not a .dsk bundle.** SNES loads from cartridge ROM
mapped into the address space. The asset system needs a ROM-mapped loader.
- **Tile pipeline.** Textures must be pre-converted to SNES tile format
(2bpp/4bpp/8bpp, 8x8 pixel tiles, CGRAM palette) at build time. This
is a completely different asset output from every other platform.
---
## Platform Inventory
A summary of what each platform's native rendering looks like after the
refactor, for reference when designing the intent API.
### Modern OpenGL (duskgl)
VAO + VBO mesh storage, GLSL shaders, FBO render targets, Z-buffer.
No fixed-function. Targets: Linux, possibly Vita (GXM is preferred).
### Legacy OpenGL (duskgllegacy)
Fixed-function pipeline: `glMatrixMode`, `glTexEnv`, client-side vertex
arrays. No VAO/VBO. Used for: very old desktop hardware, maybe PSP as
last resort (PSPGL is this). Targets: legacy desktop, embedded Linux.
### Vulkan (duskvulkan)
Explicit pipeline state objects, render passes, descriptor sets, command
buffers. Highest ceiling for performance and control. Targets: Linux
(modern), future platforms. Not immediate priority but the architecture
should not block it.
### PSP native GU (duskpsp)
The GE/GU is a display-list GPU. You build a command list in memory and
the GU DMA engine processes it asynchronously. Native vertex formats are
PSP-specific (ABGR byte order, swizzled textures for cache efficiency).
No PSPGL. Targets: PSP hardware and emulators.
### Vita (duskvita)
GXM is Sony's Vita GPU API -- closer to modern GL than GU, with explicit
shader binaries (.gxp), ring buffers, and GPU sync primitives.
### GameCube/Wii GX (duskdolphin)
Already a custom renderer. GX uses immediate-mode vertex submission
(`GX_Begin` / `GX_Position1x16` loops), TEV for texture compositing, and
hardware XFB double-buffering. Big-endian. Mostly kept as-is; may benefit
from being expressed in terms of render intents for consistency.
### Saturn VDP1/VDP2 (dusksaturn)
VDP1: command-list (32-byte structs), quad-based, affine texture mapping,
no Z-buffer (painter's algorithm). VDP2: up to 6 background planes
composited at scanline time. Big-endian dual SH-2, no FPU. Fixed-point
math required throughout.
### PlayStation 1 (duskps1)
MIPS R3000A @ 33.87 MHz, little-endian, no FPU. GTE (coprocessor 2)
handles fixed-point matrix math, perspective divide, and lighting.
GPU receives packets via DMA linked-list (the Ordering Table). Primitives:
triangles and quads natively (no dead-vertex needed). Texture mapping:
affine, same limitation as Saturn. No Z-buffer; depth is OT slot order.
VRAM is 1 MB flat (frame buffers + textures + CLUTs share it). SDK:
PSn00bSDK, which is CMake-native -- a direct fit for the dusk build system.
### Nintendo 64 (duskn64)
VR4300 @ 93.75 MHz, big-endian, real IEEE 754 FPU. Rendering is split
between the RSP (geometry: programmable MIPS SIMD, runs microcode up to
~1000 instructions in 4 KB IMEM) and the RDP (rasterization: fixed
hardware). RSP produces triangle commands from a CPU-built display list
in RDRAM. RDP features: perspective-correct texture mapping, bilinear
filtering, hardware Z-buffer. Primitives: triangles and axis-aligned rects.
TMEM is 4 KB on-chip texture cache; textures must be loaded into tiles
before drawing -- a significant memory management constraint.
SDK: libdragon (Unlicense, GCC 14, Makefile-based -- not CMake; this
requires a wrapper toolchain file for dusk's build system).
### SNES PPU/Mode7 (dusksnes)
Tile-based. VRAM holds tiles and tile maps. Mode7 provides affine transform
for one BG layer. Sprites via OAM. No frame buffer. All configuration is
memory-mapped registers. 65816 CPU, no FPU, extremely limited RAM.
---
## Threading Model
### Current model
The engine uses OS threads for async asset loading (`assetXxxLoaderAsync`).
Platforms that have pthreads or an equivalent RTOS (Linux, PSP, Vita) run
worker threads that load data in the background while the game loop runs.
The main thread polls or blocks on completion.
### The problem
Several target platforms have no OS threading whatsoever, and others have
hardware-specific async mechanisms that are nothing like pthreads.
### Per-platform reality
| Platform | Threading | Async mechanism |
|---|---|---|
| Linux | pthreads | Worker threads (current) |
| Vita | SceKernelThread | Per-SDK threads |
| PSP | SceKernelThread | Per-SDK threads |
| GameCube/Wii | libogc LWP | Lightweight processes |
| Saturn | None (OS) | Slave SH-2 for fixed jobs; CD-ROM via interrupt/callback |
| PlayStation 1 | None (OS) | V-blank ISR, 7 DMA channels, CD-ROM callbacks |
| Nintendo 64 | libdragon preview only | PI DMA for cartridge; RSP for parallel compute |
| SNES | None | DMA (GPDMA/HDMA); NMI V-blank; SPC700 audio is a separate CPU |
**Saturn slave SH-2**: The second SH-2 is not a general-purpose thread.
It runs a fixed subroutine you hand-load. The typical use is offloading
heavy per-frame computation (geometry transforms, depth sort) while the
master SH-2 handles game logic. Communication is via shared WRAM with
cache-through addresses to avoid coherency bugs. There is no scheduler
and no yield -- it runs to completion.
**SNES DMA**: GPDMA copies blocks of data (ROM to WRAM, WRAM to VRAM)
and halts the CPU for the duration -- it is synchronous from the game's
perspective. HDMA runs per-scanline during H-blank, writing to PPU
registers without CPU involvement; this is how Mode7 perspective is
achieved. Neither is "async" in the programming sense.
**SNES NMI**: The V-blank NMI fires at the start of every V-blank period.
This is the only safe window to write to VRAM and PPU registers. All
critical PPU updates must complete within ~1.2ms (the V-blank window).
### Proposed model
Introduce a compile-time threading capability flag:
```
DUSK_THREAD_PTHREAD -- Linux, maybe Vita
DUSK_THREAD_SCEKERNEL -- PSP, Vita SDK
DUSK_THREAD_LWP -- GameCube/Wii libogc
DUSK_THREAD_SLAVE_SH2 -- Saturn slave CPU (job dispatch only)
DUSK_THREAD_NONE -- SNES (and Saturn master thread view)
```
The asset loader's async path is gated on having a threading capability.
When `DUSK_THREAD_NONE` is defined, `assetXxxLoaderAsync` either does not
exist or is an alias for the synchronous version. On Saturn, the slave SH-2
is exposed as a distinct API (`sh2JobDispatch`, `sh2JobWait`) used only for
compute-heavy work, not for I/O.
### Asset loading without threads
**Saturn**: CD-ROM access is initiated via SBL/CDC routines and completes
via interrupt callback. The engine's asset loading loop can poll the
callback flag in the main loop rather than blocking a thread. This is
interrupt-driven cooperative async, not preemptive.
**SNES**: There is no loading. Assets live in ROM, mapped directly into the
65816 address space. "Loading a texture" means computing a pointer into ROM
and copying the tile data to VRAM during V-blank via GPDMA. The asset system
on SNES is essentially a VRAM/CGRAM allocator and a DMA scheduler, not a
file loader.
### Asset system changes
The asset pipeline needs to accommodate three loading models:
1. **File-based** (Linux, PSP, Vita, Saturn CD): open file, read bytes,
close. Can be sync or thread-async.
2. **DMA/interrupt** (Saturn CD-ROM, GC DVD): initiate transfer, poll or
callback on completion, no thread blocked.
3. **ROM-mapped** (SNES): data is already in the address space; "loading"
is a VRAM DMA copy scheduled for V-blank, not file I/O.
The `assetstream_t` abstraction that currently wraps file I/O needs a third
backend for ROM-mapped data, and the async path needs to support
callback-based completion as an alternative to thread-based blocking.
---
## What Needs to Change
### 1. Render intent API (new, in src/dusk/)
Replace `mesh_t` / `shader_t` / `meshDraw()` as scene-facing APIs with
`renderqueue_t` and intent submission functions. `src/dusk/` defines the
intent types and submission API; platforms implement the flush.
### 2. Platform renderer directories
Move rendering implementations out of `duskgl/` as a shared layer and
into fully self-contained platform directories. `duskgl/` becomes the
*modern GL* platform only. Add `duskgllegacy/`, `duskvulkan/` as peers.
### 3. Asset pipeline: platform-native texture formats
The offline asset compiler must produce per-platform texture bundles in
native formats. The runtime texture loader expects pre-converted data,
not RGBA. `textureformat_t` grows to cover all platform formats but each
platform only ever sees the formats it natively supports.
### 4. UI system (first-class, separate from 3D)
New `src/dusk/ui/` subsystem with `uiBegin` / `uiEnd` and intent types
for rects, sprites, and text. Platforms implement the flush independently.
The 3D spritebatch is retired or scoped to world-space billboards only.
### 5. Fixed-point / no-FPU math
`float_t` needs a fixed-point mode. Proposed: define `fixed_t` as a
16.16 signed integer; define `DUSK_MATH_FIXED` for platforms that require
it (Saturn, SNES). Engine math utilities (`mathSin`, `mathCos`, etc.)
have fixed-point implementations selected by this flag. `float_t` on
FPU-less platforms becomes a typedef for `fixed_t`.
### 6. Background plane abstraction (bgplane_t)
New concept in `src/dusk/display/bgplane/`. A BG plane has a tile map or
bitmap source, scroll offsets, a palette reference, and optional affine
parameters (for Mode7-style use). On GL platforms: rendered as a
fullscreen textured quad or shader pass. On Saturn: VDP2 config. On SNES:
PPU BG layer config.
### 7. Memory system: static pool mode
For SNES (and possibly Saturn), the general-purpose allocator may be
unviable. A compile-time static pool mode (`DUSK_MEMORY_STATIC`) that uses
a fixed-size arena instead of dynamic allocation. All `memoryAllocate`
calls hit the pool; `memoryFree` is a no-op or a stack pop.
### 8. Script runtime: optional
JerryScript requires too much RAM for SNES and is marginal on Saturn.
The scripting system should be compile-time optional (`DUSK_SCRIPTING`),
not assumed present. SNES/Saturn scenes would be compiled C.
---
## What to Keep
- Platform macro abstraction pattern (`displayplatform.h`, etc.) -- works,
no reason to change.
- Directory structure convention for platform directories.
- Entity-component system -- platform-agnostic, unaffected.
- Asset loading + `.dsk` bundle concept (extended for platform formats).
- The broad subsystem layout: asset, input, display, log, network, save,
system, time.
---
## Open Questions
1. **Render intent granularity**: How much does the intent API need to
express? A MESH intent works on GL/N64 but degrades poorly on Saturn
(must split into quads) and is impossible on SNES. Should MESH be a
valid intent with a "best effort" contract, or excluded from the portable
API entirely?
2. **Threading abstraction depth**: Should `DUSK_THREAD_SLAVE_SH2` be a
first-class concept in the engine's job system, or a Saturn-internal
implementation detail the core never sees? Same question applies to N64's
RSP as a compute co-processor.
3. **Asset loading async contract**: When a platform has no threads, should
`assetLoadAsync` be a no-op alias for `assetLoadSync`, or return
immediately with a completion flag to poll? The polling model is more
honest but requires all call sites to handle it.
4. **N64 build system**: libdragon uses GNU Make, not CMake. Options are:
(a) write a CMake toolchain file that wraps n64.mk, (b) maintain a
parallel Makefile just for N64, or (c) wait for upstream CMake support.
Which is acceptable long-term?
5. **N64 RSP microcode**: Standard libdragon microcodes (Fast3D/F3DEX2) or
Tiny3D (community microcode with full T&L + skinning)? Writing custom
microcode is powerful but limited to ~1000 MIPS SIMD instructions.
This decision gates what 3D features the N64 port can support.
6. **PSPGL fate**: Drop immediately in favor of native GU, or keep as a
fallback (`duskgllegacy`) while native GU is built? The two can coexist
during transition.
7. **Vulkan priority**: Design the intent API with Vulkan in mind from the
start, or add it later? Vulkan's explicit pipeline state model may
conflict with how stateful platforms (Saturn, SNES) expect things to work.
8. **Background planes on modern platforms**: Does `bgplane_t` degrade to a
fullscreen textured quad on GL/Vulkan/N64, or should modern platforms
support actual background scene rendering (3D world behind the foreground)?
9. **PS1 ordering table depth**: The OT is a fixed-size array (e.g. 4096
slots). Depth precision = number of slots. How deep should the engine's
default OT be, and should this be configurable per-scene?
10. **Fixed-point strategy**: Does `float_t` transparently become `fixed_t`
on FPU-less platforms (Saturn, PS1, SNES), or do we require explicit
`fixed_t` in math-heavy paths? Transparent is easiest to port; explicit
is faster.
11. **SNES V-blank budget**: All VRAM writes must finish within ~1.2ms.
Does the engine need a V-blank work queue with a budget checker, or is
this left to the game to manage manually?
12. **SNES scripting**: JerryScript is out. Pure compiled C, or a lighter
scripting layer (Lua is ~100 KB -- tight but possible)?
13. **Asset compiler**: New standalone tool, or an extension of the existing
asset pipeline? Part of the CMake build or a separate pre-build step?
---
## Proposed Sequence (Draft)
### Phase 1 -- Intent API (no behavior change)
1. Design and stabilize `renderqueue_t` and intent types
2. Refactor modern GL path to submit through render intents (same output,
new plumbing)
3. Refactor Dolphin path the same way
4. Validate no regressions on Linux + GameCube
### Phase 2 -- UI system
5. Extract UI rendering from the 3D path into `src/dusk/ui/`
6. Implement UI flush for GL and Dolphin
7. Wire existing UI elements through the new system
### Phase 3 -- Platform splits
8. Split `duskgl/` into `duskgl/` (modern) and `duskgllegacy/` (fixed-func)
9. Port PSP to native GU (`duskpsp/display/` rewrite, drop PSPGL dependency)
10. Stub `duskvulkan/` structure for future implementation
### Phase 4 -- Asset pipeline
11. Design platform-native texture format system
12. Extend asset compiler for per-platform output
13. Update texture loader to expect pre-converted data
### Phase 5 -- Saturn
14. CMake toolchain for SH-2 cross-compile (yaul / libyaul toolchain)
15. `src/dusksaturn/` -- input (SMPC), asset (CD-ROM), log, system
16. VDP1 backend for render queue (quads, polygons, painter's sort)
17. VDP2 backend for bgplane_t (tile maps, scroll, palette)
18. Fixed-point math mode (`DUSK_MATH_FIXED`)
19. UI backend (VDP2 plane(s))
### Phase 6 -- PlayStation 1
20. CMake toolchain wrapping PSn00bSDK (already CMake-native)
21. `src/duskps1/` -- input (BIOS pad), asset (CD-ROM libpsxcd), log, system
22. GTE integration for fixed-point math (reuse `DUSK_MATH_FIXED` path)
23. Ordering table builder for render queue (painter's sort, DMA linked-list)
24. GPU packet backend for intents (tris, quads, rects)
25. UI backend (separate GPU packet chain after world OT)
### Phase 7 -- Nintendo 64
26. CMake toolchain wrapping libdragon (n64.mk wrapper or toolchain file)
27. `src/duskn64/` -- input (N64 controller via PIF), asset (PI DMA /
DragonFS), log, system
28. RSP display list builder for render queue (Z-buffer path, no sorting)
29. TMEM tile management for textures
30. RDP rectangle backend for UI
31. Decide on RSP microcode (Tiny3D vs standard F3DEX2)
### Phase 8 -- SNES
32. SNES toolchain (cc65 or llvm-mos 65816 target)
33. Static memory pool mode (`DUSK_MEMORY_STATIC`)
34. PPU tile pipeline + VRAM management
35. Mode7 overworld implementation
36. OAM sprite system
37. BG layer UI
38. Scripting-optional build (`DUSK_SCRIPTING` off)
@@ -0,0 +1,28 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
display.c
)
# Subdirectories
add_subdirectory(framebuffer)
add_subdirectory(mesh)
add_subdirectory(screen)
add_subdirectory(shader)
add_subdirectory(spritebatch)
add_subdirectory(text)
add_subdirectory(texture)
# Color definitions
dusk_run_python(
dusk_color_defs
tools.color.csv
--csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h
)
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_color_defs)
@@ -0,0 +1,23 @@
name,r,g,b,a
black,0,0,0,1
white,1,1,1,1
red,1,0,0,1
green,0,1,0,1
blue,0,0,1,1
yellow,1,1,0,1
cyan,0,1,1,1
magenta,1,0,1,1
transparent,0,0,0,0
transparent_white,1,1,1,0
transparent_black,0,0,0,0
gray,0.5,0.5,0.5,1
light_gray,0.75,0.75,0.75,1
dark_gray,0.25,0.25,0.25,1
orange,1,0.65,0,1
purple,0.5,0,0.5,1
brown,0.6,0.4,0.2,1
pink,1,0.75,0.8,1
lime,0.75,1,0,1
navy,0,0,0.5,1
teal,0,0.5,0.5,1
cornflower_blue,0.39,0.58,0.93,1
1 name r g b a
2 black 0 0 0 1
3 white 1 1 1 1
4 red 1 0 0 1
5 green 0 1 0 1
6 blue 0 0 1 1
7 yellow 1 1 0 1
8 cyan 0 1 1 1
9 magenta 1 0 1 1
10 transparent 0 0 0 0
11 transparent_white 1 1 1 0
12 transparent_black 0 0 0 0
13 gray 0.5 0.5 0.5 1
14 light_gray 0.75 0.75 0.75 1
15 dark_gray 0.25 0.25 0.25 1
16 orange 1 0.65 0 1
17 purple 0.5 0 0.5 1
18 brown 0.6 0.4 0.2 1
19 pink 1 0.75 0.8 1
20 lime 0.75 1 0 1
21 navy 0 0 0.5 1
22 teal 0 0.5 0.5 1
23 cornflower_blue 0.39 0.58 0.93 1
@@ -0,0 +1,116 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/display.h"
#include "display/framebuffer/framebuffer.h"
#include "scene/scene.h"
#include "display/spritebatch/spritebatch.h"
#include "display/mesh/quad.h"
#include "display/mesh/cube.h"
#include "display/mesh/sphere.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
#include "display/mesh/triprism.h"
#include "display/screen/screen.h"
#include "ui/ui.h"
#include "display/text/text.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
#include "asset/asset.h"
#include "display/shader/shaderlist.h"
#include "time/time.h"
display_t DISPLAY = { 0 };
errorret_t displayInit(void) {
memoryZero(&DISPLAY, sizeof(DISPLAY));
#ifdef displayPlatformInit
errorChain(displayPlatformInit());
#endif
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
errorChain(textureInit(
&TEXTURE_WHITE, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
));
errorChain(textureInit(
&TEXTURE_TEST, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
));
// Standard meshes
errorChain(quadInit());
errorChain(cubeInit());
errorChain(sphereInit());
errorChain(planeInit());
errorChain(capsuleInit());
errorChain(triPrismInit());
errorChain(frameBufferInitBackBuffer());
errorChain(spriteBatchInit());
errorChain(textInit());
errorChain(screenInit());
// Setup initial shader with default values
errorChain(shaderListInit());
errorOk();
}
errorret_t displayUpdate(void) {
#ifdef displayPlatformUpdate
errorChain(displayPlatformUpdate());
#endif
// Reset state
spriteBatchClear();
errorChain(frameBufferBind(NULL));
// Bind screen and render scene
errorChain(screenBind());
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
SCREEN.background
);
errorChain(sceneRender());
// Finish up
screenUnbind();
screenRender();
// Swap and return.
#ifdef displayPlatformSwap
errorChain(displayPlatformSwap());
#endif
errorOk();
}
errorret_t displaySetState(displaystate_t state) {
#ifdef displayPlatformSetState
errorChain(displayPlatformSetState(state));
#endif
errorOk();
}
errorret_t displayDispose(void) {
errorChain(shaderListDispose());
errorChain(spriteBatchDispose());
screenDispose();
errorChain(textDispose());
errorChain(textureDispose(&TEXTURE_WHITE));
errorChain(textureDispose(&TEXTURE_TEST));
#ifdef displayPlatformDispose
displayPlatformDispose();
#endif
// For now, we just return an OK error.
errorOk();
}
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/displayplatform.h"
// Expecting some definitions to be provided
#ifndef DUSK_DISPLAY_SIZE_DYNAMIC
#ifndef DUSK_DISPLAY_WIDTH
#error "DUSK_DISPLAY_WIDTH must be defined."
#endif
#ifndef DUSK_DISPLAY_HEIGHT
#error "DUSK_DISPLAY_HEIGHT must be defined"
#endif
#define DUSK_DISPLAY_WIDTH_DEFAULT DUSK_DISPLAY_WIDTH
#define DUSK_DISPLAY_HEIGHT_DEFAULT DUSK_DISPLAY_HEIGHT
#else
#ifndef DUSK_DISPLAY_WIDTH_DEFAULT
#error "DUSK_DISPLAY_WIDTH_DEFAULT must be defined."
#endif
#ifndef DUSK_DISPLAY_HEIGHT_DEFAULT
#error "DUSK_DISPLAY_HEIGHT_DEFAULT must be defined."
#endif
#ifdef DUSK_DISPLAY_WIDTH
#error "DUSK_DISPLAY_WIDTH should not be defined."
#endif
#ifdef DUSK_DISPLAY_HEIGHT
#error "DUSK_DISPLAY_HEIGHT should not be defined."
#endif
#endif
// Main Display Struct, platform-speicifc
typedef displayplatform_t display_t;
extern display_t DISPLAY;
/**
* Initializes the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayInit(void);
/**
* Tells the display system to actually draw the frame.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayUpdate(void);
/**
* Sets the display state.
*
* @param state The state to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySetState(displaystate_t state);
/**
* Disposes of the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayDispose(void);
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define DISPLAY_STATE_FLAG_CULL (1 << 0)
#define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1)
#define DISPLAY_STATE_FLAG_BLEND (1 << 2)
typedef struct {
uint8_t flags;
} displaystate_t;
+1 -1
View File
@@ -56,9 +56,9 @@ add_subdirectory(animation)
add_subdirectory(event) add_subdirectory(event)
add_subdirectory(assert) add_subdirectory(assert)
add_subdirectory(asset) add_subdirectory(asset)
add_subdirectory(cutscene)
add_subdirectory(console) add_subdirectory(console)
add_subdirectory(display) add_subdirectory(display)
add_subdirectory(render)
add_subdirectory(log) add_subdirectory(log)
add_subdirectory(engine) add_subdirectory(engine)
add_subdirectory(error) add_subdirectory(error)
+1 -1
View File
@@ -12,6 +12,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(display) # add_subdirectory(display) # disabled: pending rop-based asset loader rewrite
add_subdirectory(locale) add_subdirectory(locale)
add_subdirectory(json) add_subdirectory(json)
+4 -22
View File
@@ -10,33 +10,15 @@
assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = { assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
[ASSET_LOADER_TYPE_NULL] = { 0 }, [ASSET_LOADER_TYPE_NULL] = { 0 },
[ASSET_LOADER_TYPE_MESH] = {
.loadSync = assetMeshLoaderSync,
.loadAsync = assetMeshLoaderAsync,
.dispose = assetMeshDispose
},
[ASSET_LOADER_TYPE_TEXTURE] = {
.loadSync = assetTextureLoaderSync,
.loadAsync = assetTextureLoaderAsync,
.dispose = assetTextureDispose
},
[ASSET_LOADER_TYPE_TILESET] = {
.loadSync = assetTilesetLoaderSync,
.loadAsync = assetTilesetLoaderAsync,
.dispose = assetTilesetDispose
},
[ASSET_LOADER_TYPE_LOCALE] = { [ASSET_LOADER_TYPE_LOCALE] = {
.loadSync = assetLocaleLoaderSync, .loadSync = assetLocaleLoaderSync,
.loadAsync = assetLocaleLoaderAsync, .loadAsync = assetLocaleLoaderAsync,
.dispose = assetLocaleDispose .dispose = assetLocaleDispose
}, },
[ASSET_LOADER_TYPE_JSON] = { [ASSET_LOADER_TYPE_JSON] = {
.loadSync = assetJsonLoaderSync, .loadSync = assetJsonLoaderSync,
.loadAsync = assetJsonLoaderAsync, .loadAsync = assetJsonLoaderAsync,
.dispose = assetJsonDispose .dispose = assetJsonDispose
}, },
}; };
+3 -20
View File
@@ -6,46 +6,29 @@
*/ */
#pragma once #pragma once
#include "asset/loader/display/assetmeshloader.h"
#include "asset/loader/display/assettextureloader.h"
#include "asset/loader/display/assettilesetloader.h"
#include "asset/loader/locale/assetlocaleloader.h" #include "asset/loader/locale/assetlocaleloader.h"
#include "asset/loader/json/assetjsonloader.h" #include "asset/loader/json/assetjsonloader.h"
typedef enum { typedef enum {
ASSET_LOADER_TYPE_NULL, ASSET_LOADER_TYPE_NULL,
ASSET_LOADER_TYPE_MESH,
ASSET_LOADER_TYPE_TEXTURE,
ASSET_LOADER_TYPE_TILESET,
ASSET_LOADER_TYPE_LOCALE, ASSET_LOADER_TYPE_LOCALE,
ASSET_LOADER_TYPE_JSON, ASSET_LOADER_TYPE_JSON,
ASSET_LOADER_TYPE_COUNT ASSET_LOADER_TYPE_COUNT
} assetloadertype_t; } assetloadertype_t;
typedef union { typedef union {
assetmeshloaderinput_t mesh;
assettextureloaderinput_t texture;
assettilesetloaderinput_t tileset;
assetlocaleloaderinput_t locale; assetlocaleloaderinput_t locale;
assetjsonloaderinput_t json; assetjsonloaderinput_t json;
} assetloaderinput_t; } assetloaderinput_t;
typedef union { typedef union {
assetmeshloaderloading_t mesh;
assettextureloaderloading_t texture;
assettilesetloaderloading_t tileset;
assetlocaleloaderloading_t locale; assetlocaleloaderloading_t locale;
assetjsonloaderloading_t json; assetjsonloaderloading_t json;
} assetloaderloading_t; } assetloaderloading_t;
typedef union { typedef union {
assetmeshoutput_t mesh;
assettextureoutput_t texture;
assettilesetoutput_t tileset;
assetlocaleoutput_t locale; assetlocaleoutput_t locale;
assetjsonoutput_t json; assetjsonoutput_t json;
} assetloaderoutput_t; } assetloaderoutput_t;
typedef struct assetloading_s assetloading_t; typedef struct assetloading_s assetloading_t;
+16 -29
View File
@@ -12,9 +12,6 @@
#include "input/input.h" #include "input/input.h"
#include "log/log.h" #include "log/log.h"
#include "engine/engine.h" #include "engine/engine.h"
#include "display/shader/shaderunlit.h"
#include "display/text/text.h"
#include "display/spritebatch/spritebatch.h"
console_t CONSOLE; console_t CONSOLE;
@@ -22,9 +19,9 @@ void consoleInit(void) {
memoryZero(&CONSOLE, sizeof(console_t)); memoryZero(&CONSOLE, sizeof(console_t));
CONSOLE.visible = true; CONSOLE.visible = true;
#ifdef DUSK_CONSOLE_POSIX #ifdef DUSK_CONSOLE_POSIX
threadMutexInit(&CONSOLE.printMutex); threadMutexInit(&CONSOLE.printMutex);
#endif #endif
} }
void consolePrint(const char_t *message, ...) { void consolePrint(const char_t *message, ...) {
@@ -35,9 +32,9 @@ void consolePrint(const char_t *message, ...) {
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args); int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
va_end(args); va_end(args);
#ifdef DUSK_CONSOLE_POSIX #ifdef DUSK_CONSOLE_POSIX
threadMutexLock(&CONSOLE.printMutex); threadMutexLock(&CONSOLE.printMutex);
#endif #endif
memoryMove( memoryMove(
CONSOLE.line[0], CONSOLE.line[0],
@@ -46,17 +43,17 @@ void consolePrint(const char_t *message, ...) {
); );
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1); memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
#ifdef DUSK_CONSOLE_POSIX #ifdef DUSK_CONSOLE_POSIX
threadMutexUnlock(&CONSOLE.printMutex); threadMutexUnlock(&CONSOLE.printMutex);
#endif #endif
logDebug("%s\n", buffer); logDebug("%s\n", buffer);
} }
void consoleUpdate(void) { void consoleUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC #ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) return; if(TIME.dynamicUpdate) return;
#endif #endif
if(inputPressed(INPUT_ACTION_CONSOLE)) { if(inputPressed(INPUT_ACTION_CONSOLE)) {
CONSOLE.visible = !CONSOLE.visible; CONSOLE.visible = !CONSOLE.visible;
@@ -64,21 +61,11 @@ void consoleUpdate(void) {
} }
errorret_t consoleDraw(void) { errorret_t consoleDraw(void) {
if(!CONSOLE.visible) errorOk(); errorOk();
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
errorChain(textDraw(
0, FONT_DEFAULT.tileset->tileHeight * i,
CONSOLE.line[i],
COLOR_RED,
&FONT_DEFAULT
));
}
return spriteBatchFlush();
} }
void consoleDispose(void) { void consoleDispose(void) {
#ifdef DUSK_CONSOLE_POSIX #ifdef DUSK_CONSOLE_POSIX
threadMutexDispose(&CONSOLE.printMutex); threadMutexDispose(&CONSOLE.printMutex);
#endif #endif
} }
-96
View File
@@ -1,96 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "cutscene.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#include "time/time.h"
cutscene_t CUTSCENE;
errorret_t cutsceneInit(void) {
memoryZero(&CUTSCENE, sizeof(cutscene_t));
errorOk();
}
errorret_t cutsceneUpdate(void) {
#ifdef DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) {
errorOk();
}
#endif
if(!CUTSCENE.active) errorOk();
cutsceneevent_t *event = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(event->onUpdate) errorChain(event->onUpdate());
errorOk();
}
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
) {
assertNotNull(events, "Events cannot be null");
assertTrue(eventCount > 0, "Event count must be greater than zero");
assertTrue(
eventCount <= CUTSCENE_EVENT_COUNT_MAX,
"Event count exceeds CUTSCENE_EVENT_COUNT_MAX"
);
if(CUTSCENE.active) {
errorChain(cutsceneStop());
}
memoryCopy(CUTSCENE.events, events, sizeof(cutsceneevent_t) * eventCount);
CUTSCENE.eventCount = eventCount;
CUTSCENE.eventCurrent = 0;
CUTSCENE.active = true;
cutsceneevent_t *firstEvent = &CUTSCENE.events[0];
if(firstEvent->onStart) errorChain(firstEvent->onStart());
errorOk();
}
errorret_t cutsceneAdvance(void) {
if(!CUTSCENE.active) errorOk();
cutsceneevent_t *currentEvent = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(currentEvent->onEnd) errorChain(currentEvent->onEnd());
CUTSCENE.eventCurrent++;
if(CUTSCENE.eventCurrent >= CUTSCENE.eventCount) {
if(CUTSCENE.onStop) errorChain(CUTSCENE.onStop());
CUTSCENE.active = false;
errorOk();
}
cutsceneevent_t *nextEvent = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(nextEvent->onStart) errorChain(nextEvent->onStart());
consolePrint("Cutscene advance");
errorOk();
}
errorret_t cutsceneStop(void) {
if(!CUTSCENE.active) errorOk();
cutsceneevent_t *currentEvent = &CUTSCENE.events[CUTSCENE.eventCurrent];
if(currentEvent->onEnd) errorChain(currentEvent->onEnd());
if(CUTSCENE.onStop) errorChain(CUTSCENE.onStop());
CUTSCENE.active = false;
errorOk();
}
errorret_t cutsceneDispose(void) {
errorChain(cutsceneStop());
errorOk();
}
bool_t cutsceneIsActive(void) {
return CUTSCENE.active;
}
-85
View File
@@ -1,85 +0,0 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "error/error.h"
#define CUTSCENE_EVENT_COUNT_MAX 16
typedef struct {
errorret_t (*onStart)(void);
errorret_t (*onEnd)(void);
errorret_t (*onUpdate)(void);
} cutsceneevent_t;
typedef struct {
cutsceneevent_t events[CUTSCENE_EVENT_COUNT_MAX];
uint8_t eventCount;
uint8_t eventCurrent;
errorret_t (*onStop)(void);
bool_t active;
} cutscene_t;
extern cutscene_t CUTSCENE;
/**
* Initializes the cutscene manager.
*
* @return Any error state that happened.
*/
errorret_t cutsceneInit(void);
/**
* Ticks the active cutscene event, calling its onUpdate callback.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneUpdate(void);
/**
* Copies the given event array and begins playing from the first
* event. If a cutscene is already playing it is stopped first.
*
* @param events Array of events to copy.
* @param eventCount Number of events. Must be > 0 and
* <= CUTSCENE_EVENT_COUNT_MAX.
* @return Any error state that happened.
*/
errorret_t cutscenePlay(
const cutsceneevent_t *events,
const uint8_t eventCount
);
/**
* Ends the current event and starts the next one.
* Marks the cutscene as inactive after the last event ends.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneAdvance(void);
/**
* Ends the current event and stops the cutscene immediately.
* Does nothing when no cutscene is playing.
*
* @return Any error state that happened.
*/
errorret_t cutsceneStop(void);
/**
* Disposes of the cutscene manager, stopping any active cutscene.
*
* @return Any error state that happened.
*/
errorret_t cutsceneDispose(void);
/**
* Returns whether a cutscene is currently playing.
*
* @return true if a cutscene is active.
*/
bool_t cutsceneIsActive(void);
+1 -12
View File
@@ -1,24 +1,13 @@
# Copyright (c) 2025 Dominic Masters # Copyright (c) 2026 Dominic Masters
# #
# 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
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
display.c display.c
) )
# Subdirectories
add_subdirectory(framebuffer)
add_subdirectory(mesh)
add_subdirectory(screen)
add_subdirectory(shader)
add_subdirectory(spritebatch)
add_subdirectory(text)
add_subdirectory(texture)
# Color definitions
dusk_run_python( dusk_run_python(
dusk_color_defs dusk_color_defs
tools.color.csv tools.color.csv
+14 -90
View File
@@ -6,111 +6,35 @@
*/ */
#include "display/display.h" #include "display/display.h"
#include "display/framebuffer/framebuffer.h" #include "render/ropbuffer.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "display/spritebatch/spritebatch.h"
#include "display/mesh/quad.h"
#include "display/mesh/cube.h"
#include "display/mesh/sphere.h"
#include "display/mesh/plane.h"
#include "display/mesh/capsule.h"
#include "display/mesh/triprism.h"
#include "display/screen/screen.h"
#include "ui/ui.h"
#include "display/text/text.h"
#include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/string.h"
#include "asset/asset.h"
#include "display/shader/shaderlist.h"
#include "time/time.h"
display_t DISPLAY = { 0 }; display_t DISPLAY = { 0 };
errorret_t displayInit(void) { errorret_t displayInit(void) {
memoryZero(&DISPLAY, sizeof(DISPLAY)); memoryZero(&DISPLAY, sizeof(DISPLAY));
#ifdef displayPlatformInit
#ifdef displayPlatformInit errorChain(displayPlatformInit());
errorChain(displayPlatformInit()); #endif
#endif
errorChain(displaySetState((displaystate_t){ .flags = 0 }));
errorChain(textureInit(
&TEXTURE_WHITE, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_WHITE_PIXELS }
));
errorChain(textureInit(
&TEXTURE_TEST, 4, 4,
TEXTURE_FORMAT_RGBA, (texturedata_t){ .rgbaColors = TEXTURE_TEST_PIXELS }
));
// Standard meshes
errorChain(quadInit());
errorChain(cubeInit());
errorChain(sphereInit());
errorChain(planeInit());
errorChain(capsuleInit());
errorChain(triPrismInit());
errorChain(frameBufferInitBackBuffer());
errorChain(spriteBatchInit());
errorChain(textInit());
errorChain(screenInit());
// Setup initial shader with default values
errorChain(shaderListInit());
errorOk(); errorOk();
} }
errorret_t displayUpdate(void) { errorret_t displayUpdate(void) {
#ifdef displayPlatformUpdate ropBufferReset(&ROPBUFFER);
errorChain(displayPlatformUpdate());
#endif
// Reset state
spriteBatchClear();
errorChain(frameBufferBind(NULL));
// Bind screen and render scene
errorChain(screenBind());
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
SCREEN.background
);
errorChain(sceneRender()); errorChain(sceneRender());
#ifdef displayPlatformFlush
// Finish up errorChain(displayPlatformFlush(&ROPBUFFER));
screenUnbind(); #endif
screenRender(); #ifdef displayPlatformSwap
errorChain(displayPlatformSwap());
// Swap and return. #endif
#ifdef displayPlatformSwap
errorChain(displayPlatformSwap());
#endif
errorOk();
}
errorret_t displaySetState(displaystate_t state) {
#ifdef displayPlatformSetState
errorChain(displayPlatformSetState(state));
#endif
errorOk(); errorOk();
} }
errorret_t displayDispose(void) { errorret_t displayDispose(void) {
errorChain(shaderListDispose()); #ifdef displayPlatformDispose
errorChain(spriteBatchDispose()); displayPlatformDispose();
screenDispose(); #endif
errorChain(textDispose());
errorChain(textureDispose(&TEXTURE_WHITE));
errorChain(textureDispose(&TEXTURE_TEST));
#ifdef displayPlatformDispose
displayPlatformDispose();
#endif
// For now, we just return an OK error.
errorOk(); errorOk();
} }
+1 -48
View File
@@ -7,59 +7,12 @@
#pragma once #pragma once
#include "display/displayplatform.h" #include "display/displayplatform.h"
#include "display/displaystate.h"
// Expecting some definitions to be provided
#ifndef DUSK_DISPLAY_SIZE_DYNAMIC
#ifndef DUSK_DISPLAY_WIDTH
#error "DUSK_DISPLAY_WIDTH must be defined."
#endif
#ifndef DUSK_DISPLAY_HEIGHT
#error "DUSK_DISPLAY_HEIGHT must be defined"
#endif
#define DUSK_DISPLAY_WIDTH_DEFAULT DUSK_DISPLAY_WIDTH
#define DUSK_DISPLAY_HEIGHT_DEFAULT DUSK_DISPLAY_HEIGHT
#else
#ifndef DUSK_DISPLAY_WIDTH_DEFAULT
#error "DUSK_DISPLAY_WIDTH_DEFAULT must be defined."
#endif
#ifndef DUSK_DISPLAY_HEIGHT_DEFAULT
#error "DUSK_DISPLAY_HEIGHT_DEFAULT must be defined."
#endif
#ifdef DUSK_DISPLAY_WIDTH
#error "DUSK_DISPLAY_WIDTH should not be defined."
#endif
#ifdef DUSK_DISPLAY_HEIGHT
#error "DUSK_DISPLAY_HEIGHT should not be defined."
#endif
#endif
// Main Display Struct, platform-speicifc
typedef displayplatform_t display_t; typedef displayplatform_t display_t;
extern display_t DISPLAY; extern display_t DISPLAY;
/**
* Initializes the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayInit(void); errorret_t displayInit(void);
/**
* Tells the display system to actually draw the frame.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayUpdate(void); errorret_t displayUpdate(void);
/**
* Sets the display state.
*
* @param state The state to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t displaySetState(displaystate_t state);
/**
* Disposes of the display system.
* @return An errorret_t indicating success or failure.
*/
errorret_t displayDispose(void); errorret_t displayDispose(void);
+2 -2
View File
@@ -8,9 +8,9 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
#define DISPLAY_STATE_FLAG_CULL (1 << 0) #define DISPLAY_STATE_FLAG_CULL (1 << 0)
#define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1) #define DISPLAY_STATE_FLAG_DEPTH_TEST (1 << 1)
#define DISPLAY_STATE_FLAG_BLEND (1 << 2) #define DISPLAY_STATE_FLAG_BLEND (1 << 2)
typedef struct { typedef struct {
uint8_t flags; uint8_t flags;
+4 -35
View File
@@ -9,19 +9,12 @@
#include "util/memory.h" #include "util/memory.h"
#include "time/time.h" #include "time/time.h"
#include "input/input.h" #include "input/input.h"
#include "locale/localemanager.h"
#include "rpg/rpg.h"
#include "display/display.h" #include "display/display.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "cutscene/cutscene.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "ui/ui.h"
#include "ui/uitextbox.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "network/network.h"
#include "system/system.h" #include "system/system.h"
#include "console/console.h" #include "console/console.h"
#include "save/save.h"
engine_t ENGINE; engine_t ENGINE;
@@ -29,48 +22,32 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
assertInit(); assertInit();
memoryZero(&ENGINE, sizeof(engine_t)); memoryZero(&ENGINE, sizeof(engine_t));
ENGINE.running = true; ENGINE.running = true;
ENGINE.argc = argc; ENGINE.argc = argc;
ENGINE.argv = argv; ENGINE.argv = argv;
ENGINE.version = DUSK_VERSION; ENGINE.version = DUSK_VERSION;
// Init systems. Order is important.
errorChain(systemInit()); errorChain(systemInit());
timeInit(); timeInit();
consoleInit(); consoleInit();
errorChain(inputInit()); errorChain(inputInit());
errorChain(assetInit()); errorChain(assetInit());
// errorChain(saveInit());
errorChain(localeManagerInit());
errorChain(displayInit()); errorChain(displayInit());
errorChain(uiInit());
errorChain(uiTextboxInit());
errorChain(cutsceneInit());
errorChain(rpgInit());
errorChain(networkInit());
errorChain(sceneInit()); errorChain(sceneInit());
consolePrint("Engine initialized"); consolePrint("Engine initialized");
sceneSet(SCENE_TYPE_OVERWORLD); sceneSet(SCENE_TYPE_TEST);
errorOk(); errorOk();
} }
errorret_t engineUpdate(void) { errorret_t engineUpdate(void) {
// Order here is important.
errorChain(networkUpdate());
timeUpdate(); timeUpdate();
inputUpdate(); inputUpdate();
consoleUpdate(); consoleUpdate();
errorChain(rpgUpdate());
uiUpdate();
errorChain(uiTextboxUpdate());
errorChain(cutsceneUpdate());
errorChain(sceneUpdate()); errorChain(sceneUpdate());
errorChain(assetUpdate()); errorChain(assetUpdate());
// Render
errorChain(displayUpdate()); errorChain(displayUpdate());
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false; if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
errorOk(); errorOk();
} }
@@ -80,17 +57,9 @@ void engineExit(void) {
} }
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
uiTextboxDispose();
cutsceneDispose();
errorChain(sceneDispose()); errorChain(sceneDispose());
errorChain(networkDispose());
errorChain(rpgDispose());
localeManagerDispose();
uiDispose();
consoleDispose(); consoleDispose();
errorChain(displayDispose()); errorChain(displayDispose());
// errorChain(saveDispose());
errorChain(assetDispose()); errorChain(assetDispose());
errorOk(); errorOk();
} }
+3 -20
View File
@@ -6,35 +6,18 @@
*/ */
#pragma once #pragma once
// Important to be included first:
#include "display/display.h"
#include "error/error.h" #include "error/error.h"
typedef struct { typedef struct {
bool_t running; bool_t running;
int32_t argc; int32_t argc;
const char_t **argv; const char_t **argv;
const char_t *version; const char_t *version;
} engine_t; } engine_t;
extern engine_t ENGINE; extern engine_t ENGINE;
/**
* Initializes the engine.
*
* @param argc The argument count from main().
* @param argv The argument vector from main().
*/
errorret_t engineInit(const int32_t argc, const char_t **argv); errorret_t engineInit(const int32_t argc, const char_t **argv);
/**
* Updates the engine.
*/
errorret_t engineUpdate(void); errorret_t engineUpdate(void);
void engineExit(void);
/**
* Shuts down the engine.
*/
errorret_t engineDispose(void); errorret_t engineDispose(void);
@@ -3,8 +3,8 @@
# 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
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
texturegl.c ropbuffer.c
render.c
) )
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "render/render.h"
void renderClear(color_t color) {
ropclear_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_CLEAR);
cmd->color = color;
}
void renderSprite(
int16_t x, int16_t y,
int16_t w, int16_t h,
color_t tint
) {
ropsprite_t *cmd = ropBufferAlloc(&ROPBUFFER, ROP_DRAW_SPRITE);
cmd->x = x;
cmd->y = y;
cmd->w = w;
cmd->h = h;
cmd->uvX = 0;
cmd->uvY = 0;
cmd->uvW = 255;
cmd->uvH = 255;
cmd->tint = tint;
}
+18
View File
@@ -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 "render/rop.h"
#include "render/ropbuffer.h"
void renderClear(color_t color);
void renderSprite(
int16_t x, int16_t y,
int16_t w, int16_t h,
color_t tint
);
+53
View File
@@ -0,0 +1,53 @@
/**
* 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");
+25
View File
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "render/ropbuffer.h"
#include "util/memory.h"
#include "assert/assert.h"
ropbuffer_t ROPBUFFER;
void ropBufferReset(ropbuffer_t *buf) {
buf->count = 0;
}
void *ropBufferAlloc(ropbuffer_t *buf, ropop_t op) {
assertTrue(buf->count < ROPBUFFER_MAX_COMMANDS, "ROP buffer is full");
uint8_t *ptr = buf->data + (buf->count * ROP_SIZE);
memoryZero(ptr, ROP_SIZE);
((ropheader_t *)ptr)->op = (uint8_t)op;
buf->count++;
return ptr;
}
+21
View File
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "render/rop.h"
#define ROPBUFFER_MAX_COMMANDS 4096
typedef struct {
uint8_t data[ROPBUFFER_MAX_COMMANDS * ROP_SIZE];
uint32_t count;
} ropbuffer_t;
extern ropbuffer_t ROPBUFFER;
void ropBufferReset(ropbuffer_t *buf);
void *ropBufferAlloc(ropbuffer_t *buf, ropop_t op);
-1
View File
@@ -15,6 +15,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
add_subdirectory(cutscene) add_subdirectory(cutscene)
add_subdirectory(entity) add_subdirectory(entity)
add_subdirectory(overworld) add_subdirectory(overworld)
add_subdirectory(story) add_subdirectory(story)
add_subdirectory(item) add_subdirectory(item)
+5 -15
View File
@@ -8,26 +8,16 @@
#pragma once #pragma once
#include "rpg/overworld/tile.h" #include "rpg/overworld/tile.h"
#include "worldpos.h" #include "worldpos.h"
#include "display/mesh/quad.h" #include "display/color.h"
#include "display/spritebatch/spritebatch.h"
// #define CHUNK_MESH_COUNT_MAX 3
#define CHUNK_VERTEX_COUNT (QUAD_VERTEX_COUNT * CHUNK_TILE_COUNT)
#define CHUNK_ENTITY_COUNT_MAX 10 #define CHUNK_ENTITY_COUNT_MAX 10
typedef struct chunk_s { typedef struct chunk_s {
chunkpos_t position; chunkpos_t position;
tile_t tiles[CHUNK_TILE_COUNT]; tile_t tiles[CHUNK_TILE_COUNT];
color_t testColor;
meshvertex_t vertices[CHUNK_VERTEX_COUNT]; uint32_t vertCount;
uint32_t vertCount; uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
mesh_t mesh;
color_t testColor;
// uint8_t meshCount;
// meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
// mesh_t meshes[CHUNK_MESH_COUNT_MAX];
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
} chunk_t; } chunk_t;
/** /**
+10 -151
View File
@@ -17,18 +17,6 @@ map_t MAP;
errorret_t mapInit() { errorret_t mapInit() {
memoryZero(&MAP, sizeof(map_t)); memoryZero(&MAP, sizeof(map_t));
// Setup chunk meshes
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
chunk_t *chunk = &MAP.chunks[i];
errorChain(meshInit(
&chunk->mesh,
MESH_PRIMITIVE_TYPE_TRIANGLES,
CHUNK_VERTEX_COUNT,
chunk->vertices
));
}
// Perform "initial load"
MAP.loaded = true; MAP.loaded = true;
int32_t i = 0; int32_t i = 0;
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
@@ -52,61 +40,6 @@ bool_t mapIsLoaded() {
return MAP.loaded; return MAP.loaded;
} }
// errorret_t mapLoad(const char_t *path, const chunkpos_t position) {
// assertStrLenMin(path, 1, "Map file path cannot be empty");
// assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long");
// if(stringCompare(MAP.filePath, path) == 0) {
// // Same map, no need to reload
// errorOk();
// }
// chunkindex_t i;
// // Unload all loaded chunks
// if(mapIsLoaded()) {
// for(i = 0; i < MAP_CHUNK_COUNT; i++) {
// mapChunkUnload(&MAP.chunks[i]);
// }
// }
// // Store the map file path
// stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX);
// // Determine directory path (it is dirname)
// stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX);
// char_t *last = stringFindLastChar(MAP.dirPath, '/');
// if(last == NULL) errorThrow("Invalid map file path");
// // Store filename, sans extension
// stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX);
// *last = '\0'; // Terminate to get directory path
// last = stringFindLastChar(MAP.fileName, '.');
// if(last == NULL) errorThrow("Map file name has no extension");
// *last = '\0'; // Terminate to remove extension
// // Reset map position
// MAP.chunkPosition = position;
// // Perform "initial load"
// i = 0;
// for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
// for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
// for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
// chunk_t *chunk = &MAP.chunks[i];
// chunk->position.x = x + position.x;
// chunk->position.y = y + position.y;
// chunk->position.z = z + position.z;
// MAP.chunkOrder[i] = chunk;
// errorChain(mapChunkLoad(chunk));
// i++;
// }
// }
// }
// errorOk();
// }
errorret_t mapPositionSet(const chunkpos_t newPos) { errorret_t mapPositionSet(const chunkpos_t newPos) {
if(!mapIsLoaded()) errorThrow("No map loaded"); if(!mapIsLoaded()) errorThrow("No map loaded");
@@ -115,43 +48,31 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
errorOk(); errorOk();
} }
// Determine which chunks remain loaded
chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0}; chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0};
chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0}; chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0};
uint32_t remainingCount = 0; uint32_t remainingCount = 0;
uint32_t freedCount = 0; uint32_t freedCount = 0;
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
// Will this chunk remain loaded?
chunk_t *chunk = &MAP.chunks[i]; chunk_t *chunk = &MAP.chunks[i];
if( if(
chunk->position.x >= newPos.x && chunk->position.x >= newPos.x &&
chunk->position.x < newPos.x + MAP_CHUNK_WIDTH && chunk->position.x < newPos.x + MAP_CHUNK_WIDTH &&
chunk->position.y >= newPos.y && chunk->position.y >= newPos.y &&
chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT && chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT &&
chunk->position.z >= newPos.z && chunk->position.z >= newPos.z &&
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
) { ) {
// Stays loaded
chunksRemaining[remainingCount++] = i; chunksRemaining[remainingCount++] = i;
continue; continue;
} }
// Not remaining loaded
chunksFreed[freedCount++] = i; chunksFreed[freedCount++] = i;
} }
// Unload the freed chunks
for(chunkindex_t i = 0; i < freedCount; i++) { for(chunkindex_t i = 0; i < freedCount; i++) {
chunk_t *chunk = &MAP.chunks[chunksFreed[i]]; mapChunkUnload(&MAP.chunks[chunksFreed[i]]);
mapChunkUnload(chunk);
} }
// This can probably be optimized later, for now we check each chunk and see
// if it needs loading or not, and update the chunk order
chunkindex_t orderIndex = 0; chunkindex_t orderIndex = 0;
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) { for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) { for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
@@ -160,7 +81,6 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
}; };
// Is this chunk already loaded (was not unloaded earlier)?
chunkindex_t chunkIndex = -1; chunkindex_t chunkIndex = -1;
for(chunkindex_t i = 0; i < remainingCount; i++) { for(chunkindex_t i = 0; i < remainingCount; i++) {
chunk_t *chunk = &MAP.chunks[chunksRemaining[i]]; chunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
@@ -169,9 +89,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
break; break;
} }
// Need to load this chunk
if(chunkIndex == -1) { if(chunkIndex == -1) {
// Find a freed chunk to reuse
chunkIndex = chunksFreed[--freedCount]; chunkIndex = chunksFreed[--freedCount];
chunk_t *chunk = &MAP.chunks[chunkIndex]; chunk_t *chunk = &MAP.chunks[chunkIndex];
chunk->position = newChunkPos; chunk->position = newChunkPos;
@@ -183,25 +101,21 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
} }
} }
// Update map position
MAP.chunkPosition = newPos; MAP.chunkPosition = newPos;
errorOk(); errorOk();
} }
void mapUpdate() { void mapUpdate() {
} }
errorret_t mapDispose() { errorret_t mapDispose() {
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
mapChunkUnload(&MAP.chunks[i]); mapChunkUnload(&MAP.chunks[i]);
errorChain(meshDispose(&MAP.chunks[i].mesh));
} }
errorOk(); errorOk();
} }
void mapChunkUnload(chunk_t* chunk) { void mapChunkUnload(chunk_t *chunk) {
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
if(chunk->entities[i] == 0xFF) break; if(chunk->entities[i] == 0xFF) break;
entity_t *entity = &ENTITIES[chunk->entities[i]]; entity_t *entity = &ENTITIES[chunk->entities[i]];
@@ -210,76 +124,21 @@ void mapChunkUnload(chunk_t* chunk) {
chunk->vertCount = 0; chunk->vertCount = 0;
} }
errorret_t mapChunkLoad(chunk_t* chunk) { errorret_t mapChunkLoad(chunk_t *chunk) {
if(!mapIsLoaded()) errorThrow("No map loaded"); if(!mapIsLoaded()) errorThrow("No map loaded");
color_t color = COLOR_WHITE; color_t color = COLOR_WHITE;
if(chunk->position.y % 2 == 0) { if(chunk->position.y % 2 == 0) {
if(chunk->position.x % 2 == 0) { color = (chunk->position.x % 2 == 0) ? COLOR_BLACK : COLOR_WHITE;
color = COLOR_BLACK;
} else {
color = COLOR_WHITE;
}
} else { } else {
if(chunk->position.x % 2 == 0) { color = (chunk->position.x % 2 == 0) ? COLOR_WHITE : COLOR_BLACK;
color = COLOR_WHITE;
} else {
color = COLOR_BLACK;
}
} }
// if(chunk->position.x == 0 && chunk->position.y == 0 && chunk->position.z == 0) {
// color = COLOR_RED;
// }
chunk->testColor = color; chunk->testColor = color;
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
chunk->vertCount = 0; chunk->vertCount = 0;
if(chunk->position.z != 0) {
errorOk();
}
// Set Chunk sprites.
vec3 spriteMin = {
chunk->position.x * CHUNK_WIDTH,
chunk->position.y * CHUNK_HEIGHT,
chunk->position.z * CHUNK_DEPTH
};
spritebatchsprite_t sprites[CHUNK_TILE_COUNT];
uint32_t i = 0;
for(uint8_t x = 0; x < CHUNK_WIDTH; x++) {
for(uint8_t y = 0; y < CHUNK_HEIGHT; y++) {
glm_vec3_copy(spriteMin, sprites[i].min);
glm_vec3_add(
sprites[i].min,
(vec3){ x, y, 0 },
sprites[i].min
);
glm_vec3_copy(sprites[i].min, sprites[i].max);
glm_vec3_add(
sprites[i].max,
(vec3){ 1, 1, 0 },
sprites[i].max
);
glm_vec2_copy((vec2){ 0, 0 }, sprites[i].uvMin);
glm_vec2_copy((vec2){ 1, 1 }, sprites[i].uvMax);
i++;
}
}
chunk->vertCount = i * QUAD_VERTEX_COUNT;
spriteBatchBufferToMesh(
sprites,
i,
chunk->vertices,
chunk->vertCount
);
errorChain(meshFlush(&chunk->mesh, 0, chunk->vertCount));
errorOk(); errorOk();
} }
@@ -304,7 +163,7 @@ chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
return chunkPosToIndex(&relPos); return chunkPosToIndex(&relPos);
} }
chunk_t* mapGetChunk(const uint8_t index) { chunk_t *mapGetChunk(const uint8_t index) {
if(index >= MAP_CHUNK_COUNT) return NULL; if(index >= MAP_CHUNK_COUNT) return NULL;
if(!mapIsLoaded()) return NULL; if(!mapIsLoaded()) return NULL;
return MAP.chunkOrder[index]; return MAP.chunkOrder[index];
+1
View File
@@ -6,6 +6,7 @@
*/ */
#pragma once #pragma once
#include "error/error.h"
#include "rpg/overworld/chunk.h" #include "rpg/overworld/chunk.h"
#define MAP_FILE_PATH_MAX 128 #define MAP_FILE_PATH_MAX 128
+1
View File
@@ -11,3 +11,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(overworld) add_subdirectory(overworld)
add_subdirectory(test)
+5 -180
View File
@@ -5,199 +5,24 @@
* https://opensource.org/licenses/MIT * https://opensource.org/licenses/MIT
*/ */
#include "scene/scene.h" #include "scene/overworld/sceneoverworld.h"
#include "console/console.h"
#include "assert/assert.h"
#include "display/shader/shader.h"
#include "display/screen/screen.h"
#include "display/shader/shaderunlit.h"
#include "display/spritebatch/spritebatch.h"
#include "rpg/overworld/map.h"
#include "rpg/entity/entity.h"
#include "rpg/rpgcamera.h"
#include "util/math.h"
errorret_t sceneOverworldInit(scenedata_t *sceneData) { errorret_t sceneOverworldInit(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null"); (void)sceneData;
errorOk(); errorOk();
} }
errorret_t sceneOverworldUpdate(scenedata_t *sceneData) { errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null"); (void)sceneData;
errorOk(); errorOk();
} }
errorret_t sceneOverworldRender(scenedata_t *sceneData) { errorret_t sceneOverworldRender(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null"); (void)sceneData;
mat4 proj, model, eye;
errorChain(shaderBind(&SHADER_UNLIT));
// Model
glm_mat4_identity(model);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// Camera projection
float_t fov = glm_rad(45.0f);
glm_perspective(
fov,
(float_t)SCREEN.width / (float_t)SCREEN.height,
0.1f,
100.0f,
proj
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
// Camera view
{
vec3 target = { 0.5f, 0.5f, 0.5f };
if(RPG_CAMERA.mode == RPG_CAMERA_MODE_FOLLOW_ENTITY) {
entity_t *followed = &ENTITIES[RPG_CAMERA.followEntity.followEntityId];
if(followed->type != ENTITY_TYPE_NULL) {
float_t walkT = followed->animation == ENTITY_ANIM_WALK
? fixedToFloat(
fixedDiv(followed->animTime, ENTITY_ANIM_WALK_DURATION)
)
: 0.0f;
target[0] = mathLerp(
fixedToFloat(followed->position[0]),
fixedToFloat(followed->lastPosition[0]),
walkT
) + 0.5f;
target[1] = mathLerp(
fixedToFloat(followed->position[1]),
fixedToFloat(followed->lastPosition[1]),
walkT
) + 0.5f;
target[2] = mathLerp(
fixedToFloat(followed->position[2]),
fixedToFloat(followed->lastPosition[2]),
walkT
) + 0.5f;
}
} else {
worldpos_t camPos = rpgCameraGetPosition();
target[0] = (float_t)camPos.x + 0.5f;
target[1] = (float_t)camPos.y + 0.5f;
target[2] = (float_t)camPos.z + 0.5f;
}
float_t pixelsPerUnit = 16.0f;
float_t worldH = (float_t)SCREEN.height / pixelsPerUnit;
float_t eyeZ = (worldH * 0.5f) / tanf(fov * 0.5f);
float_t offset = -16.0f;
glm_lookat(
(vec3){ target[0], target[1] + offset, target[2] + eyeZ },
target,
(vec3){ 0, 1, 0 },
eye
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye));
}
// Chunks
{
shadermaterial_t chunkMaterial = {
.unlit = {
.color = COLOR_WHITE,
.texture = NULL
}
};
uint32_t i = 0;
for(uint8_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
for(uint8_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
for(uint8_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
chunk_t *chunk = &MAP.chunks[i];
if(chunk->vertCount == 0) {
i++;
continue;
}
chunkMaterial.unlit.color = chunk->testColor;
errorChain(shaderSetMaterial(&SHADER_UNLIT, &chunkMaterial));
errorChain(meshDraw(&chunk->mesh, 0, chunk->vertCount));
i++;
}
}
}
}
// Entities
{
uint8_t spriteCount = 0;
spritebatchsprite_t sprites[ENTITY_COUNT];
for(uint8_t i = 0; i < ENTITY_COUNT; i++) {
entity_t *ent = &ENTITIES[i];
if(ent->type == ENTITY_TYPE_NULL) continue;
float_t walkT = ent->animation == ENTITY_ANIM_WALK
? fixedToFloat(fixedDiv(ent->animTime, ENTITY_ANIM_WALK_DURATION))
: 0.0f;
vec3 position = {
mathLerp(
fixedToFloat(ent->position[0]),
fixedToFloat(ent->lastPosition[0]),
walkT
),
mathLerp(
fixedToFloat(ent->position[1]),
fixedToFloat(ent->lastPosition[1]),
walkT
),
mathLerp(
fixedToFloat(ent->position[2]),
fixedToFloat(ent->lastPosition[2]),
walkT
) + 0.01f
};
glm_vec3_copy(position, sprites[spriteCount].min);
glm_vec3_copy(position, sprites[spriteCount].max);
glm_vec3_add(
sprites[spriteCount].max,
(vec3){ 1, 1, 0 },
sprites[spriteCount].max
);
glm_vec2_copy((vec2){ 0, 0 }, sprites[spriteCount].uvMin);
glm_vec2_copy((vec2){ 1, 1 }, sprites[spriteCount].uvMax);
spriteCount++;
}
if(spriteCount) {
shadermaterial_t material = {
.unlit = {
.color = COLOR_CYAN,
.texture = NULL
}
};
// material.unlit.texture = &TEXTURE_TEST;
spriteBatchBuffer(sprites, spriteCount, &SHADER_UNLIT, material);
spriteBatchFlush();
}
}
errorOk(); errorOk();
} }
errorret_t sceneOverworldDispose(scenedata_t *sceneData) { errorret_t sceneOverworldDispose(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null"); (void)sceneData;
errorOk(); errorOk();
} }
+6 -42
View File
@@ -7,12 +7,7 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
#include "time/time.h" #include "time/time.h"
#include "display/screen/screen.h"
#include "display/shader/shaderunlit.h"
#include "display/display.h"
#include "ui/ui.h"
#include "asset/asset.h" #include "asset/asset.h"
#include "asset/loader/assetloader.h"
#include "console/console.h" #include "console/console.h"
scene_t SCENE; scene_t SCENE;
@@ -23,7 +18,6 @@ errorret_t sceneInit(void) {
} }
errorret_t sceneUpdate(void) { errorret_t sceneUpdate(void) {
// Handle scene change.
if(SCENE.next != SCENE_TYPE_NULL) { if(SCENE.next != SCENE_TYPE_NULL) {
if( if(
SCENE.current != SCENE_TYPE_NULL && SCENE.current != SCENE_TYPE_NULL &&
@@ -33,7 +27,7 @@ errorret_t sceneUpdate(void) {
} }
SCENE.current = SCENE.next; SCENE.current = SCENE.next;
SCENE.next = SCENE_TYPE_NULL; SCENE.next = SCENE_TYPE_NULL;
if( if(
SCENE.current != SCENE_TYPE_NULL && SCENE.current != SCENE_TYPE_NULL &&
@@ -43,11 +37,11 @@ errorret_t sceneUpdate(void) {
} }
} }
#if DUSK_TIME_DYNAMIC #if DUSK_TIME_DYNAMIC
if(TIME.dynamicUpdate) { if(TIME.dynamicUpdate) {
errorOk(); errorOk();
} }
#endif #endif
if( if(
SCENE.current != SCENE_TYPE_NULL && SCENE.current != SCENE_TYPE_NULL &&
@@ -60,41 +54,12 @@ errorret_t sceneUpdate(void) {
} }
errorret_t sceneRender(void) { errorret_t sceneRender(void) {
// Scene rendering
if( if(
SCENE.current != SCENE_TYPE_NULL && SCENE.current != SCENE_TYPE_NULL &&
SCENE_TYPES[SCENE.current].render != NULL SCENE_TYPES[SCENE.current].render != NULL
) { ) {
errorChain(SCENE_TYPES[SCENE.current].render(&SCENE.data)); errorChain(SCENE_TYPES[SCENE.current].render(&SCENE.data));
} }
// UI Rendering
mat4 proj, view, ident;
glm_mat4_identity(ident);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, ident));
glm_ortho(
0.0f, SCREEN.width,
SCREEN.height, 0.0f,
0.1f, 100.0f,
proj
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
glm_lookat(
(vec3){ 0.0f, 0.0f, 1.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
view
);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_BLEND
}));
errorChain(uiRender());
errorOk(); errorOk();
} }
@@ -110,6 +75,5 @@ errorret_t sceneDispose(void) {
) { ) {
errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data)); errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data));
} }
errorOk(); errorOk();
} }
+10 -4
View File
@@ -11,10 +11,16 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = {
[SCENE_TYPE_NULL] = { 0 }, [SCENE_TYPE_NULL] = { 0 },
[SCENE_TYPE_OVERWORLD] = { [SCENE_TYPE_OVERWORLD] = {
.init = sceneOverworldInit, .init = sceneOverworldInit,
.update = sceneOverworldUpdate, .update = sceneOverworldUpdate,
.render = sceneOverworldRender, .render = sceneOverworldRender,
.dispose = sceneOverworldDispose .dispose = sceneOverworldDispose
}, },
};
[SCENE_TYPE_TEST] = {
.init = sceneTestInit,
.update = sceneTestUpdate,
.render = sceneTestRender,
.dispose = sceneTestDispose
},
};
+3 -2
View File
@@ -8,9 +8,11 @@
#pragma once #pragma once
#include "scene/scenebase.h" #include "scene/scenebase.h"
#include "scene/overworld/sceneoverworld.h" #include "scene/overworld/sceneoverworld.h"
#include "scene/test/scenetest.h"
typedef union scenedata_u { typedef union scenedata_u {
sceneoverworld_t overworld; sceneoverworld_t overworld;
scenetest_t test;
} scenedata_t; } scenedata_t;
typedef errorret_t (*scenecallback_t)(scenedata_t *); typedef errorret_t (*scenecallback_t)(scenedata_t *);
@@ -24,9 +26,8 @@ typedef struct {
typedef enum { typedef enum {
SCENE_TYPE_NULL, SCENE_TYPE_NULL,
SCENE_TYPE_OVERWORLD, SCENE_TYPE_OVERWORLD,
SCENE_TYPE_TEST,
SCENE_TYPE_COUNT SCENE_TYPE_COUNT
} scenetype_t; } scenetype_t;
@@ -3,8 +3,7 @@
# 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
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
meshgl.c scenetest.c
) )
+32
View File
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scene/test/scenetest.h"
#include "render/render.h"
#include "display/color.h"
errorret_t sceneTestInit(scenedata_t *data) {
(void)data;
errorOk();
}
errorret_t sceneTestUpdate(scenedata_t *data) {
(void)data;
errorOk();
}
errorret_t sceneTestRender(scenedata_t *data) {
(void)data;
renderClear(color(32, 32, 48, 255));
renderSprite(100, 100, 32, 32, COLOR_WHITE);
errorOk();
}
errorret_t sceneTestDispose(scenedata_t *data) {
(void)data;
errorOk();
}
+18
View File
@@ -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 "scene/scenebase.h"
typedef struct {
int32_t unused;
} scenetest_t;
errorret_t sceneTestInit(scenedata_t *data);
errorret_t sceneTestUpdate(scenedata_t *data);
errorret_t sceneTestRender(scenedata_t *data);
errorret_t sceneTestDispose(scenedata_t *data);
+1 -12
View File
@@ -3,15 +3,4 @@
# 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 # Display-dependent UI sources are temporarily disabled pending rop-based rewrite.
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
ui.c
uifps.c
uielement.c
uiframe.c
uifullbox.c
uiloading.c
uitextbox.c
uiplayerpos.c
)
+1 -3
View File
@@ -3,12 +3,10 @@
# 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
# Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}
) )
# Subdirs
add_subdirectory(display)
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(render)
-16
View File
@@ -1,16 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
displaygl.c
)
# Subdirs
add_subdirectory(framebuffer)
add_subdirectory(texture)
add_subdirectory(mesh)
add_subdirectory(shader)
-22
View File
@@ -1,22 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "displaygl.h"
errorret_t displayOpenGLInit(void) {
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
errorChain(errorGLCheck());
#if DUSK_OPENGL_LEGACY
glDisable(GL_LIGHTING);// PSP defaults this on?
errorChain(errorGLCheck());
glShadeModel(GL_SMOOTH); // Fixes color on PSP?
errorChain(errorGLCheck());
#endif
errorOk();
}
-16
View File
@@ -1,16 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/errorgl.h"
/**
* Initializes the OpenGL specific contexts for rendering.
*
* @return An errorret_t indicating success or failure of the initialization.
*/
errorret_t displayOpenGLInit(void);
@@ -1,10 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
framebuffergl.c
)
@@ -1,149 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/display.h"
#include "display/framebuffer/framebuffer.h"
#include "assert/assertgl.h"
#include "util/memory.h"
errorret_t frameBufferGLInitBackBuffer(void) {
errorOk();
}
uint32_t frameBufferGLGetWidth(const framebuffer_t *framebuffer) {
if(framebuffer == NULL) {
return 0;
}
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight);
return windowWidth;
#else
return DUSK_DISPLAY_WIDTH;
#endif
}
return framebuffer->texture.width;
}
uint32_t frameBufferGLGetHeight(const framebuffer_t *framebuffer) {
if(framebuffer == NULL) {
return 0;
}
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
int32_t windowWidth, windowHeight;
SDL_GetWindowSize(DISPLAY.window, &windowWidth, &windowHeight);
return windowHeight;
#else
return DUSK_DISPLAY_HEIGHT;
#endif
}
return framebuffer->texture.height;
}
errorret_t frameBufferGLBind(framebuffer_t *framebuffer) {
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
} else {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
}
glViewport(
0, 0,
frameBufferGetWidth(framebuffer), frameBufferGetHeight(framebuffer)
);
#else
glViewport(
0, 0,
DUSK_DISPLAY_WIDTH, DUSK_DISPLAY_HEIGHT
);
#endif
errorChain(errorGLCheck());
errorOk();
}
void frameBufferGLClear(const uint8_t flags, const color_t color) {
GLbitfield glFlags = 0;
if(flags & FRAMEBUFFER_CLEAR_COLOR) {
glFlags |= GL_COLOR_BUFFER_BIT;
glClearColor(
color.r / 255.0f,
color.g / 255.0f,
color.b / 255.0f,
color.a / 255.0f
);
assertNoGLError("Failed to set clear color");
}
if(flags & FRAMEBUFFER_CLEAR_DEPTH) {
glFlags |= GL_DEPTH_BUFFER_BIT;
}
glClear(glFlags);
assertNoGLError("Failed to clear framebuffer");
}
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
errorret_t frameBufferGLInit(
framebuffer_t *fb,
const uint32_t width,
const uint32_t height
) {
assertNotNull(fb, "Framebuffer cannot be NULL");
assertTrue(width > 0 && height > 0, "W/H must be greater than 0");
memoryZero(fb, sizeof(framebuffer_t));
textureInit(&fb->texture, width, height, TEXTURE_FORMAT_RGBA,
(texturedata_t){ .rgbaColors = NULL }
);
errorChain(errorGLCheck());
glGenFramebuffersEXT(1, &fb->id);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb->id);
errorChain(errorGLCheck());
glFramebufferTexture2DEXT(
GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, fb->texture.id, 0
);
errorChain(errorGLCheck());
if(
glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) !=
GL_FRAMEBUFFER_COMPLETE_EXT
) {
assertUnreachable("Framebuffer is not complete");
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
errorChain(errorGLCheck());
errorOk();
}
errorret_t frameBufferGLDispose(framebuffer_t *framebuffer) {
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
if(framebuffer == &FRAMEBUFFER_BACKBUFFER) {
assertUnreachable("Cannot dispose of backbuffer");
}
errorChain(textureDispose(&framebuffer->texture));
glDeleteFramebuffersEXT(1, &framebuffer->id);
errorChain(errorGLCheck());
errorOk();
}
#endif
@@ -1,78 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/texture/texture.h"
#include "error/errorgl.h"
typedef struct {
GLuint id;
texture_t texture;
} framebuffergl_t;
/**
* Initializes the backbuffer framebuffer. (OpenGL implementation).
*/
errorret_t frameBufferGLInitBackBuffer(void);
/**
* Gets the height of the framebuffer. (OpenGL implementation).
*
* @param framebuffer The framebuffer to get the height of.
* @return The height of the framebuffer, or 0 if the framebuffer is NULL.
*/
uint32_t frameBufferGLGetWidth(const framebuffergl_t *framebuffer);
/**
* Initializes an OpenGL style framebuffer.
*
* @param fb The framebuffer to initialize.
* @param width The width of the framebuffer.
* @param height The height of the framebuffer.
* @return Either error or not.
*/
uint32_t frameBufferGLGetHeight(const framebuffergl_t *framebuffer);
/**
* Gets the width of the framebuffer. (OpenGL implementation).
*
* @param framebuffer The framebuffer to get the width of.
* @return The width of the framebuffer, or 0 if the framebuffer is NULL.
*/
errorret_t frameBufferGLBind(framebuffergl_t *framebuffer);
/**
* Clears the framebuffer with the specified flags and color.
*
* @param flags The clear flags.
* @param color The clear color.
*/
void frameBufferGLClear(const uint8_t flags, const color_t color);
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
/**
* Initializes an OpenGL style framebuffer.
*
* @param fb The framebuffer to initialize.
* @param width The width of the framebuffer.
* @param height The height of the framebuffer.
* @return Either error or not.
*/
errorret_t frameBufferGLInit(
framebuffergl_t *fb,
const uint32_t width,
const uint32_t height
);
/**
* Disposes of the framebuffer. Will also be used for request disposing of the
* backbuffer.
*
* @param framebuffer The framebuffer to dispose of.
*/
errorret_t frameBufferGLDispose(framebuffergl_t *framebuffer);
#endif
@@ -1,21 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/framebuffer/framebuffergl.h"
typedef framebuffergl_t framebufferplatform_t;
#define frameBufferPlatformInitBackBuffer frameBufferGLInitBackBuffer
#define frameBufferPlatformGetWidth frameBufferGLGetWidth
#define frameBufferPlatformGetHeight frameBufferGLGetHeight
#define frameBufferPlatformBind frameBufferGLBind
#define frameBufferPlatformClear frameBufferGLClear
#ifdef DUSK_DISPLAY_SIZE_DYNAMIC
#define frameBufferPlatformInit frameBufferGLInit
#define frameBufferPlatformDispose frameBufferGLDispose
#endif
-196
View File
@@ -1,196 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/mesh/mesh.h"
#include "assert/assertgl.h"
#include "error/errorgl.h"
#include "display/shader/shadergl.h"
errorret_t meshInitGL(
meshgl_t *mesh,
const meshprimitivetypegl_t primitiveType,
const int32_t vertexCount,
const meshvertex_t *vertices
) {
assertNotNull(mesh, "Mesh cannot be NULL");
assertNotNull(vertices, "Vertices cannot be NULL");
assertTrue(vertexCount > 0, "Vertex count must be greater than 0");
mesh->primitiveType = primitiveType;
mesh->vertexCount = vertexCount;
mesh->vertices = vertices;
#ifdef DUSK_OPENGL_LEGACY
// Nothing needed.
#if MESH_ENABLE_COLOR
glEnableClientState(GL_COLOR_ARRAY);
errorChain(errorGLCheck());
#endif
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
errorChain(errorGLCheck());
glEnableClientState(GL_VERTEX_ARRAY);
errorChain(errorGLCheck());
#else
// Generate Vertex Buffer Object
glGenBuffers(1, &mesh->vboId);
errorChain(errorGLCheck());
glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId);
errorChain(errorGLCheck());
glBufferData(
GL_ARRAY_BUFFER,
vertexCount * sizeof(meshvertex_t),
vertices,
GL_DYNAMIC_DRAW
);
errorChain(errorGLCheck());
// Generate Vertex Array Object
glGenVertexArrays(1, &mesh->vaoId);
errorChain(errorGLCheck());
glBindVertexArray(mesh->vaoId);
errorChain(errorGLCheck());
glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId);
errorChain(errorGLCheck());
// Set up vertex attribute pointers
glVertexAttribPointer(
0,
MESH_VERTEX_POS_SIZE,
GL_FLOAT,
GL_FALSE,
sizeof(meshvertex_t),
(const GLvoid*)offsetof(meshvertex_t, pos)
);
errorChain(errorGLCheck());
glEnableVertexAttribArray(0);
errorChain(errorGLCheck());
glVertexAttribPointer(
1,
MESH_VERTEX_UV_SIZE,
GL_FLOAT,
GL_FALSE,
sizeof(meshvertex_t),
(const GLvoid*)offsetof(meshvertex_t, uv)
);
errorChain(errorGLCheck());
glEnableVertexAttribArray(1);
errorChain(errorGLCheck());
#if MESH_ENABLE_COLOR
glVertexAttribPointer(
2,
sizeof(color_t) / sizeof(GLubyte),
GL_UNSIGNED_BYTE,
GL_TRUE,
sizeof(meshvertex_t),
(const GLvoid*)offsetof(meshvertex_t, color)
);
errorChain(errorGLCheck());
glEnableVertexAttribArray(2);
errorChain(errorGLCheck());
#endif
// Unbind VAO and VBO to prevent accidental modification
glBindBuffer(GL_ARRAY_BUFFER, 0);
errorChain(errorGLCheck());
glBindVertexArray(0);
errorChain(errorGLCheck());
#endif
errorOk();
}
errorret_t meshFlushGL(
meshgl_t *mesh,
const int32_t vertOffset,
const int32_t vertCount
) {
#ifdef DUSK_OPENGL_LEGACY
// Nothing doing, we use the glClientState stuff.
#else
glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId);
errorChain(errorGLCheck());
glBufferData(
GL_ARRAY_BUFFER,
mesh->vertexCount * sizeof(meshvertex_t),
mesh->vertices,
// vertCount * sizeof(meshvertex_t),
// &mesh->vertices[vertOffset],
GL_DYNAMIC_DRAW
);
errorChain(errorGLCheck());
#endif
errorOk();
}
errorret_t meshDrawGL(
const meshgl_t *mesh,
const int32_t offset,
const int32_t count
) {
#ifdef DUSK_OPENGL_LEGACY
// Legacy pointer style rendering
const GLsizei stride = sizeof(meshvertex_t);
#if MESH_ENABLE_COLOR
glColorPointer(
sizeof(color4b_t),
GL_UNSIGNED_BYTE,
stride,
(const GLvoid*)&mesh->vertices[offset].color
);
#endif
glTexCoordPointer(
MESH_VERTEX_UV_SIZE,
GL_FLOAT,
stride,
(const GLvoid*)&mesh->vertices[offset].uv[0]
);
glVertexPointer(
MESH_VERTEX_POS_SIZE,
GL_FLOAT,
stride,
(const GLvoid*)&mesh->vertices[offset].pos[0]
);
// Shader may have model matrix here
errorChain(shaderLegacyMatrixUpdate());
glDrawArrays(mesh->primitiveType, 0, count);
errorChain(errorGLCheck());
#else
// Modern VAO/VBO rendering
glBindVertexArray(mesh->vaoId);
errorChain(errorGLCheck());
glDrawArrays(mesh->primitiveType, offset, count);
errorChain(errorGLCheck());
glBindVertexArray(0);
errorChain(errorGLCheck());
#endif
errorOk();
}
int32_t meshGetVertexCountGL(const meshgl_t *mesh) {
return mesh->vertexCount;
}
errorret_t meshDisposeGL(meshgl_t *mesh) {
#ifdef DUSK_OPENGL_LEGACY
// No dynamic resources to free for this mesh implementation
#else
glDeleteBuffers(1, &mesh->vboId);
errorChain(errorGLCheck());
glDeleteVertexArrays(1, &mesh->vaoId);
errorChain(errorGLCheck());
#endif
errorOk();
}
-89
View File
@@ -1,89 +0,0 @@
/**
* 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/mesh/meshvertex.h"
typedef enum {
MESH_PRIMITIVE_TYPE_TRIANGLES = GL_TRIANGLES,
MESH_PRIMITIVE_TYPE_LINES = GL_LINES,
MESH_PRIMITIVE_TYPE_POINTS = GL_POINTS,
} meshprimitivetypegl_t;
typedef struct {
int32_t vertexCount;
meshprimitivetypegl_t primitiveType;
const meshvertex_t *vertices;
#ifdef DUSK_OPENGL_LEGACY
// Nothing needed
#else
GLuint vaoId;
GLuint vboId;
#endif
} meshgl_t;
/**
* Initializes a mesh for OpenGL.
*
* @param mesh The mesh to initialize.
* @param primitiveType The OpenGL primitive type (e.g., GL_TRIANGLES).
* @param vertexCount The number of vertices in the mesh.
* @param vertices The vertex data for the mesh.
* @return An errorret_t indicating success or failure.
*/
errorret_t meshInitGL(
meshgl_t *mesh,
const meshprimitivetypegl_t primitiveType,
const int32_t vertexCount,
const meshvertex_t *vertices
);
/**
* Flushes the vertices (stored in memory) to the GPU.
*
* @param mesh Mesh to flush vertices for.
* @param vertOffset First vertice index to flush.
* @param vertCount Count of vertices to flush.
* @return Error state.
*/
errorret_t meshFlushGL(
meshgl_t *mesh,
const int32_t vertOffset,
const int32_t vertCount
);
/**
* Draws a mesh using OpenGL.
*
* @param mesh The mesh to draw.
* @param vertexOffset The offset in the vertex array to start drawing from.
* @param vertexCount The number of vertices to draw. If -1, draws all vertices.
* @return An errorret_t indicating success or failure.
*/
errorret_t meshDrawGL(
const meshgl_t *mesh,
const int32_t vertexOffset,
const int32_t vertexCount
);
/**
* Gets the vertex count of a mesh used for OpenGL.
*
* @param mesh The mesh to get the vertex count from.
* @return The vertex count of the mesh.
*/
int32_t meshGetVertexCountGL(const meshgl_t *mesh);
/**
* Disposes a mesh used for OpenGL.
*
* @param mesh The mesh to dispose.
* @return An errorret_t indicating success or failure.
*/
errorret_t meshDisposeGL(meshgl_t *mesh);
-18
View File
@@ -1,18 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "meshgl.h"
typedef meshprimitivetypegl_t meshprimitivetypeplatform_t;
typedef meshgl_t meshplatform_t;
#define meshInitPlatform meshInitGL
#define meshFlushPlatform meshFlushGL
#define meshDrawPlatform meshDrawGL
#define meshGetVertexCountPlatform meshGetVertexCountGL
#define meshDisposePlatform meshDisposeGL
-11
View File
@@ -1,11 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
shadergl.c
shaderunlitgl.c
)
-415
View File
@@ -1,415 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "shadergl.h"
#include "util/memory.h"
#include "util/string.h"
#include "assert/assertgl.h"
#include "display/shader/shaderunlit.h"
#ifdef DUSK_OPENGL_LEGACY
shaderlegacygl_t SHADER_LEGACY = { 0 };
#endif
errorret_t shaderInitGL(shadergl_t *shader, const shaderdefinitiongl_t *def) {
assertNotNull(shader, "Shader cannot be null");
assertNotNull(def, "Shader definition cannot be null");
memoryZero(shader, sizeof(shadergl_t));
shader->definition = def;
#ifdef DUSK_OPENGL_LEGACY
glm_mat4_identity(shader->view);
glm_mat4_identity(shader->proj);
glm_mat4_identity(shader->model);
SHADER_LEGACY.boundShader = NULL;
errorOk();
#else
assertNotNull(def->vert, "Vertex shader source cannot be null");
assertNotNull(def->frag, "Fragment shader source cannot be null");
// Create vertex shader
shader->vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
errorret_t err = errorGLCheck();
errorChain(err);
glShaderSource(shader->vertexShaderId, 1, &def->vert, NULL);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteShader(shader->vertexShaderId);
errorChain(err);
}
glCompileShader(shader->vertexShaderId);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteShader(shader->vertexShaderId);
errorChain(err);
}
GLint ok = 0;
glGetShaderiv(shader->vertexShaderId, GL_COMPILE_STATUS, &ok);
if(!ok) {
GLchar log[1024];
glGetShaderInfoLog(shader->vertexShaderId, sizeof(log), NULL, log);
glDeleteShader(shader->vertexShaderId);
errorThrow("Vertex shader compilation failed: %s", log);
}
// Create fragment shader
shader->fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteShader(shader->vertexShaderId);
errorChain(err);
}
glShaderSource(shader->fragmentShaderId, 1, &def->frag, NULL);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorChain(err);
}
glCompileShader(shader->fragmentShaderId);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorChain(err);
}
glGetShaderiv(shader->fragmentShaderId, GL_COMPILE_STATUS, &ok);
if(!ok) {
GLchar log[1024];
glGetShaderInfoLog(shader->fragmentShaderId, sizeof(log), NULL, log);
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorThrow("Fragment shader compilation failed: %s", log);
}
// Create shader program
shader->shaderProgramId = glCreateProgram();
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorChain(err);
}
glAttachShader(shader->shaderProgramId, shader->vertexShaderId);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteProgram(shader->shaderProgramId);
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorChain(err);
}
glAttachShader(shader->shaderProgramId, shader->fragmentShaderId);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteProgram(shader->shaderProgramId);
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorChain(err);
}
glLinkProgram(shader->shaderProgramId);
err = errorGLCheck();
if(errorIsNotOk(err)) {
glDeleteProgram(shader->shaderProgramId);
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorChain(err);
}
ok = 0;
glGetProgramiv(shader->shaderProgramId, GL_LINK_STATUS, &ok);
if(!ok) {
GLchar log[1024];
glGetProgramInfoLog(shader->shaderProgramId, sizeof(log), NULL, log);
glDeleteProgram(shader->shaderProgramId);
glDeleteShader(shader->vertexShaderId);
glDeleteShader(shader->fragmentShaderId);
errorThrow("Shader program linking failed: %s", log);
}
#endif
errorOk();
}
errorret_t shaderParamGetLocationGL(
shadergl_t *shader,
const char_t *name,
GLint *location
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertNotNull(location, "Location cannot be null");
#ifdef DUSK_OPENGL_LEGACY
assertUnreachable("Cannot get uniform locations on legacy opengl.");
#else
*location = glGetUniformLocation(shader->shaderProgramId, name);
errorChain(errorGLCheck());
if(*location == -1) {
errorThrow("Uniform '%s' not found in shader.", name);
}
#endif
errorOk();
}
errorret_t shaderSetMatrixGL(
shadergl_t *shader,
const char_t *name,
mat4 mat
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertNotNull(mat, "Matrix data cannot be null");
#ifdef DUSK_OPENGL_LEGACY
assertTrue(
SHADER_LEGACY.boundShader == shader,
"Shader must be bound to set legacy matrices."
);
// Use unaligned copy to safely handle possibly unaligned input matrices
if(stringCompare(name, SHADER_UNLIT_PROJECTION) == 0) {
SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_PROJ;
glm_mat4_ucopy(mat, shader->proj);
} else if(stringCompare(name, SHADER_UNLIT_VIEW) == 0) {
SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_VIEW;
glm_mat4_ucopy(mat, shader->view);
} else if(stringCompare(name, SHADER_UNLIT_MODEL) == 0) {
SHADER_LEGACY.dirty |= SHADER_LEGACY_DIRTY_MODEL;
glm_mat4_ucopy(mat, shader->model);
} else {
assertUnreachable("Cannot use a custom matrix on legacy opengl.");
}
#else
GLint location;
errorChain(shaderParamGetLocationGL(shader, name, &location));
glUniformMatrix4fv(location, 1, GL_FALSE, (const GLfloat *)mat);
errorChain(errorGLCheck());
#endif
errorOk();
}
errorret_t shaderSetTextureGL(
shadergl_t *shader,
const char_t *name,
texture_t *texture
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
#ifdef DUSK_OPENGL_LEGACY
assertStringEqual(
name,
SHADER_UNLIT_TEXTURE,
"Only one texture supported in legacy opengl."
);
// glActiveTexture(GL_TEXTURE0);
errorChain(errorGLCheck());
if(texture == NULL) {
glDisable(GL_TEXTURE_2D);
errorChain(errorGLCheck());
errorOk();
}
glEnable(GL_TEXTURE_2D);
errorChain(errorGLCheck());
glBindTexture(GL_TEXTURE_2D, texture->id);
errorChain(errorGLCheck());
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
// errorChain(errorGLCheck());
#else
assertNotNull(shader->definition, "Shader definition cannot be null");
assertNotNull(shader->definition->setTexture, "Shader cannot do textures.");
errorChain(shader->definition->setTexture(shader, name, texture));
#endif
errorOk();
}
errorret_t shaderSetColorGL(
shadergl_t *shader,
const char_t *name,
color_t color
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
#ifdef DUSK_OPENGL_LEGACY
// if(color.a == 0) {
// glDisable(GL_TEXTURE_2D);
// errorChain(errorGLCheck());
// errorOk();
// }
// glActiveTexture(GL_TEXTURE1);
// errorChain(errorGLCheck());
// if(color.r == 255 && color.g == 255 && color.b == 255) {
// glDisable(GL_TEXTURE_2D);
// errorChain(errorGLCheck());
// errorOk();
// }
// glEnable(GL_TEXTURE_2D);
// errorChain(errorGLCheck());
// glBindTexture(GL_TEXTURE_2D, TEXTURE_WHITE.id);
// errorChain(errorGLCheck());
// GLfloat tint[4] = {
// ((float_t)color.r) / 255.0f,
// ((float_t)color.g) / 255.0f,
// ((float_t)color.b) / 255.0f,
// ((float_t)color.a) / 255.0f
// };
// glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, tint);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT);
// errorChain(errorGLCheck());
// glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
// errorChain(errorGLCheck());
glColor4f(
(float_t)color.r / 255.0f,
(float_t)color.g / 255.0f,
(float_t)color.b / 255.0f,
(float_t)color.a / 255.0f
);
errorChain(errorGLCheck());
#else
GLint location;
errorChain(shaderParamGetLocationGL(shader, name, &location));
glUniform4f(
location,
color.r / 255.0f,
color.g / 255.0f,
color.b / 255.0f,
color.a / 255.0f
);
errorChain(errorGLCheck());
#endif
errorOk();
}
errorret_t shaderBindGL(shadergl_t *shader) {
#ifdef DUSK_OPENGL_LEGACY
assertNotNull(shader, "Cannot bind a null shader.");
SHADER_LEGACY.boundShader = shader;
SHADER_LEGACY.dirty = (
SHADER_LEGACY_DIRTY_MODEL |
SHADER_LEGACY_DIRTY_PROJ |
SHADER_LEGACY_DIRTY_VIEW
);
#else
assertNotNull(shader, "Shader cannot be null");
glUseProgram(shader->shaderProgramId);
errorChain(errorGLCheck());
#endif
errorOk();
}
errorret_t shaderDisposeGL(shadergl_t *shader) {
assertNotNull(shader, "Shader cannot be null");
#ifdef DUSK_OPENGL_LEGACY
SHADER_LEGACY.boundShader = NULL;
#else
if(shader->shaderProgramId != 0) {
glDeleteProgram(shader->shaderProgramId);
}
if(shader->vertexShaderId != 0) {
glDeleteShader(shader->vertexShaderId);
}
if(shader->fragmentShaderId != 0) {
glDeleteShader(shader->fragmentShaderId);
}
assertNoGLError("Failed disposing shader");
#endif
memoryZero(shader, sizeof(shadergl_t));
errorOk();
}
#ifdef DUSK_OPENGL_LEGACY
errorret_t shaderLegacyMatrixUpdate() {
assertNotNull(SHADER_LEGACY.boundShader, "No shader is currently bound.");
if((SHADER_LEGACY.dirty & SHADER_LEGACY_DIRTY_PROJ) != 0) {
glMatrixMode(GL_PROJECTION);
errorChain(errorGLCheck());
glLoadIdentity();
errorChain(errorGLCheck());
glMultMatrixf((const GLfloat *)SHADER_LEGACY.boundShader->proj);
errorChain(errorGLCheck());
}
if(
(SHADER_LEGACY.dirty &
(SHADER_LEGACY_DIRTY_VIEW | SHADER_LEGACY_DIRTY_MODEL)) != 0
) {
glMatrixMode(GL_MODELVIEW);
errorChain(errorGLCheck());
mat4 viewModel;
glm_mat4_mul(
SHADER_LEGACY.boundShader->view,
SHADER_LEGACY.boundShader->model,
viewModel
);
glLoadMatrixf((const GLfloat *)viewModel);
errorChain(errorGLCheck());
}
SHADER_LEGACY.dirty = 0;
errorOk();
}
#endif
-148
View File
@@ -1,148 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/errorgl.h"
#include "display/texture/texture.h"
typedef struct shadergl_s shadergl_t;
typedef union shadermaterial_u shadermaterial_t;
typedef errorret_t (*shadersettexturefn_t)(
shadergl_t *,
const char_t *,
texture_t *
);
typedef struct {
errorret_t (*setMaterial)(shadergl_t *, const shadermaterial_t *);
#ifdef DUSK_OPENGL_LEGACY
#else
errorret_t (*setTexture)(shadergl_t *, const char_t *, texture_t *);
const char_t *vert;
const char_t *frag;
#endif
} shaderdefinitiongl_t;
typedef struct shadergl_s {
const shaderdefinitiongl_t *definition;
#ifdef DUSK_OPENGL_LEGACY
mat4 view;
mat4 proj;
mat4 model;
#else
GLuint shaderProgramId;
GLuint vertexShaderId;
GLuint fragmentShaderId;
#endif
} shadergl_t;
#if DUSK_OPENGL_LEGACY
typedef struct {
shadergl_t *boundShader;
uint_fast8_t dirty;
} shaderlegacygl_t;
extern shaderlegacygl_t SHADER_LEGACY;
#define SHADER_LEGACY_DIRTY_PROJ (1 << 0)
#define SHADER_LEGACY_DIRTY_VIEW (1 << 1)
#define SHADER_LEGACY_DIRTY_MODEL (1 << 2)
#endif
/**
* Initializes a shader.
*
* @param shader The shader to initialize.
* @param def The definition of the shader to initialize with.
* @return An errorret_t indicating success or failure.
*/
errorret_t shaderInitGL(shadergl_t *shader, const shaderdefinitiongl_t *def);
/**
* Binds a shader for use in rendering.
*
* @param shader The shader to bind.
* @return An errorret_t indicating success or failure.
*/
errorret_t shaderBindGL(shadergl_t *shader);
/**
* Retrieves the location of a shader uniform parameter.
*
* @param shader The shader to query.
* @param name The name of the uniform parameter.
* @param location Output parameter to receive the location of the uniform.
* @return An errorret_t indicating success or failure.
*/
errorret_t shaderParamGetLocationGL(
shadergl_t *shader,
const char_t *name,
GLint *location
);
/**
* Sets a mat4 uniform parameter in the shader.
*
* @param shader The shader to update.
* @param name The name of the uniform parameter.
* @param mat The 4x4 matrix data to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t shaderSetMatrixGL(
shadergl_t *shader,
const char_t *name,
mat4 matrix
);
/**
* Sets a color uniform parameter in the shader.
*
* @param shader The shader to update.
* @param name The name of the uniform parameter.
* @param color The color data to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t shaderSetTextureGL(
shadergl_t *shader,
const char_t *name,
texture_t *texture
);
/**
* Sets a color uniform parameter in the shader.
*
* @param shader The shader to update.
* @param name The name of the uniform parameter.
* @param color The color data to set.
* @return An errorret_t indicating success or failure.
*/
errorret_t shaderSetColorGL(
shadergl_t *shader,
const char_t *name,
color_t color
);
/**
* Disposes of a shader, freeing any associated resources.
*
* @param shader The shader to dispose.
*/
errorret_t shaderDisposeGL(shadergl_t *shader);
#ifdef DUSK_OPENGL_LEGACY
/**
* During mesh rendering, this is requesting the legacy system to push all
* shaders necessary to render the currently bound shader's matrices.
*
* @return Any error state.
*/
errorret_t shaderLegacyMatrixUpdate();
#endif
@@ -1,19 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "shadergl.h"
typedef shadergl_t shaderplatform_t;
typedef shaderdefinitiongl_t shaderdefinitionplatform_t;
#define shaderInitPlatform shaderInitGL
#define shaderBindPlatform shaderBindGL
#define shaderSetMatrixPlatform shaderSetMatrixGL
#define shaderSetTexturePlatform shaderSetTextureGL
#define shaderSetColorPlatform shaderSetColorGL
#define shaderDisposePlatform shaderDisposeGL
-213
View File
@@ -1,213 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/shader/shaderunlit.h"
#include "assert/assertgl.h"
#ifdef DUSK_OPENGL_LEGACY
shaderdefinition_t SHADER_UNLIT_DEFINITION = {
.setMaterial = shaderUnlitSetMaterial,
};
#else
errorret_t shaderUnlitSetTextureGL(
shadergl_t *shader,
const char_t *name,
texture_t *texture
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertStringEqual(
name,
SHADER_UNLIT_TEXTURE,
"Only one texture supported in unlit shader."
);
GLint locTexture, locType, locColorCount, locColors;
errorChain(shaderParamGetLocationGL(shader, "u_TextureType", &locType));
// NULL textures
if(texture == NULL) {
glUniform1i(locType, 0);
errorChain(errorGLCheck());
errorOk();
}
// Set texture.
glActiveTexture(GL_TEXTURE0);
errorChain(errorGLCheck());
glBindTexture(GL_TEXTURE_2D, texture->id);
errorChain(errorGLCheck());
errorChain(shaderParamGetLocationGL(shader, name, &locTexture));
glUniform1i(locTexture, 0);
errorChain(errorGLCheck());
// Set texture type
if(texture->format == TEXTURE_FORMAT_PALETTE) {
glUniform1i(locType, 2);
errorChain(errorGLCheck());
shaderParamGetLocationGL(shader, "u_ColorCount", &locColorCount);
glUniform1i(locColorCount, texture->palette->count);
errorChain(errorGLCheck());
shaderParamGetLocationGL(shader, "u_Colors", &locColors);
GLuint paletteData[texture->palette->count];
for(size_t i = 0; i < texture->palette->count; i++) {
color_t color = texture->palette->colors[i];
paletteData[i] = (
((uint32_t)color.r << 24) |
((uint32_t)color.g << 16) |
((uint32_t)color.b << 8) |
((uint32_t)color.a << 0)
);
}
glUniform1uiv(locColors, texture->palette->count, paletteData);
errorChain(errorGLCheck());
} else {
glUniform1i(locType, 1);
errorChain(errorGLCheck());
}
errorOk();
}
shaderdefinition_t SHADER_UNLIT_DEFINITION = {
.setMaterial = shaderUnlitSetMaterial,
.setTexture = shaderUnlitSetTextureGL,
.vert =
#ifdef DUSK_OPENGL_ES
"#version 300 es\n"
"precision mediump float;\n"
// Attributes
"layout(location = 0) in vec3 a_Pos;\n"
"layout(location = 1) in vec2 a_TexCoord;\n"
#if MESH_ENABLE_COLOR
"layout(location = 2) in vec4 a_Color;\n"
#endif
// Uniforms
"uniform mat4 u_Proj;\n"
"uniform mat4 u_View;\n"
"uniform mat4 u_Model;\n"
// Vertex shader outputs
"out vec4 v_Color;\n"
"out vec2 v_TexCoord;\n"
"void main() {\n"
" gl_Position = u_Proj * u_View * u_Model * vec4(a_Pos, 1.0);\n"
#if MESH_ENABLE_COLOR
" v_Color = a_Color;\n"
#else
" v_Color = vec4(1.0);\n"
#endif
" v_TexCoord = a_TexCoord;\n"
"}\n",
#else
"#version 330 core\n"
// Attributes
"layout(location = 0) in vec3 a_Pos;\n"
"layout(location = 1) in vec2 a_TexCoord;\n"
#if MESH_ENABLE_COLOR
"layout(location = 2) in vec4 a_Color;\n"
#endif
// Uniforms
"uniform mat4 u_Proj;\n"
"uniform mat4 u_View;\n"
"uniform mat4 u_Model;\n"
// Vertex shader outputs
"out vec4 v_Color;\n"
"out vec2 v_TexCoord;\n"
"void main() {\n"
" gl_Position = u_Proj * u_View * u_Model * vec4(a_Pos, 1.0);\n"
#if MESH_ENABLE_COLOR
" v_Color = a_Color;\n"
#else
" v_Color = vec4(1.0);\n"
#endif
" v_TexCoord = a_TexCoord;\n"
"}\n",
#endif
.frag =
#ifdef DUSK_OPENGL_ES
"#version 300 es\n"
"precision mediump float;\n"
// Uniforms
"uniform sampler2D u_Texture;\n"
"uniform int u_TextureType;\n"
"uniform uint u_Colors[256];\n"// For paletted textures.
"uniform int u_ColorCount;\n"
"uniform vec4 u_Color;\n"
// Fragment shader inputs
"in vec4 v_Color;\n"
"in vec2 v_TexCoord;\n"
// Fragment shader output
"out vec4 FragColor;\n"
"void main() {\n"
" if(u_TextureType == 0) {\n"// No texture
" FragColor = v_Color * u_Color;\n"
" return;\n"
" }\n"
" if(u_TextureType == 1) {\n"// Regular texture
" FragColor = texture(u_Texture, v_TexCoord) * v_Color * u_Color;\n"
" return;\n"
" }\n"
" if(u_TextureType == 2) {\n"// Paletted texture
" vec4 texColor = texture(u_Texture, v_TexCoord);\n"
" uint index = uint(floor(texColor.r * 255.0));\n"
" uint palColor = u_Colors[index];\n"
" float r = float((palColor >> 24) & 0xFFu) / 255.0;\n"
" float g = float((palColor >> 16) & 0xFFu) / 255.0;\n"
" float b = float((palColor >> 8) & 0xFFu) / 255.0;\n"
" float a = float((palColor >> 0) & 0xFFu) / 255.0;\n"
" FragColor = vec4(r, g, b, a) * u_Color;\n"
" return;\n"
" }\n"
" FragColor = v_Color * u_Color;\n"// Unknown texture type?
"}\n",
#else
"#version 330 core\n"
// Uniforms
"uniform sampler2D u_Texture;\n"
"uniform int u_TextureType;\n"
"uniform uint u_Colors[256];\n"// For paletted textures.
"uniform int u_ColorCount;\n"
"uniform vec4 u_Color;\n"
// Fragment shader inputs
"in vec4 v_Color;\n"
"in vec2 v_TexCoord;\n"
// Fragment shader output
"out vec4 FragColor;\n"
"void main() {\n"
" if(u_TextureType == 0) {\n"// No texture
" FragColor = v_Color * u_Color;\n"
" return;\n"
" }\n"
" if(u_TextureType == 1) {\n"// Regular texture
" FragColor = texture(u_Texture, v_TexCoord) * v_Color * u_Color;\n"
" return;\n"
" }\n"
" if(u_TextureType == 2) {\n"// Paletted texture
" vec4 texColor = texture(u_Texture, v_TexCoord);\n"
" uint index = uint(floor(texColor.r * 255.0));\n"
" uint palColor = u_Colors[index];\n"
" float r = float((palColor >> 24) & 0xFFu) / 255.0;\n"
" float g = float((palColor >> 16) & 0xFFu) / 255.0;\n"
" float b = float((palColor >> 8) & 0xFFu) / 255.0;\n"
" float a = float((palColor >> 0) & 0xFFu) / 255.0;\n"
" FragColor = vec4(r, g, b, a) * u_Color;\n"
" return;\n"
" }\n"
" FragColor = v_Color * u_Color;\n"// Unknown texture type?
"}\n",
#endif
};
#endif
-95
View File
@@ -1,95 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "display/texture/texture.h"
#include "assert/assert.h"
#include "error/errorgl.h"
#include "util/memory.h"
errorret_t textureInitGL(
texturegl_t *texture,
const int32_t width,
const int32_t height,
const textureformatgl_t format,
const texturedata_t data
) {
glGenTextures(1, &texture->id);
errorChain(errorGLCheck());
glBindTexture(GL_TEXTURE_2D, texture->id);
errorChain(errorGLCheck());
switch(format) {
case TEXTURE_FORMAT_RGBA:
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, (void*)data.rgbaColors
);
errorChain(errorGLCheck());
break;
case TEXTURE_FORMAT_PALETTE:
texture->palette = data.paletted.palette;
assertTrue(
texture->palette == &PALETTES[0],
"Only the first palette is supported in legacy opengl."
);
#ifdef DUSK_OPENGL_LEGACY
glColorTableEXT(
GL_TEXTURE_2D, GL_RGBA, texture->palette->count, GL_RGBA,
GL_UNSIGNED_BYTE, (const void*)texture->palette->colors
);
errorChain(errorGLCheck());
glTexImage2D(
GL_TEXTURE_2D,
0, GL_COLOR_INDEX8_EXT,
width, height,
0, GL_COLOR_INDEX8_EXT,
GL_UNSIGNED_BYTE, (void*)data.paletted.indices
);
errorChain(errorGLCheck());
#else
// For modern systems we send to only the R channel and the shader does
// the rest.
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RED, width, height, 0,
GL_RED, GL_UNSIGNED_BYTE, (void*)data.paletted.indices
);
errorChain(errorGLCheck());
#endif
break;
default:
assertUnreachable("Unknown texture format");
break;
}
errorChain(errorGLCheck());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
errorChain(errorGLCheck());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
errorChain(errorGLCheck());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
errorChain(errorGLCheck());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
errorChain(errorGLCheck());
glBindTexture(GL_TEXTURE_2D, 0);
errorChain(errorGLCheck());
errorOk();
}
errorret_t textureDisposeGL(texturegl_t *texture) {
assertNotNull(texture, "Texture cannot be NULL");
assertTrue(texture->id != 0, "Texture ID must be valid");
glDeleteTextures(1, &texture->id);
errorChain(errorGLCheck());
errorOk();
}

Some files were not shown because too many files have changed in this diff Show More