From b8c184dd133b7b3708c5492451804095797b9182 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Thu, 2 Apr 2026 21:26:38 +0200 Subject: [PATCH 1/3] feat(gpu): impl simple gpu, screen and keyboard --- build.zig | 5 +- src/main.c | 208 +-------- src/ncomplib.c | 1084 +++++++++++++++++++++++++++++++++++++++++--- src/ncomplib.h | 22 +- src/neonucleus.c | 1119 ++++++++++++++++++++++++++++++++++++++++------ src/neonucleus.h | 185 +++++++- 6 files changed, 2195 insertions(+), 428 deletions(-) diff --git a/build.zig b/build.zig index 9a81bdc..19aab8f 100644 --- a/build.zig +++ b/build.zig @@ -58,7 +58,10 @@ fn compileRaylib(b: *std.Build, os: std.Target.Os.Tag, buildOpts: LibBuildOpts, const targetArg = std.fmt.allocPrint(b.allocator, "-Dtarget={s}", .{targetStr}) catch unreachable; defer b.allocator.free(targetArg); - const raylib = b.addSystemCommand(&.{ "zig", "build", targetArg }); + + // passing it breaks it for some reason? + // TODO: make it not break + const raylib = b.addSystemCommand(&.{ "zig", "build"}); raylib.setCwd(b.path("foreign/raylib/")); raylib.stdio = .inherit; diff --git a/src/main.c b/src/main.c index 75c2d04..8b71e00 100644 --- a/src/main.c +++ b/src/main.c @@ -323,207 +323,6 @@ double ne_energy_accumulator(void *state, nn_Computer *c, double n) { } - -#ifdef NN_WINDOWS -// Quick self-tests for Windows-specific fixes -// These run before anything else so failures are caught early - -static jmp_buf nn_test_jmpbuf; -static volatile int nn_test_caught; - -static void nn_test_crash_handler(int sig) { - nn_test_caught = 1; - longjmp(nn_test_jmpbuf, 1); -} - -static int nn_test_try(void (*func)(void *), void *arg) { - nn_test_caught = 0; - signal(SIGSEGV, nn_test_crash_handler); - signal(SIGABRT, nn_test_crash_handler); - if(setjmp(nn_test_jmpbuf) == 0) { - func(arg); - } - signal(SIGSEGV, SIG_DFL); - signal(SIGABRT, SIG_DFL); - return nn_test_caught; -} - -static int nn_test_failed = 0; -static int nn_test_passed = 0; - -static void nn_test_report(const char *name, int crashed, int expected_crash) { - if(crashed && !expected_crash) { - printf("[CRASH] %s\n", name); - nn_test_failed++; - } else if(!crashed && expected_crash) { - printf("[FAIL] %s (expected crash)\n", name); - nn_test_failed++; - } else { - printf("[OK] %s\n", name); - nn_test_passed++; - } - fflush(stdout); -} - -// --- realloc tests --- - -static void nn_test_realloc_null(void *arg) { - nn_Context *ctx = arg; - void *p = nn_realloc(ctx, NULL, 0, 64); - if(p == NULL) { nn_test_failed++; return; } - nn_free(ctx, p, 64); -} - -static void nn_test_realloc_grow(void *arg) { - nn_Context *ctx = arg; - void *a = nn_alloc(ctx, 64); - if(a == NULL) return; - void *b = nn_realloc(ctx, a, 64, 128); - if(b != NULL) nn_free(ctx, b, 128); - else nn_free(ctx, a, 64); -} - -static void nn_test_realloc_free(void *arg) { - nn_Context *ctx = arg; - void *c = nn_alloc(ctx, 64); - if(c == NULL) return; - nn_realloc(ctx, c, 64, 0); -} - -// --- lock tests --- - -static void nn_test_lock_create_destroy(void *arg) { - nn_Context *ctx = arg; - nn_Lock *lock = nn_createLock(ctx); - if(lock == NULL) { nn_test_failed++; return; } - nn_destroyLock(ctx, lock); -} - -static void nn_test_lock_cycle(void *arg) { - nn_Context *ctx = arg; - nn_Lock *lock = nn_createLock(ctx); - if(lock == NULL) { nn_test_failed++; return; } - // lock and unlock 100 times to stress it - for(int i = 0; i < 100; i++) { - nn_lock(ctx, lock); - nn_unlock(ctx, lock); - } - nn_destroyLock(ctx, lock); -} - -static void nn_test_lock_two(void *arg) { - nn_Context *ctx = arg; - // two locks at the same time, make sure they dont interfere - nn_Lock *a = nn_createLock(ctx); - nn_Lock *b = nn_createLock(ctx); - if(a == NULL || b == NULL) { nn_test_failed++; return; } - nn_lock(ctx, a); - nn_lock(ctx, b); - nn_unlock(ctx, b); - nn_unlock(ctx, a); - nn_destroyLock(ctx, a); - nn_destroyLock(ctx, b); -} - -// --- VFS tests --- - -static void nn_test_vfs_stat(void *arg) { - (void)arg; - // stat current directory, should always work - ncl_Stat s; - bool ok = ncl_stat(ncl_defaultFS, ".", &s); - if(!ok) nn_test_failed++; - if(!s.isDirectory) nn_test_failed++; -} - -static void nn_test_vfs_dir(void *arg) { - (void)arg; - void *dir = ncl_opendir(ncl_defaultFS, "."); - if(dir == NULL) { nn_test_failed++; return; } - char name[NN_MAX_PATH]; - // just read one entry, dont care what it is - ncl_readdir(ncl_defaultFS, dir, name); - ncl_closedir(ncl_defaultFS, dir); -} - -static void nn_test_vfs_mkdir_remove(void *arg) { - (void)arg; - const char *testdir = "nn_test_tmpdir"; - ncl_mkdir(ncl_defaultFS, testdir); - ncl_Stat s; - bool ok = ncl_stat(ncl_defaultFS, testdir, &s); - if(!ok || !s.isDirectory) nn_test_failed++; - ncl_remove(ncl_defaultFS, testdir); - // should be gone now - ok = ncl_stat(ncl_defaultFS, testdir, &s); - if(ok) nn_test_failed++; -} - -static void nn_test_vfs_seek(void *arg) { - (void)arg; - // write a small file, seek around, read back - const char *path = "nn_test_seekfile"; - const char *data = "abcdefghij"; - void *f = ncl_openfile(ncl_defaultFS, path, "w"); - if(f == NULL) { nn_test_failed++; return; } - ncl_writefile(ncl_defaultFS, f, data, 10); - ncl_closefile(ncl_defaultFS, f); - - f = ncl_openfile(ncl_defaultFS, path, "r"); - if(f == NULL) { nn_test_failed++; ncl_remove(ncl_defaultFS, path); return; } - // seek to offset 5 from start - int off = 5; - bool ok = ncl_seekfile(ncl_defaultFS, f, NN_SEEK_SET, &off); - if(!ok || off != 5) nn_test_failed++; - // read from there - char buf[5]; - size_t len = 5; - ok = ncl_readfile(ncl_defaultFS, f, buf, &len); - if(!ok || len != 5) nn_test_failed++; - // should be "fghij" - if(buf[0] != 'f' || buf[4] != 'j') nn_test_failed++; - ncl_closefile(ncl_defaultFS, f); - ncl_remove(ncl_defaultFS, path); -} - -static void nn_run_selftests(nn_Context *ctx) { - printf("--- nn self tests ---\n"); - fflush(stdout); - - nn_test_report("realloc(NULL)", - nn_test_try(nn_test_realloc_null, ctx), 0); - nn_test_report("realloc(ptr, grow)", - nn_test_try(nn_test_realloc_grow, ctx), 0); - nn_test_report("realloc(ptr, free)", - nn_test_try(nn_test_realloc_free, ctx), 0); - - nn_test_report("lock create/destroy", - nn_test_try(nn_test_lock_create_destroy, ctx), 0); - nn_test_report("lock 100 cycles", - nn_test_try(nn_test_lock_cycle, ctx), 0); - nn_test_report("two locks interleaved", - nn_test_try(nn_test_lock_two, ctx), 0); - - nn_test_report("vfs stat cwd", - nn_test_try(nn_test_vfs_stat, NULL), 0); - nn_test_report("vfs readdir cwd", - nn_test_try(nn_test_vfs_dir, NULL), 0); - nn_test_report("vfs mkdir/remove", - nn_test_try(nn_test_vfs_mkdir_remove, NULL), 0); - nn_test_report("vfs seek", - nn_test_try(nn_test_vfs_seek, NULL), 0); - - printf("--- %d passed, %d failed ---\n\n", nn_test_passed, nn_test_failed); - fflush(stdout); - - if(nn_test_failed > 0) { - printf("self tests failed, aborting\n"); - fflush(stdout); - exit(1); - } -} -#endif - int main(int argc, char **argv) { const char *player = getenv("USER"); #ifdef NN_WINDOWS @@ -608,7 +407,11 @@ int main(int argc, char **argv) { nn_Component *screen = ncl_createScreen(u, NULL, &nn_defaultScreens[3]); nn_Component *gpuCard = ncl_createGPU(u, NULL, &nn_defaultGPUs[3]); + nn_Component *keyboard = nn_createComponent( + u, "mainKB", "keyboard"); + ncl_ScreenState *scrstate = nn_getComponentState(screen); + ncl_mountKeyboard(scrstate, "mainKB"); { // draw test const char *s = "hello there"; @@ -638,7 +441,7 @@ restart:; nn_mountComponent(c, eepromCard, 0); nn_mountComponent(c, managedfs, 1); nn_mountComponent(c, gpuCard, 2); - + nn_mountComponent(c, keyboard, -1); while(true) { if(WindowShouldClose()) break; @@ -773,6 +576,7 @@ cleanup:; nn_dropComponent(managedfs); nn_dropComponent(screen); nn_dropComponent(gpuCard); + nn_dropComponent(keyboard); // rip the universe nn_destroyUniverse(u); UnloadFont(font); diff --git a/src/ncomplib.c b/src/ncomplib.c index 8f0ce2b..4722af0 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -1035,8 +1035,17 @@ nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, c return c; } +nn_Component *ncl_createTmpFS(nn_Universe *universe, const nn_Filesystem *fs); + nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); +nn_Component *ncl_createTmpDrive(nn_Universe *universe, const nn_EEPROM *eeprom, const char *data, size_t datalen); nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *path, bool isReadonly); +nn_Component *ncl_createTmpEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const char *code, size_t codelen); + +size_t ncl_getEEPROMData(nn_Component *component, char *buf); +void ncl_setEEPROMData(nn_Component *component, const char *data, size_t len); + +ncl_VFS ncl_getVFS(nn_Component *component); ncl_VFS ncl_setVFS(nn_Component *component, ncl_VFS vfs); @@ -1098,25 +1107,102 @@ static void ncl_recomputeScreen(const ncl_ScreenState *state) { } static nn_Exit ncl_screenHandler(nn_ScreenRequest *req) { - nn_Context *ctx = req->ctx; - nn_Computer *C = req->computer; - ncl_ScreenState *state = req->state; - const nn_ScreenConfig *conf = req->screen; + nn_Context *ctx = req->ctx; + nn_Computer *C = req->computer; + ncl_ScreenState *st = req->state; - if(req->action == NN_SCREEN_DROP) { - for(size_t i = 0; i < state->keyboardCount; i++) { - nn_strfree(ctx, state->keyboards[i]); - } - nn_destroyLock(ctx, state->lock); - nn_free(ctx, state->pixels, sizeof(ncl_ScreenPixel) * state->conf.maxWidth * state->conf.maxHeight); - nn_free(ctx, state->palette, sizeof(int) * state->conf.paletteColors); - nn_free(ctx, state->resolvedPalette, sizeof(int) * state->conf.paletteColors); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } + if(req->action == NN_SCREEN_DROP) { + for(size_t i = 0; i < st->keyboardCount; i++) + nn_strfree(ctx, st->keyboards[i]); + nn_destroyLock(ctx, st->lock); + nn_free(ctx, st->pixels, + sizeof(ncl_ScreenPixel) + * st->conf.maxWidth * st->conf.maxHeight); + nn_free(ctx, st->palette, + sizeof(int) * st->conf.paletteColors); + nn_free(ctx, st->resolvedPalette, + sizeof(int) * st->conf.paletteColors); + nn_free(ctx, st, sizeof(*st)); + return NN_OK; + } + if(req->action == NN_SCREEN_ISON) { + nn_lock(ctx, st->lock); + req->power.isOn = + (st->flags & NCL_SCREEN_ON) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_TURNON) { + nn_lock(ctx, st->lock); + bool was = (st->flags & NCL_SCREEN_ON) != 0; + st->flags |= NCL_SCREEN_ON; + req->power.wasOn = !was; + req->power.isOn = true; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_TURNOFF) { + nn_lock(ctx, st->lock); + bool was = (st->flags & NCL_SCREEN_ON) != 0; + st->flags &= ~NCL_SCREEN_ON; + req->power.wasOn = was; + req->power.isOn = false; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_GETASPECTRATIO) { + // single-block screen + req->aspect.w = 1; + req->aspect.h = 1; + return NN_OK; + } + if(req->action == NN_SCREEN_GETKEYBOARDS) { + nn_lock(ctx, st->lock); + for(size_t i = 0; i < st->keyboardCount; i++) + nn_pushstring(C, st->keyboards[i]); + req->kbCount = st->keyboardCount; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_SETPRECISE) { + nn_lock(ctx, st->lock); + if(req->flag) + st->flags |= NCL_SCREEN_PRECISE; + else + st->flags &= ~NCL_SCREEN_PRECISE; + req->flag = + (st->flags & NCL_SCREEN_PRECISE) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_ISPRECISE) { + nn_lock(ctx, st->lock); + req->flag = + (st->flags & NCL_SCREEN_PRECISE) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_SETTOUCHINVERTED) { + nn_lock(ctx, st->lock); + if(req->flag) + st->flags |= NCL_SCREEN_TOUCHINVERTED; + else + st->flags &= ~NCL_SCREEN_TOUCHINVERTED; + req->flag = + (st->flags & NCL_SCREEN_TOUCHINVERTED) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_ISTOUCHINVERTED) { + nn_lock(ctx, st->lock); + req->flag = + (st->flags & NCL_SCREEN_TOUCHINVERTED) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } - if(C) nn_setError(C, "ncl-screen: not implemented yet"); - return NN_EBADCALL; + if(C) nn_setError(C, "ncl-screen: bad action"); + return NN_EBADCALL; } nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config) { @@ -1187,6 +1273,7 @@ static ncl_ScreenState *ncl_getBoundScreen(ncl_GPUState *gpu, nn_Computer *C) { return nn_getComponentState(c); } +/* static void ncl_getGPULimits(ncl_GPUState *gpu, nn_Computer *C, int *maxWidth, int *maxHeight, char *maxDepth) { int w = gpu->conf.maxWidth, h = gpu->conf.maxHeight; char d = gpu->conf.maxDepth; @@ -1203,64 +1290,854 @@ static void ncl_getGPULimits(ncl_GPUState *gpu, nn_Computer *C, int *maxWidth, i *maxHeight = h; *maxDepth = d; } +*/ -static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { - nn_Context *ctx = req->ctx; - nn_Computer *C = req->computer; - ncl_GPUState *state = req->state; - const nn_GPU *gpu = req->gpu; - if(req->action == NN_GPU_DROP) { - for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { - if(state->vram[i] != NULL) ncl_freeVRAM(ctx, state->vram[i]); - } - if(state->screenAddress != NULL) nn_strfree(ctx, state->screenAddress); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } - if(C != NULL) nn_setError(C, "ncl-gpu: not implemented yet"); - return NN_EBADCALL; +static void ncl_getGPULimitsWithScreen( + ncl_GPUState *gpu, ncl_ScreenState *screen, + int *maxWidth, int *maxHeight, char *maxDepth) +{ + int w = gpu->conf.maxWidth; + int h = gpu->conf.maxHeight; + char d = gpu->conf.maxDepth; + if(screen != NULL) { + if(w > screen->conf.maxWidth) + w = screen->conf.maxWidth; + if(h > screen->conf.maxHeight) + h = screen->conf.maxHeight; + if(d > screen->conf.maxDepth) + d = screen->conf.maxDepth; + } + *maxWidth = w; + *maxHeight = h; + *maxDepth = d; } -nn_Component *ncl_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu) { - nn_Context *ctx = nn_getUniverseContext(universe); - nn_Lock *lock = NULL; - ncl_GPUState *state = NULL; - nn_Component *c = NULL; +// helper: get the target buffer for the active index. +// Returns NULL + sets error on failure. +// If index==0 and screen is non-NULL, caller must use +// the screen pixel functions instead. +static ncl_VRAMBuf *ncl_getVRAMBuf( + ncl_GPUState *st, int idx, nn_Computer *C) +{ + if(idx <= 0 || idx >= NCL_MAX_VRAMBUF) { + if(C) nn_setError(C, "invalid buffer index"); + return NULL; + } + ncl_VRAMBuf *b = st->vram[idx]; + if(b == NULL && C) + nn_setError(C, "no such buffer"); + return b; +} - lock = nn_createLock(ctx); - if(lock == NULL) goto fail; +static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { + nn_Context *ctx = req->ctx; + nn_Computer *C = req->computer; + ncl_GPUState *st = req->state; - state = nn_alloc(ctx, sizeof(*state)); - if(state == NULL) goto fail; + if(req->action == NN_GPU_DROP) { + for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] != NULL) + ncl_freeVRAM(ctx, st->vram[i]); + } + if(st->screenAddress != NULL) + nn_strfree(ctx, st->screenAddress); + nn_destroyLock(ctx, st->lock); + nn_free(ctx, st, sizeof(*st)); + return NN_OK; + } - state->ctx = ctx; - state->lock = lock; - state->conf = *gpu; - state->vramFree = gpu->totalVRAM; - state->screenAddress = NULL; - state->currentFg = 0xFFFFFF; - state->currentBg = 0x000000; - state->activeBuffer = 0; - state->isFgPalette = false; - state->isBgPalette = false; - for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { - state->vram[i] = NULL; - } + // bind + if(req->action == NN_GPU_BIND) { + nn_Component *sc = + nn_getComponent(C, req->bind.address); + if(sc == NULL) { + nn_setError(C, "no such component"); + return NN_EBADCALL; + } + const char *tid = nn_getComponentTypeID(sc); + if(strcmp(tid, NCL_SCREEN) != 0) { + nn_setError(C, "not a screen"); + return NN_EBADCALL; + } + nn_lock(ctx, st->lock); + if(st->screenAddress != NULL) + nn_strfree(ctx, st->screenAddress); + st->screenAddress = + nn_strdup(ctx, req->bind.address); + nn_unlock(ctx, st->lock); - c = nn_createGPU(universe, address, gpu, state, ncl_gpuHandler); - if(c == NULL) goto fail; + if(req->bind.reset) { + ncl_ScreenState *scr = + nn_getComponentState(sc); + ncl_lockScreen(scr); + ncl_resetScreen(scr); + ncl_unlockScreen(scr); + } + return NN_OK; + } + // getScreen + if(req->action == NN_GPU_GETSCREEN) { + nn_lock(ctx, st->lock); + if(st->screenAddress != NULL) { + size_t len = strlen(st->screenAddress); + if(len >= NN_MAX_ADDRESS) + len = NN_MAX_ADDRESS - 1; + memcpy(req->screenAddr, + st->screenAddress, len); + req->screenAddr[len] = '\0'; + } + nn_unlock(ctx, st->lock); + return NN_OK; + } + // getBackground + if(req->action == NN_GPU_GETBG) { + nn_lock(ctx, st->lock); + req->color.color = st->currentBg; + req->color.isPalette = st->isBgPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // setBackground + if(req->action == NN_GPU_SETBG) { + nn_lock(ctx, st->lock); + req->color.oldColor = st->currentBg; + req->color.wasPalette = st->isBgPalette; + req->color.oldPaletteIdx = + st->isBgPalette ? st->currentBg : -1; + st->currentBg = req->color.color; + st->isBgPalette = req->color.isPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // getForeground + if(req->action == NN_GPU_GETFG) { + nn_lock(ctx, st->lock); + req->color.color = st->currentFg; + req->color.isPalette = st->isFgPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // setForeground + if(req->action == NN_GPU_SETFG) { + nn_lock(ctx, st->lock); + req->color.oldColor = st->currentFg; + req->color.wasPalette = st->isFgPalette; + req->color.oldPaletteIdx = + st->isFgPalette ? st->currentFg : -1; + st->currentFg = req->color.color; + st->isFgPalette = req->color.isPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // getPaletteColor + if(req->action == NN_GPU_GETPALETTE) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + int idx = req->palette.index; + if(idx < 0 || idx >= scr->conf.paletteColors) { + ncl_unlockScreen(scr); + nn_setError(C, "invalid palette index"); + return NN_EBADCALL; + } + req->palette.color = scr->palette[idx]; + ncl_unlockScreen(scr); + return NN_OK; + } + // setPaletteColor + if(req->action == NN_GPU_SETPALETTE) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + int idx = req->palette.index; + if(idx < 0 + || idx >= scr->conf.editableColors) { + ncl_unlockScreen(scr); + nn_setError(C, "invalid palette index"); + return NN_EBADCALL; + } + req->palette.oldColor = scr->palette[idx]; + scr->palette[idx] = req->palette.color; + scr->resolvedPalette[idx] = + nn_mapDepth(req->palette.color, scr->depth); + ncl_unlockScreen(scr); + return NN_OK; + } + // maxDepth + if(req->action == NN_GPU_MAXDEPTH) { + nn_lock(ctx, st->lock); + char d = st->conf.maxDepth; + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr != NULL) { + ncl_lockScreen(scr); + if(scr->conf.maxDepth < d) + d = scr->conf.maxDepth; + ncl_unlockScreen(scr); + } + req->depth.depth = d; + return NN_OK; + } + // getDepth + if(req->action == NN_GPU_GETDEPTH) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + req->depth.depth = st->conf.maxDepth; + return NN_OK; + } + ncl_lockScreen(scr); + req->depth.depth = scr->depth; + ncl_unlockScreen(scr); + return NN_OK; + } + // setDepth + if(req->action == NN_GPU_SETDEPTH) { + char want = req->depth.depth; + if(nn_depthName(want) == NULL) { + nn_setError(C, "unsupported depth"); + return NN_EBADCALL; + } + nn_lock(ctx, st->lock); + int maxD = st->conf.maxDepth; + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + if(scr->conf.maxDepth < maxD) + maxD = scr->conf.maxDepth; + if(want > maxD) { + ncl_unlockScreen(scr); + nn_setError(C, "unsupported depth"); + return NN_EBADCALL; + } + req->depth.oldDepth = scr->depth; + scr->depth = want; + ncl_recomputeScreen(scr); + ncl_unlockScreen(scr); + return NN_OK; + } + // maxResolution + if(req->action == NN_GPU_MAXRES) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + int w, h; char d; + if(scr != NULL) ncl_lockScreen(scr); + ncl_getGPULimitsWithScreen( + st, scr, &w, &h, &d); + if(scr != NULL) ncl_unlockScreen(scr); + req->resolution.width = w; + req->resolution.height = h; + return NN_OK; + } + // getResolution + if(req->action == NN_GPU_GETRES) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + req->resolution.width = scr->width; + req->resolution.height = scr->height; + ncl_unlockScreen(scr); + return NN_OK; + } + // setResolution + if(req->action == NN_GPU_SETRES) { + int w = req->resolution.width; + int h = req->resolution.height; + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + int maxW, maxH; char maxD; + if(scr != NULL) ncl_lockScreen(scr); + ncl_getGPULimitsWithScreen( + st, scr, &maxW, &maxH, &maxD); + if(w < 1 || h < 1 + || w > maxW || h > maxH) { + if(scr != NULL) ncl_unlockScreen(scr); + nn_setError(C, "unsupported resolution"); + return NN_EBADCALL; + } + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + scr->width = w; + scr->height = h; + if(scr->viewportWidth > w) + scr->viewportWidth = w; + if(scr->viewportHeight > h) + scr->viewportHeight = h; + ncl_unlockScreen(scr); + return NN_OK; + } + // getViewport + if(req->action == NN_GPU_GETVIEWPORT) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + req->resolution.width = scr->viewportWidth; + req->resolution.height = scr->viewportHeight; + ncl_unlockScreen(scr); + return NN_OK; + } + // setViewport + if(req->action == NN_GPU_SETVIEWPORT) { + int w = req->resolution.width; + int h = req->resolution.height; + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + if(w < 1 || h < 1 + || w > scr->width || h > scr->height) { + ncl_unlockScreen(scr); + nn_setError(C, + "viewport exceeds resolution"); + return NN_EBADCALL; + } + scr->viewportWidth = w; + scr->viewportHeight = h; + ncl_unlockScreen(scr); + return NN_OK; + } + // get + if(req->action == NN_GPU_GET) { + nn_lock(ctx, st->lock); + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); - if(nn_setComponentTypeID(c, NCL_GPU)) goto fail; + ncl_ScreenPixel px; + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + px = ncl_getRealScreenPixel( + scr, req->get.x, req->get.y); + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + px = ncl_vramGet( + b, req->get.x, req->get.y); + nn_unlock(ctx, st->lock); + } + req->get.codepoint = px.codepoint; + req->get.fg = px.storedFg; + req->get.bg = px.storedBg; + req->get.fgIdx = (px.realFg < 0) + ? px.storedFg : -1; + req->get.bgIdx = (px.realBg < 0) + ? px.storedBg : -1; + return NN_OK; + } + // set + if(req->action == NN_GPU_SET) { + nn_lock(ctx, st->lock); + int fg = st->currentFg; + int bg = st->currentBg; + bool fgP = st->isFgPalette; + bool bgP = st->isBgPalette; + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); + + int x = req->set.x, y = req->set.y; + const char *s = req->set.value; + size_t len = req->set.len; + bool vert = req->set.vertical; + + ncl_ScreenPixel px; + px.storedFg = fg; + px.storedBg = bg; + px.realFg = fgP ? -1 : fg; + px.realBg = bgP ? -1 : bg; + + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + // depth-map direct colors + if(!fgP) px.realFg = + nn_mapDepth(fg, scr->depth); + if(!bgP) px.realBg = + nn_mapDepth(bg, scr->depth); + size_t i = 0; + while(i < len) { + size_t cw = + nn_unicode_validateFirstChar( + s + i, len - i); + if(cw == 0) { cw = 1; px.codepoint = + (unsigned char)s[i]; } + else px.codepoint = + nn_unicode_firstCodepoint(s + i); + ncl_setRealScreenPixel( + scr, x, y, px); + i += cw; + if(vert) y++; else x++; + } + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + size_t i = 0; + while(i < len) { + size_t cw = + nn_unicode_validateFirstChar( + s + i, len - i); + if(cw == 0) { cw = 1; px.codepoint = + (unsigned char)s[i]; } + else px.codepoint = + nn_unicode_firstCodepoint(s + i); + ncl_vramSet(b, x, y, px); + i += cw; + if(vert) y++; else x++; + } + nn_unlock(ctx, st->lock); + } + return NN_OK; + } + // copy + if(req->action == NN_GPU_COPY) { + int sx = req->copy.x, sy = req->copy.y; + int w = req->copy.w, h = req->copy.h; + int tx = req->copy.tx, ty = req->copy.ty; + + nn_lock(ctx, st->lock); + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); + + // overlap-safe iteration order + int y0 = (ty > 0) ? sy+h-1 : sy; + int y1 = (ty > 0) ? sy-1 : sy+h; + int dy = (ty > 0) ? -1 : 1; + int x0 = (tx > 0) ? sx+w-1 : sx; + int x1 = (tx > 0) ? sx-1 : sx+w; + int dx = (tx > 0) ? -1 : 1; + + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + for(int y = y0; y != y1; y += dy) + for(int x = x0; x != x1; x += dx) { + ncl_ScreenPixel p = + ncl_getRealScreenPixel( + scr, x, y); + ncl_setRealScreenPixel( + scr, x+tx, y+ty, p); + } + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + for(int y = y0; y != y1; y += dy) + for(int x = x0; x != x1; x += dx) { + ncl_ScreenPixel p = + ncl_vramGet(b, x, y); + ncl_vramSet(b, x+tx, y+ty, p); + } + nn_unlock(ctx, st->lock); + } + return NN_OK; + } + // fill + if(req->action == NN_GPU_FILL) { + nn_lock(ctx, st->lock); + int fg = st->currentFg; + int bg = st->currentBg; + bool fgP = st->isFgPalette; + bool bgP = st->isBgPalette; + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); + + ncl_ScreenPixel px; + px.codepoint = req->fill.codepoint; + px.storedFg = fg; + px.storedBg = bg; + px.realFg = fgP ? -1 : fg; + px.realBg = bgP ? -1 : bg; + + int x0 = req->fill.x, y0 = req->fill.y; + int w = req->fill.w, h = req->fill.h; + + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + if(!fgP) px.realFg = + nn_mapDepth(fg, scr->depth); + if(!bgP) px.realBg = + nn_mapDepth(bg, scr->depth); + for(int y = y0; y < y0+h; y++) + for(int x = x0; x < x0+w; x++) + ncl_setRealScreenPixel( + scr, x, y, px); + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + for(int y = y0; y < y0+h; y++) + for(int x = x0; x < x0+w; x++) + ncl_vramSet(b, x, y, px); + nn_unlock(ctx, st->lock); + } + return NN_OK; + } + // VRAM: getActiveBuffer + if(req->action == NN_GPU_GETACTIVEBUF) { + nn_lock(ctx, st->lock); + req->buffer.index = st->activeBuffer; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: setActiveBuffer + if(req->action == NN_GPU_SETACTIVEBUF) { + int idx = req->buffer.index; + nn_lock(ctx, st->lock); + if(idx != 0 && (idx < 0 + || idx >= NCL_MAX_VRAMBUF + || st->vram[idx] == NULL)) { + nn_unlock(ctx, st->lock); + nn_setError(C, "invalid buffer index"); + return NN_EBADCALL; + } + st->activeBuffer = idx; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: buffers + if(req->action == NN_GPU_BUFFERS) { + nn_lock(ctx, st->lock); + size_t count = 0; + for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] != NULL) { + nn_pushinteger(C, i); + count++; + } + } + nn_unlock(ctx, st->lock); + req->bufCount = count; + return NN_OK; + } + // VRAM: allocateBuffer + if(req->action == NN_GPU_ALLOCBUF) { + int w = req->allocBuf.w; + int h = req->allocBuf.h; + // default to current GPU max if 0 + if(w <= 0) w = st->conf.maxWidth; + if(h <= 0) h = st->conf.maxHeight; + size_t cost = (size_t)w * h; + nn_lock(ctx, st->lock); + if(cost > st->vramFree) { + nn_unlock(ctx, st->lock); + nn_setError(C, "not enough video memory"); + return NN_EBADCALL; + } + int slot = -1; + for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] == NULL) { + slot = i; + break; + } + } + if(slot < 0) { + nn_unlock(ctx, st->lock); + nn_setError(C, "too many buffers"); + return NN_EBADCALL; + } + ncl_VRAMBuf *b = ncl_allocVRAM(ctx, w, h); + if(b == NULL) { + nn_unlock(ctx, st->lock); + nn_setError(C, "allocation failed"); + return NN_EBADCALL; + } + st->vram[slot] = b; + st->vramFree -= cost; + nn_unlock(ctx, st->lock); + req->allocBuf.index = slot; + return NN_OK; + } + // VRAM: freeBuffer + if(req->action == NN_GPU_FREEBUF) { + int idx = req->buffer.index; + if(idx <= 0 || idx >= NCL_MAX_VRAMBUF) { + nn_setError(C, "invalid buffer index"); + return NN_EBADCALL; + } + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = st->vram[idx]; + if(b == NULL) { + nn_unlock(ctx, st->lock); + nn_setError(C, "no such buffer"); + return NN_EBADCALL; + } + st->vramFree += (size_t)b->width * b->height; + st->vram[idx] = NULL; + if(st->activeBuffer == idx) + st->activeBuffer = 0; + nn_unlock(ctx, st->lock); + ncl_freeVRAM(ctx, b); + return NN_OK; + } + // VRAM: freeAllBuffers + if(req->action == NN_GPU_FREEALLBUFS) { + nn_lock(ctx, st->lock); + for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] != NULL) { + st->vramFree += + (size_t)st->vram[i]->width + * st->vram[i]->height; + ncl_freeVRAM(ctx, st->vram[i]); + st->vram[i] = NULL; + } + } + st->activeBuffer = 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: freeMemory + if(req->action == NN_GPU_FREEMEM) { + nn_lock(ctx, st->lock); + req->memory = st->vramFree; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: getBufferSize + if(req->action == NN_GPU_GETBUFSIZE) { + int idx = req->bufSize.index; + if(idx == 0) { + // return screen resolution + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + req->bufSize.w = 0; + req->bufSize.h = 0; + return NN_OK; + } + ncl_lockScreen(scr); + req->bufSize.w = scr->width; + req->bufSize.h = scr->height; + ncl_unlockScreen(scr); + return NN_OK; + } + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, idx, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + req->bufSize.w = b->width; + req->bufSize.h = b->height; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: bitblt + if(req->action == NN_GPU_BITBLT) { + int dstI = req->bitblt.dst; + int srcI = req->bitblt.src; + int col = req->bitblt.col; + int row = req->bitblt.row; + int w = req->bitblt.w; + int h = req->bitblt.h; + int fc = req->bitblt.fromCol; + int fr = req->bitblt.fromRow; + + nn_lock(ctx, st->lock); + if(srcI == 0 && st->activeBuffer != 0) + srcI = st->activeBuffer; + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + + ncl_VRAMBuf *sb = NULL, *db = NULL; + if(srcI != 0) { + sb = ncl_getVRAMBuf(st, srcI, C); + if(sb == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + if(w == 0) w = sb->width; + if(h == 0) h = sb->height; + } + if(dstI != 0) { + db = ncl_getVRAMBuf(st, dstI, C); + if(db == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + } + nn_unlock(ctx, st->lock); + + // Lock both resources once for the + // entire blit, not per-pixel. + bool needScreen = + (srcI == 0 || dstI == 0); + if(needScreen && scr != NULL) + ncl_lockScreen(scr); + if(sb != NULL || db != NULL) + nn_lock(ctx, st->lock); + + for(int y = 0; y < h; y++) { + for(int x = 0; x < w; x++) { + ncl_ScreenPixel p; + int rx = fc + x, ry = fr + y; + if(srcI == 0) { + if(scr == NULL) continue; + p = ncl_getRealScreenPixel( + scr, rx, ry); + } else { + p = ncl_vramGet(sb, rx, ry); + } + int wx = col + x, wy = row + y; + if(dstI == 0) { + if(scr == NULL) continue; + p.realFg = nn_mapDepth( + p.storedFg, scr->depth); + p.realBg = nn_mapDepth( + p.storedBg, scr->depth); + ncl_setRealScreenPixel( + scr, wx, wy, p); + } else { + ncl_vramSet(db, wx, wy, p); + } + } + } + + if(sb != NULL || db != NULL) + nn_unlock(ctx, st->lock); + if(needScreen && scr != NULL) + ncl_unlockScreen(scr); + return NN_OK; + } + + if(C) nn_setError(C, + "ncl-gpu: not implemented"); + return NN_EBADCALL; +} + +nn_Component *ncl_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu) +{ + nn_Context *ctx = nn_getUniverseContext(universe); + nn_Lock *lock = NULL; + ncl_GPUState *state = NULL; + nn_Component *c = NULL; + + lock = nn_createLock(ctx); + if(lock == NULL) goto fail; + + state = nn_alloc(ctx, sizeof(*state)); + if(state == NULL) goto fail; + + state->ctx = ctx; + state->lock = lock; + state->conf = *gpu; + state->vramFree = gpu->totalVRAM; + state->screenAddress = NULL; + state->currentFg = 0xFFFFFF; + state->currentBg = 0x000000; + state->activeBuffer = 0; + state->isFgPalette = false; + state->isBgPalette = false; + for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) + state->vram[i] = NULL; + + c = nn_createGPU(universe, address, + gpu, state, ncl_gpuHandler); + if(c == NULL) goto fail; + + if(nn_setComponentTypeID(c, NCL_GPU)) goto fail; + return c; - return c; fail: - if(c != NULL) { - nn_dropComponent(c); - return NULL; - } - if(lock != NULL) nn_destroyLock(ctx, lock); - nn_free(ctx, state, sizeof(*state)); - return NULL; + if(c != NULL) { + nn_dropComponent(c); + return NULL; + } + if(lock != NULL) nn_destroyLock(ctx, lock); + nn_free(ctx, state, sizeof(*state)); + return NULL; } void ncl_lockScreen(ncl_ScreenState *state) { @@ -1340,13 +2217,76 @@ void ncl_setScreenDepth(ncl_ScreenState *state, char depth) { state->depth = depth; } -nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, const char *keyboardAddress); -void ncl_unmountKeyboard(ncl_ScreenState *state, const char *keyboardAddress); -bool ncl_hasKeyboard(ncl_ScreenState *state, const char *keyboardAddress); -const char *ncl_getKeyboard(ncl_ScreenState *state, size_t idx); +nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, + const char *keyboardAddress) +{ + nn_lock(state->ctx, state->lock); + if(state->keyboardCount >= NCL_MAX_KEYBOARD) { + nn_unlock(state->ctx, state->lock); + return NN_ELIMIT; + } + char *addr = nn_strdup( + state->ctx, keyboardAddress); + if(addr == NULL) { + nn_unlock(state->ctx, state->lock); + return NN_ENOMEM; + } + state->keyboards[state->keyboardCount++] = addr; + nn_unlock(state->ctx, state->lock); + return NN_OK; +} + +void ncl_unmountKeyboard(ncl_ScreenState *state, + const char *keyboardAddress) +{ + nn_lock(state->ctx, state->lock); + size_t j = 0; + for(size_t i = 0; i < state->keyboardCount; i++) { + if(strcmp(state->keyboards[i], + keyboardAddress) == 0) { + nn_strfree(state->ctx, + state->keyboards[i]); + } else { + state->keyboards[j++] = + state->keyboards[i]; + } + } + state->keyboardCount = j; + nn_unlock(state->ctx, state->lock); +} + +bool ncl_hasKeyboard(ncl_ScreenState *state, + const char *keyboardAddress) +{ + nn_lock(state->ctx, state->lock); + for(size_t i = 0; i < state->keyboardCount; i++) { + if(strcmp(state->keyboards[i], + keyboardAddress) == 0) { + nn_unlock(state->ctx, state->lock); + return true; + } + } + nn_unlock(state->ctx, state->lock); + return false; +} + +const char *ncl_getKeyboard(ncl_ScreenState *state, + size_t idx) +{ + if(idx >= state->keyboardCount) return NULL; + return state->keyboards[idx]; +} // general stuff +bool ncl_isNCLID(const char *type) { + return strncmp(NCL_PREFIX, type, strlen(NCL_PREFIX)); +} + +bool ncl_isNCLComponent(nn_Component *component) { + return ncl_isNCLID(nn_getComponentTypeID(component)); +} + void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) { stat->labellen = 0; stat->isReadonly = false; @@ -1425,14 +2365,18 @@ bool ncl_makeReadonly(nn_Component *component) { } if(strcmp(ty, NCL_DRIVE) == 0) { ncl_DriveState *drv = state; + nn_lock(drv->ctx, drv->lock); drv->isReadonly = true; drv->usage++; + nn_unlock(drv->ctx, drv->lock); return true; } if(strcmp(ty, NCL_EEPROM) == 0) { ncl_EEState *ee = state; + nn_lock(ee->ctx, ee->lock); ee->isReadonly = true; ee->usage++; + nn_unlock(ee->ctx, ee->lock); return true; } return false; @@ -1444,3 +2388,5 @@ nn_Exit ncl_encodeComponentState(nn_Universe *universe, nn_Component *comp, ncl_ void ncl_freeEncodedState(nn_Universe *universe, ncl_EncodedState *state); nn_Exit ncl_loadComponentState(nn_Component *comp, const ncl_EncodedState *state); +size_t ncl_getLabel(nn_Component *c, char buf[NN_MAX_LABEL]); +size_t ncl_setLabel(nn_Component *c, const char *label, size_t len); diff --git a/src/ncomplib.h b/src/ncomplib.h index 74f7159..24dc65f 100644 --- a/src/ncomplib.h +++ b/src/ncomplib.h @@ -3,6 +3,8 @@ #include "neonucleus.h" +#define NCL_PREFIX "ncl-" + #define NCL_EEPROM "ncl-eeprom" #define NCL_FS "ncl-filesystem" #define NCL_DRIVE "ncl-drive" @@ -194,9 +196,6 @@ size_t ncl_getLabel(nn_Component *c, char buf[NN_MAX_LABEL]); size_t ncl_setLabel(nn_Component *c, const char *label, size_t len); nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs, bool isReadonly); -nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); -// data is stored interally -nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *path, bool isReadonly); #define NCL_VFS_NAMEMAX 32 @@ -207,13 +206,17 @@ nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const // and tmpfs. nn_Component *ncl_createTmpFS(nn_Universe *universe, const nn_Filesystem *fs); -// creates a temporary EEPROM, with some initial code -// the data is stored internally -nn_Component *ncl_createTmpEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const char *code, size_t codelen); +nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); // creates a temporary drive, with some initial data nn_Component *ncl_createTmpDrive(nn_Universe *universe, const nn_EEPROM *eeprom, const char *data, size_t datalen); +// data is stored interally +nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *path, bool isReadonly); +// creates a temporary EEPROM, with some initial code +// the data is stored internally +nn_Component *ncl_createTmpEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const char *code, size_t codelen); + // Gets the VFS bound to a filesystem, drive or eeprom. // Returns the default FS if the component is not recognized. ncl_VFS ncl_getVFS(nn_Component *component); @@ -293,11 +296,18 @@ typedef struct ncl_ComponentStat { }; } ncl_ComponentStat; +bool ncl_isNCLID(const char *type); +bool ncl_isNCLComponent(nn_Component *component); void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat); // For EEPROMs, filesystems, drives // Returns whether it was successful or not. bool ncl_makeReadonly(nn_Component *component); +// Returns the amount of data written. +// The capacity MUST be at least the data size of the EEPROM. +size_t ncl_getEEPROMData(nn_Component *component, char *buf); +void ncl_setEEPROMData(nn_Component *component, const char *data, size_t len); + void ncl_lockScreen(ncl_ScreenState *state); void ncl_unlockScreen(ncl_ScreenState *state); void ncl_resetScreen(ncl_ScreenState *state); diff --git a/src/neonucleus.c b/src/neonucleus.c index a41a94e..95319d4 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -520,16 +520,31 @@ static void *nn_defaultAlloc(void *_, void *memory, size_t oldSize, size_t newSi static double nn_defaultTime(void *_) { #ifndef NN_BAREMETAL #ifdef NN_POSIX - struct timespec s; - if(clock_gettime(CLOCK_REALTIME, &s)) return 0; - return s.tv_sec + (double)s.tv_nsec / 1000000000; + struct timespec s; + if(clock_gettime(CLOCK_REALTIME, &s)) return 0; + return s.tv_sec + (double)s.tv_nsec / 1000000000; +#elif defined(NN_WINDOWS) + // Without this, nn_defaultTime returns 0 on Windows. + // This breaks nn_getUptime(), which is (currentTime - creationTimestamp). + // If currentTime is always 0, uptime is always negative or 0. + // OpenOS relies on computer.uptime() for all timeouts: + // computer.pullSignal(timeout) compares computer.uptime() against a deadline. + // If uptime never advances, pullSignal(2) returns instantly instead of waiting. + // To verify: in OpenOS, run `lua -e "print(computer.uptime())"`. + // Before fix: always prints 0 or a tiny constant. + // After fix: prints seconds since boot, increasing each call. + // QueryPerformanceCounter is the highest-resolution monotonic clock on Windows. + // It is available since Windows 2000 and cannot fail on Vista+. + LARGE_INTEGER freq, count; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + return (double)count.QuadPart + / (double)freq.QuadPart; #else - // time does not exist... yet! - return 0; + return 0; #endif #else - // time does not exist - return 0; + return 0; #endif } @@ -542,6 +557,26 @@ static size_t nn_defaultRng(void *_) { #endif } +#ifdef NN_WINDOWS +// rand_s() requires _CRT_RAND_S defined before . +// However, we can avoid that dependency by using the Win32 +// API directly. RtlGenRandom (aka SystemFunction036) is +// available on all Windows versions since XP and does not +// require linking any extra library it lives in advapi32 +// which is always implicitly linked. +// It fills a buffer with cryptographically strong random bytes. +// To verify: print nn_rand() in a loop on Windows. +BOOLEAN NTAPI SystemFunction036( + PVOID RandomBuffer, ULONG RandomBufferLength); +#pragma comment(lib, "advapi32") + +static size_t nn_windowsRng(void *_) { + unsigned int v = 0; + SystemFunction036(&v, sizeof(v)); + return v; +} +#endif + static void nn_defaultLock(void *state, nn_LockRequest *req) { (void)state; #ifndef NN_BAREMETAL @@ -605,41 +640,68 @@ static void nn_defaultLock(void *state, nn_LockRequest *req) { return; } #elif defined(NN_THREAD_WINDOWS) - switch(req->action) { - case NN_LOCK_CREATE:; - req->lock = CreateMutex(NULL, FALSE, NULL); - return; // don't fall into destroy - case NN_LOCK_DESTROY:; - CloseHandle(req->lock); - return; - case NN_LOCK_LOCK:; - WaitForSingleObject(req->lock, INFINITE); - return; - case NN_LOCK_UNLOCK:; - ReleaseMutex(req->lock); - return; - } + // The original code used CreateMutex/WaitForSingleObject/ReleaseMutex. + // Windows Mutexes are kernel objects: every lock/unlock is a syscall + // into the NT kernel (NtWaitForSingleObject / NtReleaseMutant), + // even when uncontended. They also support cross-process sharing + // and abandonment detection, none of which NN needs. + // + // CRITICAL_SECTION is a user-mode construct that uses a spinlock + // with a kernel fallback (only enters kernel on actual contention). + // For uncontended locks (the common case), EnterCriticalSection + // is just an interlocked compare-exchange so no syscall at all. + switch(req->action) { + case NN_LOCK_CREATE:; + CRITICAL_SECTION *cs = + malloc(sizeof(CRITICAL_SECTION)); + if(cs == NULL) { req->lock = NULL; return; } + InitializeCriticalSection(cs); + req->lock = cs; + return; + case NN_LOCK_DESTROY:; + DeleteCriticalSection(req->lock); + free(req->lock); + return; + case NN_LOCK_LOCK:; + EnterCriticalSection(req->lock); + return; + case NN_LOCK_UNLOCK:; + LeaveCriticalSection(req->lock); + return; + } #endif #endif } void nn_initContext(nn_Context *ctx) { - ctx->state = NULL; - ctx->alloc = nn_defaultAlloc; - ctx->time = nn_defaultTime; + ctx->state = NULL; + ctx->alloc = nn_defaultAlloc; + ctx->time = nn_defaultTime; #ifndef NN_BAREMETAL - // someone pointed out that running this multiple times - // in 1 second can cause the RNG to loop. - // However, if you call this function multiple times at all, - // that's on you. - srand(time(NULL)); - ctx->rngMaximum = RAND_MAX; + // RAND_MAX on MSVC is 32767 (15 bits), and nn_randomUUID() calls nn_rand() & 0xF per hex digit, + // but with only 15 bits total per rand() call, the UUIDs have very low entropy. nn_randf() divides by + // (rngMaximum+1), so 15 bits gives ~0.00003 granularity instead of ~0.0000000005 with 31 bits. + // To verify: generate 1000 UUIDs, check for duplicates + // or patterns. With 15 bits of entropy per call and + // 32 hex digits, collisions become plausible. + // On Windows we use rand_s() which returns a full + // 32-bit cryptographic random number without needing + // srand(). On POSIX, rand()+srand() remains fine as + // RAND_MAX is typically 2^31-1. +#ifdef NN_WINDOWS + ctx->rngMaximum = UINT_MAX; + ctx->rng = nn_windowsRng; #else - ctx->rngMaximum = 1; + srand(time(NULL)); + ctx->rngMaximum = RAND_MAX; + ctx->rng = nn_defaultRng; #endif - ctx->rng = nn_defaultRng; - ctx->lock = nn_defaultLock; +#else + ctx->rngMaximum = 1; + ctx->rng = nn_defaultRng; +#endif + ctx->lock = nn_defaultLock; } // some util data structures @@ -1605,20 +1667,23 @@ void nn_getComponentMethods(nn_Component *c, const char **methodnames, size_t *l *len = enabled; } -bool nn_hasComponentMethod(nn_Component *c, const char *method) { - nn_MethodEntry *ent = nn_getComponentMethodEntry(c, method); - if(ent == NULL) return false; +bool nn_hasComponentMethod(nn_Component *c, + const char *method) +{ + nn_MethodEntry *ent = + nn_getComponentMethodEntry(c, method); + if(ent == NULL) return false; - nn_ComponentRequest req; - req.ctx = &c->universe->ctx; - req.computer = NULL; - req.state = c->state; - req.action = NN_COMP_CHECKMETHOD; - req.methodIdx = ent->idx; - // by default, yes - req.methodEnabled = true; - c->handler(&req); - return req.methodEnabled; + nn_ComponentRequest req; + req.ctx = &c->universe->ctx; + req.computer = NULL; + req.state = c->state; + req.classState = c->classState; // Don't remove it. It segfaults. + req.action = NN_COMP_CHECKMETHOD; + req.methodIdx = ent->idx; + req.methodEnabled = true; + c->handler(&req); + return req.methodEnabled; } const char *nn_getComponentDoc(nn_Component *c, const char *method) { @@ -3897,98 +3962,898 @@ nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const n return c; } -typedef struct nn_ScreenState { - nn_Context *ctx; - nn_ScreenConfig scrconf; - nn_ScreenHandler *handler; -} nn_ScreenState; +typedef enum nn_ScreenNum { + NN_SCRNUM_ISON, + NN_SCRNUM_TURNON, + NN_SCRNUM_TURNOFF, + NN_SCRNUM_GETASPECTRATIO, + NN_SCRNUM_GETKEYBOARDS, + NN_SCRNUM_SETPRECISE, + NN_SCRNUM_ISPRECISE, + NN_SCRNUM_SETTOUCHINVERTED, + NN_SCRNUM_ISTOUCHINVERTED, + NN_SCRNUM_COUNT, +} nn_ScreenNum; + +typedef struct nn_ScreenClassState { + nn_Context *ctx; + nn_ScreenConfig scrconf; + nn_ScreenHandler *handler; +} nn_ScreenClassState; static nn_Exit nn_screenHandler(nn_ComponentRequest *req) { - if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; - if(req->action == NN_COMP_SIGNAL) return NN_OK; - nn_Context *ctx = req->ctx; - nn_ScreenState *state = req->classState; - nn_Computer *C = req->computer; - nn_ScreenRequest sreq; - sreq.ctx = ctx; - sreq.state = req->state; - sreq.computer = C; - sreq.screen = &state->scrconf; + if(req->action == NN_COMP_SIGNAL) return NN_OK; + nn_Context *ctx = req->ctx; + nn_ScreenClassState *cls = req->classState; + nn_Computer *C = req->computer; - if(req->action == NN_COMP_DROP) { - sreq.action = NN_SCREEN_DROP; - state->handler(&sreq); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } + // Feature-gated methods + if(req->action == NN_COMP_CHECKMETHOD) { + nn_ScreenNum m = req->methodIdx; + if(m == NN_SCRNUM_SETPRECISE + || m == NN_SCRNUM_ISPRECISE) + req->methodEnabled = + (cls->scrconf.features + & NN_SCRF_PRECISE) != 0; + if(m == NN_SCRNUM_SETTOUCHINVERTED + || m == NN_SCRNUM_ISTOUCHINVERTED) + req->methodEnabled = + (cls->scrconf.features + & NN_SCRF_TOUCHINVERTED) != 0; + return NN_OK; + } - nn_setError(C, "screen: not yet implemented"); - return NN_EBADCALL; + nn_ScreenRequest s; + s.ctx = ctx; + s.state = req->state; + s.computer = C; + s.screen = &cls->scrconf; + + if(req->action == NN_COMP_DROP) { + s.action = NN_SCREEN_DROP; + cls->handler(&s); + nn_free(ctx, cls, sizeof(*cls)); + return NN_OK; + } + + nn_ScreenNum m = req->methodIdx; + nn_Exit e = NN_OK; + + if(m == NN_SCRNUM_ISON) { + s.action = NN_SCREEN_ISON; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.power.isOn); + } + if(m == NN_SCRNUM_TURNON) { + s.action = NN_SCREEN_TURNON; + e = cls->handler(&s); + if(e) return e; + e = nn_pushbool(C, s.power.wasOn); + if(e) return e; + e = nn_pushbool(C, s.power.isOn); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + if(m == NN_SCRNUM_TURNOFF) { + s.action = NN_SCREEN_TURNOFF; + e = cls->handler(&s); + if(e) return e; + e = nn_pushbool(C, s.power.wasOn); + if(e) return e; + e = nn_pushbool(C, s.power.isOn); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + if(m == NN_SCRNUM_GETASPECTRATIO) { + s.action = NN_SCREEN_GETASPECTRATIO; + e = cls->handler(&s); + if(e) return e; + e = nn_pushinteger(C, s.aspect.w); + if(e) return e; + e = nn_pushinteger(C, s.aspect.h); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + if(m == NN_SCRNUM_GETKEYBOARDS) { + // handler pushes addresses onto C's stack + s.action = NN_SCREEN_GETKEYBOARDS; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pusharraytable(C, s.kbCount); + } + if(m == NN_SCRNUM_SETPRECISE) { + if(nn_checkboolean(C, 0, + "bad argument #1 (boolean expected)")) + return NN_EBADCALL; + s.action = NN_SCREEN_SETPRECISE; + s.flag = nn_toboolean(C, 0); + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + if(m == NN_SCRNUM_ISPRECISE) { + s.action = NN_SCREEN_ISPRECISE; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + if(m == NN_SCRNUM_SETTOUCHINVERTED) { + if(nn_checkboolean(C, 0, + "bad argument #1 (boolean expected)")) + return NN_EBADCALL; + s.action = NN_SCREEN_SETTOUCHINVERTED; + s.flag = nn_toboolean(C, 0); + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + if(m == NN_SCRNUM_ISTOUCHINVERTED) { + s.action = NN_SCREEN_ISTOUCHINVERTED; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + + nn_setError(C, "screen: not implemented"); + return NN_EBADCALL; } -nn_Component *nn_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *scrconf, void *state, nn_ScreenHandler *handler) { - nn_Component *c = nn_createComponent(universe, address, "screen"); - if(c == NULL) return NULL; - // TODO: methods - nn_Context *ctx = &universe->ctx; - nn_ScreenState *scrstate = nn_alloc(ctx, sizeof(*scrstate)); - if(scrstate == NULL) { - nn_dropComponent(c); - return NULL; - } - scrstate->ctx = ctx; - scrstate->scrconf = *scrconf; - scrstate->handler = handler; - nn_setComponentState(c, state); - nn_setComponentClassState(c, scrstate); - nn_setComponentHandler(c, nn_screenHandler); - return c; +// Replace nn_createScreen entirely: +nn_Component *nn_createScreen( + nn_Universe *universe, const char *address, + const nn_ScreenConfig *scrconf, void *state, + nn_ScreenHandler *handler) +{ + nn_Component *c = nn_createComponent( + universe, address, "screen"); + if(c == NULL) return NULL; + + const nn_Method methods[NN_SCRNUM_COUNT] = { + [NN_SCRNUM_ISON] = { + "isOn", + "function():boolean -- Screen powered?", + NN_DIRECT}, + [NN_SCRNUM_TURNON] = { + "turnOn", + "function():boolean,boolean -- Turn on", + NN_INDIRECT}, + [NN_SCRNUM_TURNOFF] = { + "turnOff", + "function():boolean,boolean -- Turn off", + NN_INDIRECT}, + [NN_SCRNUM_GETASPECTRATIO] = { + "getAspectRatio", + "function():number,number -- Block ratio", + NN_DIRECT}, + [NN_SCRNUM_GETKEYBOARDS] = { + "getKeyboards", + "function():table -- Attached keyboards", + NN_DIRECT}, + [NN_SCRNUM_SETPRECISE] = { + "setPrecise", + "function(on:boolean):boolean" + " -- High-precision mouse", + NN_DIRECT}, + [NN_SCRNUM_ISPRECISE] = { + "isPrecise", + "function():boolean -- Precision enabled?", + NN_DIRECT}, + [NN_SCRNUM_SETTOUCHINVERTED] = { + "setTouchModeInverted", + "function(on:boolean):boolean" + " -- Invert touch mode", + NN_DIRECT}, + [NN_SCRNUM_ISTOUCHINVERTED] = { + "isTouchModeInverted", + "function():boolean -- Touch inverted?", + NN_DIRECT}, + }; + + nn_Exit e = nn_setComponentMethodsArray( + c, methods, NN_SCRNUM_COUNT); + if(e) { nn_dropComponent(c); return NULL; } + + nn_Context *ctx = &universe->ctx; + nn_ScreenClassState *cls = + nn_alloc(ctx, sizeof(*cls)); + if(cls == NULL) { + nn_dropComponent(c); + return NULL; + } + cls->ctx = ctx; + cls->scrconf = *scrconf; + cls->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, cls); + nn_setComponentHandler(c, nn_screenHandler); + return c; } -typedef struct nn_GPUState { - nn_Context *ctx; - nn_GPU gpu; - nn_GPUHandler *handler; -} nn_GPUState; +typedef enum nn_GPUNum { + NN_GPUNUM_BIND, + NN_GPUNUM_GETSCREEN, + NN_GPUNUM_GETBG, + NN_GPUNUM_SETBG, + NN_GPUNUM_GETFG, + NN_GPUNUM_SETFG, + NN_GPUNUM_GETPALETTE, + NN_GPUNUM_SETPALETTE, + NN_GPUNUM_MAXDEPTH, + NN_GPUNUM_GETDEPTH, + NN_GPUNUM_SETDEPTH, + NN_GPUNUM_MAXRES, + NN_GPUNUM_GETRES, + NN_GPUNUM_SETRES, + NN_GPUNUM_GETVIEWPORT, + NN_GPUNUM_SETVIEWPORT, + NN_GPUNUM_GET, + NN_GPUNUM_SET, + NN_GPUNUM_COPY, + NN_GPUNUM_FILL, + NN_GPUNUM_GETACTIVEBUF, + NN_GPUNUM_SETACTIVEBUF, + NN_GPUNUM_BUFFERS, + NN_GPUNUM_ALLOCBUF, + NN_GPUNUM_FREEBUF, + NN_GPUNUM_FREEALLBUFS, + NN_GPUNUM_TOTALMEM, + NN_GPUNUM_FREEMEM, + NN_GPUNUM_GETBUFSIZE, + NN_GPUNUM_BITBLT, + NN_GPUNUM_COUNT, +} nn_GPUNum; + +typedef struct nn_GPUClassState { + nn_Context *ctx; + nn_GPU gpu; + nn_GPUHandler *handler; +} nn_GPUClassState; static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { - if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; - if(req->action == NN_COMP_SIGNAL) return NN_OK; - nn_Context *ctx = req->ctx; - nn_GPUState *state = req->classState; - nn_Computer *C = req->computer; - nn_GPURequest greq; - greq.ctx = ctx; - greq.state = req->state; - greq.computer = C; - greq.gpu = &state->gpu; + if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; + if(req->action == NN_COMP_SIGNAL) return NN_OK; + nn_Context *ctx = req->ctx; + nn_GPUClassState *cls = req->classState; + nn_Computer *C = req->computer; - if(req->action == NN_COMP_DROP) { - greq.action = NN_GPU_DROP; - state->handler(&greq); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } + nn_GPURequest g; + g.ctx = ctx; + g.state = req->state; + g.computer = C; + g.gpu = &cls->gpu; - nn_setError(C, "gpu: not yet implemented"); - return NN_EBADCALL; + if(req->action == NN_COMP_DROP) { + g.action = NN_GPU_DROP; + cls->handler(&g); + nn_free(ctx, cls, sizeof(*cls)); + return NN_OK; + } + + nn_GPUNum m = req->methodIdx; + nn_Exit e = NN_OK; + + // bind + if(m == NN_GPUNUM_BIND) { + if(nn_checkstring(C, 0, + "bad argument #1 (string expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 1, true); + if(e) return e; + g.action = NN_GPU_BIND; + g.bind.address = nn_tostring(C, 0); + g.bind.reset = nn_toboolean(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // getScreen + if(m == NN_GPUNUM_GETSCREEN) { + g.action = NN_GPU_GETSCREEN; + g.screenAddr[0] = '\0'; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + if(g.screenAddr[0] == '\0') + return nn_pushnull(C); + return nn_pushstring(C, g.screenAddr); + } + // getBackground + if(m == NN_GPUNUM_GETBG) { + g.action = NN_GPU_GETBG; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.color); + if(e) return e; + e = nn_pushbool(C, g.color.isPalette); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setBackground + if(m == NN_GPUNUM_SETBG) { + if(nn_checknumber(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 1, false); + if(e) return e; + g.action = NN_GPU_SETBG; + g.color.color = nn_tointeger(C, 0); + g.color.isPalette = nn_toboolean(C, 1); + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.oldColor); + if(e) return e; + req->returnCount = 1; + if(g.color.oldPaletteIdx >= 0) { + e = nn_pushinteger(C, g.color.oldPaletteIdx); + if(e) return e; + req->returnCount = 2; + } + return NN_OK; + } + // getForeground + if(m == NN_GPUNUM_GETFG) { + g.action = NN_GPU_GETFG; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.color); + if(e) return e; + e = nn_pushbool(C, g.color.isPalette); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setForeground + if(m == NN_GPUNUM_SETFG) { + if(nn_checknumber(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 1, false); + if(e) return e; + g.action = NN_GPU_SETFG; + g.color.color = nn_tointeger(C, 0); + g.color.isPalette = nn_toboolean(C, 1); + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.oldColor); + if(e) return e; + req->returnCount = 1; + if(g.color.oldPaletteIdx >= 0) { + e = nn_pushinteger(C, g.color.oldPaletteIdx); + if(e) return e; + req->returnCount = 2; + } + return NN_OK; + } + // getPaletteColor + if(m == NN_GPUNUM_GETPALETTE) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_GETPALETTE; + g.palette.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.palette.color); + } + // setPaletteColor + if(m == NN_GPUNUM_SETPALETTE) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETPALETTE; + g.palette.index = nn_tointeger(C, 0); + g.palette.color = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.palette.oldColor); + } + // maxDepth + if(m == NN_GPUNUM_MAXDEPTH) { + g.action = NN_GPU_MAXDEPTH; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.depth.depth); + } + // getDepth + if(m == NN_GPUNUM_GETDEPTH) { + g.action = NN_GPU_GETDEPTH; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.depth.depth); + } + // setDepth + if(m == NN_GPUNUM_SETDEPTH) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETDEPTH; + g.depth.depth = (char)nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + const char *name = nn_depthName(g.depth.oldDepth); + if(name == NULL) name = "Unknown"; + req->returnCount = 1; + return nn_pushstring(C, name); + } + // maxResolution + if(m == NN_GPUNUM_MAXRES) { + g.action = NN_GPU_MAXRES; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.resolution.width); + if(e) return e; + e = nn_pushinteger(C, g.resolution.height); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // getResolution + if(m == NN_GPUNUM_GETRES) { + g.action = NN_GPU_GETRES; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.resolution.width); + if(e) return e; + e = nn_pushinteger(C, g.resolution.height); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setResolution + if(m == NN_GPUNUM_SETRES) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETRES; + g.resolution.width = nn_tointeger(C, 0); + g.resolution.height = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + // push screen_resized via getScreen + nn_GPURequest s = g; + s.action = NN_GPU_GETSCREEN; + s.screenAddr[0] = '\0'; + cls->handler(&s); + if(s.screenAddr[0] != '\0') { + nn_pushScreenResized(C, s.screenAddr, + g.resolution.width, g.resolution.height); + } + req->returnCount = 1; + return nn_pushbool(C, true); + } + // getViewport + if(m == NN_GPUNUM_GETVIEWPORT) { + g.action = NN_GPU_GETVIEWPORT; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.resolution.width); + if(e) return e; + e = nn_pushinteger(C, g.resolution.height); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setViewport + if(m == NN_GPUNUM_SETVIEWPORT) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETVIEWPORT; + g.resolution.width = nn_tointeger(C, 0); + g.resolution.height = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // get + if(m == NN_GPUNUM_GET) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_GET; + g.get.x = nn_tointeger(C, 0); + g.get.y = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + char buf[NN_MAX_UNICODE_BUFFER]; + size_t len = nn_unicode_codepointToChar( + buf, g.get.codepoint); + e = nn_pushlstring(C, buf, len); + if(e) return e; + e = nn_pushinteger(C, g.get.fg); + if(e) return e; + e = nn_pushinteger(C, g.get.bg); + if(e) return e; + req->returnCount = 3; + if(g.get.fgIdx >= 0) { + e = nn_pushinteger(C, g.get.fgIdx); + if(e) return e; + } else { + e = nn_pushnull(C); + if(e) return e; + } + if(g.get.bgIdx >= 0) { + e = nn_pushinteger(C, g.get.bgIdx); + if(e) return e; + } else { + e = nn_pushnull(C); + if(e) return e; + } + req->returnCount = 5; + return NN_OK; + } + // set + if(m == NN_GPUNUM_SET) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + if(nn_checkstring(C, 2, + "bad argument #3 (string expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 3, false); + if(e) return e; + g.action = NN_GPU_SET; + g.set.x = nn_tointeger(C, 0); + g.set.y = nn_tointeger(C, 1); + g.set.value = nn_tolstring(C, 2, &g.set.len); + g.set.vertical = nn_toboolean(C, 3); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // copy + if(m == NN_GPUNUM_COPY) { + for(int i = 0; i < 6; i++) { + if(nn_checkinteger(C, i, + "bad argument (number expected)")) + return NN_EBADCALL; + } + g.action = NN_GPU_COPY; + g.copy.x = nn_tointeger(C, 0); + g.copy.y = nn_tointeger(C, 1); + g.copy.w = nn_tointeger(C, 2); + g.copy.h = nn_tointeger(C, 3); + g.copy.tx = nn_tointeger(C, 4); + g.copy.ty = nn_tointeger(C, 5); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // fill + if(m == NN_GPUNUM_FILL) { + for(int i = 0; i < 4; i++) { + if(nn_checkinteger(C, i, + "bad argument (number expected)")) + return NN_EBADCALL; + } + if(nn_checkstring(C, 4, + "bad argument #5 (string expected)")) + return NN_EBADCALL; + g.action = NN_GPU_FILL; + g.fill.x = nn_tointeger(C, 0); + g.fill.y = nn_tointeger(C, 1); + g.fill.w = nn_tointeger(C, 2); + g.fill.h = nn_tointeger(C, 3); + g.fill.codepoint = nn_unicode_firstCodepoint( + nn_tostring(C, 4)); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // VRAM: getActiveBuffer + if(m == NN_GPUNUM_GETACTIVEBUF) { + g.action = NN_GPU_GETACTIVEBUF; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.buffer.index); + } + // VRAM: setActiveBuffer + if(m == NN_GPUNUM_SETACTIVEBUF) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETACTIVEBUF; + g.buffer.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.buffer.index); + } + // VRAM: buffers + if(m == NN_GPUNUM_BUFFERS) { + g.action = NN_GPU_BUFFERS; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pusharraytable(C, g.bufCount); + } + // VRAM: allocateBuffer + if(m == NN_GPUNUM_ALLOCBUF) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + e = nn_defaultinteger(C, 1, 0); + if(e) return e; + g.action = NN_GPU_ALLOCBUF; + g.allocBuf.w = nn_tointeger(C, 0); + g.allocBuf.h = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.allocBuf.index); + } + // VRAM: freeBuffer + if(m == NN_GPUNUM_FREEBUF) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + g.action = NN_GPU_FREEBUF; + g.buffer.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // VRAM: freeAllBuffers + if(m == NN_GPUNUM_FREEALLBUFS) { + g.action = NN_GPU_FREEALLBUFS; + e = cls->handler(&g); + if(e) return e; + return NN_OK; + } + // VRAM: totalMemory + if(m == NN_GPUNUM_TOTALMEM) { + req->returnCount = 1; + return nn_pushinteger(C, cls->gpu.totalVRAM); + } + // VRAM: freeMemory + if(m == NN_GPUNUM_FREEMEM) { + g.action = NN_GPU_FREEMEM; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.memory); + } + // VRAM: getBufferSize + if(m == NN_GPUNUM_GETBUFSIZE) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + g.action = NN_GPU_GETBUFSIZE; + g.bufSize.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.bufSize.w); + if(e) return e; + e = nn_pushinteger(C, g.bufSize.h); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // VRAM: bitblt + if(m == NN_GPUNUM_BITBLT) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + for(int i = 1; i < 8; i++) { + e = nn_defaultinteger(C, i, 0); + if(e) return e; + } + g.action = NN_GPU_BITBLT; + g.bitblt.dst = nn_tointeger(C, 0); + g.bitblt.col = nn_tointeger(C, 1); + g.bitblt.row = nn_tointeger(C, 2); + g.bitblt.w = nn_tointeger(C, 3); + g.bitblt.h = nn_tointeger(C, 4); + g.bitblt.src = nn_tointeger(C, 5); + g.bitblt.fromCol = nn_tointeger(C, 6); + g.bitblt.fromRow = nn_tointeger(C, 7); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + + nn_setError(C, "gpu: not implemented"); + return NN_EBADCALL; } -nn_Component *nn_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu, void *state, nn_GPUHandler *handler) { - nn_Component *c = nn_createComponent(universe, address, "gpu"); - if(c == NULL) return NULL; - // TODO: methods - nn_Context *ctx = &universe->ctx; - nn_GPUState *gpustate = nn_alloc(ctx, sizeof(*gpustate)); - if(gpustate == NULL) { - nn_dropComponent(c); - return NULL; - } - gpustate->ctx = ctx; - gpustate->gpu = *gpu; - gpustate->handler = handler; - nn_setComponentState(c, state); - nn_setComponentClassState(c, gpustate); - nn_setComponentHandler(c, nn_gpuHandler); - return c; -} +// Replace nn_createGPU entirely: +nn_Component *nn_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu, void *state, + nn_GPUHandler *handler) +{ + nn_Component *c = nn_createComponent( + universe, address, "gpu"); + if(c == NULL) return NULL; + + const nn_Method methods[NN_GPUNUM_COUNT] = { + [NN_GPUNUM_BIND] = { + "bind", + "function(address:string[,reset:boolean])" + ":boolean -- Bind to screen", + NN_INDIRECT}, + [NN_GPUNUM_GETSCREEN] = { + "getScreen", + "function():string -- Bound screen address", + NN_DIRECT}, + [NN_GPUNUM_GETBG] = { + "getBackground", + "function():number,boolean -- Current bg", + NN_DIRECT}, + [NN_GPUNUM_SETBG] = { + "setBackground", + "function(color:number[,palette:boolean])" + ":number[,number] -- Set bg", + NN_DIRECT}, + [NN_GPUNUM_GETFG] = { + "getForeground", + "function():number,boolean -- Current fg", + NN_DIRECT}, + [NN_GPUNUM_SETFG] = { + "setForeground", + "function(color:number[,palette:boolean])" + ":number[,number] -- Set fg", + NN_DIRECT}, + [NN_GPUNUM_GETPALETTE] = { + "getPaletteColor", + "function(index:number):number" + " -- Get palette color", + NN_DIRECT}, + [NN_GPUNUM_SETPALETTE] = { + "setPaletteColor", + "function(index:number,value:number):number" + " -- Set palette color", + NN_DIRECT}, + [NN_GPUNUM_MAXDEPTH] = { + "maxDepth", + "function():number -- Max color depth", + NN_DIRECT}, + [NN_GPUNUM_GETDEPTH] = { + "getDepth", + "function():number -- Current depth", + NN_DIRECT}, + [NN_GPUNUM_SETDEPTH] = { + "setDepth", + "function(depth:number):string -- Set depth", + NN_DIRECT}, + [NN_GPUNUM_MAXRES] = { + "maxResolution", + "function():number,number -- Max resolution", + NN_DIRECT}, + [NN_GPUNUM_GETRES] = { + "getResolution", + "function():number,number -- Resolution", + NN_DIRECT}, + [NN_GPUNUM_SETRES] = { + "setResolution", + "function(w:number,h:number):boolean" + " -- Set resolution", + NN_DIRECT}, + [NN_GPUNUM_GETVIEWPORT] = { + "getViewport", + "function():number,number -- Viewport size", + NN_DIRECT}, + [NN_GPUNUM_SETVIEWPORT] = { + "setViewport", + "function(w:number,h:number):boolean" + " -- Set viewport", + NN_DIRECT}, + [NN_GPUNUM_GET] = { + "get", + "function(x:number,y:number):string," + "number,number,number?,number? -- Read pixel", + NN_DIRECT}, + [NN_GPUNUM_SET] = { + "set", + "function(x:number,y:number,s:string" + "[,vertical:boolean]):boolean -- Write text", + NN_DIRECT}, + [NN_GPUNUM_COPY] = { + "copy", + "function(x,y,w,h,tx,ty):boolean" + " -- Copy region", + NN_DIRECT}, + [NN_GPUNUM_FILL] = { + "fill", + "function(x,y,w,h,char):boolean" + " -- Fill region", + NN_DIRECT}, + [NN_GPUNUM_GETACTIVEBUF] = { + "getActiveBuffer", + "function():number -- Active buffer index", + NN_DIRECT}, + [NN_GPUNUM_SETACTIVEBUF] = { + "setActiveBuffer", + "function(index:number):number" + " -- Set active buffer", + NN_DIRECT}, + [NN_GPUNUM_BUFFERS] = { + "buffers", + "function():table -- List buffer indices", + NN_DIRECT}, + [NN_GPUNUM_ALLOCBUF] = { + "allocateBuffer", + "function([w:number,h:number]):number" + " -- Allocate VRAM page", + NN_DIRECT}, + [NN_GPUNUM_FREEBUF] = { + "freeBuffer", + "function([index:number]):boolean" + " -- Free VRAM page", + NN_DIRECT}, + [NN_GPUNUM_FREEALLBUFS] = { + "freeAllBuffers", + "function() -- Free all VRAM pages", + NN_DIRECT}, + [NN_GPUNUM_TOTALMEM] = { + "totalMemory", + "function():number -- Total VRAM", + NN_DIRECT}, + [NN_GPUNUM_FREEMEM] = { + "freeMemory", + "function():number -- Free VRAM", + NN_DIRECT}, + [NN_GPUNUM_GETBUFSIZE] = { + "getBufferSize", + "function([index:number]):number,number" + " -- Buffer dimensions", + NN_DIRECT}, + [NN_GPUNUM_BITBLT] = { + "bitblt", + "function([dst,col,row,w,h,src,fc,fr])" + ":boolean -- Blit between buffers", + NN_DIRECT}, + }; + + nn_Exit e = nn_setComponentMethodsArray( + c, methods, NN_GPUNUM_COUNT); + if(e) { nn_dropComponent(c); return NULL; } + + nn_Context *ctx = &universe->ctx; + nn_GPUClassState *cls = nn_alloc(ctx, sizeof(*cls)); + if(cls == NULL) { + nn_dropComponent(c); + return NULL; + } + cls->ctx = ctx; + cls->gpu = *gpu; + cls->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, cls); + nn_setComponentHandler(c, nn_gpuHandler); + return c; +} \ No newline at end of file diff --git a/src/neonucleus.h b/src/neonucleus.h index d0bdeb6..5b1a911 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -1229,24 +1229,8 @@ typedef struct nn_ScreenConfig { // OC has 3 tiers, NN adds a 4th one as well. extern const nn_ScreenConfig nn_defaultScreens[4]; -typedef enum nn_ScreenAction { - NN_SCREEN_DROP, -} nn_ScreenAction; - -typedef struct nn_ScreenRequest { - nn_Context *ctx; - nn_Computer *computer; - void *state; - const nn_ScreenConfig *screen; - nn_ScreenAction action; -} nn_ScreenRequest; - -typedef nn_Exit (nn_ScreenHandler)(nn_ScreenRequest *req); - -nn_Component *nn_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *scrconf, void *state, nn_ScreenHandler *handler); // GPU class - typedef struct nn_GPU { // the minimum between these and the screen's // are the maximum width/height/depth supported. @@ -1275,20 +1259,175 @@ typedef struct nn_GPU { extern const nn_GPU nn_defaultGPUs[4]; typedef enum nn_GPUAction { - NN_GPU_DROP, + NN_GPU_DROP, + NN_GPU_BIND, + NN_GPU_GETSCREEN, + NN_GPU_GETBG, + NN_GPU_SETBG, + NN_GPU_GETFG, + NN_GPU_SETFG, + NN_GPU_GETPALETTE, + NN_GPU_SETPALETTE, + NN_GPU_MAXDEPTH, + NN_GPU_GETDEPTH, + NN_GPU_SETDEPTH, + NN_GPU_MAXRES, + NN_GPU_GETRES, + NN_GPU_SETRES, + NN_GPU_GETVIEWPORT, + NN_GPU_SETVIEWPORT, + NN_GPU_GET, + NN_GPU_SET, + NN_GPU_COPY, + NN_GPU_FILL, + NN_GPU_GETACTIVEBUF, + NN_GPU_SETACTIVEBUF, + NN_GPU_BUFFERS, + NN_GPU_ALLOCBUF, + NN_GPU_FREEBUF, + NN_GPU_FREEALLBUFS, + NN_GPU_FREEMEM, + NN_GPU_GETBUFSIZE, + NN_GPU_BITBLT, } nn_GPUAction; typedef struct nn_GPURequest { - nn_Context *ctx; - nn_Computer *computer; - void *state; - const nn_GPU *gpu; - nn_GPUAction action; + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_GPU *gpu; + nn_GPUAction action; + union { + struct { + const char *address; + bool reset; + } bind; + // GETSCREEN result + char screenAddr[NN_MAX_ADDRESS]; + // GET/SET BG/FG + struct { + int color; + bool isPalette; + int oldColor; + bool wasPalette; + int oldPaletteIdx; // -1 if none + } color; + // GET/SET PALETTE + struct { + int index; + int color; + int oldColor; + } palette; + // MAXDEPTH / GETDEPTH / SETDEPTH + struct { + char depth; + char oldDepth; + } depth; + // MAXRES/GETRES/SETRES/GETVIEWPORT/SETVIEWPORT + struct { + int width; + int height; + } resolution; + // GET pixel + struct { + int x, y; + nn_codepoint codepoint; + int fg, bg; + int fgIdx, bgIdx; // -1 if not palette + } get; + // SET string + struct { + int x, y; + const char *value; + size_t len; + bool vertical; + } set; + // COPY + struct { + int x, y, w, h, tx, ty; + } copy; + // FILL + struct { + int x, y, w, h; + nn_codepoint codepoint; + } fill; + // GET/SET ACTIVE BUFFER, FREE BUFFER + struct { + int index; + } buffer; + // ALLOCATE BUFFER + struct { + int w, h, index; + } allocBuf; + // TOTALMEM / FREEMEM + size_t memory; + // GETBUFSIZE + struct { + int index, w, h; + } bufSize; + // BITBLT + struct { + int dst, col, row, w, h; + int src, fromCol, fromRow; + } bitblt; + // BUFFERS / count returned here, indices + // pushed on stack by handler + size_t bufCount; + }; } nn_GPURequest; typedef nn_Exit (nn_GPUHandler)(nn_GPURequest *req); -nn_Component *nn_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu, void *state, nn_GPUHandler *handler); +nn_Component *nn_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu, void *state, + nn_GPUHandler *handler); + +typedef enum nn_ScreenAction { + NN_SCREEN_DROP, + NN_SCREEN_ISON, + NN_SCREEN_TURNON, + NN_SCREEN_TURNOFF, + NN_SCREEN_GETASPECTRATIO, + NN_SCREEN_GETKEYBOARDS, + NN_SCREEN_SETPRECISE, + NN_SCREEN_ISPRECISE, + NN_SCREEN_SETTOUCHINVERTED, + NN_SCREEN_ISTOUCHINVERTED, +} nn_ScreenAction; + +typedef struct nn_ScreenRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_ScreenConfig *screen; + nn_ScreenAction action; + union { + // turnOn / turnOff / isOn + struct { + bool wasOn; + bool isOn; + } power; + // getAspectRatio + struct { + int w, h; + } aspect; + // getKeyboards — addresses pushed on stack by + // handler; count returned here + size_t kbCount; + // setPrecise / isPrecise / + // setTouchModeInverted / isTouchModeInverted + bool flag; + }; +} nn_ScreenRequest; + +typedef nn_Exit (nn_ScreenHandler)(nn_ScreenRequest *req); + +nn_Component *nn_createScreen( + nn_Universe *universe, const char *address, + const nn_ScreenConfig *scrconf, void *state, + nn_ScreenHandler *handler +); // Colors and palettes. // Do note that the From 2d0767847f3280a9a9f2bdc309ec91cbfc37852f Mon Sep 17 00:00:00 2001 From: shorekeeper Date: Fri, 3 Apr 2026 00:09:08 +0300 Subject: [PATCH 2/3] remove test suite completely, we're blind --- src/main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.c b/src/main.c index 8b71e00..5849dca 100644 --- a/src/main.c +++ b/src/main.c @@ -339,9 +339,7 @@ int main(int argc, char **argv) { nn_Context ctx; nn_initContext(&ctx); nn_initPalettes(); -#ifdef NN_WINDOWS - nn_run_selftests(&ctx); -#endif + ne_memSand sand; sand.buf = NULL; From d41e110cdf41ce3434bfea821d94f364ec60ac29 Mon Sep 17 00:00:00 2001 From: shorekeeper Date: Fri, 3 Apr 2026 00:44:00 +0300 Subject: [PATCH 3/3] Fix some GPU bugs and add clamping --- src/glyphcache.c | 212 +++++++++ src/glyphcache.h | 19 + src/main.c | 212 +-------- src/ncomplib.c | 1061 ++++++++++++++++++++++++++++++++++++++++--- src/neonucleus.c | 1121 ++++++++++++++++++++++++++++++++++++++++------ src/neonucleus.h | 185 +++++++- 6 files changed, 2386 insertions(+), 424 deletions(-) create mode 100644 src/glyphcache.c create mode 100644 src/glyphcache.h diff --git a/src/glyphcache.c b/src/glyphcache.c new file mode 100644 index 0000000..cba5a21 --- /dev/null +++ b/src/glyphcache.c @@ -0,0 +1,212 @@ +#include +#include "glyphcache.h" +#include +#include +#include + +/* + * Dynamic glyph cache for raylib + * (C) - "Raylib's text renderer handles unicode like garbage" + * Problem: raylib's LoadFont only loads ~95 ASCII glyphs. + * LoadFontEx can load arbitrary codepoints, but you must know + * them upfront, and rebuilding every frame is expensive. + * + * So we should lazily collect codepoints the screen actually uses, + * rebuild the font atlas only when new ones appear. + * Typically this stabilises after the first few frames. + * +*/ + +#define GC_INITIAL_CAP 4096 +#define GC_BUCKET_COUNT 8192 // must be power of 2 +#define GC_BUCKET_MASK (GC_BUCKET_COUNT - 1) + +// Codepoint set (open-addressing hash set) + +typedef struct { + int *slots; // 0 = empty sentinel (U+0000 never needed) + size_t count; + size_t cap; // always a power of 2 +} CpSet; + +static void cpset_init(CpSet *s) { + s->cap = GC_BUCKET_COUNT; + s->count = 0; + s->slots = calloc(s->cap, sizeof(int)); +} + +static void cpset_free(CpSet *s) { + free(s->slots); + s->slots = NULL; + s->count = 0; +} + +static bool cpset_contains(const CpSet *s, int cp) { + size_t idx = (unsigned)cp & (s->cap - 1); + for(size_t i = 0; i < s->cap; i++) { + size_t j = (idx + i) & (s->cap - 1); + if(s->slots[j] == 0) return false; + if(s->slots[j] == cp) return true; + } + return false; +} + +static void cpset_grow(CpSet *s); + +// Returns true if the codepoint was newly inserted. +static bool cpset_insert(CpSet *s, int cp) { + if(cp == 0) return false; // sentinel + if(cpset_contains(s, cp)) return false; + + if(s->count * 4 >= s->cap * 3) cpset_grow(s); + + size_t idx = (unsigned)cp & (s->cap - 1); + for(size_t i = 0; i < s->cap; i++) { + size_t j = (idx + i) & (s->cap - 1); + if(s->slots[j] == 0) { + s->slots[j] = cp; + s->count++; + return true; + } + } + return false; // should never happen after grow +} + +static void cpset_grow(CpSet *s) { + size_t oldCap = s->cap; + int *old = s->slots; + + s->cap *= 2; + s->slots = calloc(s->cap, sizeof(int)); + s->count = 0; + + for(size_t i = 0; i < oldCap; i++) + if(old[i] != 0) cpset_insert(s, old[i]); + + free(old); +} + +// Fill dst (must hold at least s->count ints). +static void cpset_collect(const CpSet *s, int *dst) { + size_t n = 0; + for(size_t i = 0; i < s->cap; i++) + if(s->slots[i] != 0) dst[n++] = s->slots[i]; +} + +// Glyph cache + +struct ncl_GlyphCache { + char *fontPath; + int fontSize; + Font font; + bool fontLoaded; + bool dirty; // new codepoints since last rebuild + CpSet known; // all codepoints we have glyphs for +}; + +// Pre-seed the most common ranges so the first frame is not barren. +static void gc_seed(ncl_GlyphCache *gc) { + // ASCII printable + for(int i = 0x0020; i <= 0x007E; i++) cpset_insert(&gc->known, i); + + // Latin-1 Supplement + for(int i = 0x00A0; i <= 0x00FF; i++) cpset_insert(&gc->known, i); + + // Cyrillic (common) + for(int i = 0x0400; i <= 0x04FF; i++) cpset_insert(&gc->known, i); + + // General punctuation + for(int i = 0x2010; i <= 0x2027; i++) cpset_insert(&gc->known, i); + + // Box drawing + for(int i = 0x2500; i <= 0x257F; i++) cpset_insert(&gc->known, i); + + // Block elements + for(int i = 0x2580; i <= 0x259F; i++) cpset_insert(&gc->known, i); + + // Geometric shapes (partial) + for(int i = 0x25A0; i <= 0x25FF; i++) cpset_insert(&gc->known, i); + + // Braille patterns + for(int i = 0x2800; i <= 0x28FF; i++) cpset_insert(&gc->known, i); + + // Powerline / private use (common in OC themes) + for(int i = 0xE000; i <= 0xE0FF; i++) cpset_insert(&gc->known, i); + + gc->dirty = true; +} + +static void gc_rebuild(ncl_GlyphCache *gc) { + if(gc->fontLoaded) UnloadFont(gc->font); + + size_t n = gc->known.count; + if(n == 0) { gc->fontLoaded = false; gc->dirty = false; return; } + + int *cps = malloc(sizeof(int) * n); + cpset_collect(&gc->known, cps); + + gc->font = LoadFontEx(gc->fontPath, gc->fontSize, cps, (int)n); + gc->fontLoaded = true; + gc->dirty = false; + + // Let raylib use bilinear for scaled glyphs, nearest for 1:1. + SetTextureFilter(gc->font.texture, TEXTURE_FILTER_POINT); + + free(cps); + + fprintf(stderr, "[glyphcache] rebuilt atlas: %zu glyphs, tex %dx%d\n", + n, gc->font.texture.width, gc->font.texture.height); +} + +// Public API + +ncl_GlyphCache *ncl_createGlyphCache(const char *fontPath, int fontSize) { + ncl_GlyphCache *gc = calloc(1, sizeof(*gc)); + gc->fontPath = strdup(fontPath); + gc->fontSize = fontSize; + gc->fontLoaded = false; + gc->dirty = false; + cpset_init(&gc->known); + gc_seed(gc); + gc_rebuild(gc); + return gc; +} + +void ncl_destroyGlyphCache(ncl_GlyphCache *gc) { + if(!gc) return; + if(gc->fontLoaded) UnloadFont(gc->font); + cpset_free(&gc->known); + free(gc->fontPath); + free(gc); +} + +Font ncl_getFont(ncl_GlyphCache *gc) { + return gc->font; +} + +void ncl_flushGlyphs(ncl_GlyphCache *gc) { + if(gc->dirty) gc_rebuild(gc); +} + +void ncl_needGlyph(ncl_GlyphCache *gc, nn_codepoint cp) { + if(cp == 0) return; + if(cpset_insert(&gc->known, (int)cp)) + gc->dirty = true; +} + +void ncl_drawGlyph(ncl_GlyphCache *gc, nn_codepoint cp, + Vector2 pos, float size, Color tint) +{ + ncl_needGlyph(gc, cp); + DrawTextCodepoint(gc->font, (int)cp, pos, size, tint); +} + +int ncl_cellWidth(ncl_GlyphCache *gc) { + // Measure 'A' as the reference cell. + if(!gc->fontLoaded) return 8; + return MeasureTextEx(gc->font, "A", (float)gc->fontSize, 0).x; +} + +int ncl_cellHeight(ncl_GlyphCache *gc) { + return gc->fontSize; +} \ No newline at end of file diff --git a/src/glyphcache.h b/src/glyphcache.h new file mode 100644 index 0000000..c3ba199 --- /dev/null +++ b/src/glyphcache.h @@ -0,0 +1,19 @@ +#ifndef NCL_GLYPHCACHE_H +#define NCL_GLYPHCACHE_H + +#include +#include "neonucleus.h" + +typedef struct ncl_GlyphCache ncl_GlyphCache; + +ncl_GlyphCache *ncl_createGlyphCache(const char *fontPath, int fontSize); +void ncl_destroyGlyphCache(ncl_GlyphCache *gc); +Font ncl_getFont(ncl_GlyphCache *gc); +void ncl_flushGlyphs(ncl_GlyphCache *gc); +void ncl_needGlyph(ncl_GlyphCache *gc, nn_codepoint cp); +void ncl_drawGlyph(ncl_GlyphCache *gc, nn_codepoint cp, + Vector2 pos, float size, Color tint); +int ncl_cellWidth(ncl_GlyphCache *gc); +int ncl_cellHeight(ncl_GlyphCache *gc); + +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 75c2d04..5849dca 100644 --- a/src/main.c +++ b/src/main.c @@ -323,207 +323,6 @@ double ne_energy_accumulator(void *state, nn_Computer *c, double n) { } - -#ifdef NN_WINDOWS -// Quick self-tests for Windows-specific fixes -// These run before anything else so failures are caught early - -static jmp_buf nn_test_jmpbuf; -static volatile int nn_test_caught; - -static void nn_test_crash_handler(int sig) { - nn_test_caught = 1; - longjmp(nn_test_jmpbuf, 1); -} - -static int nn_test_try(void (*func)(void *), void *arg) { - nn_test_caught = 0; - signal(SIGSEGV, nn_test_crash_handler); - signal(SIGABRT, nn_test_crash_handler); - if(setjmp(nn_test_jmpbuf) == 0) { - func(arg); - } - signal(SIGSEGV, SIG_DFL); - signal(SIGABRT, SIG_DFL); - return nn_test_caught; -} - -static int nn_test_failed = 0; -static int nn_test_passed = 0; - -static void nn_test_report(const char *name, int crashed, int expected_crash) { - if(crashed && !expected_crash) { - printf("[CRASH] %s\n", name); - nn_test_failed++; - } else if(!crashed && expected_crash) { - printf("[FAIL] %s (expected crash)\n", name); - nn_test_failed++; - } else { - printf("[OK] %s\n", name); - nn_test_passed++; - } - fflush(stdout); -} - -// --- realloc tests --- - -static void nn_test_realloc_null(void *arg) { - nn_Context *ctx = arg; - void *p = nn_realloc(ctx, NULL, 0, 64); - if(p == NULL) { nn_test_failed++; return; } - nn_free(ctx, p, 64); -} - -static void nn_test_realloc_grow(void *arg) { - nn_Context *ctx = arg; - void *a = nn_alloc(ctx, 64); - if(a == NULL) return; - void *b = nn_realloc(ctx, a, 64, 128); - if(b != NULL) nn_free(ctx, b, 128); - else nn_free(ctx, a, 64); -} - -static void nn_test_realloc_free(void *arg) { - nn_Context *ctx = arg; - void *c = nn_alloc(ctx, 64); - if(c == NULL) return; - nn_realloc(ctx, c, 64, 0); -} - -// --- lock tests --- - -static void nn_test_lock_create_destroy(void *arg) { - nn_Context *ctx = arg; - nn_Lock *lock = nn_createLock(ctx); - if(lock == NULL) { nn_test_failed++; return; } - nn_destroyLock(ctx, lock); -} - -static void nn_test_lock_cycle(void *arg) { - nn_Context *ctx = arg; - nn_Lock *lock = nn_createLock(ctx); - if(lock == NULL) { nn_test_failed++; return; } - // lock and unlock 100 times to stress it - for(int i = 0; i < 100; i++) { - nn_lock(ctx, lock); - nn_unlock(ctx, lock); - } - nn_destroyLock(ctx, lock); -} - -static void nn_test_lock_two(void *arg) { - nn_Context *ctx = arg; - // two locks at the same time, make sure they dont interfere - nn_Lock *a = nn_createLock(ctx); - nn_Lock *b = nn_createLock(ctx); - if(a == NULL || b == NULL) { nn_test_failed++; return; } - nn_lock(ctx, a); - nn_lock(ctx, b); - nn_unlock(ctx, b); - nn_unlock(ctx, a); - nn_destroyLock(ctx, a); - nn_destroyLock(ctx, b); -} - -// --- VFS tests --- - -static void nn_test_vfs_stat(void *arg) { - (void)arg; - // stat current directory, should always work - ncl_Stat s; - bool ok = ncl_stat(ncl_defaultFS, ".", &s); - if(!ok) nn_test_failed++; - if(!s.isDirectory) nn_test_failed++; -} - -static void nn_test_vfs_dir(void *arg) { - (void)arg; - void *dir = ncl_opendir(ncl_defaultFS, "."); - if(dir == NULL) { nn_test_failed++; return; } - char name[NN_MAX_PATH]; - // just read one entry, dont care what it is - ncl_readdir(ncl_defaultFS, dir, name); - ncl_closedir(ncl_defaultFS, dir); -} - -static void nn_test_vfs_mkdir_remove(void *arg) { - (void)arg; - const char *testdir = "nn_test_tmpdir"; - ncl_mkdir(ncl_defaultFS, testdir); - ncl_Stat s; - bool ok = ncl_stat(ncl_defaultFS, testdir, &s); - if(!ok || !s.isDirectory) nn_test_failed++; - ncl_remove(ncl_defaultFS, testdir); - // should be gone now - ok = ncl_stat(ncl_defaultFS, testdir, &s); - if(ok) nn_test_failed++; -} - -static void nn_test_vfs_seek(void *arg) { - (void)arg; - // write a small file, seek around, read back - const char *path = "nn_test_seekfile"; - const char *data = "abcdefghij"; - void *f = ncl_openfile(ncl_defaultFS, path, "w"); - if(f == NULL) { nn_test_failed++; return; } - ncl_writefile(ncl_defaultFS, f, data, 10); - ncl_closefile(ncl_defaultFS, f); - - f = ncl_openfile(ncl_defaultFS, path, "r"); - if(f == NULL) { nn_test_failed++; ncl_remove(ncl_defaultFS, path); return; } - // seek to offset 5 from start - int off = 5; - bool ok = ncl_seekfile(ncl_defaultFS, f, NN_SEEK_SET, &off); - if(!ok || off != 5) nn_test_failed++; - // read from there - char buf[5]; - size_t len = 5; - ok = ncl_readfile(ncl_defaultFS, f, buf, &len); - if(!ok || len != 5) nn_test_failed++; - // should be "fghij" - if(buf[0] != 'f' || buf[4] != 'j') nn_test_failed++; - ncl_closefile(ncl_defaultFS, f); - ncl_remove(ncl_defaultFS, path); -} - -static void nn_run_selftests(nn_Context *ctx) { - printf("--- nn self tests ---\n"); - fflush(stdout); - - nn_test_report("realloc(NULL)", - nn_test_try(nn_test_realloc_null, ctx), 0); - nn_test_report("realloc(ptr, grow)", - nn_test_try(nn_test_realloc_grow, ctx), 0); - nn_test_report("realloc(ptr, free)", - nn_test_try(nn_test_realloc_free, ctx), 0); - - nn_test_report("lock create/destroy", - nn_test_try(nn_test_lock_create_destroy, ctx), 0); - nn_test_report("lock 100 cycles", - nn_test_try(nn_test_lock_cycle, ctx), 0); - nn_test_report("two locks interleaved", - nn_test_try(nn_test_lock_two, ctx), 0); - - nn_test_report("vfs stat cwd", - nn_test_try(nn_test_vfs_stat, NULL), 0); - nn_test_report("vfs readdir cwd", - nn_test_try(nn_test_vfs_dir, NULL), 0); - nn_test_report("vfs mkdir/remove", - nn_test_try(nn_test_vfs_mkdir_remove, NULL), 0); - nn_test_report("vfs seek", - nn_test_try(nn_test_vfs_seek, NULL), 0); - - printf("--- %d passed, %d failed ---\n\n", nn_test_passed, nn_test_failed); - fflush(stdout); - - if(nn_test_failed > 0) { - printf("self tests failed, aborting\n"); - fflush(stdout); - exit(1); - } -} -#endif - int main(int argc, char **argv) { const char *player = getenv("USER"); #ifdef NN_WINDOWS @@ -540,9 +339,7 @@ int main(int argc, char **argv) { nn_Context ctx; nn_initContext(&ctx); nn_initPalettes(); -#ifdef NN_WINDOWS - nn_run_selftests(&ctx); -#endif + ne_memSand sand; sand.buf = NULL; @@ -608,7 +405,11 @@ int main(int argc, char **argv) { nn_Component *screen = ncl_createScreen(u, NULL, &nn_defaultScreens[3]); nn_Component *gpuCard = ncl_createGPU(u, NULL, &nn_defaultGPUs[3]); + nn_Component *keyboard = nn_createComponent( + u, "mainKB", "keyboard"); + ncl_ScreenState *scrstate = nn_getComponentState(screen); + ncl_mountKeyboard(scrstate, "mainKB"); { // draw test const char *s = "hello there"; @@ -638,7 +439,7 @@ restart:; nn_mountComponent(c, eepromCard, 0); nn_mountComponent(c, managedfs, 1); nn_mountComponent(c, gpuCard, 2); - + nn_mountComponent(c, keyboard, -1); while(true) { if(WindowShouldClose()) break; @@ -773,6 +574,7 @@ cleanup:; nn_dropComponent(managedfs); nn_dropComponent(screen); nn_dropComponent(gpuCard); + nn_dropComponent(keyboard); // rip the universe nn_destroyUniverse(u); UnloadFont(font); diff --git a/src/ncomplib.c b/src/ncomplib.c index 65ee1ba..e19156e 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -1107,25 +1107,102 @@ static void ncl_recomputeScreen(const ncl_ScreenState *state) { } static nn_Exit ncl_screenHandler(nn_ScreenRequest *req) { - nn_Context *ctx = req->ctx; - nn_Computer *C = req->computer; - ncl_ScreenState *state = req->state; - const nn_ScreenConfig *conf = req->screen; + nn_Context *ctx = req->ctx; + nn_Computer *C = req->computer; + ncl_ScreenState *st = req->state; - if(req->action == NN_SCREEN_DROP) { - for(size_t i = 0; i < state->keyboardCount; i++) { - nn_strfree(ctx, state->keyboards[i]); - } - nn_destroyLock(ctx, state->lock); - nn_free(ctx, state->pixels, sizeof(ncl_ScreenPixel) * state->conf.maxWidth * state->conf.maxHeight); - nn_free(ctx, state->palette, sizeof(int) * state->conf.paletteColors); - nn_free(ctx, state->resolvedPalette, sizeof(int) * state->conf.paletteColors); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } + if(req->action == NN_SCREEN_DROP) { + for(size_t i = 0; i < st->keyboardCount; i++) + nn_strfree(ctx, st->keyboards[i]); + nn_destroyLock(ctx, st->lock); + nn_free(ctx, st->pixels, + sizeof(ncl_ScreenPixel) + * st->conf.maxWidth * st->conf.maxHeight); + nn_free(ctx, st->palette, + sizeof(int) * st->conf.paletteColors); + nn_free(ctx, st->resolvedPalette, + sizeof(int) * st->conf.paletteColors); + nn_free(ctx, st, sizeof(*st)); + return NN_OK; + } + if(req->action == NN_SCREEN_ISON) { + nn_lock(ctx, st->lock); + req->power.isOn = + (st->flags & NCL_SCREEN_ON) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_TURNON) { + nn_lock(ctx, st->lock); + bool was = (st->flags & NCL_SCREEN_ON) != 0; + st->flags |= NCL_SCREEN_ON; + req->power.wasOn = !was; + req->power.isOn = true; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_TURNOFF) { + nn_lock(ctx, st->lock); + bool was = (st->flags & NCL_SCREEN_ON) != 0; + st->flags &= ~NCL_SCREEN_ON; + req->power.wasOn = was; + req->power.isOn = false; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_GETASPECTRATIO) { + // single-block screen + req->aspect.w = 1; + req->aspect.h = 1; + return NN_OK; + } + if(req->action == NN_SCREEN_GETKEYBOARDS) { + nn_lock(ctx, st->lock); + for(size_t i = 0; i < st->keyboardCount; i++) + nn_pushstring(C, st->keyboards[i]); + req->kbCount = st->keyboardCount; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_SETPRECISE) { + nn_lock(ctx, st->lock); + if(req->flag) + st->flags |= NCL_SCREEN_PRECISE; + else + st->flags &= ~NCL_SCREEN_PRECISE; + req->flag = + (st->flags & NCL_SCREEN_PRECISE) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_ISPRECISE) { + nn_lock(ctx, st->lock); + req->flag = + (st->flags & NCL_SCREEN_PRECISE) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_SETTOUCHINVERTED) { + nn_lock(ctx, st->lock); + if(req->flag) + st->flags |= NCL_SCREEN_TOUCHINVERTED; + else + st->flags &= ~NCL_SCREEN_TOUCHINVERTED; + req->flag = + (st->flags & NCL_SCREEN_TOUCHINVERTED) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + if(req->action == NN_SCREEN_ISTOUCHINVERTED) { + nn_lock(ctx, st->lock); + req->flag = + (st->flags & NCL_SCREEN_TOUCHINVERTED) != 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } - if(C) nn_setError(C, "ncl-screen: not implemented yet"); - return NN_EBADCALL; + if(C) nn_setError(C, "ncl-screen: bad action"); + return NN_EBADCALL; } nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config) { @@ -1196,6 +1273,7 @@ static ncl_ScreenState *ncl_getBoundScreen(ncl_GPUState *gpu, nn_Computer *C) { return nn_getComponentState(c); } +/* static void ncl_getGPULimits(ncl_GPUState *gpu, nn_Computer *C, int *maxWidth, int *maxHeight, char *maxDepth) { int w = gpu->conf.maxWidth, h = gpu->conf.maxHeight; char d = gpu->conf.maxDepth; @@ -1212,64 +1290,854 @@ static void ncl_getGPULimits(ncl_GPUState *gpu, nn_Computer *C, int *maxWidth, i *maxHeight = h; *maxDepth = d; } +*/ -static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { - nn_Context *ctx = req->ctx; - nn_Computer *C = req->computer; - ncl_GPUState *state = req->state; - const nn_GPU *gpu = req->gpu; - if(req->action == NN_GPU_DROP) { - for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { - if(state->vram[i] != NULL) ncl_freeVRAM(ctx, state->vram[i]); - } - if(state->screenAddress != NULL) nn_strfree(ctx, state->screenAddress); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } - if(C != NULL) nn_setError(C, "ncl-gpu: not implemented yet"); - return NN_EBADCALL; +static void ncl_getGPULimitsWithScreen( + ncl_GPUState *gpu, ncl_ScreenState *screen, + int *maxWidth, int *maxHeight, char *maxDepth) +{ + int w = gpu->conf.maxWidth; + int h = gpu->conf.maxHeight; + char d = gpu->conf.maxDepth; + if(screen != NULL) { + if(w > screen->conf.maxWidth) + w = screen->conf.maxWidth; + if(h > screen->conf.maxHeight) + h = screen->conf.maxHeight; + if(d > screen->conf.maxDepth) + d = screen->conf.maxDepth; + } + *maxWidth = w; + *maxHeight = h; + *maxDepth = d; } -nn_Component *ncl_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu) { - nn_Context *ctx = nn_getUniverseContext(universe); - nn_Lock *lock = NULL; - ncl_GPUState *state = NULL; - nn_Component *c = NULL; +// helper: get the target buffer for the active index. +// Returns NULL + sets error on failure. +// If index==0 and screen is non-NULL, caller must use +// the screen pixel functions instead. +static ncl_VRAMBuf *ncl_getVRAMBuf( + ncl_GPUState *st, int idx, nn_Computer *C) +{ + if(idx <= 0 || idx >= NCL_MAX_VRAMBUF) { + if(C) nn_setError(C, "invalid buffer index"); + return NULL; + } + ncl_VRAMBuf *b = st->vram[idx]; + if(b == NULL && C) + nn_setError(C, "no such buffer"); + return b; +} - lock = nn_createLock(ctx); - if(lock == NULL) goto fail; +static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { + nn_Context *ctx = req->ctx; + nn_Computer *C = req->computer; + ncl_GPUState *st = req->state; - state = nn_alloc(ctx, sizeof(*state)); - if(state == NULL) goto fail; + if(req->action == NN_GPU_DROP) { + for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] != NULL) + ncl_freeVRAM(ctx, st->vram[i]); + } + if(st->screenAddress != NULL) + nn_strfree(ctx, st->screenAddress); + nn_destroyLock(ctx, st->lock); + nn_free(ctx, st, sizeof(*st)); + return NN_OK; + } - state->ctx = ctx; - state->lock = lock; - state->conf = *gpu; - state->vramFree = gpu->totalVRAM; - state->screenAddress = NULL; - state->currentFg = 0xFFFFFF; - state->currentBg = 0x000000; - state->activeBuffer = 0; - state->isFgPalette = false; - state->isBgPalette = false; - for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { - state->vram[i] = NULL; - } + // bind + if(req->action == NN_GPU_BIND) { + nn_Component *sc = + nn_getComponent(C, req->bind.address); + if(sc == NULL) { + nn_setError(C, "no such component"); + return NN_EBADCALL; + } + const char *tid = nn_getComponentTypeID(sc); + if(strcmp(tid, NCL_SCREEN) != 0) { + nn_setError(C, "not a screen"); + return NN_EBADCALL; + } + nn_lock(ctx, st->lock); + if(st->screenAddress != NULL) + nn_strfree(ctx, st->screenAddress); + st->screenAddress = + nn_strdup(ctx, req->bind.address); + nn_unlock(ctx, st->lock); - c = nn_createGPU(universe, address, gpu, state, ncl_gpuHandler); - if(c == NULL) goto fail; + if(req->bind.reset) { + ncl_ScreenState *scr = + nn_getComponentState(sc); + ncl_lockScreen(scr); + ncl_resetScreen(scr); + ncl_unlockScreen(scr); + } + return NN_OK; + } + // getScreen + if(req->action == NN_GPU_GETSCREEN) { + nn_lock(ctx, st->lock); + if(st->screenAddress != NULL) { + size_t len = strlen(st->screenAddress); + if(len >= NN_MAX_ADDRESS) + len = NN_MAX_ADDRESS - 1; + memcpy(req->screenAddr, + st->screenAddress, len); + req->screenAddr[len] = '\0'; + } + nn_unlock(ctx, st->lock); + return NN_OK; + } + // getBackground + if(req->action == NN_GPU_GETBG) { + nn_lock(ctx, st->lock); + req->color.color = st->currentBg; + req->color.isPalette = st->isBgPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // setBackground + if(req->action == NN_GPU_SETBG) { + nn_lock(ctx, st->lock); + req->color.oldColor = st->currentBg; + req->color.wasPalette = st->isBgPalette; + req->color.oldPaletteIdx = + st->isBgPalette ? st->currentBg : -1; + st->currentBg = req->color.color; + st->isBgPalette = req->color.isPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // getForeground + if(req->action == NN_GPU_GETFG) { + nn_lock(ctx, st->lock); + req->color.color = st->currentFg; + req->color.isPalette = st->isFgPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // setForeground + if(req->action == NN_GPU_SETFG) { + nn_lock(ctx, st->lock); + req->color.oldColor = st->currentFg; + req->color.wasPalette = st->isFgPalette; + req->color.oldPaletteIdx = + st->isFgPalette ? st->currentFg : -1; + st->currentFg = req->color.color; + st->isFgPalette = req->color.isPalette; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // getPaletteColor + if(req->action == NN_GPU_GETPALETTE) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + int idx = req->palette.index; + if(idx < 0 || idx >= scr->conf.paletteColors) { + ncl_unlockScreen(scr); + nn_setError(C, "invalid palette index"); + return NN_EBADCALL; + } + req->palette.color = scr->palette[idx]; + ncl_unlockScreen(scr); + return NN_OK; + } + // setPaletteColor + if(req->action == NN_GPU_SETPALETTE) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + int idx = req->palette.index; + if(idx < 0 + || idx >= scr->conf.editableColors) { + ncl_unlockScreen(scr); + nn_setError(C, "invalid palette index"); + return NN_EBADCALL; + } + req->palette.oldColor = scr->palette[idx]; + scr->palette[idx] = req->palette.color; + scr->resolvedPalette[idx] = + nn_mapDepth(req->palette.color, scr->depth); + ncl_unlockScreen(scr); + return NN_OK; + } + // maxDepth + if(req->action == NN_GPU_MAXDEPTH) { + nn_lock(ctx, st->lock); + char d = st->conf.maxDepth; + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr != NULL) { + ncl_lockScreen(scr); + if(scr->conf.maxDepth < d) + d = scr->conf.maxDepth; + ncl_unlockScreen(scr); + } + req->depth.depth = d; + return NN_OK; + } + // getDepth + if(req->action == NN_GPU_GETDEPTH) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + req->depth.depth = st->conf.maxDepth; + return NN_OK; + } + ncl_lockScreen(scr); + req->depth.depth = scr->depth; + ncl_unlockScreen(scr); + return NN_OK; + } + // setDepth + if(req->action == NN_GPU_SETDEPTH) { + char want = req->depth.depth; + if(nn_depthName(want) == NULL) { + nn_setError(C, "unsupported depth"); + return NN_EBADCALL; + } + nn_lock(ctx, st->lock); + int maxD = st->conf.maxDepth; + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + if(scr->conf.maxDepth < maxD) + maxD = scr->conf.maxDepth; + if(want > maxD) { + ncl_unlockScreen(scr); + nn_setError(C, "unsupported depth"); + return NN_EBADCALL; + } + req->depth.oldDepth = scr->depth; + scr->depth = want; + ncl_recomputeScreen(scr); + ncl_unlockScreen(scr); + return NN_OK; + } + // maxResolution + if(req->action == NN_GPU_MAXRES) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + int w, h; char d; + if(scr != NULL) ncl_lockScreen(scr); + ncl_getGPULimitsWithScreen( + st, scr, &w, &h, &d); + if(scr != NULL) ncl_unlockScreen(scr); + req->resolution.width = w; + req->resolution.height = h; + return NN_OK; + } + // getResolution + if(req->action == NN_GPU_GETRES) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + req->resolution.width = scr->width; + req->resolution.height = scr->height; + ncl_unlockScreen(scr); + return NN_OK; + } + // setResolution + if(req->action == NN_GPU_SETRES) { + int w = req->resolution.width; + int h = req->resolution.height; + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + int maxW, maxH; char maxD; + if(scr != NULL) ncl_lockScreen(scr); + ncl_getGPULimitsWithScreen( + st, scr, &maxW, &maxH, &maxD); + if(w < 1 || h < 1 + || w > maxW || h > maxH) { + if(scr != NULL) ncl_unlockScreen(scr); + nn_setError(C, "unsupported resolution"); + return NN_EBADCALL; + } + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + scr->width = w; + scr->height = h; + if(scr->viewportWidth > w) + scr->viewportWidth = w; + if(scr->viewportHeight > h) + scr->viewportHeight = h; + ncl_unlockScreen(scr); + return NN_OK; + } + // getViewport + if(req->action == NN_GPU_GETVIEWPORT) { + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + req->resolution.width = scr->viewportWidth; + req->resolution.height = scr->viewportHeight; + ncl_unlockScreen(scr); + return NN_OK; + } + // setViewport + if(req->action == NN_GPU_SETVIEWPORT) { + int w = req->resolution.width; + int h = req->resolution.height; + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + if(w < 1 || h < 1 + || w > scr->width || h > scr->height) { + ncl_unlockScreen(scr); + nn_setError(C, + "viewport exceeds resolution"); + return NN_EBADCALL; + } + scr->viewportWidth = w; + scr->viewportHeight = h; + ncl_unlockScreen(scr); + return NN_OK; + } + // get + if(req->action == NN_GPU_GET) { + nn_lock(ctx, st->lock); + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); - if(nn_setComponentTypeID(c, NCL_GPU)) goto fail; + ncl_ScreenPixel px; + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + px = ncl_getRealScreenPixel( + scr, req->get.x, req->get.y); + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + px = ncl_vramGet( + b, req->get.x, req->get.y); + nn_unlock(ctx, st->lock); + } + req->get.codepoint = px.codepoint; + req->get.fg = px.storedFg; + req->get.bg = px.storedBg; + req->get.fgIdx = (px.realFg < 0) + ? px.storedFg : -1; + req->get.bgIdx = (px.realBg < 0) + ? px.storedBg : -1; + return NN_OK; + } + // set + if(req->action == NN_GPU_SET) { + nn_lock(ctx, st->lock); + int fg = st->currentFg; + int bg = st->currentBg; + bool fgP = st->isFgPalette; + bool bgP = st->isBgPalette; + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); + + int x = req->set.x, y = req->set.y; + const char *s = req->set.value; + size_t len = req->set.len; + bool vert = req->set.vertical; + + ncl_ScreenPixel px; + px.storedFg = fg; + px.storedBg = bg; + px.realFg = fgP ? -1 : fg; + px.realBg = bgP ? -1 : bg; + + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + // depth-map direct colors + if(!fgP) px.realFg = + nn_mapDepth(fg, scr->depth); + if(!bgP) px.realBg = + nn_mapDepth(bg, scr->depth); + size_t i = 0; + while(i < len) { + size_t cw = + nn_unicode_validateFirstChar( + s + i, len - i); + if(cw == 0) { cw = 1; px.codepoint = + (unsigned char)s[i]; } + else px.codepoint = + nn_unicode_firstCodepoint(s + i); + ncl_setRealScreenPixel( + scr, x, y, px); + i += cw; + if(vert) y++; else x++; + } + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + size_t i = 0; + while(i < len) { + size_t cw = + nn_unicode_validateFirstChar( + s + i, len - i); + if(cw == 0) { cw = 1; px.codepoint = + (unsigned char)s[i]; } + else px.codepoint = + nn_unicode_firstCodepoint(s + i); + ncl_vramSet(b, x, y, px); + i += cw; + if(vert) y++; else x++; + } + nn_unlock(ctx, st->lock); + } + return NN_OK; + } + // copy + if(req->action == NN_GPU_COPY) { + int sx = req->copy.x, sy = req->copy.y; + int w = req->copy.w, h = req->copy.h; + int tx = req->copy.tx, ty = req->copy.ty; + + nn_lock(ctx, st->lock); + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); + + // overlap-safe iteration order + int y0 = (ty > 0) ? sy+h-1 : sy; + int y1 = (ty > 0) ? sy-1 : sy+h; + int dy = (ty > 0) ? -1 : 1; + int x0 = (tx > 0) ? sx+w-1 : sx; + int x1 = (tx > 0) ? sx-1 : sx+w; + int dx = (tx > 0) ? -1 : 1; + + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + for(int y = y0; y != y1; y += dy) + for(int x = x0; x != x1; x += dx) { + ncl_ScreenPixel p = + ncl_getRealScreenPixel( + scr, x, y); + ncl_setRealScreenPixel( + scr, x+tx, y+ty, p); + } + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + for(int y = y0; y != y1; y += dy) + for(int x = x0; x != x1; x += dx) { + ncl_ScreenPixel p = + ncl_vramGet(b, x, y); + ncl_vramSet(b, x+tx, y+ty, p); + } + nn_unlock(ctx, st->lock); + } + return NN_OK; + } + // fill + if(req->action == NN_GPU_FILL) { + nn_lock(ctx, st->lock); + int fg = st->currentFg; + int bg = st->currentBg; + bool fgP = st->isFgPalette; + bool bgP = st->isBgPalette; + int active = st->activeBuffer; + ncl_ScreenState *scr = + (active == 0) ? + ncl_getBoundScreen(st, C) : NULL; + nn_unlock(ctx, st->lock); + + ncl_ScreenPixel px; + px.codepoint = req->fill.codepoint; + px.storedFg = fg; + px.storedBg = bg; + px.realFg = fgP ? -1 : fg; + px.realBg = bgP ? -1 : bg; + + int x0 = req->fill.x, y0 = req->fill.y; + int w = req->fill.w, h = req->fill.h; + + if(active == 0) { + if(scr == NULL) { + nn_setError(C, "no screen"); + return NN_EBADCALL; + } + ncl_lockScreen(scr); + if(!fgP) px.realFg = + nn_mapDepth(fg, scr->depth); + if(!bgP) px.realBg = + nn_mapDepth(bg, scr->depth); + for(int y = y0; y < y0+h; y++) + for(int x = x0; x < x0+w; x++) + ncl_setRealScreenPixel( + scr, x, y, px); + ncl_unlockScreen(scr); + } else { + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, active, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + for(int y = y0; y < y0+h; y++) + for(int x = x0; x < x0+w; x++) + ncl_vramSet(b, x, y, px); + nn_unlock(ctx, st->lock); + } + return NN_OK; + } + // VRAM: getActiveBuffer + if(req->action == NN_GPU_GETACTIVEBUF) { + nn_lock(ctx, st->lock); + req->buffer.index = st->activeBuffer; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: setActiveBuffer + if(req->action == NN_GPU_SETACTIVEBUF) { + int idx = req->buffer.index; + nn_lock(ctx, st->lock); + if(idx != 0 && (idx < 0 + || idx >= NCL_MAX_VRAMBUF + || st->vram[idx] == NULL)) { + nn_unlock(ctx, st->lock); + nn_setError(C, "invalid buffer index"); + return NN_EBADCALL; + } + st->activeBuffer = idx; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: buffers + if(req->action == NN_GPU_BUFFERS) { + nn_lock(ctx, st->lock); + size_t count = 0; + for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] != NULL) { + nn_pushinteger(C, i); + count++; + } + } + nn_unlock(ctx, st->lock); + req->bufCount = count; + return NN_OK; + } + // VRAM: allocateBuffer + if(req->action == NN_GPU_ALLOCBUF) { + int w = req->allocBuf.w; + int h = req->allocBuf.h; + // default to current GPU max if 0 + if(w <= 0) w = st->conf.maxWidth; + if(h <= 0) h = st->conf.maxHeight; + size_t cost = (size_t)w * h; + nn_lock(ctx, st->lock); + if(cost > st->vramFree) { + nn_unlock(ctx, st->lock); + nn_setError(C, "not enough video memory"); + return NN_EBADCALL; + } + int slot = -1; + for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] == NULL) { + slot = i; + break; + } + } + if(slot < 0) { + nn_unlock(ctx, st->lock); + nn_setError(C, "too many buffers"); + return NN_EBADCALL; + } + ncl_VRAMBuf *b = ncl_allocVRAM(ctx, w, h); + if(b == NULL) { + nn_unlock(ctx, st->lock); + nn_setError(C, "allocation failed"); + return NN_EBADCALL; + } + st->vram[slot] = b; + st->vramFree -= cost; + nn_unlock(ctx, st->lock); + req->allocBuf.index = slot; + return NN_OK; + } + // VRAM: freeBuffer + if(req->action == NN_GPU_FREEBUF) { + int idx = req->buffer.index; + if(idx <= 0 || idx >= NCL_MAX_VRAMBUF) { + nn_setError(C, "invalid buffer index"); + return NN_EBADCALL; + } + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = st->vram[idx]; + if(b == NULL) { + nn_unlock(ctx, st->lock); + nn_setError(C, "no such buffer"); + return NN_EBADCALL; + } + st->vramFree += (size_t)b->width * b->height; + st->vram[idx] = NULL; + if(st->activeBuffer == idx) + st->activeBuffer = 0; + nn_unlock(ctx, st->lock); + ncl_freeVRAM(ctx, b); + return NN_OK; + } + // VRAM: freeAllBuffers + if(req->action == NN_GPU_FREEALLBUFS) { + nn_lock(ctx, st->lock); + for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { + if(st->vram[i] != NULL) { + st->vramFree += + (size_t)st->vram[i]->width + * st->vram[i]->height; + ncl_freeVRAM(ctx, st->vram[i]); + st->vram[i] = NULL; + } + } + st->activeBuffer = 0; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: freeMemory + if(req->action == NN_GPU_FREEMEM) { + nn_lock(ctx, st->lock); + req->memory = st->vramFree; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: getBufferSize + if(req->action == NN_GPU_GETBUFSIZE) { + int idx = req->bufSize.index; + if(idx == 0) { + // return screen resolution + nn_lock(ctx, st->lock); + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + nn_unlock(ctx, st->lock); + if(scr == NULL) { + req->bufSize.w = 0; + req->bufSize.h = 0; + return NN_OK; + } + ncl_lockScreen(scr); + req->bufSize.w = scr->width; + req->bufSize.h = scr->height; + ncl_unlockScreen(scr); + return NN_OK; + } + nn_lock(ctx, st->lock); + ncl_VRAMBuf *b = + ncl_getVRAMBuf(st, idx, C); + if(b == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + req->bufSize.w = b->width; + req->bufSize.h = b->height; + nn_unlock(ctx, st->lock); + return NN_OK; + } + // VRAM: bitblt + if(req->action == NN_GPU_BITBLT) { + int dstI = req->bitblt.dst; + int srcI = req->bitblt.src; + int col = req->bitblt.col; + int row = req->bitblt.row; + int w = req->bitblt.w; + int h = req->bitblt.h; + int fc = req->bitblt.fromCol; + int fr = req->bitblt.fromRow; + + nn_lock(ctx, st->lock); + if(srcI == 0 && st->activeBuffer != 0) + srcI = st->activeBuffer; + ncl_ScreenState *scr = + ncl_getBoundScreen(st, C); + + ncl_VRAMBuf *sb = NULL, *db = NULL; + if(srcI != 0) { + sb = ncl_getVRAMBuf(st, srcI, C); + if(sb == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + if(w == 0) w = sb->width; + if(h == 0) h = sb->height; + } + if(dstI != 0) { + db = ncl_getVRAMBuf(st, dstI, C); + if(db == NULL) { + nn_unlock(ctx, st->lock); + return NN_EBADCALL; + } + } + nn_unlock(ctx, st->lock); + + // Lock both resources once for the + // entire blit, not per-pixel. + bool needScreen = + (srcI == 0 || dstI == 0); + if(needScreen && scr != NULL) + ncl_lockScreen(scr); + if(sb != NULL || db != NULL) + nn_lock(ctx, st->lock); + + for(int y = 0; y < h; y++) { + for(int x = 0; x < w; x++) { + ncl_ScreenPixel p; + int rx = fc + x, ry = fr + y; + if(srcI == 0) { + if(scr == NULL) continue; + p = ncl_getRealScreenPixel( + scr, rx, ry); + } else { + p = ncl_vramGet(sb, rx, ry); + } + int wx = col + x, wy = row + y; + if(dstI == 0) { + if(scr == NULL) continue; + p.realFg = nn_mapDepth( + p.storedFg, scr->depth); + p.realBg = nn_mapDepth( + p.storedBg, scr->depth); + ncl_setRealScreenPixel( + scr, wx, wy, p); + } else { + ncl_vramSet(db, wx, wy, p); + } + } + } + + if(sb != NULL || db != NULL) + nn_unlock(ctx, st->lock); + if(needScreen && scr != NULL) + ncl_unlockScreen(scr); + return NN_OK; + } + + if(C) nn_setError(C, + "ncl-gpu: not implemented"); + return NN_EBADCALL; +} + +nn_Component *ncl_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu) +{ + nn_Context *ctx = nn_getUniverseContext(universe); + nn_Lock *lock = NULL; + ncl_GPUState *state = NULL; + nn_Component *c = NULL; + + lock = nn_createLock(ctx); + if(lock == NULL) goto fail; + + state = nn_alloc(ctx, sizeof(*state)); + if(state == NULL) goto fail; + + state->ctx = ctx; + state->lock = lock; + state->conf = *gpu; + state->vramFree = gpu->totalVRAM; + state->screenAddress = NULL; + state->currentFg = 0xFFFFFF; + state->currentBg = 0x000000; + state->activeBuffer = 0; + state->isFgPalette = false; + state->isBgPalette = false; + for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) + state->vram[i] = NULL; + + c = nn_createGPU(universe, address, + gpu, state, ncl_gpuHandler); + if(c == NULL) goto fail; + + if(nn_setComponentTypeID(c, NCL_GPU)) goto fail; + return c; - return c; fail: - if(c != NULL) { - nn_dropComponent(c); - return NULL; - } - if(lock != NULL) nn_destroyLock(ctx, lock); - nn_free(ctx, state, sizeof(*state)); - return NULL; + if(c != NULL) { + nn_dropComponent(c); + return NULL; + } + if(lock != NULL) nn_destroyLock(ctx, lock); + nn_free(ctx, state, sizeof(*state)); + return NULL; } void ncl_lockScreen(ncl_ScreenState *state) { @@ -1349,10 +2217,65 @@ void ncl_setScreenDepth(ncl_ScreenState *state, char depth) { state->depth = depth; } -nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, const char *keyboardAddress); -void ncl_unmountKeyboard(ncl_ScreenState *state, const char *keyboardAddress); -bool ncl_hasKeyboard(ncl_ScreenState *state, const char *keyboardAddress); -const char *ncl_getKeyboard(ncl_ScreenState *state, size_t idx); +nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, + const char *keyboardAddress) +{ + nn_lock(state->ctx, state->lock); + if(state->keyboardCount >= NCL_MAX_KEYBOARD) { + nn_unlock(state->ctx, state->lock); + return NN_ELIMIT; + } + char *addr = nn_strdup( + state->ctx, keyboardAddress); + if(addr == NULL) { + nn_unlock(state->ctx, state->lock); + return NN_ENOMEM; + } + state->keyboards[state->keyboardCount++] = addr; + nn_unlock(state->ctx, state->lock); + return NN_OK; +} + +void ncl_unmountKeyboard(ncl_ScreenState *state, + const char *keyboardAddress) +{ + nn_lock(state->ctx, state->lock); + size_t j = 0; + for(size_t i = 0; i < state->keyboardCount; i++) { + if(strcmp(state->keyboards[i], + keyboardAddress) == 0) { + nn_strfree(state->ctx, + state->keyboards[i]); + } else { + state->keyboards[j++] = + state->keyboards[i]; + } + } + state->keyboardCount = j; + nn_unlock(state->ctx, state->lock); +} + +bool ncl_hasKeyboard(ncl_ScreenState *state, + const char *keyboardAddress) +{ + nn_lock(state->ctx, state->lock); + for(size_t i = 0; i < state->keyboardCount; i++) { + if(strcmp(state->keyboards[i], + keyboardAddress) == 0) { + nn_unlock(state->ctx, state->lock); + return true; + } + } + nn_unlock(state->ctx, state->lock); + return false; +} + +const char *ncl_getKeyboard(ncl_ScreenState *state, + size_t idx) +{ + if(idx >= state->keyboardCount) return NULL; + return state->keyboards[idx]; +} // general stuff diff --git a/src/neonucleus.c b/src/neonucleus.c index b97c897..77526bc 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -520,16 +520,31 @@ static void *nn_defaultAlloc(void *_, void *memory, size_t oldSize, size_t newSi static double nn_defaultTime(void *_) { #ifndef NN_BAREMETAL #ifdef NN_POSIX - struct timespec s; - if(clock_gettime(CLOCK_REALTIME, &s)) return 0; - return s.tv_sec + (double)s.tv_nsec / 1000000000; + struct timespec s; + if(clock_gettime(CLOCK_REALTIME, &s)) return 0; + return s.tv_sec + (double)s.tv_nsec / 1000000000; +#elif defined(NN_WINDOWS) + // Without this, nn_defaultTime returns 0 on Windows. + // This breaks nn_getUptime(), which is (currentTime - creationTimestamp). + // If currentTime is always 0, uptime is always negative or 0. + // OpenOS relies on computer.uptime() for all timeouts: + // computer.pullSignal(timeout) compares computer.uptime() against a deadline. + // If uptime never advances, pullSignal(2) returns instantly instead of waiting. + // To verify: in OpenOS, run `lua -e "print(computer.uptime())"`. + // Before fix: always prints 0 or a tiny constant. + // After fix: prints seconds since boot, increasing each call. + // QueryPerformanceCounter is the highest-resolution monotonic clock on Windows. + // It is available since Windows 2000 and cannot fail on Vista+. + LARGE_INTEGER freq, count; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + return (double)count.QuadPart + / (double)freq.QuadPart; #else - // time does not exist... yet! - return 0; + return 0; #endif #else - // time does not exist - return 0; + return 0; #endif } @@ -542,6 +557,26 @@ static size_t nn_defaultRng(void *_) { #endif } +#ifdef NN_WINDOWS +// rand_s() requires _CRT_RAND_S defined before . +// However, we can avoid that dependency by using the Win32 +// API directly. RtlGenRandom (aka SystemFunction036) is +// available on all Windows versions since XP and does not +// require linking any extra library it lives in advapi32 +// which is always implicitly linked. +// It fills a buffer with cryptographically strong random bytes. +// To verify: print nn_rand() in a loop on Windows. +BOOLEAN NTAPI SystemFunction036( + PVOID RandomBuffer, ULONG RandomBufferLength); +#pragma comment(lib, "advapi32") + +static size_t nn_windowsRng(void *_) { + unsigned int v = 0; + SystemFunction036(&v, sizeof(v)); + return v; +} +#endif + static void nn_defaultLock(void *state, nn_LockRequest *req) { (void)state; #ifndef NN_BAREMETAL @@ -605,41 +640,68 @@ static void nn_defaultLock(void *state, nn_LockRequest *req) { return; } #elif defined(NN_THREAD_WINDOWS) - switch(req->action) { - case NN_LOCK_CREATE:; - req->lock = CreateMutex(NULL, FALSE, NULL); - return; // don't fall into destroy - case NN_LOCK_DESTROY:; - CloseHandle(req->lock); - return; - case NN_LOCK_LOCK:; - WaitForSingleObject(req->lock, INFINITE); - return; - case NN_LOCK_UNLOCK:; - ReleaseMutex(req->lock); - return; - } + // The original code used CreateMutex/WaitForSingleObject/ReleaseMutex. + // Windows Mutexes are kernel objects: every lock/unlock is a syscall + // into the NT kernel (NtWaitForSingleObject / NtReleaseMutant), + // even when uncontended. They also support cross-process sharing + // and abandonment detection, none of which NN needs. + // + // CRITICAL_SECTION is a user-mode construct that uses a spinlock + // with a kernel fallback (only enters kernel on actual contention). + // For uncontended locks (the common case), EnterCriticalSection + // is just an interlocked compare-exchange so no syscall at all. + switch(req->action) { + case NN_LOCK_CREATE:; + CRITICAL_SECTION *cs = + malloc(sizeof(CRITICAL_SECTION)); + if(cs == NULL) { req->lock = NULL; return; } + InitializeCriticalSection(cs); + req->lock = cs; + return; + case NN_LOCK_DESTROY:; + DeleteCriticalSection(req->lock); + free(req->lock); + return; + case NN_LOCK_LOCK:; + EnterCriticalSection(req->lock); + return; + case NN_LOCK_UNLOCK:; + LeaveCriticalSection(req->lock); + return; + } #endif #endif } void nn_initContext(nn_Context *ctx) { - ctx->state = NULL; - ctx->alloc = nn_defaultAlloc; - ctx->time = nn_defaultTime; + ctx->state = NULL; + ctx->alloc = nn_defaultAlloc; + ctx->time = nn_defaultTime; #ifndef NN_BAREMETAL - // someone pointed out that running this multiple times - // in 1 second can cause the RNG to loop. - // However, if you call this function multiple times at all, - // that's on you. - srand(time(NULL)); - ctx->rngMaximum = RAND_MAX; + // RAND_MAX on MSVC is 32767 (15 bits), and nn_randomUUID() calls nn_rand() & 0xF per hex digit, + // but with only 15 bits total per rand() call, the UUIDs have very low entropy. nn_randf() divides by + // (rngMaximum+1), so 15 bits gives ~0.00003 granularity instead of ~0.0000000005 with 31 bits. + // To verify: generate 1000 UUIDs, check for duplicates + // or patterns. With 15 bits of entropy per call and + // 32 hex digits, collisions become plausible. + // On Windows we use rand_s() which returns a full + // 32-bit cryptographic random number without needing + // srand(). On POSIX, rand()+srand() remains fine as + // RAND_MAX is typically 2^31-1. +#ifdef NN_WINDOWS + ctx->rngMaximum = UINT_MAX; + ctx->rng = nn_windowsRng; #else - ctx->rngMaximum = 1; + srand(time(NULL)); + ctx->rngMaximum = RAND_MAX; + ctx->rng = nn_defaultRng; #endif - ctx->rng = nn_defaultRng; - ctx->lock = nn_defaultLock; +#else + ctx->rngMaximum = 1; + ctx->rng = nn_defaultRng; +#endif + ctx->lock = nn_defaultLock; } // some util data structures @@ -1605,20 +1667,23 @@ void nn_getComponentMethods(nn_Component *c, const char **methodnames, size_t *l *len = enabled; } -bool nn_hasComponentMethod(nn_Component *c, const char *method) { - nn_MethodEntry *ent = nn_getComponentMethodEntry(c, method); - if(ent == NULL) return false; +bool nn_hasComponentMethod(nn_Component *c, + const char *method) +{ + nn_MethodEntry *ent = + nn_getComponentMethodEntry(c, method); + if(ent == NULL) return false; - nn_ComponentRequest req; - req.ctx = &c->universe->ctx; - req.computer = NULL; - req.state = c->state; - req.action = NN_COMP_CHECKMETHOD; - req.methodIdx = ent->idx; - // by default, yes - req.methodEnabled = true; - c->handler(&req); - return req.methodEnabled; + nn_ComponentRequest req; + req.ctx = &c->universe->ctx; + req.computer = NULL; + req.state = c->state; + req.classState = c->classState; // Don't remove it. It segfaults. + req.action = NN_COMP_CHECKMETHOD; + req.methodIdx = ent->idx; + req.methodEnabled = true; + c->handler(&req); + return req.methodEnabled; } const char *nn_getComponentDoc(nn_Component *c, const char *method) { @@ -3903,98 +3968,900 @@ nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const n return c; } -typedef struct nn_ScreenState { - nn_Context *ctx; - nn_ScreenConfig scrconf; - nn_ScreenHandler *handler; -} nn_ScreenState; +typedef enum nn_ScreenNum { + NN_SCRNUM_ISON, + NN_SCRNUM_TURNON, + NN_SCRNUM_TURNOFF, + NN_SCRNUM_GETASPECTRATIO, + NN_SCRNUM_GETKEYBOARDS, + NN_SCRNUM_SETPRECISE, + NN_SCRNUM_ISPRECISE, + NN_SCRNUM_SETTOUCHINVERTED, + NN_SCRNUM_ISTOUCHINVERTED, + NN_SCRNUM_COUNT, +} nn_ScreenNum; + +typedef struct nn_ScreenClassState { + nn_Context *ctx; + nn_ScreenConfig scrconf; + nn_ScreenHandler *handler; +} nn_ScreenClassState; static nn_Exit nn_screenHandler(nn_ComponentRequest *req) { - if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; - if(req->action == NN_COMP_SIGNAL) return NN_OK; - nn_Context *ctx = req->ctx; - nn_ScreenState *state = req->classState; - nn_Computer *C = req->computer; - nn_ScreenRequest sreq; - sreq.ctx = ctx; - sreq.state = req->state; - sreq.computer = C; - sreq.screen = &state->scrconf; + if(req->action == NN_COMP_SIGNAL) return NN_OK; + nn_Context *ctx = req->ctx; + nn_ScreenClassState *cls = req->classState; + nn_Computer *C = req->computer; - if(req->action == NN_COMP_DROP) { - sreq.action = NN_SCREEN_DROP; - state->handler(&sreq); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } + // Feature-gated methods + if(req->action == NN_COMP_CHECKMETHOD) { + nn_ScreenNum m = req->methodIdx; + if(m == NN_SCRNUM_SETPRECISE + || m == NN_SCRNUM_ISPRECISE) + req->methodEnabled = + (cls->scrconf.features + & NN_SCRF_PRECISE) != 0; + if(m == NN_SCRNUM_SETTOUCHINVERTED + || m == NN_SCRNUM_ISTOUCHINVERTED) + req->methodEnabled = + (cls->scrconf.features + & NN_SCRF_TOUCHINVERTED) != 0; + return NN_OK; + } - nn_setError(C, "screen: not yet implemented"); - return NN_EBADCALL; + nn_ScreenRequest s; + s.ctx = ctx; + s.state = req->state; + s.computer = C; + s.screen = &cls->scrconf; + + if(req->action == NN_COMP_DROP) { + s.action = NN_SCREEN_DROP; + cls->handler(&s); + nn_free(ctx, cls, sizeof(*cls)); + return NN_OK; + } + + nn_ScreenNum m = req->methodIdx; + nn_Exit e = NN_OK; + + if(m == NN_SCRNUM_ISON) { + s.action = NN_SCREEN_ISON; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.power.isOn); + } + if(m == NN_SCRNUM_TURNON) { + s.action = NN_SCREEN_TURNON; + e = cls->handler(&s); + if(e) return e; + e = nn_pushbool(C, s.power.wasOn); + if(e) return e; + e = nn_pushbool(C, s.power.isOn); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + if(m == NN_SCRNUM_TURNOFF) { + s.action = NN_SCREEN_TURNOFF; + e = cls->handler(&s); + if(e) return e; + e = nn_pushbool(C, s.power.wasOn); + if(e) return e; + e = nn_pushbool(C, s.power.isOn); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + if(m == NN_SCRNUM_GETASPECTRATIO) { + s.action = NN_SCREEN_GETASPECTRATIO; + e = cls->handler(&s); + if(e) return e; + e = nn_pushinteger(C, s.aspect.w); + if(e) return e; + e = nn_pushinteger(C, s.aspect.h); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + if(m == NN_SCRNUM_GETKEYBOARDS) { + // handler pushes addresses onto C's stack + s.action = NN_SCREEN_GETKEYBOARDS; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pusharraytable(C, s.kbCount); + } + if(m == NN_SCRNUM_SETPRECISE) { + if(nn_checkboolean(C, 0, + "bad argument #1 (boolean expected)")) + return NN_EBADCALL; + s.action = NN_SCREEN_SETPRECISE; + s.flag = nn_toboolean(C, 0); + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + if(m == NN_SCRNUM_ISPRECISE) { + s.action = NN_SCREEN_ISPRECISE; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + if(m == NN_SCRNUM_SETTOUCHINVERTED) { + if(nn_checkboolean(C, 0, + "bad argument #1 (boolean expected)")) + return NN_EBADCALL; + s.action = NN_SCREEN_SETTOUCHINVERTED; + s.flag = nn_toboolean(C, 0); + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + if(m == NN_SCRNUM_ISTOUCHINVERTED) { + s.action = NN_SCREEN_ISTOUCHINVERTED; + e = cls->handler(&s); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, s.flag); + } + + nn_setError(C, "screen: not implemented"); + return NN_EBADCALL; } -nn_Component *nn_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *scrconf, void *state, nn_ScreenHandler *handler) { - nn_Component *c = nn_createComponent(universe, address, "screen"); - if(c == NULL) return NULL; - // TODO: methods - nn_Context *ctx = &universe->ctx; - nn_ScreenState *scrstate = nn_alloc(ctx, sizeof(*scrstate)); - if(scrstate == NULL) { - nn_dropComponent(c); - return NULL; - } - scrstate->ctx = ctx; - scrstate->scrconf = *scrconf; - scrstate->handler = handler; - nn_setComponentState(c, state); - nn_setComponentClassState(c, scrstate); - nn_setComponentHandler(c, nn_screenHandler); - return c; +// Replace nn_createScreen entirely: +nn_Component *nn_createScreen( + nn_Universe *universe, const char *address, + const nn_ScreenConfig *scrconf, void *state, + nn_ScreenHandler *handler) +{ + nn_Component *c = nn_createComponent( + universe, address, "screen"); + if(c == NULL) return NULL; + + const nn_Method methods[NN_SCRNUM_COUNT] = { + [NN_SCRNUM_ISON] = { + "isOn", + "function():boolean -- Screen powered?", + NN_DIRECT}, + [NN_SCRNUM_TURNON] = { + "turnOn", + "function():boolean,boolean -- Turn on", + NN_INDIRECT}, + [NN_SCRNUM_TURNOFF] = { + "turnOff", + "function():boolean,boolean -- Turn off", + NN_INDIRECT}, + [NN_SCRNUM_GETASPECTRATIO] = { + "getAspectRatio", + "function():number,number -- Block ratio", + NN_DIRECT}, + [NN_SCRNUM_GETKEYBOARDS] = { + "getKeyboards", + "function():table -- Attached keyboards", + NN_DIRECT}, + [NN_SCRNUM_SETPRECISE] = { + "setPrecise", + "function(on:boolean):boolean" + " -- High-precision mouse", + NN_DIRECT}, + [NN_SCRNUM_ISPRECISE] = { + "isPrecise", + "function():boolean -- Precision enabled?", + NN_DIRECT}, + [NN_SCRNUM_SETTOUCHINVERTED] = { + "setTouchModeInverted", + "function(on:boolean):boolean" + " -- Invert touch mode", + NN_DIRECT}, + [NN_SCRNUM_ISTOUCHINVERTED] = { + "isTouchModeInverted", + "function():boolean -- Touch inverted?", + NN_DIRECT}, + }; + + nn_Exit e = nn_setComponentMethodsArray( + c, methods, NN_SCRNUM_COUNT); + if(e) { nn_dropComponent(c); return NULL; } + + nn_Context *ctx = &universe->ctx; + nn_ScreenClassState *cls = + nn_alloc(ctx, sizeof(*cls)); + if(cls == NULL) { + nn_dropComponent(c); + return NULL; + } + cls->ctx = ctx; + cls->scrconf = *scrconf; + cls->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, cls); + nn_setComponentHandler(c, nn_screenHandler); + return c; } -typedef struct nn_GPUState { - nn_Context *ctx; - nn_GPU gpu; - nn_GPUHandler *handler; -} nn_GPUState; +typedef enum nn_GPUNum { + NN_GPUNUM_BIND, + NN_GPUNUM_GETSCREEN, + NN_GPUNUM_GETBG, + NN_GPUNUM_SETBG, + NN_GPUNUM_GETFG, + NN_GPUNUM_SETFG, + NN_GPUNUM_GETPALETTE, + NN_GPUNUM_SETPALETTE, + NN_GPUNUM_MAXDEPTH, + NN_GPUNUM_GETDEPTH, + NN_GPUNUM_SETDEPTH, + NN_GPUNUM_MAXRES, + NN_GPUNUM_GETRES, + NN_GPUNUM_SETRES, + NN_GPUNUM_GETVIEWPORT, + NN_GPUNUM_SETVIEWPORT, + NN_GPUNUM_GET, + NN_GPUNUM_SET, + NN_GPUNUM_COPY, + NN_GPUNUM_FILL, + NN_GPUNUM_GETACTIVEBUF, + NN_GPUNUM_SETACTIVEBUF, + NN_GPUNUM_BUFFERS, + NN_GPUNUM_ALLOCBUF, + NN_GPUNUM_FREEBUF, + NN_GPUNUM_FREEALLBUFS, + NN_GPUNUM_TOTALMEM, + NN_GPUNUM_FREEMEM, + NN_GPUNUM_GETBUFSIZE, + NN_GPUNUM_BITBLT, + NN_GPUNUM_COUNT, +} nn_GPUNum; + +typedef struct nn_GPUClassState { + nn_Context *ctx; + nn_GPU gpu; + nn_GPUHandler *handler; +} nn_GPUClassState; static nn_Exit nn_gpuHandler(nn_ComponentRequest *req) { - if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; - if(req->action == NN_COMP_SIGNAL) return NN_OK; - nn_Context *ctx = req->ctx; - nn_GPUState *state = req->classState; - nn_Computer *C = req->computer; - nn_GPURequest greq; - greq.ctx = ctx; - greq.state = req->state; - greq.computer = C; - greq.gpu = &state->gpu; + if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; + if(req->action == NN_COMP_SIGNAL) return NN_OK; + nn_Context *ctx = req->ctx; + nn_GPUClassState *cls = req->classState; + nn_Computer *C = req->computer; - if(req->action == NN_COMP_DROP) { - greq.action = NN_GPU_DROP; - state->handler(&greq); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } + nn_GPURequest g; + g.ctx = ctx; + g.state = req->state; + g.computer = C; + g.gpu = &cls->gpu; - nn_setError(C, "gpu: not yet implemented"); - return NN_EBADCALL; + if(req->action == NN_COMP_DROP) { + g.action = NN_GPU_DROP; + cls->handler(&g); + nn_free(ctx, cls, sizeof(*cls)); + return NN_OK; + } + + nn_GPUNum m = req->methodIdx; + nn_Exit e = NN_OK; + + // bind + if(m == NN_GPUNUM_BIND) { + if(nn_checkstring(C, 0, + "bad argument #1 (string expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 1, true); + if(e) return e; + g.action = NN_GPU_BIND; + g.bind.address = nn_tostring(C, 0); + g.bind.reset = nn_toboolean(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // getScreen + if(m == NN_GPUNUM_GETSCREEN) { + g.action = NN_GPU_GETSCREEN; + g.screenAddr[0] = '\0'; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + if(g.screenAddr[0] == '\0') + return nn_pushnull(C); + return nn_pushstring(C, g.screenAddr); + } + // getBackground + if(m == NN_GPUNUM_GETBG) { + g.action = NN_GPU_GETBG; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.color); + if(e) return e; + e = nn_pushbool(C, g.color.isPalette); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setBackground + if(m == NN_GPUNUM_SETBG) { + if(nn_checknumber(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 1, false); + if(e) return e; + g.action = NN_GPU_SETBG; + g.color.color = nn_tointeger(C, 0); + g.color.isPalette = nn_toboolean(C, 1); + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.oldColor); + if(e) return e; + req->returnCount = 1; + if(g.color.oldPaletteIdx >= 0) { + e = nn_pushinteger(C, g.color.oldPaletteIdx); + if(e) return e; + req->returnCount = 2; + } + return NN_OK; + } + // getForeground + if(m == NN_GPUNUM_GETFG) { + g.action = NN_GPU_GETFG; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.color); + if(e) return e; + e = nn_pushbool(C, g.color.isPalette); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setForeground + if(m == NN_GPUNUM_SETFG) { + if(nn_checknumber(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 1, false); + if(e) return e; + g.action = NN_GPU_SETFG; + g.color.color = nn_tointeger(C, 0); + g.color.isPalette = nn_toboolean(C, 1); + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.color.oldColor); + if(e) return e; + req->returnCount = 1; + if(g.color.oldPaletteIdx >= 0) { + e = nn_pushinteger(C, g.color.oldPaletteIdx); + if(e) return e; + req->returnCount = 2; + } + return NN_OK; + } + // getPaletteColor + if(m == NN_GPUNUM_GETPALETTE) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_GETPALETTE; + g.palette.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.palette.color); + } + // setPaletteColor + if(m == NN_GPUNUM_SETPALETTE) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETPALETTE; + g.palette.index = nn_tointeger(C, 0); + g.palette.color = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.palette.oldColor); + } + // maxDepth + if(m == NN_GPUNUM_MAXDEPTH) { + g.action = NN_GPU_MAXDEPTH; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.depth.depth); + } + // getDepth + if(m == NN_GPUNUM_GETDEPTH) { + g.action = NN_GPU_GETDEPTH; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.depth.depth); + } + // setDepth + if(m == NN_GPUNUM_SETDEPTH) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETDEPTH; + g.depth.depth = (char)nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + const char *name = nn_depthName(g.depth.oldDepth); + if(name == NULL) name = "Unknown"; + req->returnCount = 1; + return nn_pushstring(C, name); + } + // maxResolution + if(m == NN_GPUNUM_MAXRES) { + g.action = NN_GPU_MAXRES; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.resolution.width); + if(e) return e; + e = nn_pushinteger(C, g.resolution.height); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // getResolution + if(m == NN_GPUNUM_GETRES) { + g.action = NN_GPU_GETRES; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.resolution.width); + if(e) return e; + e = nn_pushinteger(C, g.resolution.height); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setResolution + if(m == NN_GPUNUM_SETRES) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETRES; + g.resolution.width = nn_tointeger(C, 0); + g.resolution.height = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + // signal is best-effort, resolution is + // already changed at this point + nn_GPURequest s = g; + s.action = NN_GPU_GETSCREEN; + s.screenAddr[0] = '\0'; + if(cls->handler(&s) == NN_OK + && s.screenAddr[0] != '\0') { + nn_pushScreenResized(C, s.screenAddr, + g.resolution.width, + g.resolution.height); + } + req->returnCount = 1; + return nn_pushbool(C, true); + } + // getViewport + if(m == NN_GPUNUM_GETVIEWPORT) { + g.action = NN_GPU_GETVIEWPORT; + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.resolution.width); + if(e) return e; + e = nn_pushinteger(C, g.resolution.height); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // setViewport + if(m == NN_GPUNUM_SETVIEWPORT) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETVIEWPORT; + g.resolution.width = nn_tointeger(C, 0); + g.resolution.height = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // get + if(m == NN_GPUNUM_GET) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_GET; + g.get.x = nn_tointeger(C, 0); + g.get.y = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + char buf[NN_MAX_UNICODE_BUFFER]; + size_t len = nn_unicode_codepointToChar( + buf, g.get.codepoint); + e = nn_pushlstring(C, buf, len); + if(e) return e; + e = nn_pushinteger(C, g.get.fg); + if(e) return e; + e = nn_pushinteger(C, g.get.bg); + if(e) return e; + req->returnCount = 3; + if(g.get.fgIdx >= 0) { + e = nn_pushinteger(C, g.get.fgIdx); + if(e) return e; + } else { + e = nn_pushnull(C); + if(e) return e; + } + if(g.get.bgIdx >= 0) { + e = nn_pushinteger(C, g.get.bgIdx); + if(e) return e; + } else { + e = nn_pushnull(C); + if(e) return e; + } + req->returnCount = 5; + return NN_OK; + } + // set + if(m == NN_GPUNUM_SET) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + if(nn_checkinteger(C, 1, + "bad argument #2 (number expected)")) + return NN_EBADCALL; + if(nn_checkstring(C, 2, + "bad argument #3 (string expected)")) + return NN_EBADCALL; + e = nn_defaultboolean(C, 3, false); + if(e) return e; + g.action = NN_GPU_SET; + g.set.x = nn_tointeger(C, 0); + g.set.y = nn_tointeger(C, 1); + g.set.value = nn_tolstring(C, 2, &g.set.len); + g.set.vertical = nn_toboolean(C, 3); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // copy + if(m == NN_GPUNUM_COPY) { + for(int i = 0; i < 6; i++) { + if(nn_checkinteger(C, i, + "bad argument (number expected)")) + return NN_EBADCALL; + } + g.action = NN_GPU_COPY; + g.copy.x = nn_tointeger(C, 0); + g.copy.y = nn_tointeger(C, 1); + g.copy.w = nn_tointeger(C, 2); + g.copy.h = nn_tointeger(C, 3); + g.copy.tx = nn_tointeger(C, 4); + g.copy.ty = nn_tointeger(C, 5); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // fill + if(m == NN_GPUNUM_FILL) { + for(int i = 0; i < 4; i++) { + if(nn_checkinteger(C, i, + "bad argument (number expected)")) + return NN_EBADCALL; + } + if(nn_checkstring(C, 4, + "bad argument #5 (string expected)")) + return NN_EBADCALL; + g.action = NN_GPU_FILL; + g.fill.x = nn_tointeger(C, 0); + g.fill.y = nn_tointeger(C, 1); + g.fill.w = nn_tointeger(C, 2); + g.fill.h = nn_tointeger(C, 3); + g.fill.codepoint = nn_unicode_firstCodepoint( + nn_tostring(C, 4)); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // VRAM: getActiveBuffer + if(m == NN_GPUNUM_GETACTIVEBUF) { + g.action = NN_GPU_GETACTIVEBUF; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.buffer.index); + } + // VRAM: setActiveBuffer + if(m == NN_GPUNUM_SETACTIVEBUF) { + if(nn_checkinteger(C, 0, + "bad argument #1 (number expected)")) + return NN_EBADCALL; + g.action = NN_GPU_SETACTIVEBUF; + g.buffer.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.buffer.index); + } + // VRAM: buffers + if(m == NN_GPUNUM_BUFFERS) { + g.action = NN_GPU_BUFFERS; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pusharraytable(C, g.bufCount); + } + // VRAM: allocateBuffer + if(m == NN_GPUNUM_ALLOCBUF) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + e = nn_defaultinteger(C, 1, 0); + if(e) return e; + g.action = NN_GPU_ALLOCBUF; + g.allocBuf.w = nn_tointeger(C, 0); + g.allocBuf.h = nn_tointeger(C, 1); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.allocBuf.index); + } + // VRAM: freeBuffer + if(m == NN_GPUNUM_FREEBUF) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + g.action = NN_GPU_FREEBUF; + g.buffer.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + // VRAM: freeAllBuffers + if(m == NN_GPUNUM_FREEALLBUFS) { + g.action = NN_GPU_FREEALLBUFS; + e = cls->handler(&g); + if(e) return e; + return NN_OK; + } + // VRAM: totalMemory + if(m == NN_GPUNUM_TOTALMEM) { + req->returnCount = 1; + return nn_pushinteger(C, cls->gpu.totalVRAM); + } + // VRAM: freeMemory + if(m == NN_GPUNUM_FREEMEM) { + g.action = NN_GPU_FREEMEM; + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushinteger(C, g.memory); + } + // VRAM: getBufferSize + if(m == NN_GPUNUM_GETBUFSIZE) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + g.action = NN_GPU_GETBUFSIZE; + g.bufSize.index = nn_tointeger(C, 0); + e = cls->handler(&g); + if(e) return e; + e = nn_pushinteger(C, g.bufSize.w); + if(e) return e; + e = nn_pushinteger(C, g.bufSize.h); + if(e) return e; + req->returnCount = 2; + return NN_OK; + } + // VRAM: bitblt + if(m == NN_GPUNUM_BITBLT) { + e = nn_defaultinteger(C, 0, 0); + if(e) return e; + for(int i = 1; i < 8; i++) { + e = nn_defaultinteger(C, i, 0); + if(e) return e; + } + g.action = NN_GPU_BITBLT; + g.bitblt.dst = nn_tointeger(C, 0); + g.bitblt.col = nn_tointeger(C, 1); + g.bitblt.row = nn_tointeger(C, 2); + g.bitblt.w = nn_tointeger(C, 3); + g.bitblt.h = nn_tointeger(C, 4); + g.bitblt.src = nn_tointeger(C, 5); + g.bitblt.fromCol = nn_tointeger(C, 6); + g.bitblt.fromRow = nn_tointeger(C, 7); + e = cls->handler(&g); + if(e) return e; + req->returnCount = 1; + return nn_pushbool(C, true); + } + + nn_setError(C, "gpu: not implemented"); + return NN_EBADCALL; } -nn_Component *nn_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu, void *state, nn_GPUHandler *handler) { - nn_Component *c = nn_createComponent(universe, address, "gpu"); - if(c == NULL) return NULL; - // TODO: methods - nn_Context *ctx = &universe->ctx; - nn_GPUState *gpustate = nn_alloc(ctx, sizeof(*gpustate)); - if(gpustate == NULL) { - nn_dropComponent(c); - return NULL; - } - gpustate->ctx = ctx; - gpustate->gpu = *gpu; - gpustate->handler = handler; - nn_setComponentState(c, state); - nn_setComponentClassState(c, gpustate); - nn_setComponentHandler(c, nn_gpuHandler); - return c; -} +// Replace nn_createGPU entirely: +nn_Component *nn_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu, void *state, + nn_GPUHandler *handler) +{ + nn_Component *c = nn_createComponent( + universe, address, "gpu"); + if(c == NULL) return NULL; + + const nn_Method methods[NN_GPUNUM_COUNT] = { + [NN_GPUNUM_BIND] = { + "bind", + "function(address:string[,reset:boolean])" + ":boolean -- Bind to screen", + NN_INDIRECT}, + [NN_GPUNUM_GETSCREEN] = { + "getScreen", + "function():string -- Bound screen address", + NN_DIRECT}, + [NN_GPUNUM_GETBG] = { + "getBackground", + "function():number,boolean -- Current bg", + NN_DIRECT}, + [NN_GPUNUM_SETBG] = { + "setBackground", + "function(color:number[,palette:boolean])" + ":number[,number] -- Set bg", + NN_DIRECT}, + [NN_GPUNUM_GETFG] = { + "getForeground", + "function():number,boolean -- Current fg", + NN_DIRECT}, + [NN_GPUNUM_SETFG] = { + "setForeground", + "function(color:number[,palette:boolean])" + ":number[,number] -- Set fg", + NN_DIRECT}, + [NN_GPUNUM_GETPALETTE] = { + "getPaletteColor", + "function(index:number):number" + " -- Get palette color", + NN_DIRECT}, + [NN_GPUNUM_SETPALETTE] = { + "setPaletteColor", + "function(index:number,value:number):number" + " -- Set palette color", + NN_DIRECT}, + [NN_GPUNUM_MAXDEPTH] = { + "maxDepth", + "function():number -- Max color depth", + NN_DIRECT}, + [NN_GPUNUM_GETDEPTH] = { + "getDepth", + "function():number -- Current depth", + NN_DIRECT}, + [NN_GPUNUM_SETDEPTH] = { + "setDepth", + "function(depth:number):string -- Set depth", + NN_DIRECT}, + [NN_GPUNUM_MAXRES] = { + "maxResolution", + "function():number,number -- Max resolution", + NN_DIRECT}, + [NN_GPUNUM_GETRES] = { + "getResolution", + "function():number,number -- Resolution", + NN_DIRECT}, + [NN_GPUNUM_SETRES] = { + "setResolution", + "function(w:number,h:number):boolean" + " -- Set resolution", + NN_DIRECT}, + [NN_GPUNUM_GETVIEWPORT] = { + "getViewport", + "function():number,number -- Viewport size", + NN_DIRECT}, + [NN_GPUNUM_SETVIEWPORT] = { + "setViewport", + "function(w:number,h:number):boolean" + " -- Set viewport", + NN_DIRECT}, + [NN_GPUNUM_GET] = { + "get", + "function(x:number,y:number):string," + "number,number,number?,number? -- Read pixel", + NN_DIRECT}, + [NN_GPUNUM_SET] = { + "set", + "function(x:number,y:number,s:string" + "[,vertical:boolean]):boolean -- Write text", + NN_DIRECT}, + [NN_GPUNUM_COPY] = { + "copy", + "function(x,y,w,h,tx,ty):boolean" + " -- Copy region", + NN_DIRECT}, + [NN_GPUNUM_FILL] = { + "fill", + "function(x,y,w,h,char):boolean" + " -- Fill region", + NN_DIRECT}, + [NN_GPUNUM_GETACTIVEBUF] = { + "getActiveBuffer", + "function():number -- Active buffer index", + NN_DIRECT}, + [NN_GPUNUM_SETACTIVEBUF] = { + "setActiveBuffer", + "function(index:number):number" + " -- Set active buffer", + NN_DIRECT}, + [NN_GPUNUM_BUFFERS] = { + "buffers", + "function():table -- List buffer indices", + NN_DIRECT}, + [NN_GPUNUM_ALLOCBUF] = { + "allocateBuffer", + "function([w:number,h:number]):number" + " -- Allocate VRAM page", + NN_DIRECT}, + [NN_GPUNUM_FREEBUF] = { + "freeBuffer", + "function([index:number]):boolean" + " -- Free VRAM page", + NN_DIRECT}, + [NN_GPUNUM_FREEALLBUFS] = { + "freeAllBuffers", + "function() -- Free all VRAM pages", + NN_DIRECT}, + [NN_GPUNUM_TOTALMEM] = { + "totalMemory", + "function():number -- Total VRAM", + NN_DIRECT}, + [NN_GPUNUM_FREEMEM] = { + "freeMemory", + "function():number -- Free VRAM", + NN_DIRECT}, + [NN_GPUNUM_GETBUFSIZE] = { + "getBufferSize", + "function([index:number]):number,number" + " -- Buffer dimensions", + NN_DIRECT}, + [NN_GPUNUM_BITBLT] = { + "bitblt", + "function([dst,col,row,w,h,src,fc,fr])" + ":boolean -- Blit between buffers", + NN_DIRECT}, + }; + + nn_Exit e = nn_setComponentMethodsArray( + c, methods, NN_GPUNUM_COUNT); + if(e) { nn_dropComponent(c); return NULL; } + + nn_Context *ctx = &universe->ctx; + nn_GPUClassState *cls = nn_alloc(ctx, sizeof(*cls)); + if(cls == NULL) { + nn_dropComponent(c); + return NULL; + } + cls->ctx = ctx; + cls->gpu = *gpu; + cls->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, cls); + nn_setComponentHandler(c, nn_gpuHandler); + return c; +} \ No newline at end of file diff --git a/src/neonucleus.h b/src/neonucleus.h index d0bdeb6..5b1a911 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -1229,24 +1229,8 @@ typedef struct nn_ScreenConfig { // OC has 3 tiers, NN adds a 4th one as well. extern const nn_ScreenConfig nn_defaultScreens[4]; -typedef enum nn_ScreenAction { - NN_SCREEN_DROP, -} nn_ScreenAction; - -typedef struct nn_ScreenRequest { - nn_Context *ctx; - nn_Computer *computer; - void *state; - const nn_ScreenConfig *screen; - nn_ScreenAction action; -} nn_ScreenRequest; - -typedef nn_Exit (nn_ScreenHandler)(nn_ScreenRequest *req); - -nn_Component *nn_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *scrconf, void *state, nn_ScreenHandler *handler); // GPU class - typedef struct nn_GPU { // the minimum between these and the screen's // are the maximum width/height/depth supported. @@ -1275,20 +1259,175 @@ typedef struct nn_GPU { extern const nn_GPU nn_defaultGPUs[4]; typedef enum nn_GPUAction { - NN_GPU_DROP, + NN_GPU_DROP, + NN_GPU_BIND, + NN_GPU_GETSCREEN, + NN_GPU_GETBG, + NN_GPU_SETBG, + NN_GPU_GETFG, + NN_GPU_SETFG, + NN_GPU_GETPALETTE, + NN_GPU_SETPALETTE, + NN_GPU_MAXDEPTH, + NN_GPU_GETDEPTH, + NN_GPU_SETDEPTH, + NN_GPU_MAXRES, + NN_GPU_GETRES, + NN_GPU_SETRES, + NN_GPU_GETVIEWPORT, + NN_GPU_SETVIEWPORT, + NN_GPU_GET, + NN_GPU_SET, + NN_GPU_COPY, + NN_GPU_FILL, + NN_GPU_GETACTIVEBUF, + NN_GPU_SETACTIVEBUF, + NN_GPU_BUFFERS, + NN_GPU_ALLOCBUF, + NN_GPU_FREEBUF, + NN_GPU_FREEALLBUFS, + NN_GPU_FREEMEM, + NN_GPU_GETBUFSIZE, + NN_GPU_BITBLT, } nn_GPUAction; typedef struct nn_GPURequest { - nn_Context *ctx; - nn_Computer *computer; - void *state; - const nn_GPU *gpu; - nn_GPUAction action; + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_GPU *gpu; + nn_GPUAction action; + union { + struct { + const char *address; + bool reset; + } bind; + // GETSCREEN result + char screenAddr[NN_MAX_ADDRESS]; + // GET/SET BG/FG + struct { + int color; + bool isPalette; + int oldColor; + bool wasPalette; + int oldPaletteIdx; // -1 if none + } color; + // GET/SET PALETTE + struct { + int index; + int color; + int oldColor; + } palette; + // MAXDEPTH / GETDEPTH / SETDEPTH + struct { + char depth; + char oldDepth; + } depth; + // MAXRES/GETRES/SETRES/GETVIEWPORT/SETVIEWPORT + struct { + int width; + int height; + } resolution; + // GET pixel + struct { + int x, y; + nn_codepoint codepoint; + int fg, bg; + int fgIdx, bgIdx; // -1 if not palette + } get; + // SET string + struct { + int x, y; + const char *value; + size_t len; + bool vertical; + } set; + // COPY + struct { + int x, y, w, h, tx, ty; + } copy; + // FILL + struct { + int x, y, w, h; + nn_codepoint codepoint; + } fill; + // GET/SET ACTIVE BUFFER, FREE BUFFER + struct { + int index; + } buffer; + // ALLOCATE BUFFER + struct { + int w, h, index; + } allocBuf; + // TOTALMEM / FREEMEM + size_t memory; + // GETBUFSIZE + struct { + int index, w, h; + } bufSize; + // BITBLT + struct { + int dst, col, row, w, h; + int src, fromCol, fromRow; + } bitblt; + // BUFFERS / count returned here, indices + // pushed on stack by handler + size_t bufCount; + }; } nn_GPURequest; typedef nn_Exit (nn_GPUHandler)(nn_GPURequest *req); -nn_Component *nn_createGPU(nn_Universe *universe, const char *address, const nn_GPU *gpu, void *state, nn_GPUHandler *handler); +nn_Component *nn_createGPU( + nn_Universe *universe, const char *address, + const nn_GPU *gpu, void *state, + nn_GPUHandler *handler); + +typedef enum nn_ScreenAction { + NN_SCREEN_DROP, + NN_SCREEN_ISON, + NN_SCREEN_TURNON, + NN_SCREEN_TURNOFF, + NN_SCREEN_GETASPECTRATIO, + NN_SCREEN_GETKEYBOARDS, + NN_SCREEN_SETPRECISE, + NN_SCREEN_ISPRECISE, + NN_SCREEN_SETTOUCHINVERTED, + NN_SCREEN_ISTOUCHINVERTED, +} nn_ScreenAction; + +typedef struct nn_ScreenRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_ScreenConfig *screen; + nn_ScreenAction action; + union { + // turnOn / turnOff / isOn + struct { + bool wasOn; + bool isOn; + } power; + // getAspectRatio + struct { + int w, h; + } aspect; + // getKeyboards — addresses pushed on stack by + // handler; count returned here + size_t kbCount; + // setPrecise / isPrecise / + // setTouchModeInverted / isTouchModeInverted + bool flag; + }; +} nn_ScreenRequest; + +typedef nn_Exit (nn_ScreenHandler)(nn_ScreenRequest *req); + +nn_Component *nn_createScreen( + nn_Universe *universe, const char *address, + const nn_ScreenConfig *scrconf, void *state, + nn_ScreenHandler *handler +); // Colors and palettes. // Do note that the