prog
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
|
@@ -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;
|
||||
@@ -56,9 +56,9 @@ add_subdirectory(animation)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(render)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(error)
|
||||
|
||||
@@ -12,6 +12,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(display)
|
||||
# add_subdirectory(display) # disabled: pending rop-based asset loader rewrite
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(json)
|
||||
@@ -10,33 +10,15 @@
|
||||
assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
|
||||
[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] = {
|
||||
.loadSync = assetLocaleLoaderSync,
|
||||
.loadSync = assetLocaleLoaderSync,
|
||||
.loadAsync = assetLocaleLoaderAsync,
|
||||
.dispose = assetLocaleDispose
|
||||
.dispose = assetLocaleDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_JSON] = {
|
||||
.loadSync = assetJsonLoaderSync,
|
||||
.loadSync = assetJsonLoaderSync,
|
||||
.loadAsync = assetJsonLoaderAsync,
|
||||
.dispose = assetJsonDispose
|
||||
.dispose = assetJsonDispose
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,46 +6,29 @@
|
||||
*/
|
||||
|
||||
#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/json/assetjsonloader.h"
|
||||
|
||||
typedef enum {
|
||||
ASSET_LOADER_TYPE_NULL,
|
||||
|
||||
ASSET_LOADER_TYPE_MESH,
|
||||
ASSET_LOADER_TYPE_TEXTURE,
|
||||
ASSET_LOADER_TYPE_TILESET,
|
||||
ASSET_LOADER_TYPE_LOCALE,
|
||||
ASSET_LOADER_TYPE_JSON,
|
||||
|
||||
ASSET_LOADER_TYPE_COUNT
|
||||
} assetloadertype_t;
|
||||
|
||||
typedef union {
|
||||
assetmeshloaderinput_t mesh;
|
||||
assettextureloaderinput_t texture;
|
||||
assettilesetloaderinput_t tileset;
|
||||
assetlocaleloaderinput_t locale;
|
||||
assetjsonloaderinput_t json;
|
||||
assetjsonloaderinput_t json;
|
||||
} assetloaderinput_t;
|
||||
|
||||
typedef union {
|
||||
assetmeshloaderloading_t mesh;
|
||||
assettextureloaderloading_t texture;
|
||||
assettilesetloaderloading_t tileset;
|
||||
assetlocaleloaderloading_t locale;
|
||||
assetjsonloaderloading_t json;
|
||||
assetjsonloaderloading_t json;
|
||||
} assetloaderloading_t;
|
||||
|
||||
typedef union {
|
||||
assetmeshoutput_t mesh;
|
||||
assettextureoutput_t texture;
|
||||
assettilesetoutput_t tileset;
|
||||
assetlocaleoutput_t locale;
|
||||
assetjsonoutput_t json;
|
||||
assetjsonoutput_t json;
|
||||
} assetloaderoutput_t;
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
|
||||
+17
-30
@@ -12,9 +12,6 @@
|
||||
#include "input/input.h"
|
||||
#include "log/log.h"
|
||||
#include "engine/engine.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
console_t CONSOLE;
|
||||
|
||||
@@ -22,9 +19,9 @@ void consoleInit(void) {
|
||||
memoryZero(&CONSOLE, sizeof(console_t));
|
||||
CONSOLE.visible = true;
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexInit(&CONSOLE.printMutex);
|
||||
#endif
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexInit(&CONSOLE.printMutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
va_end(args);
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexLock(&CONSOLE.printMutex);
|
||||
#endif
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexLock(&CONSOLE.printMutex);
|
||||
#endif
|
||||
|
||||
memoryMove(
|
||||
CONSOLE.line[0],
|
||||
@@ -46,17 +43,17 @@ void consolePrint(const char_t *message, ...) {
|
||||
);
|
||||
memoryCopy(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], buffer, len + 1);
|
||||
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexUnlock(&CONSOLE.printMutex);
|
||||
#endif
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexUnlock(&CONSOLE.printMutex);
|
||||
#endif
|
||||
|
||||
logDebug("%s\n", buffer);
|
||||
}
|
||||
|
||||
void consoleUpdate(void) {
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) return;
|
||||
#endif
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) return;
|
||||
#endif
|
||||
|
||||
if(inputPressed(INPUT_ACTION_CONSOLE)) {
|
||||
CONSOLE.visible = !CONSOLE.visible;
|
||||
@@ -64,21 +61,11 @@ void consoleUpdate(void) {
|
||||
}
|
||||
|
||||
errorret_t consoleDraw(void) {
|
||||
if(!CONSOLE.visible) 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();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void consoleDispose(void) {
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexDispose(&CONSOLE.printMutex);
|
||||
#endif
|
||||
}
|
||||
#ifdef DUSK_CONSOLE_POSIX
|
||||
threadMutexDispose(&CONSOLE.printMutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,28 +1,17 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
# 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
|
||||
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)
|
||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_color_defs)
|
||||
|
||||
+16
-92
@@ -1,116 +1,40 @@
|
||||
/**
|
||||
* 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 "render/ropbuffer.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());
|
||||
|
||||
#ifdef displayPlatformInit
|
||||
errorChain(displayPlatformInit());
|
||||
#endif
|
||||
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
|
||||
);
|
||||
|
||||
ropBufferReset(&ROPBUFFER);
|
||||
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
|
||||
#ifdef displayPlatformFlush
|
||||
errorChain(displayPlatformFlush(&ROPBUFFER));
|
||||
#endif
|
||||
#ifdef displayPlatformSwap
|
||||
errorChain(displayPlatformSwap());
|
||||
#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.
|
||||
#ifdef displayPlatformDispose
|
||||
displayPlatformDispose();
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#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;
|
||||
|
||||
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);
|
||||
errorret_t displayDispose(void);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -8,10 +8,10 @@
|
||||
#pragma once
|
||||
#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_BLEND (1 << 2)
|
||||
#define DISPLAY_STATE_FLAG_BLEND (1 << 2)
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags;
|
||||
} displaystate_t;
|
||||
} displaystate_t;
|
||||
|
||||
@@ -9,19 +9,12 @@
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "input/input.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "rpg/rpg.h"
|
||||
#include "display/display.h"
|
||||
#include "scene/scene.h"
|
||||
#include "cutscene/cutscene.h"
|
||||
#include "asset/asset.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "assert/assert.h"
|
||||
#include "network/network.h"
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "save/save.h"
|
||||
|
||||
engine_t ENGINE;
|
||||
|
||||
@@ -29,48 +22,32 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
assertInit();
|
||||
memoryZero(&ENGINE, sizeof(engine_t));
|
||||
ENGINE.running = true;
|
||||
ENGINE.argc = argc;
|
||||
ENGINE.argv = argv;
|
||||
ENGINE.argc = argc;
|
||||
ENGINE.argv = argv;
|
||||
ENGINE.version = DUSK_VERSION;
|
||||
|
||||
// Init systems. Order is important.
|
||||
errorChain(systemInit());
|
||||
timeInit();
|
||||
consoleInit();
|
||||
errorChain(inputInit());
|
||||
errorChain(assetInit());
|
||||
// errorChain(saveInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
errorChain(uiTextboxInit());
|
||||
errorChain(cutsceneInit());
|
||||
errorChain(rpgInit());
|
||||
errorChain(networkInit());
|
||||
errorChain(sceneInit());
|
||||
|
||||
consolePrint("Engine initialized");
|
||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
||||
|
||||
sceneSet(SCENE_TYPE_TEST);
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t engineUpdate(void) {
|
||||
// Order here is important.
|
||||
errorChain(networkUpdate());
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
errorChain(rpgUpdate());
|
||||
uiUpdate();
|
||||
errorChain(uiTextboxUpdate());
|
||||
errorChain(cutsceneUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(assetUpdate());
|
||||
|
||||
// Render
|
||||
errorChain(displayUpdate());
|
||||
|
||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||
errorOk();
|
||||
}
|
||||
@@ -80,17 +57,9 @@ void engineExit(void) {
|
||||
}
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
uiTextboxDispose();
|
||||
cutsceneDispose();
|
||||
errorChain(sceneDispose());
|
||||
errorChain(networkDispose());
|
||||
errorChain(rpgDispose());
|
||||
localeManagerDispose();
|
||||
uiDispose();
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
// errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -1,40 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Important to be included first:
|
||||
#include "display/display.h"
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct {
|
||||
bool_t running;
|
||||
int32_t argc;
|
||||
bool_t running;
|
||||
int32_t argc;
|
||||
const char_t **argv;
|
||||
const char_t *version;
|
||||
} engine_t;
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Updates the engine.
|
||||
*/
|
||||
errorret_t engineUpdate(void);
|
||||
|
||||
/**
|
||||
* Shuts down the engine.
|
||||
*/
|
||||
void engineExit(void);
|
||||
errorret_t engineDispose(void);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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
|
||||
texturegl.c
|
||||
)
|
||||
ropbuffer.c
|
||||
render.c
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -15,6 +15,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(overworld)
|
||||
|
||||
add_subdirectory(story)
|
||||
add_subdirectory(item)
|
||||
@@ -8,26 +8,16 @@
|
||||
#pragma once
|
||||
#include "rpg/overworld/tile.h"
|
||||
#include "worldpos.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/color.h"
|
||||
|
||||
// #define CHUNK_MESH_COUNT_MAX 3
|
||||
#define CHUNK_VERTEX_COUNT (QUAD_VERTEX_COUNT * CHUNK_TILE_COUNT)
|
||||
#define CHUNK_ENTITY_COUNT_MAX 10
|
||||
|
||||
typedef struct chunk_s {
|
||||
chunkpos_t position;
|
||||
tile_t tiles[CHUNK_TILE_COUNT];
|
||||
|
||||
meshvertex_t vertices[CHUNK_VERTEX_COUNT];
|
||||
uint32_t vertCount;
|
||||
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];
|
||||
tile_t tiles[CHUNK_TILE_COUNT];
|
||||
color_t testColor;
|
||||
uint32_t vertCount;
|
||||
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||
} chunk_t;
|
||||
|
||||
/**
|
||||
|
||||
+15
-156
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -17,18 +17,6 @@ map_t MAP;
|
||||
errorret_t mapInit() {
|
||||
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;
|
||||
int32_t i = 0;
|
||||
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||
@@ -52,61 +40,6 @@ bool_t mapIsLoaded() {
|
||||
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) {
|
||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||
|
||||
@@ -115,43 +48,31 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Determine which chunks remain loaded
|
||||
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 freedCount = 0;
|
||||
uint32_t freedCount = 0;
|
||||
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
// Will this chunk remain loaded?
|
||||
chunk_t *chunk = &MAP.chunks[i];
|
||||
if(
|
||||
chunk->position.x >= newPos.x &&
|
||||
chunk->position.x < newPos.x + MAP_CHUNK_WIDTH &&
|
||||
|
||||
chunk->position.y >= newPos.y &&
|
||||
chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT &&
|
||||
|
||||
chunk->position.z >= newPos.z &&
|
||||
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
|
||||
) {
|
||||
// Stays loaded
|
||||
chunksRemaining[remainingCount++] = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not remaining loaded
|
||||
chunksFreed[freedCount++] = i;
|
||||
}
|
||||
|
||||
// Unload the freed chunks
|
||||
for(chunkindex_t i = 0; i < freedCount; i++) {
|
||||
chunk_t *chunk = &MAP.chunks[chunksFreed[i]];
|
||||
mapChunkUnload(chunk);
|
||||
mapChunkUnload(&MAP.chunks[chunksFreed[i]]);
|
||||
}
|
||||
|
||||
// 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;
|
||||
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
|
||||
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
|
||||
@@ -159,8 +80,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
const chunkpos_t newChunkPos = {
|
||||
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
|
||||
};
|
||||
|
||||
// Is this chunk already loaded (was not unloaded earlier)?
|
||||
|
||||
chunkindex_t chunkIndex = -1;
|
||||
for(chunkindex_t i = 0; i < remainingCount; i++) {
|
||||
chunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
|
||||
@@ -169,9 +89,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Need to load this chunk
|
||||
if(chunkIndex == -1) {
|
||||
// Find a freed chunk to reuse
|
||||
chunkIndex = chunksFreed[--freedCount];
|
||||
chunk_t *chunk = &MAP.chunks[chunkIndex];
|
||||
chunk->position = newChunkPos;
|
||||
@@ -183,25 +101,21 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update map position
|
||||
MAP.chunkPosition = newPos;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void mapUpdate() {
|
||||
|
||||
}
|
||||
|
||||
errorret_t mapDispose() {
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
mapChunkUnload(&MAP.chunks[i]);
|
||||
errorChain(meshDispose(&MAP.chunks[i].mesh));
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void mapChunkUnload(chunk_t* chunk) {
|
||||
void mapChunkUnload(chunk_t *chunk) {
|
||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||
if(chunk->entities[i] == 0xFF) break;
|
||||
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
||||
@@ -210,76 +124,21 @@ void mapChunkUnload(chunk_t* chunk) {
|
||||
chunk->vertCount = 0;
|
||||
}
|
||||
|
||||
errorret_t mapChunkLoad(chunk_t* chunk) {
|
||||
errorret_t mapChunkLoad(chunk_t *chunk) {
|
||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||
|
||||
|
||||
color_t color = COLOR_WHITE;
|
||||
if(chunk->position.y % 2 == 0) {
|
||||
if(chunk->position.x % 2 == 0) {
|
||||
color = COLOR_BLACK;
|
||||
} else {
|
||||
color = COLOR_WHITE;
|
||||
}
|
||||
color = (chunk->position.x % 2 == 0) ? COLOR_BLACK : COLOR_WHITE;
|
||||
} else {
|
||||
if(chunk->position.x % 2 == 0) {
|
||||
color = COLOR_WHITE;
|
||||
} else {
|
||||
color = COLOR_BLACK;
|
||||
}
|
||||
color = (chunk->position.x % 2 == 0) ? COLOR_WHITE : COLOR_BLACK;
|
||||
}
|
||||
// if(chunk->position.x == 0 && chunk->position.y == 0 && chunk->position.z == 0) {
|
||||
// color = COLOR_RED;
|
||||
// }
|
||||
chunk->testColor = color;
|
||||
|
||||
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
|
||||
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
|
||||
|
||||
memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles));
|
||||
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -304,7 +163,7 @@ chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
|
||||
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(!mapIsLoaded()) return NULL;
|
||||
return MAP.chunkOrder[index];
|
||||
@@ -322,4 +181,4 @@ tile_t mapGetTile(const worldpos_t position) {
|
||||
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
|
||||
return chunk->tiles[tileIndex];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "rpg/overworld/chunk.h"
|
||||
|
||||
#define MAP_FILE_PATH_MAX 128
|
||||
|
||||
@@ -10,4 +10,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(test)
|
||||
@@ -1,203 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "scene/scene.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"
|
||||
#include "scene/overworld/sceneoverworld.h"
|
||||
|
||||
errorret_t sceneOverworldInit(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
|
||||
|
||||
|
||||
(void)sceneData;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
|
||||
|
||||
|
||||
(void)sceneData;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
(void)sceneData;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t sceneOverworldDispose(scenedata_t *sceneData) {
|
||||
assertNotNull(sceneData, "Scene data cannot be null");
|
||||
|
||||
|
||||
|
||||
(void)sceneData;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
+6
-42
@@ -7,12 +7,7 @@
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.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/loader/assetloader.h"
|
||||
#include "console/console.h"
|
||||
|
||||
scene_t SCENE;
|
||||
@@ -23,7 +18,6 @@ errorret_t sceneInit(void) {
|
||||
}
|
||||
|
||||
errorret_t sceneUpdate(void) {
|
||||
// Handle scene change.
|
||||
if(SCENE.next != SCENE_TYPE_NULL) {
|
||||
if(
|
||||
SCENE.current != SCENE_TYPE_NULL &&
|
||||
@@ -33,7 +27,7 @@ errorret_t sceneUpdate(void) {
|
||||
}
|
||||
|
||||
SCENE.current = SCENE.next;
|
||||
SCENE.next = SCENE_TYPE_NULL;
|
||||
SCENE.next = SCENE_TYPE_NULL;
|
||||
|
||||
if(
|
||||
SCENE.current != SCENE_TYPE_NULL &&
|
||||
@@ -43,11 +37,11 @@ errorret_t sceneUpdate(void) {
|
||||
}
|
||||
}
|
||||
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) {
|
||||
errorOk();
|
||||
}
|
||||
#endif
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) {
|
||||
errorOk();
|
||||
}
|
||||
#endif
|
||||
|
||||
if(
|
||||
SCENE.current != SCENE_TYPE_NULL &&
|
||||
@@ -60,41 +54,12 @@ errorret_t sceneUpdate(void) {
|
||||
}
|
||||
|
||||
errorret_t sceneRender(void) {
|
||||
// Scene rendering
|
||||
if(
|
||||
SCENE.current != SCENE_TYPE_NULL &&
|
||||
SCENE_TYPES[SCENE.current].render != NULL
|
||||
) {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -110,6 +75,5 @@ errorret_t sceneDispose(void) {
|
||||
) {
|
||||
errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data));
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
@@ -11,10 +11,16 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = {
|
||||
[SCENE_TYPE_NULL] = { 0 },
|
||||
|
||||
[SCENE_TYPE_OVERWORLD] = {
|
||||
.init = sceneOverworldInit,
|
||||
.update = sceneOverworldUpdate,
|
||||
.render = sceneOverworldRender,
|
||||
.init = sceneOverworldInit,
|
||||
.update = sceneOverworldUpdate,
|
||||
.render = sceneOverworldRender,
|
||||
.dispose = sceneOverworldDispose
|
||||
},
|
||||
};
|
||||
|
||||
[SCENE_TYPE_TEST] = {
|
||||
.init = sceneTestInit,
|
||||
.update = sceneTestUpdate,
|
||||
.render = sceneTestRender,
|
||||
.dispose = sceneTestDispose
|
||||
},
|
||||
};
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
#pragma once
|
||||
#include "scene/scenebase.h"
|
||||
#include "scene/overworld/sceneoverworld.h"
|
||||
#include "scene/test/scenetest.h"
|
||||
|
||||
typedef union scenedata_u {
|
||||
sceneoverworld_t overworld;
|
||||
scenetest_t test;
|
||||
} scenedata_t;
|
||||
|
||||
typedef errorret_t (*scenecallback_t)(scenedata_t *);
|
||||
@@ -24,9 +26,8 @@ typedef struct {
|
||||
|
||||
typedef enum {
|
||||
SCENE_TYPE_NULL,
|
||||
|
||||
SCENE_TYPE_OVERWORLD,
|
||||
|
||||
SCENE_TYPE_TEST,
|
||||
SCENE_TYPE_COUNT
|
||||
} scenetype_t;
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# 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
|
||||
meshgl.c
|
||||
)
|
||||
scenetest.c
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -3,15 +3,4 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
ui.c
|
||||
uifps.c
|
||||
uielement.c
|
||||
uiframe.c
|
||||
uifullbox.c
|
||||
uiloading.c
|
||||
uitextbox.c
|
||||
uiplayerpos.c
|
||||
)
|
||||
# Display-dependent UI sources are temporarily disabled pending rop-based rewrite.
|
||||
@@ -3,12 +3,10 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Includes
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(render)
|
||||
|
||||
@@ -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)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user