summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kon/kondebug.h748
-rw-r--r--src/impl/kondebug.c2
2 files changed, 750 insertions, 0 deletions
diff --git a/kon/kondebug.h b/kon/kondebug.h
new file mode 100644
index 0000000..9fbe881
--- /dev/null
+++ b/kon/kondebug.h
@@ -0,0 +1,748 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * kondebug.h
+ *
+ * Copyright (c) 2026 KonAki
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef KONDEBUG_H
+#define KONDEBUG_H
+
+#include <stdint.h>
+
+#include "konwinlib.h"
+#include "konsofren.h"
+#include "konfont.h"
+#include "konassets.h"
+
+/*** definition ***/
+
+typedef struct kon_debug kon_debug_t;
+
+typedef enum kon_debugVarType {
+ KON_VAR_BOOL = 0,
+ KON_VAR_INT,
+ KON_VAR_FLOAT
+} kon_debugVarType_t;
+
+/* argv[0] is the command name */
+typedef void (*kon_debugCommandFn)(kon_debug_t *debug, void *user, int argc, char **argv);
+
+#ifdef KON_DEVELOPER
+
+kon_debug_t *kon_debugCreate(kon_font_t *font);
+void kon_debugDestroy(kon_debug_t *debug);
+
+/* defaults are F1 for the overlay and ` for the console */
+void kon_debugSetKeys(kon_debug_t *debug, int overlayKey, int consoleKey);
+void kon_debugSetAssets(kon_debug_t *debug, kon_assets_t *assets);
+
+/* name and help have to stay valid, use string literals */
+int kon_debugCommand(kon_debug_t *debug, const char *name, const char *help, kon_debugCommandFn fn, void *user);
+int kon_debugVar(kon_debug_t *debug, const char *name, kon_debugVarType_t type, void *value);
+
+void kon_debugLog(kon_debug_t *debug, const char *format, ...);
+
+/* returns 1 when the event was used by the console, so the game should skip it */
+int kon_debugFeedEvent(kon_debug_t *debug, const kon_event_t *event);
+int kon_debugConsoleOpen(const kon_debug_t *debug);
+
+/* call once a frame, draw last so it ends up on top */
+void kon_debugFrame(kon_debug_t *debug);
+void kon_debugDraw(kon_debug_t *debug, kon_framebuffer_t *fb);
+
+float kon_debugFps(const kon_debug_t *debug);
+
+#else
+
+static inline kon_debug_t *kon_debugCreate(kon_font_t *font) { (void)font; return NULL; }
+static inline void kon_debugDestroy(kon_debug_t *debug) { (void)debug; }
+static inline void kon_debugSetKeys(kon_debug_t *debug, int overlayKey, int consoleKey) { (void)debug; (void)overlayKey; (void)consoleKey; }
+static inline void kon_debugSetAssets(kon_debug_t *debug, kon_assets_t *assets) { (void)debug; (void)assets; }
+static inline int kon_debugCommand(kon_debug_t *debug, const char *name, const char *help, kon_debugCommandFn fn, void *user) { (void)debug; (void)name; (void)help; (void)fn; (void)user; return 0; }
+static inline int kon_debugVar(kon_debug_t *debug, const char *name, kon_debugVarType_t type, void *value) { (void)debug; (void)name; (void)type; (void)value; return 0; }
+#define kon_debugLog(...) ((void)0)
+static inline int kon_debugFeedEvent(kon_debug_t *debug, const kon_event_t *event) { (void)debug; (void)event; return 0; }
+static inline int kon_debugConsoleOpen(const kon_debug_t *debug) { (void)debug; return 0; }
+static inline void kon_debugFrame(kon_debug_t *debug) { (void)debug; }
+static inline void kon_debugDraw(kon_debug_t *debug, kon_framebuffer_t *fb) { (void)debug; (void)fb; }
+static inline float kon_debugFps(const kon_debug_t *debug) { (void)debug; return 0.0f; }
+
+#endif
+
+#endif
+
+/*** implementation ***/
+
+#if defined(KONDEBUG_IMPLEMENTATION) && defined(KON_DEVELOPER)
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "kontime.h"
+
+#define KON_DEBUG_LOGLINES 256
+#define KON_DEBUG_LINELEN 160
+#define KON_DEBUG_INPUT 256
+#define KON_DEBUG_HISTORY 16
+#define KON_DEBUG_COMMANDS 64
+#define KON_DEBUG_VARS 64
+#define KON_DEBUG_ARGS 16
+#define KON_DEBUG_GRAPH 120
+#define KON_DEBUG_TYPES 8
+
+typedef struct kon_debugCmd {
+ const char *name;
+ const char *help;
+ kon_debugCommandFn fn;
+ void *user;
+} kon_debugCmd_t;
+
+typedef struct kon_debugVarEntry {
+ const char *name;
+ kon_debugVarType_t type;
+ void *value;
+ int watched;
+} kon_debugVarEntry_t;
+
+struct kon_debug {
+ kon_font_t *font;
+ kon_assets_t *assets;
+ int lineH, ascent;
+
+ int overlayKey, consoleKey;
+ int overlay;
+ int open;
+ float slide;
+ int swallowTick;
+
+ char log[KON_DEBUG_LOGLINES][KON_DEBUG_LINELEN];
+ int logCount, logHead, scroll;
+
+ char input[KON_DEBUG_INPUT];
+ int length, cursor;
+ char history[KON_DEBUG_HISTORY][KON_DEBUG_INPUT];
+ int historyCount, historyPos;
+
+ kon_debugCmd_t commands[KON_DEBUG_COMMANDS];
+ int commandCount;
+ kon_debugVarEntry_t vars[KON_DEBUG_VARS];
+ int varCount;
+
+ kon_time last;
+ double frameDt;
+ double accumTime;
+ int accumFrames;
+ float fps;
+ float graph[KON_DEBUG_GRAPH];
+ int graphHead;
+};
+
+/*** private helpers ***/
+
+static void kon_debugText_(const kon_debug_t *d, kon_framebuffer_t *fb, int x, int y, const char *s, uint32_t color)
+{
+ kon_drawText(fb, d->font, x, y + d->ascent, s, color);
+}
+
+static void kon_debugPushLine_(kon_debug_t *d, const char *line)
+{
+ int slot = (d->logHead + d->logCount) % KON_DEBUG_LOGLINES;
+
+ if (d->logCount == KON_DEBUG_LOGLINES) {
+ slot = d->logHead;
+ d->logHead = (d->logHead + 1) % KON_DEBUG_LOGLINES;
+ } else {
+ d->logCount++;
+ }
+ snprintf(d->log[slot], KON_DEBUG_LINELEN, "%s", line);
+ if (d->scroll > 0 && d->scroll < d->logCount) d->scroll++;
+}
+
+static const char *kon_debugVarText_(const kon_debugVarEntry_t *v, char *buffer, size_t size)
+{
+ switch (v->type) {
+ case KON_VAR_BOOL: snprintf(buffer, size, "%s", *(int *)v->value ? "true" : "false"); break;
+ case KON_VAR_INT: snprintf(buffer, size, "%d", *(int *)v->value); break;
+ default: snprintf(buffer, size, "%g", (double)*(float *)v->value); break;
+ }
+ return buffer;
+}
+
+static int kon_debugTokenize_(char *line, char **argv, int max)
+{
+ int argc = 0;
+
+ while (*line && argc < max) {
+ while (*line == ' ' || *line == '\t') line++;
+ if (!*line) break;
+ if (*line == '"') {
+ argv[argc++] = ++line;
+ while (*line && *line != '"') line++;
+ } else {
+ argv[argc++] = line;
+ while (*line && *line != ' ' && *line != '\t') line++;
+ }
+ if (*line) *line++ = 0;
+ }
+ return argc;
+}
+
+static void kon_debugSetVar_(kon_debug_t *d, kon_debugVarEntry_t *v, const char *text)
+{
+ char out[64];
+
+ switch (v->type) {
+ case KON_VAR_BOOL:
+ if (!strcmp(text, "1") || !strcmp(text, "true") || !strcmp(text, "on")) *(int *)v->value = 1;
+ else if (!strcmp(text, "0") || !strcmp(text, "false") || !strcmp(text, "off")) *(int *)v->value = 0;
+ else {
+ kon_debugLog(d, "%s expects 0 or 1", v->name);
+ return;
+ }
+ break;
+ case KON_VAR_INT: *(int *)v->value = atoi(text); break;
+ default: *(float *)v->value = (float)atof(text); break;
+ }
+ kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out)));
+}
+
+static void kon_debugExecute_(kon_debug_t *d, const char *line)
+{
+ char copy[KON_DEBUG_INPUT], *argv[KON_DEBUG_ARGS];
+ int argc, i;
+
+ snprintf(copy, sizeof(copy), "%s", line);
+ argc = kon_debugTokenize_(copy, argv, KON_DEBUG_ARGS);
+ if (argc == 0) return;
+
+ for (i = 0; i < d->commandCount; i++) {
+ if (!strcmp(argv[0], d->commands[i].name)) {
+ d->commands[i].fn(d, d->commands[i].user, argc, argv);
+ return;
+ }
+ }
+ for (i = 0; i < d->varCount; i++) {
+ kon_debugVarEntry_t *v = &d->vars[i];
+ char out[64];
+
+ if (strcmp(argv[0], v->name)) continue;
+ if (argc > 1) kon_debugSetVar_(d, v, argv[1]);
+ else kon_debugLog(d, "%s = %s", v->name, kon_debugVarText_(v, out, sizeof(out)));
+ return;
+ }
+ kon_debugLog(d, "unknown command '%s', try help", argv[0]);
+}
+
+static void kon_debugComplete_(kon_debug_t *d)
+{
+ const char *matches[KON_DEBUG_COMMANDS + KON_DEBUG_VARS];
+ int count = 0, i, common;
+ size_t prefix = (size_t)d->cursor;
+
+ for (i = 0; i < d->cursor; i++) if (d->input[i] == ' ') return;
+ if (prefix == 0) return;
+
+ for (i = 0; i < d->commandCount; i++) if (!strncmp(d->commands[i].name, d->input, prefix)) matches[count++] = d->commands[i].name;
+ for (i = 0; i < d->varCount; i++) if (!strncmp(d->vars[i].name, d->input, prefix)) matches[count++] = d->vars[i].name;
+ if (count == 0) return;
+
+ common = (int)strlen(matches[0]);
+ for (i = 1; i < count; i++) {
+ int n = 0;
+
+ while (n < common && matches[i][n] == matches[0][n]) n++;
+ common = n;
+ }
+
+ if (count > 1) {
+ char line[KON_DEBUG_LINELEN] = "";
+
+ for (i = 0; i < count; i++) {
+ if (strlen(line) + strlen(matches[i]) + 2 >= sizeof(line)) break;
+ strcat(line, matches[i]);
+ strcat(line, " ");
+ }
+ kon_debugLog(d, "%s", line);
+ }
+
+ {
+ char rest[KON_DEBUG_INPUT];
+ int add = common - d->cursor + (count == 1 ? 1 : 0);
+
+ snprintf(rest, sizeof(rest), "%s", d->input + d->cursor);
+ if (d->length + add >= KON_DEBUG_INPUT) return;
+ memcpy(d->input, matches[0], (size_t)common);
+ if (count == 1) d->input[common] = ' ';
+ memcpy(d->input + common + (count == 1 ? 1 : 0), rest, strlen(rest) + 1);
+ d->length = (int)strlen(d->input);
+ d->cursor = common + (count == 1 ? 1 : 0);
+ }
+}
+
+static void kon_debugInsert_(kon_debug_t *d, char c)
+{
+ if (d->length + 1 >= KON_DEBUG_INPUT) return;
+ memmove(d->input + d->cursor + 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1);
+ d->input[d->cursor++] = c;
+ d->length++;
+}
+
+static void kon_debugSetInput_(kon_debug_t *d, const char *text)
+{
+ snprintf(d->input, sizeof(d->input), "%s", text);
+ d->length = (int)strlen(d->input);
+ d->cursor = d->length;
+}
+
+static void kon_debugSubmit_(kon_debug_t *d)
+{
+ char line[KON_DEBUG_INPUT + 4];
+
+ if (d->length == 0) return;
+ snprintf(line, sizeof(line), "> %s", d->input);
+ kon_debugPushLine_(d, line);
+
+ if (d->historyCount == 0 || strcmp(d->history[d->historyCount - 1], d->input)) {
+ if (d->historyCount == KON_DEBUG_HISTORY) {
+ memmove(d->history[0], d->history[1], sizeof(d->history[0]) * (KON_DEBUG_HISTORY - 1));
+ d->historyCount--;
+ }
+ snprintf(d->history[d->historyCount++], KON_DEBUG_INPUT, "%s", d->input);
+ }
+ d->historyPos = d->historyCount;
+
+ {
+ char copy[KON_DEBUG_INPUT];
+
+ snprintf(copy, sizeof(copy), "%s", d->input);
+ d->input[0] = 0;
+ d->length = d->cursor = 0;
+ d->scroll = 0;
+ kon_debugExecute_(d, copy);
+ }
+}
+
+/*** builtin commands ***/
+
+static void kon_debugCmdHelp_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ int i;
+
+ (void)user;
+ if (argc > 1) {
+ for (i = 0; i < d->commandCount; i++) {
+ if (!strcmp(argv[1], d->commands[i].name)) {
+ kon_debugLog(d, "%s: %s", d->commands[i].name, d->commands[i].help);
+ return;
+ }
+ }
+ kon_debugLog(d, "no help for '%s'", argv[1]);
+ return;
+ }
+ kon_debugLog(d, "commands:");
+ for (i = 0; i < d->commandCount; i++) kon_debugLog(d, " %-10s %s", d->commands[i].name, d->commands[i].help);
+ kon_debugLog(d, "variables (type the name to read, name value to set):");
+ for (i = 0; i < d->varCount; i++) kon_debugLog(d, " %s", d->vars[i].name);
+}
+
+static void kon_debugCmdClear_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ (void)user; (void)argc; (void)argv;
+ d->logCount = d->logHead = d->scroll = 0;
+}
+
+static void kon_debugCmdEcho_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ char line[KON_DEBUG_LINELEN] = "";
+ int i;
+
+ (void)user;
+ for (i = 1; i < argc; i++) {
+ if (strlen(line) + strlen(argv[i]) + 2 >= sizeof(line)) break;
+ strcat(line, argv[i]);
+ strcat(line, " ");
+ }
+ kon_debugLog(d, "%s", line);
+}
+
+static void kon_debugCmdFps_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ (void)user;
+ d->overlay = argc > 1 ? atoi(argv[1]) & 3 : (d->overlay + 1) & 3;
+}
+
+static void kon_debugCmdWatch_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ int i;
+
+ (void)user;
+ if (argc < 2) {
+ kon_debugLog(d, "usage: watch <variable>");
+ return;
+ }
+ for (i = 0; i < d->varCount; i++) {
+ if (!strcmp(argv[1], d->vars[i].name)) {
+ d->vars[i].watched = !d->vars[i].watched;
+ if (d->overlay == 0) d->overlay = 1;
+ return;
+ }
+ }
+ kon_debugLog(d, "no variable '%s'", argv[1]);
+}
+
+static void kon_debugCmdAssets_(kon_debug_t *d, void *user, int argc, char **argv)
+{
+ int i, count;
+ kon_assetInfo_t info;
+
+ (void)user; (void)argc; (void)argv;
+ if (!d->assets) {
+ kon_debugLog(d, "no asset manager attached");
+ return;
+ }
+ count = kon_assetsCount(d->assets);
+ for (i = 0; i < count; i++) {
+ if (!kon_assetsInfo(d->assets, i, &info)) continue;
+ kon_debugLog(d, "%-7s %3d ref %8lu b %s%s", info.typeName, info.refs, (unsigned long)info.bytes, info.name, info.loose ? " (loose)" : "");
+ }
+ kon_debugLog(d, "%d assets, %lu bytes", count, (unsigned long)kon_assetsBytes(d->assets));
+}
+
+/*** debug ***/
+
+kon_debug_t *kon_debugCreate(kon_font_t *font)
+{
+ kon_debug_t *d;
+ int h = 0;
+
+ if (!font) return NULL;
+ d = calloc(1, sizeof(kon_debug_t));
+ if (!d) return NULL;
+
+ d->font = font;
+ kon_measureText(font, "A", NULL, &h);
+ d->ascent = h * 4 / 5;
+ d->lineH = h + 2;
+ d->overlayKey = KON_KEY_F1;
+ d->consoleKey = KON_KEY_GRAVE;
+ d->last = kon_getTime();
+
+ kon_debugCommand(d, "help", "list commands, or help <command>", kon_debugCmdHelp_, NULL);
+ kon_debugCommand(d, "clear", "clear the console", kon_debugCmdClear_, NULL);
+ kon_debugCommand(d, "echo", "print text", kon_debugCmdEcho_, NULL);
+ kon_debugCommand(d, "fps", "overlay mode 0-3, or cycle", kon_debugCmdFps_, NULL);
+ kon_debugCommand(d, "watch", "show a variable in the overlay", kon_debugCmdWatch_, NULL);
+ kon_debugCommand(d, "assets", "list loaded assets", kon_debugCmdAssets_, NULL);
+ kon_debugLog(d, "type help for commands, tab completes");
+ return d;
+}
+
+void kon_debugDestroy(kon_debug_t *d)
+{
+ free(d);
+}
+
+void kon_debugSetKeys(kon_debug_t *d, int overlayKey, int consoleKey)
+{
+ if (!d) return;
+ d->overlayKey = overlayKey;
+ d->consoleKey = consoleKey;
+}
+
+void kon_debugSetAssets(kon_debug_t *d, kon_assets_t *assets)
+{
+ if (!d) return;
+ d->assets = assets;
+}
+
+int kon_debugCommand(kon_debug_t *d, const char *name, const char *help, kon_debugCommandFn fn, void *user)
+{
+ if (!d) return 0;
+ kon_debugCmd_t *c;
+
+ if (d->commandCount == KON_DEBUG_COMMANDS) return 0;
+ c = &d->commands[d->commandCount++];
+ c->name = name;
+ c->help = help;
+ c->fn = fn;
+ c->user = user;
+ return 1;
+}
+
+int kon_debugVar(kon_debug_t *d, const char *name, kon_debugVarType_t type, void *value)
+{
+ if (!d) return 0;
+ kon_debugVarEntry_t *v;
+
+ if (d->varCount == KON_DEBUG_VARS) return 0;
+ v = &d->vars[d->varCount++];
+ v->name = name;
+ v->type = type;
+ v->value = value;
+ v->watched = 0;
+ return 1;
+}
+
+void kon_debugLog(kon_debug_t *d, const char *format, ...)
+{
+ if (!d) return;
+ char line[KON_DEBUG_LINELEN];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(line, sizeof(line), format, args);
+ va_end(args);
+ kon_debugPushLine_(d, line);
+}
+
+int kon_debugConsoleOpen(const kon_debug_t *d)
+{
+ if (!d) return 0;
+ return d->open;
+}
+
+float kon_debugFps(const kon_debug_t *d)
+{
+ if (!d) return 0.0f;
+ return d->fps;
+}
+
+int kon_debugFeedEvent(kon_debug_t *d, const kon_event_t *e)
+{
+ if (!d) return 0;
+
+ if (e->type == KON_EVENT_KEY_DOWN) {
+ if (e->key == d->consoleKey) {
+ d->open = !d->open;
+ d->swallowTick = 1;
+ return 1;
+ }
+ d->swallowTick = 0;
+ if (e->key == d->overlayKey) {
+ d->overlay = (d->overlay + 1) & 3;
+ return 1;
+ }
+ }
+ if (!d->open) return 0;
+
+ if (e->type == KON_EVENT_TEXT) {
+ if (d->swallowTick && e->codepoint == '`') {
+ d->swallowTick = 0;
+ return 1;
+ }
+ if (e->codepoint >= 32 && e->codepoint <= 126) kon_debugInsert_(d, (char)e->codepoint);
+ return 1;
+ }
+ if (e->type == KON_EVENT_KEY_UP) return 1;
+ if (e->type != KON_EVENT_KEY_DOWN) return 0;
+
+ switch (e->key) {
+ case KON_KEY_ENTER: kon_debugSubmit_(d); break;
+ case KON_KEY_ESCAPE: d->open = 0; break;
+ case KON_KEY_TAB: kon_debugComplete_(d); break;
+ case KON_KEY_LEFT: if (d->cursor > 0) d->cursor--; break;
+ case KON_KEY_RIGHT: if (d->cursor < d->length) d->cursor++; break;
+ case KON_KEY_BACKSPACE:
+ if (d->cursor > 0) {
+ memmove(d->input + d->cursor - 1, d->input + d->cursor, (size_t)(d->length - d->cursor) + 1);
+ d->cursor--;
+ d->length--;
+ }
+ break;
+ case KON_KEY_DELETE:
+ if (d->cursor < d->length) {
+ memmove(d->input + d->cursor, d->input + d->cursor + 1, (size_t)(d->length - d->cursor));
+ d->length--;
+ }
+ break;
+ case KON_KEY_UP:
+ if (d->historyPos > 0) kon_debugSetInput_(d, d->history[--d->historyPos]);
+ break;
+ case KON_KEY_DOWN:
+ if (d->historyPos < d->historyCount - 1) kon_debugSetInput_(d, d->history[++d->historyPos]);
+ else {
+ d->historyPos = d->historyCount;
+ kon_debugSetInput_(d, "");
+ }
+ break;
+ case KON_KEY_PAGE_UP: d->scroll += 8; break;
+ case KON_KEY_PAGE_DOWN: d->scroll -= 8; break;
+ default: break;
+ }
+ if (d->scroll < 0) d->scroll = 0;
+ if (d->scroll > d->logCount - 1) d->scroll = d->logCount > 0 ? d->logCount - 1 : 0;
+ return 1;
+}
+
+void kon_debugFrame(kon_debug_t *d)
+{
+ if (!d) return;
+ kon_time now = kon_getTime();
+ double dt = now - d->last;
+
+ d->last = now;
+ d->frameDt = dt;
+ d->accumTime += dt;
+ d->accumFrames++;
+ if (d->accumTime >= 0.5) {
+ d->fps = (float)((double)d->accumFrames / d->accumTime);
+ d->accumTime = 0.0;
+ d->accumFrames = 0;
+ }
+ d->graph[d->graphHead] = (float)(dt * 1000.0);
+ d->graphHead = (d->graphHead + 1) % KON_DEBUG_GRAPH;
+}
+
+static void kon_debugDrawOverlay_(kon_debug_t *d, kon_framebuffer_t *fb)
+{
+ char line[128];
+ int x = 4, y = 4, i, rows;
+ uint32_t white = 0xFFFFFFFF, dim = 0xFFB0B0C0;
+ float worst = 0.0f;
+
+ snprintf(line, sizeof(line), "%.0f fps %.2f ms", (double)d->fps, d->frameDt * 1000.0);
+ kon_fillRectangle(fb, x - 2, y - 2, 170, d->lineH + 4, 0xB0000000);
+ kon_debugText_(d, fb, x, y, line, white);
+ y += d->lineH + 4;
+
+ if (d->overlay >= 1) {
+ kon_fillRectangle(fb, x - 2, y - 2, KON_DEBUG_GRAPH + 4, 34, 0xB0000000);
+ for (i = 0; i < KON_DEBUG_GRAPH; i++) {
+ float ms = d->graph[(d->graphHead + i) % KON_DEBUG_GRAPH];
+ int h = (int)(ms * 30.0f / 33.3f);
+ uint32_t color = ms < 17.5f ? 0xFF50D060 : (ms < 34.0f ? 0xFFE0C040 : 0xFFE05050);
+
+ if (ms > worst) worst = ms;
+ if (h > 30) h = 30;
+ if (h < 1) h = 1;
+ kon_fillRectangle(fb, x + i, y + 30 - h, 1, h, color);
+ }
+ y += 36;
+ }
+
+ for (i = 0; i < d->varCount; i++) {
+ char value[64];
+
+ if (!d->vars[i].watched) continue;
+ snprintf(line, sizeof(line), "%s = %s", d->vars[i].name, kon_debugVarText_(&d->vars[i], value, sizeof(value)));
+ kon_fillRectangle(fb, x - 2, y - 1, 170, d->lineH, 0xB0000000);
+ kon_debugText_(d, fb, x, y, line, dim);
+ y += d->lineH;
+ }
+
+ if (d->overlay >= 2 && d->assets) {
+ const char *names[KON_DEBUG_TYPES];
+ int counts[KON_DEBUG_TYPES], types = 0, total = kon_assetsCount(d->assets);
+ size_t bytes[KON_DEBUG_TYPES];
+ kon_assetInfo_t info;
+
+ for (i = 0; i < total; i++) {
+ int t;
+
+ if (!kon_assetsInfo(d->assets, i, &info)) continue;
+ for (t = 0; t < types; t++) if (names[t] == info.typeName) break;
+ if (t == types) {
+ if (types == KON_DEBUG_TYPES) continue;
+ names[types] = info.typeName;
+ counts[types] = 0;
+ bytes[types] = 0;
+ types++;
+ }
+ counts[t]++;
+ bytes[t] += info.bytes;
+ }
+
+ y += 2;
+ snprintf(line, sizeof(line), "assets %d, %.1f KB", total, (double)kon_assetsBytes(d->assets) / 1024.0);
+ kon_fillRectangle(fb, x - 2, y - 1, 220, d->lineH, 0xB0000000);
+ kon_debugText_(d, fb, x, y, line, white);
+ y += d->lineH;
+ for (i = 0; i < types; i++) {
+ snprintf(line, sizeof(line), " %-7s %3d %8.1f KB", names[i], counts[i], (double)bytes[i] / 1024.0);
+ kon_fillRectangle(fb, x - 2, y - 1, 220, d->lineH, 0xB0000000);
+ kon_debugText_(d, fb, x, y, line, dim);
+ y += d->lineH;
+ }
+
+ if (d->overlay >= 3) {
+ rows = (fb->height - y) / d->lineH - 1;
+ for (i = 0; i < total && i < rows; i++) {
+ if (!kon_assetsInfo(d->assets, i, &info)) continue;
+ snprintf(line, sizeof(line), "%-6s x%d %7.1fK %s%s", info.typeName, info.refs, (double)info.bytes / 1024.0, info.name, info.loose ? " *" : "");
+ kon_fillRectangle(fb, x - 2, y - 1, 330, d->lineH, 0xB0000000);
+ kon_debugText_(d, fb, x, y, line, dim);
+ y += d->lineH;
+ }
+ }
+ } else if (d->overlay >= 2) {
+ kon_debugText_(d, fb, x, y + 2, "no asset manager attached", dim);
+ }
+}
+
+static void kon_debugDrawConsole_(kon_debug_t *d, kon_framebuffer_t *fb)
+{
+ int full = fb->height / 2, height = (int)((float)full * d->slide);
+ int inputY, rows, i, line, cx, w = 0;
+ char shown[KON_DEBUG_INPUT + 4];
+
+ if (height < d->lineH + 6) return;
+
+ kon_fillRectangle(fb, 0, 0, fb->width, height, 0xE0101018);
+ kon_fillRectangle(fb, 0, height - 1, fb->width, 1, 0xFF7088E0);
+
+ inputY = height - d->lineH - 4;
+ kon_fillRectangle(fb, 0, inputY - 2, fb->width, d->lineH + 4, 0xFF181824);
+
+ rows = (inputY - 4) / d->lineH;
+ for (i = 0; i < rows; i++) {
+ int index = d->logCount - 1 - d->scroll - i;
+
+ if (index < 0) break;
+ line = (d->logHead + index) % KON_DEBUG_LOGLINES;
+ kon_debugText_(d, fb, 6, inputY - 4 - (i + 1) * d->lineH + d->lineH - 2, d->log[line], d->log[line][0] == '>' ? 0xFF9098C0 : 0xFFE0E0E8);
+ }
+
+ snprintf(shown, sizeof(shown), "> %s", d->input);
+ kon_debugText_(d, fb, 6, inputY, shown, 0xFFFFFFFF);
+
+ snprintf(shown, sizeof(shown), "> %.*s", d->cursor, d->input);
+ kon_measureText(d->font, shown, &w, NULL);
+ cx = 6 + w;
+ kon_fillRectangle(fb, cx, inputY, 2, d->lineH - 2, 0xFFFFFFFF);
+}
+
+void kon_debugDraw(kon_debug_t *d, kon_framebuffer_t *fb)
+{
+ if (!d) return;
+ float target = d->open ? 1.0f : 0.0f;
+ float step = (float)d->frameDt * 8.0f;
+
+ if (d->slide < target) d->slide = d->slide + step > target ? target : d->slide + step;
+ else if (d->slide > target) d->slide = d->slide - step < target ? target : d->slide - step;
+
+ if (d->overlay) kon_debugDrawOverlay_(d, fb);
+ if (d->slide > 0.0f) kon_debugDrawConsole_(d, fb);
+}
+
+#endif
diff --git a/src/impl/kondebug.c b/src/impl/kondebug.c
new file mode 100644
index 0000000..3a9abb1
--- /dev/null
+++ b/src/impl/kondebug.c
@@ -0,0 +1,2 @@
+#define KONDEBUG_IMPLEMENTATION
+#include "kondebug.h"