From d49194fc4d312927d110c2f8e05603e6e2b4238a Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 30 Jul 2026 15:18:07 -0500 Subject: [PATCH] Embed font into binary --- CLAUDE.md | 29 +++++ ROADMAP.md | 33 ++++++ STATUS.md | 102 ++++++++++++++++ src/dusk/display/text/CMakeLists.txt | 1 + src/dusk/display/text/font.c | 168 +++++++++++++++++++++++++++ src/dusk/display/text/font.h | 51 ++++++++ src/dusk/display/text/text.c | 23 +--- src/dusk/display/text/text.h | 2 - 8 files changed, 386 insertions(+), 23 deletions(-) create mode 100644 STATUS.md create mode 100644 src/dusk/display/text/font.c diff --git a/CLAUDE.md b/CLAUDE.md index e8edc700..75486d5a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,9 @@ # Dusk — Claude Code rules +See `STATUS.md` for a periodically-refreshed inventory of subsystem +maturity, test coverage gaps, and known open issues — check it before +assuming a subsystem is fully wired up or before picking a next task. + ## File headers Every C, H, and JS file starts with: @@ -251,6 +255,31 @@ Entity prefabs (`src/dusk/entity/entityprefab.h`) and scene prefabs --- +## Save system +Save data lives under `src/dusk/save/` (`save.h`/`savefile.h`/ +`saveplatform.h`). Slots are fixed-count (`SAVE_FILE_COUNT_MAX`), each +holding a yyjson document persisted with its byte size and a CRC32 +checksum. `saveLoad`/`saveSave` read/write the JSON fresh every call — +callers own the returned/passed `yyjson_doc`/`yyjson_mut_doc` and must +free it themselves. Actual file I/O goes through platform-specific +`saveplatform_t`/stream hooks (one implementation per platform under +`src/dusk/save/`) — do not add direct filesystem calls to the +core `save.c`, extend the platform stream hooks instead. + +## Network system +`src/dusk/network/` (`network.h`) is a connection-state layer only — a +`networkstate_t` state machine (`DISCONNECTED`/`CONNECTING`/`CONNECTED`/ +`DISCONNECTING`) plus an HTTP client (`network/http/`) used for one-off +requests. It is not a multiplayer/replication protocol — that layer +doesn't exist yet (see `ROADMAP.md` items on the socket server/client +and packet handlers). Platform-specific connection logic (e.g. PSP's +`sceNetApctl` polling, GameCube/Wii's `if_config()`) lives under +`src/dusk/network/`, wired through `networkplatform.h` macros +the same way display/input are. Whenever this eventually grows a +multiplayer protocol, apply `ROADMAP.md`'s principle: never trust +incoming packet data — validate defensively with `errorret_t`/ +`errorThrow()`, not asserts. + ## Adding a new script (JS) module Dusk embeds JerryScript (`src/dusk/script/`, fetched via `cmake/modules/Findjerryscript.cmake`). Today only `Entity`, the diff --git a/ROADMAP.md b/ROADMAP.md index b9dd89fe..27488e7e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -44,6 +44,39 @@ Tracking upcoming milestones for the engine. or is disconnected, so clients can spawn or remove the corresponding `clientplayer` entity in the world. +## Infrastructure / debt backlog + +Found during a full-codebase inventory pass (2026-07-30, see `STATUS.md` +for the full survey). These aren't new feature milestones so much as +loose ends worth closing, roughly in order of how cheap/safe they are to +fix: + +1. Re-enable `test/item` (currently commented out in + `test/CMakeLists.txt`) -- confirm it still passes and turn it back on. +2. Restore `itemgive.c/h` in `src/duskrpg/item/CMakeLists.txt` -- the + textbox UI dependency it was waiting on (`ui/textbox/`) is already + back. +3. Decide the fate of the `vita` target: `scripts/build-vita.sh` and + `docker/vita/` reference `-DDUSK_TARGET_SYSTEM=vita`, but no + `cmake/targets/vita.cmake` exists, so the build is currently broken. + Either implement it or remove the dangling scripts/Dockerfile. +4. Add per-PR CI build coverage for at least one non-Linux target (PSP, + Knulli, GameCube, Wii currently only build on tag push, so + regressions there are invisible until a release). +5. Tackle milestone 4 above (poor UI rendering performance) with a + concrete lead: extend `uiconsole.c`'s cached-mesh pattern (rebuild + only on dirty) to the general widget framework + (`uiframe.c`/`uilabel.c`/buttons/menus), which currently rebuilds and + re-uploads geometry via `spriteBatchBuffer`/`meshFlush` every frame. +6. Revisit the GameCube/Wii networking static-IP workaround in + `networkdolphin.c` -- it's standing in for an unresolved suspected + memory-corruption bug in `if_config()`'s DHCP path. +7. Decide whether the PSP dialog-based network connect UI needs to come + back -- it was removed entirely (not fixed) when the original + dialog-tearing bug proved hard to resolve. +8. Backfill unit tests for the biggest untested surfaces: `ui/` (whole + widget framework), `save/`, `script/` (JerryScript bindings), `event/`. + ## Principles - Never trust the network implicitly. Neither side (server or client) diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 00000000..581b14d2 --- /dev/null +++ b/STATUS.md @@ -0,0 +1,102 @@ +# Dusk Engine - Status Snapshot + +This is a point-in-time inventory of the codebase's maturity, test coverage, +and known gaps. It is not auto-maintained -- re-survey periodically (or +whenever picking a new roadmap milestone) rather than trusting it blindly. +See `ROADMAP.md` for the ordered feature milestones this status feeds into, +and `CLAUDE.md` for coding conventions. + +Last surveyed: 2026-07-30, at commit `e61914ba`. + +## Core engine (`src/dusk/`) + +| Subsystem | Maturity | Notes | +|------------|------------------------------------|-------| +| asset | Mature, fully wired | Model loading's "async" path is actually synchronous (`assetmodelloader.h`) -- only real stub found in core. | +| script | Mature for its current scope | Registered modules: `modulePlatform`, `moduleComponent`, `moduleEntity`, `moduleScene` only. No typed per-component JS wrappers (all components go through the generic `Component`). No unit tests. | +| entity | Mature | Engine-level prefab lists are empty sentinels; all real prefabs live in `duskrpg`. | +| scene | Mature | Same prefab-delegation pattern as entity. No JSON serialize/deserialize (removed by design). | +| save | Mature, but undocumented | Slot-based, yyjson + CRC32, platform stream hooks. Not covered in `CLAUDE.md`, no tests. | +| network | Connection-state layer only | HTTP client + connection state machine; no multiplayer/replication protocol (expected -- that's roadmap items 8-17). Not covered in `CLAUDE.md`, has HTTP tests only. | +| physics | Mature, recently churned | Recent revert/re-disable of "old ent code" suggests component wiring around physics isn't fully settled. Well tested. | +| animation | Early/mid-stage | Keyframes + easing only, no blend trees or state machines. Terse commit history ("ANIM") suggests still iterating. | +| display | Most mature/battle-tested | Backbone of the engine; dominated by platform optimization commits. | +| ui | Actively churning | Widget framework has had features added and ripped out repeatedly (story/battle UI added then removed). Rendering path is the likely root cause of roadmap item 4 (see below). No tests at all. | +| console | Small, finished for its scope | Already has the cached-mesh optimization pattern the rest of `ui/` lacks. | +| event | Clean, small, finished | Pub/sub, rebuilt to replace the old input system. | +| game | Intentionally header-only | Real implementation lives in `duskrpg/game/game.c`. | + +### UI rendering performance (roadmap item 4) +Confirmed root-cause candidate: `src/dusk/ui/debug/uiconsole.c` caches a +persistent mesh and only rebuilds on dirty/scroll change. The rest of the +widget framework (`uiframe.c`, `uilabel.c`, buttons, menus, settings +screens) still goes through `spriteBatchBuffer`/`spriteBatchFlush` and +re-uploads vertex data via `meshFlush` every frame, with a full flush +forced on every shader/material change. This is almost certainly what +"tanks framerate" on PSP. Fix direction: extend the console's cached-mesh +pattern to the general widget path (rebuild only on dirty, not every +frame). + +## Game layer (`src/duskrpg/`) + +Actively maintained, not orphaned (despite an old "remove rpg" commit deep +in history) -- last touched the same day as this survey. + +- **cutscene/** -- actively developed, matches `CLAUDE.md`'s documented + recipe exactly. 16 registered item types. +- **entity/, scene/** -- overworld player/camera/interactable components + and prefabs, active. +- **item/** -- `item.c`/`inventory.c`/`backpack.c` built via a working + `item.json` -> `itemdef.h` codegen pipeline. `itemgive.c/h` exist on + disk but are explicitly excluded from the CMake build pending textbox + UI restoration -- **that UI (`ui/textbox/`) is already restored**, so + this looks like an overdue follow-up, not a real blocker. +- **input/** -- headers only, no implementation. Contains the one + genuine TODO found in the whole `duskrpg` tree: `// TODO: Wiimote, USB + Keyboard, probably more.` +- **ui/** -- textbox restored and built; `uitestlabel.c/h` is an + intentional smoke-test scaffold, not dead code. + +## Platform layers + +| Platform | Status | +|--------------|--------| +| duskgl / dusksdl2 | Complete, shared by Linux/Knulli/PSP, no gaps found. | +| dusklinux | Complete, well-trodden. | +| duskpsp | Complete for current design. The historical dialog/tearing bug (see memory `project_psp_dialog_tearing` etc.) was **not fixed -- the dialog-based connect UI was removed entirely** (commit `d7982599`, "Simplified PSP network") in favor of a silent profile-based connect. Revisit if the dialog UX is still wanted. | +| duskdolphin (GameCube/Wii) | Functional, not a stub -- real GX-based display/mesh/shader/texture. Wii input uses only the GameCube `PAD_*` library; no WPAD/Wiimote support (`inputdolphin.h` has `#error "Wii not implemented"` gated behind macros that are never defined, so currently dormant, not a live build break). `networkdolphin.c` hardcodes a static IP as an explicit temporary workaround for a suspected DHCP-related memory-corruption bug in `if_config()` -- root cause still open. | +| vita | **Referenced by `scripts/build-vita.sh` and `docker/vita/` but no `cmake/targets/vita.cmake` exists** -- the build target is broken/unfinished at the CMake level. | + +### CI coverage gap +`.github/workflows/test.yml` only builds+tests Linux, on PRs to `main`. +`build.yml` builds all other platforms (PSP, Knulli, GameCube, Wii + +ISO variants) but **only on tag push** (release time). Vita and Dolphin +aren't in CI at all. Net effect: regressions on 4+ platforms can land +silently until a release tag is cut. + +## Test coverage gaps + +Core `src/dusk/` subsystems with **zero** unit tests: `console`, +`engine`, `event`, `game`, `input`, `log`, `save`, `script` (+ all JS +module bindings), `system`, `ui` (entire widget framework). + +`src/duskrpg/` has almost no test coverage: `test/item/test_inventory.c` +exists but **`add_subdirectory(item)` is commented out in +`test/CMakeLists.txt`**, so even that one test never runs. `cutscene`, +`entity`, `game`, `input`, `scene`, `ui` under `duskrpg` have no tests at +all. + +No stale tests were found referencing deleted systems (old JSON +serialize/deserialize, old event/cutscene modules) -- the test suite is +internally consistent with current code, just incomplete in coverage. + +## Build/tooling gaps worth knowing about + +- `assetsraw/` -> `assets/` (chunk JSON -> `.dcf`) is a manual/editor-only + step (`tools.asset.chunk`), not part of the CMake build -- committed + `.dcf` files can silently drift from their raw source. +- Several Python tool packages exist but aren't wired into any build: + `tools/color/csv/`, `tools/asset/chunk_json/`, `tools/asset/dmf/`, + `tools/asset/tiles/`, `tools/input/csv/`. Some may be superseded by + `tools/color.py`/`tools/item.py`; worth a pass to confirm which are + live vs leftover. diff --git a/src/dusk/display/text/CMakeLists.txt b/src/dusk/display/text/CMakeLists.txt index 76a85df0..bc9c3761 100644 --- a/src/dusk/display/text/CMakeLists.txt +++ b/src/dusk/display/text/CMakeLists.txt @@ -6,5 +6,6 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + font.c text.c ) \ No newline at end of file diff --git a/src/dusk/display/text/font.c b/src/dusk/display/text/font.c new file mode 100644 index 00000000..1b690bef --- /dev/null +++ b/src/dusk/display/text/font.c @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "font.h" +#include "util/memory.h" +#include "util/math.h" +#include "display/color.h" + +font_t FONT_DEFAULT; +static texture_t FONT_DEFAULT_TEXTURE; +static tileset_t FONT_DEFAULT_TILESET; + +const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][ + FONT_DEFAULT_TILE_HEIGHT +] = { + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00 }, // ! + { 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // " + { 0x00, 0x14, 0x14, 0x3E, 0x14, 0x3E, 0x14, 0x14, 0x00, 0x00 }, // # + { 0x00, 0x08, 0x1E, 0x28, 0x1C, 0x0A, 0x3C, 0x08, 0x00, 0x00 }, // $ + { 0x00, 0x00, 0x22, 0x24, 0x08, 0x12, 0x22, 0x00, 0x00, 0x00 }, // % + { 0x00, 0x08, 0x14, 0x14, 0x1A, 0x24, 0x24, 0x1A, 0x00, 0x00 }, // & + { 0x00, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ' + { 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, 0x08, 0x04, 0x00, 0x00 }, // ( + { 0x00, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x00, 0x00 }, // ) + { 0x00, 0x00, 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00, 0x00 }, // * + { 0x00, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00, 0x00 }, // + + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00 }, // , + { 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00 }, // - + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00 }, // . + { 0x00, 0x04, 0x04, 0x08, 0x08, 0x08, 0x10, 0x10, 0x00, 0x00 }, // / + { 0x00, 0x1C, 0x22, 0x26, 0x2A, 0x32, 0x22, 0x1C, 0x00, 0x00 }, // 0 + { 0x00, 0x08, 0x18, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00 }, // 1 + { 0x00, 0x1C, 0x22, 0x02, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // 2 + { 0x00, 0x1C, 0x22, 0x02, 0x0C, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // 3 + { 0x00, 0x22, 0x22, 0x22, 0x3E, 0x02, 0x02, 0x02, 0x00, 0x00 }, // 4 + { 0x00, 0x3E, 0x20, 0x20, 0x3C, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // 5 + { 0x00, 0x1C, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // 6 + { 0x00, 0x3E, 0x02, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00 }, // 7 + { 0x00, 0x1C, 0x22, 0x22, 0x1C, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // 8 + { 0x00, 0x1C, 0x22, 0x22, 0x1E, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // 9 + { 0x00, 0x00, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00 }, // : + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ; + { 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00 }, // < + { 0x00, 0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00 }, // = + { 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00 }, // > + { 0x00, 0x1C, 0x22, 0x02, 0x04, 0x08, 0x00, 0x08, 0x00, 0x00 }, // ? + { 0x00, 0x1C, 0x26, 0x2A, 0x2A, 0x26, 0x20, 0x1C, 0x00, 0x00 }, // @ + { 0x00, 0x1C, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x00, 0x00 }, // A + { 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // B + { 0x00, 0x1C, 0x22, 0x20, 0x20, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // C + { 0x00, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // D + { 0x00, 0x3E, 0x20, 0x20, 0x3C, 0x20, 0x20, 0x3E, 0x00, 0x00 }, // E + { 0x00, 0x3E, 0x20, 0x20, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00 }, // F + { 0x00, 0x1C, 0x22, 0x20, 0x2E, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // G + { 0x00, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x00, 0x00 }, // H + { 0x00, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00 }, // I + { 0x00, 0x02, 0x02, 0x02, 0x02, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // J + { 0x00, 0x22, 0x24, 0x28, 0x30, 0x28, 0x24, 0x22, 0x00, 0x00 }, // K + { 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3E, 0x00, 0x00 }, // L + { 0x00, 0x22, 0x36, 0x2A, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00 }, // M + { 0x00, 0x22, 0x22, 0x32, 0x2A, 0x26, 0x22, 0x22, 0x00, 0x00 }, // N + { 0x00, 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // O + { 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x20, 0x20, 0x20, 0x00, 0x00 }, // P + { 0x00, 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x06, 0x00 }, // Q + { 0x00, 0x3C, 0x22, 0x22, 0x3C, 0x22, 0x22, 0x22, 0x00, 0x00 }, // R + { 0x00, 0x1C, 0x22, 0x20, 0x1C, 0x02, 0x22, 0x1C, 0x00, 0x00 }, // S + { 0x00, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // T + { 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // U + { 0x00, 0x22, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00, 0x00 }, // V + { 0x00, 0x22, 0x22, 0x22, 0x22, 0x2A, 0x36, 0x22, 0x00, 0x00 }, // W + { 0x00, 0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00, 0x00 }, // X + { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // Y + { 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, 0x00 }, // Z + { 0x00, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0C, 0x00, 0x00 }, // [ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // backslash (not drawn in source font) + { 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00, 0x00 }, // ] + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ^ (not drawn in source font) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // _ (not drawn in source font) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ` (not drawn in source font) + { 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // a + { 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // b + { 0x00, 0x00, 0x00, 0x1C, 0x22, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // c + { 0x00, 0x02, 0x02, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // d + { 0x00, 0x00, 0x00, 0x1C, 0x22, 0x3E, 0x20, 0x1C, 0x00, 0x00 }, // e + { 0x00, 0x0C, 0x12, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x00, 0x00 }, // f + { 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // g + { 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00 }, // h + { 0x00, 0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00 }, // i + { 0x00, 0x02, 0x00, 0x06, 0x02, 0x02, 0x02, 0x02, 0x22, 0x1C }, // j + { 0x00, 0x20, 0x20, 0x22, 0x24, 0x38, 0x24, 0x22, 0x00, 0x00 }, // k + { 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0E, 0x00, 0x00 }, // l + { 0x00, 0x00, 0x00, 0x3C, 0x2A, 0x2A, 0x2A, 0x2A, 0x00, 0x00 }, // m + { 0x00, 0x00, 0x00, 0x3C, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00 }, // n + { 0x00, 0x00, 0x00, 0x1C, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00 }, // o + { 0x00, 0x00, 0x00, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x20, 0x20 }, // p + { 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x02 }, // q + { 0x00, 0x00, 0x00, 0x2C, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00 }, // r + { 0x00, 0x00, 0x00, 0x1E, 0x20, 0x1C, 0x02, 0x3C, 0x00, 0x00 }, // s + { 0x00, 0x10, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x0E, 0x00, 0x00 }, // t + { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // u + { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00, 0x00 }, // v + { 0x00, 0x00, 0x00, 0x22, 0x22, 0x2A, 0x2A, 0x14, 0x00, 0x00 }, // w + { 0x00, 0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, 0x00 }, // x + { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // y + { 0x00, 0x00, 0x00, 0x3E, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // z + { 0x00, 0x04, 0x08, 0x08, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, // { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // | (not drawn in source font) + { 0x00, 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00, 0x00 }, // } + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ~ (not drawn in source font) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile +}; + +errorret_t fontInitDefault(void) { + const int32_t width = (int32_t)mathNextPowTwo( + FONT_DEFAULT_COLUMNS * FONT_DEFAULT_TILE_WIDTH + ); + const int32_t height = (int32_t)mathNextPowTwo( + FONT_DEFAULT_ROWS * FONT_DEFAULT_TILE_HEIGHT + ); + + color_t *pixels = memoryAllocate(sizeof(color_t) * width * height); + memoryZero(pixels, sizeof(color_t) * width * height); + + for(uint16_t i = 0; i < FONT_DEFAULT_TILE_COUNT; i++) { + const uint16_t tileX = (i % FONT_DEFAULT_COLUMNS) * FONT_DEFAULT_TILE_WIDTH; + const uint16_t tileY = (i / FONT_DEFAULT_COLUMNS) * FONT_DEFAULT_TILE_HEIGHT; + + for(uint8_t row = 0; row < FONT_DEFAULT_TILE_HEIGHT; row++) { + const uint8_t bits = FONT_DEFAULT_GLYPHS[i][row]; + + for(uint8_t col = 0; col < FONT_DEFAULT_TILE_WIDTH; col++) { + if(!((bits >> (FONT_DEFAULT_TILE_WIDTH - 1 - col)) & 1)) continue; + pixels[((tileY + row) * width) + (tileX + col)] = COLOR_WHITE; + } + } + } + + FONT_DEFAULT_TILESET.tileWidth = FONT_DEFAULT_TILE_WIDTH; + FONT_DEFAULT_TILESET.tileHeight = FONT_DEFAULT_TILE_HEIGHT; + FONT_DEFAULT_TILESET.columns = FONT_DEFAULT_COLUMNS; + FONT_DEFAULT_TILESET.rows = FONT_DEFAULT_ROWS; + FONT_DEFAULT_TILESET.tileCount = FONT_DEFAULT_TILE_COUNT; + FONT_DEFAULT_TILESET.uv[0] = (float_t)FONT_DEFAULT_TILE_WIDTH / (float_t)width; + FONT_DEFAULT_TILESET.uv[1] = (float_t)FONT_DEFAULT_TILE_HEIGHT / (float_t)height; + + const texturedata_t data = { .rgbaColors = pixels }; + errorret_t textureResult = textureInit( + &FONT_DEFAULT_TEXTURE, width, height, TEXTURE_FORMAT_RGBA, data + ); + memoryFree(pixels); + errorChain(textureResult); + + FONT_DEFAULT.texture = &FONT_DEFAULT_TEXTURE; + FONT_DEFAULT.tileset = &FONT_DEFAULT_TILESET; + errorOk(); +} + +errorret_t fontDisposeDefault(void) { + errorChain(textureDispose(&FONT_DEFAULT_TEXTURE)); + FONT_DEFAULT.texture = NULL; + FONT_DEFAULT.tileset = NULL; + errorOk(); +} diff --git a/src/dusk/display/text/font.h b/src/dusk/display/text/font.h index 9cdc7b3b..b8a785d3 100644 --- a/src/dusk/display/text/font.h +++ b/src/dusk/display/text/font.h @@ -6,6 +6,7 @@ */ #pragma once +#include "error/error.h" #include "display/texture/texture.h" #include "display/texture/tileset.h" @@ -13,3 +14,53 @@ typedef struct { texture_t *texture; tileset_t *tileset; } font_t; + +/** + * Pixel width/height of a single default-font glyph tile. Matches the + * glyph grid baked into the (now retired) ui/minogram.png + .dtf asset + * pair this data was extracted from. + */ +#define FONT_DEFAULT_TILE_WIDTH 6 +#define FONT_DEFAULT_TILE_HEIGHT 10 + +/** Grid layout of the generated default-font texture, in tiles. */ +#define FONT_DEFAULT_COLUMNS 16 +#define FONT_DEFAULT_ROWS 6 + +/** + * Number of glyphs defined in FONT_DEFAULT_GLYPHS (FONT_DEFAULT_COLUMNS * + * FONT_DEFAULT_ROWS), covering the printable ASCII range starting at + * TEXT_CHAR_START ('!') plus a couple of unused trailing tiles. + */ +#define FONT_DEFAULT_TILE_COUNT (FONT_DEFAULT_COLUMNS * FONT_DEFAULT_ROWS) + +extern font_t FONT_DEFAULT; + +/** + * Hard coded bitmap data for the built-in default font, extracted + * pixel-for-pixel from the original ui/minogram.png glyph atlas (alpha + * >= 128 counts as set). Indexed [glyph][row], where glyph 0 corresponds + * to TEXT_CHAR_START ('!') and glyphs run consecutively through the + * printable ASCII range. Each row byte holds FONT_DEFAULT_TILE_WIDTH bit + * flags, one per pixel column: bit (FONT_DEFAULT_TILE_WIDTH - 1) is the + * leftmost pixel and bit 0 is the rightmost; 1 means the pixel is set, + * 0 means it is not. + */ +extern const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][ + FONT_DEFAULT_TILE_HEIGHT +]; + +/** + * Builds the built-in default font (texture + tileset) directly from + * FONT_DEFAULT_GLYPHS, without going through the asset system. + * + * @return Either an error or success result. + */ +errorret_t fontInitDefault(void); + +/** + * Disposes of the built-in default font created by fontInitDefault. + * + * @return Either an error or success result. + */ +errorret_t fontDisposeDefault(void); diff --git a/src/dusk/display/text/text.c b/src/dusk/display/text/text.c index 68f28f95..443c6e81 100644 --- a/src/dusk/display/text/text.c +++ b/src/dusk/display/text/text.c @@ -9,34 +9,15 @@ #include "assert/assert.h" #include "util/memory.h" #include "display/spritebatch/spritebatch.h" -#include "asset/asset.h" -#include "asset/loader/display/assettextureloader.h" -#include "asset/loader/display/assettilesetloader.h" #include "display/shader/shaderunlit.h" -font_t FONT_DEFAULT; - errorret_t textInit(void) { - assetloaderinput_t input = { .texture = TEXTURE_FORMAT_RGBA }; - assetentry_t *entryTexture = assetLock( - "ui/minogram.png", ASSET_LOADER_TYPE_TEXTURE, &input - ); - assetentry_t *entryTileset = assetLock( - "ui/minogram.dtf", ASSET_LOADER_TYPE_TILESET, NULL - ); - errorChain(assetRequireLoaded(entryTexture)); - errorChain(assetRequireLoaded(entryTileset)); - - FONT_DEFAULT.texture = &entryTexture->data.texture; - FONT_DEFAULT.tileset = &entryTileset->data.tileset; + errorChain(fontInitDefault()); errorOk(); } errorret_t textDispose(void) { - FONT_DEFAULT.texture = NULL; - FONT_DEFAULT.tileset = NULL; - assetUnlock("ui/minogram.png"); - assetUnlock("ui/minogram.dtf"); + errorChain(fontDisposeDefault()); errorOk(); } diff --git a/src/dusk/display/text/text.h b/src/dusk/display/text/text.h index 82a0dc95..4955d9d3 100644 --- a/src/dusk/display/text/text.h +++ b/src/dusk/display/text/text.h @@ -12,8 +12,6 @@ #define TEXT_CHAR_START '!' -extern font_t FONT_DEFAULT; - /** * Initializes the text system. *