From 9447da090bdb85f0ccb725117760d8461ee2263f Mon Sep 17 00:00:00 2001 From: IonutParau Date: Fri, 4 Jul 2025 18:22:17 +0200 Subject: [PATCH] big progress --- TODO.md | 5 ++- src/components/gpu.c | 93 ++++++++++++++++++++++++++++------------- src/components/screen.c | 71 +++++++++++++++++++++++++++++++ src/emulator.c | 40 +++++++----------- src/neonucleus.h | 52 ++++++++++------------- src/utils.c | 47 +++++++++++++++++++++ 6 files changed, 222 insertions(+), 86 deletions(-) diff --git a/TODO.md b/TODO.md index 65918a3..8b82d81 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,8 @@ # Parity with Vanilla OC (only the stuff that makes sense for an emulator) -- get rid of nn_busySleep - rework literally all the costs to just be heat and amount per tick -- change more methods to be direct but with buffered indirects. +- change more methods to be direct but with buffered indirects +- complete the GPU implementation (screen buffers and missing methods) - `computer` component - `modem` component - `tunnel` component @@ -10,6 +10,7 @@ - `redstone` component - `hologram` component - `internet` component +- use dynamic arrays for signals # Bugfixes diff --git a/src/components/gpu.c b/src/components/gpu.c index 3c1a239..37d15a3 100644 --- a/src/components/gpu.c +++ b/src/components/gpu.c @@ -107,9 +107,9 @@ void nni_gpu_bind(nni_gpu *gpu, void *_, nn_component *component, nn_computer *c } } size_t area = screen->width * screen->height; - nn_addHeat(computer, gpu->ctrl.pixelResetHeat * area); - nn_callCost(computer, gpu->ctrl.pixelResetCost * area); - nn_removeEnergy(computer, gpu->ctrl.pixelResetEnergy * area); + nn_addHeat(computer, gpu->ctrl.heatPerPixelReset * area); + nn_simulateBufferedIndirect(component, 1, gpu->ctrl.screenFillPerTick); + nn_removeEnergy(computer, gpu->ctrl.energyPerPixelReset * area); } gpu->currentScreen = screen; @@ -118,10 +118,6 @@ void nni_gpu_bind(nni_gpu *gpu, void *_, nn_component *component, nn_computer *c } gpu->screenAddress = nn_strdup(&gpu->alloc, addr); - nn_addHeat(computer, gpu->ctrl.bindHeat); - nn_callCost(computer, gpu->ctrl.bindCost); - nn_removeEnergy(computer, gpu->ctrl.bindEnergy); - nn_return(computer, nn_values_boolean(true)); } @@ -143,6 +139,7 @@ void nni_gpu_set(nni_gpu *gpu, void *_, nn_component *component, nn_computer *co } int current = 0; + int len = 0; while(s[current] != 0) { int codepoint = nn_unicode_codepointAt(s, current); nn_setPixel(gpu->currentScreen, x, y, nni_gpu_makePixel(gpu, s + current)); @@ -152,7 +149,10 @@ void nni_gpu_set(nni_gpu *gpu, void *_, nn_component *component, nn_computer *co x++; } current += nn_unicode_codepointSize(codepoint); + len++; } + + nn_simulateBufferedIndirect(component, 1, gpu->ctrl.screenSetsPerTick); } void nni_gpu_get(nni_gpu *gpu, void *_, nn_component *component, nn_computer *computer) { @@ -243,9 +243,7 @@ void nni_gpu_setBackground(nni_gpu *gpu, void *_, nn_component *component, nn_co gpu->currentBg = color; gpu->isBgPalette = isPalette; - nn_addHeat(computer, gpu->ctrl.colorChangeHeat); - nn_callCost(computer, gpu->ctrl.colorChangeCost); - nn_removeEnergy(computer, gpu->ctrl.colorChangeEnergy); + nn_simulateBufferedIndirect(component, 1, gpu->ctrl.screenColorChangesPerTick); nn_return(computer, nn_values_integer(old)); if(idx != -1) { @@ -277,9 +275,7 @@ void nni_gpu_setForeground(nni_gpu *gpu, void *_, nn_component *component, nn_co gpu->currentFg = color; gpu->isFgPalette = isPalette; - nn_addHeat(computer, gpu->ctrl.colorChangeHeat); - nn_callCost(computer, gpu->ctrl.colorChangeCost); - nn_removeEnergy(computer, gpu->ctrl.colorChangeEnergy); + nn_simulateBufferedIndirect(component, 1, gpu->ctrl.screenColorChangesPerTick); nn_return(computer, nn_values_integer(old)); if(idx != -1) { @@ -331,13 +327,13 @@ void nni_gpu_fill(nni_gpu *gpu, void *_, nn_component *component, nn_computer *c } } - nn_addHeat(computer, gpu->ctrl.pixelChangeHeat * changes); - nn_callCost(computer, gpu->ctrl.pixelChangeCost * changes); - nn_removeEnergy(computer, gpu->ctrl.pixelChangeEnergy * changes); + nn_addHeat(computer, gpu->ctrl.heatPerPixelChange * changes); + nn_removeEnergy(computer, gpu->ctrl.energyPerPixelChange * changes); - nn_addHeat(computer, gpu->ctrl.pixelChangeHeat * clears); - nn_callCost(computer, gpu->ctrl.pixelChangeCost * clears); - nn_removeEnergy(computer, gpu->ctrl.pixelChangeEnergy * clears); + nn_addHeat(computer, gpu->ctrl.heatPerPixelReset * clears); + nn_removeEnergy(computer, gpu->ctrl.energyPerPixelReset * clears); + + nn_simulateBufferedIndirect(component, 1, gpu->ctrl.screenFillPerTick); nn_return(computer, nn_values_boolean(true)); } @@ -388,14 +384,14 @@ void nni_gpu_copy(nni_gpu *gpu, void *_, nn_component *component, nn_computer *c } nn_dealloc(&gpu->alloc, tmpBuffer, sizeof(nn_scrchr_t) * w * h); - - nn_addHeat(computer, gpu->ctrl.pixelChangeHeat * changes); - nn_callCost(computer, gpu->ctrl.pixelChangeCost * changes); - nn_removeEnergy(computer, gpu->ctrl.pixelChangeEnergy * changes); - nn_addHeat(computer, gpu->ctrl.pixelChangeHeat * clears); - nn_callCost(computer, gpu->ctrl.pixelChangeCost * clears); - nn_removeEnergy(computer, gpu->ctrl.pixelChangeEnergy * clears); + nn_addHeat(computer, gpu->ctrl.heatPerPixelChange * changes); + nn_removeEnergy(computer, gpu->ctrl.energyPerPixelChange * changes); + + nn_addHeat(computer, gpu->ctrl.heatPerPixelReset * clears); + nn_removeEnergy(computer, gpu->ctrl.energyPerPixelReset * clears); + + nn_simulateBufferedIndirect(component, 1, gpu->ctrl.screenCopyPerTick); nn_return(computer, nn_values_boolean(true)); } @@ -407,7 +403,43 @@ void nni_gpu_getViewport(nni_gpu *gpu, void *_, nn_component *component, nn_comp nn_return(computer, nn_values_integer(h)); } void nni_gpu_getDepth(nni_gpu *gpu, void *_, nn_component *component, nn_computer *computer) { - nn_return(computer, nn_values_integer(8)); + if(gpu->currentScreen == NULL) return; + nn_return(computer, nn_values_integer(gpu->currentScreen->depth)); +} + +const char *nn_depthName(int depth) { + if(depth == 1) return "OneBit"; + if(depth == 4) return "FourBit"; + if(depth == 8) return "EightBit"; + if(depth == 16) return "SixteenBit"; + if(depth == 24) return "TwentyFourBit"; + return NULL; +} + +void nni_gpu_setDepth(nni_gpu *gpu, void *_, nn_component *component, nn_computer *computer) { + if(gpu->currentScreen == NULL) return; + int depth = nn_toInt(nn_getArgument(computer, 0)); + int maxDepth = nn_maxDepth(gpu->currentScreen); + + if(nn_depthName(depth) == NULL) { + nn_setCError(computer, "invalid depth"); + return; + } + + if(depth > maxDepth) { + nn_setCError(computer, "depth out of range"); + return; + } + + int old = nn_getDepth(gpu->currentScreen); + nn_setDepth(gpu->currentScreen, depth); + + nn_return_cstring(computer, nn_depthName(depth)); +} + +void nni_gpu_maxDepth(nni_gpu *gpu, void *_, nn_component *component, nn_computer *computer) { + if(gpu->currentScreen == NULL) return; + nn_return(computer, nn_values_integer(gpu->currentScreen->maxDepth)); } void nn_loadGraphicsCardTable(nn_universe *universe) { @@ -416,8 +448,8 @@ void nn_loadGraphicsCardTable(nn_universe *universe) { nn_defineMethod(gpuTable, "bind", false, (void *)nni_gpu_bind, NULL, "bind(addr: string[, reset: boolean = false]): boolean - Bind a GPU to a screen. Very expensive. If reset is true, it will clear the screen."); nn_defineMethod(gpuTable, "getScreen", true, (void *)nni_gpu_getScreen, NULL, "getScreen(): string"); - nn_defineMethod(gpuTable, "set", false, (void *)nni_gpu_set, NULL, "set(x: integer, y: integer, text: string[, vertical: boolean = false]) - Modifies the screen at a specific x or y. If vertical is false, it will display it horizontally. If it is true, it will display it vertically."); - nn_defineMethod(gpuTable, "get", false, (void *)nni_gpu_get, NULL, "get(x: integer, y: integer): string, integer, integer, integer?, integer? - Returns the character, foreground color, background color, foreground palette index (if applicable), background palette index (if applicable) of a pixel"); + nn_defineMethod(gpuTable, "set", true, (void *)nni_gpu_set, NULL, "set(x: integer, y: integer, text: string[, vertical: boolean = false]) - Modifies the screen at a specific x or y. If vertical is false, it will display it horizontally. If it is true, it will display it vertically."); + nn_defineMethod(gpuTable, "get", true, (void *)nni_gpu_get, NULL, "get(x: integer, y: integer): string, integer, integer, integer?, integer? - Returns the character, foreground color, background color, foreground palette index (if applicable), background palette index (if applicable) of a pixel"); nn_defineMethod(gpuTable, "maxResolution", true, (void *)nni_gpu_maxResolution, NULL, "maxResolution(): integer, integer - Gets the maximum resolution supported by the bound screen."); nn_defineMethod(gpuTable, "getResolution", true, (void *)nni_gpu_getResolution, NULL, "getResolution(): integer, integer - Gets the current resolution of the bound screen."); nn_defineMethod(gpuTable, "setResolution", true, (void *)nni_gpu_setResolution, NULL, "maxResolution(): integer, integer - Changes the resolution of the bound screen."); @@ -425,7 +457,8 @@ void nn_loadGraphicsCardTable(nn_universe *universe) { nn_defineMethod(gpuTable, "setForeground", true, (void *)nni_gpu_setForeground, NULL, "setForeground(color: integer, isPalette: boolean): integer, integer? - Sets the current foreground color. Returns the old one and palette index if applicable."); nn_defineMethod(gpuTable, "getBackground", true, (void *)nni_gpu_getBackground, NULL, "setBackground(color: integer, isPalette: boolean): integer, integer? - Sets the current background color. Returns the old one and palette index if applicable."); nn_defineMethod(gpuTable, "getForeground", true, (void *)nni_gpu_getForeground, NULL, "setForeground(color: integer, isPalette: boolean): integer, integer? - Sets the current foreground color. Returns the old one and palette index if applicable."); - nn_defineMethod(gpuTable, "getDepth", true, (void *)nni_gpu_getDepth, NULL, "getDepth(): number - The currently set color depth of the GPU/screen, in bits. Can be 1, 4 or 8."); + nn_defineMethod(gpuTable, "getDepth", true, (void *)nni_gpu_getDepth, NULL, "getDepth(): number - The currently set color depth of the screen, in bits. Can be 1, 4 or 8."); + nn_defineMethod(gpuTable, "setDepth", true, (void *)nni_gpu_setDepth, NULL, "setDepth(depth: integer): string - Changes the screen depth. Valid values can be 1, 4, 8, 16 or 24, however check maxDepth for the maximum supported value of the screen. Using a depth higher than what is supported by the screen will error. Returns the name of the new depth."); nn_defineMethod(gpuTable, "fill", true, (void *)nni_gpu_fill, NULL, "fill(x: integer, y: integer, w: integer, h: integer, s: string)"); nn_defineMethod(gpuTable, "copy", true, (void *)nni_gpu_copy, NULL, "copy(x: integer, y: integer, w: integer, h: integer, tx: integer, ty: integer) - Copies stuff"); nn_defineMethod(gpuTable, "getViewport", true, (void *)nni_gpu_getViewport, NULL, "getViewport(): integer, integer - Gets the current viewport resolution"); diff --git a/src/components/screen.c b/src/components/screen.c index 625c572..30021e0 100644 --- a/src/components/screen.c +++ b/src/components/screen.c @@ -239,3 +239,74 @@ nn_component *nn_addScreen(nn_computer *computer, nn_address address, int slot, nn_componentTable *screenTable = nn_queryUserdata(nn_getUniverse(computer), "NN:SCREEN"); return nn_newComponent(computer, address, slot, screenTable, screen); } + +void nn_getStd4BitPalette(int color[16]) { + color[0] = 0xFFF9FE; // white + color[1] = 0xF9801D; // orange + color[2] = 0xC74EBD; // magenta + color[3] = 0x3AB3DA; // lightblue + color[4] = 0xFED83D; // yellow + color[5] = 0x80C71F; // lime + color[6] = 0xF38BAA; // pink + color[7] = 0x474F52; // gray + color[8] = 0x9D9D97; // silver + color[9] = 0x169C9C; // cyan + color[10] = 0x8932B8; // purple + color[11] = 0x3C44AA; // blue + color[12] = 0x835432; // brown + color[13] = 0x5E7C16; // green + color[14] = 0xB02E26; // red + color[15] = 0x1D1D21; // black +} + +void nn_getStd8BitPalette(int color[256]) { + // source: https://ocdoc.cil.li/component:gpu + int reds[6] = {0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF}; + int greens[8] = {0x00, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF}; + int blues[5] = {0x00, 0x40, 0x80, 0xC0, 0xFF}; + + for(int r = 0; r < 6; r++) { + for(int g = 0; g < 8; g++) { + for(int b = 0; b < 5; b++) { + int i = r * 8 * 5 + g * 5 + b; + color[i] = (reds[r] << 16) | (greens[g] << 8) | (blues[b]); + } + } + } + + // TODO: turn into an algorithm + color[240] = 0x0F0F0F; + color[241] = 0x1E1E1E; + color[242] = 0x2D2D2D; + color[243] = 0x3C3C3C; + color[244] = 0x4B4B4B; + color[245] = 0x5A5A5A; + color[246] = 0x696969; + color[247] = 0x787878; + color[248] = 0x878787; + color[249] = 0x969696; + color[250] = 0xA5A5A5; + color[251] = 0xB4B4B4; + color[252] = 0xC3C3C3; + color[253] = 0xD2D2D2; + color[254] = 0xE1E1E1; + color[255] = 0xF0F0F0; +} + +int nn_mapDepth(int color, int depth) { + if(depth == 1) { + if(color == 0) return 0; + return 0xFFFFFF; + } + if(depth == 4) { + int palette[16]; + nn_getStd4BitPalette(palette); + return nn_mapColor(color, palette, 16); + } + if(depth == 8) { + int palette[256]; + nn_getStd8BitPalette(palette); + return nn_mapColor(color, palette, 256); + } + return color; +} diff --git a/src/emulator.c b/src/emulator.c index 63570bd..73358e6 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -597,31 +597,21 @@ int main() { nn_mountKeyboard(computer, "shitty keyboard", 2); nn_addScreen(computer, "Main Screen", 2, s); + // somewhat matches tier 3 in OC in terms of perTick nn_gpuControl gpuCtrl = { - .maxWidth = 240, - .maxHeight = 80, - .maxDepth = 16, + .totalVRAM = 16*1024, + .screenCopyPerTick = 4, + .screenFillPerTick = 8, + .screenSetsPerTick = 16, + .screenColorChangesPerTick = 8, - .totalVRAM = 32*1024, - .vramByteChangeCost = 0, - .vramByteChangeEnergy = 0, - .vramByteChangeHeat = 0, - .vramByteChangeLatency = 0, + .heatPerPixelChange = 0.0005, + .heatPerPixelReset = 0.0001, + .heatPerVRAMChange = 0.000015, - .pixelChangeCost = 0, - .pixelChangeEnergy = 0, - .pixelChangeHeat = 0, - .pixelChangeLatency = 0, - - .pixelResetCost = 0, - .pixelResetEnergy = 0, - .pixelResetHeat = 0, - .pixelResetLatency = 0, - - .colorChangeLatency = 0, - .colorChangeCost = 0, - .colorChangeEnergy = 0, - .colorChangeHeat = 0, + .energyPerPixelChange = 0.05, + .energyPerPixelReset = 0.01, + .energyPerVRAMChange = 0.0015, }; nn_addGPU(computer, "RTX 6090", 3, &gpuCtrl); @@ -736,13 +726,15 @@ render: float spacing = (float)pixelHeight/10; int pixelWidth = MeasureTextEx(unscii, "A", pixelHeight, spacing).x; + int depth = nn_getDepth(s); + for(size_t x = 0; x < scrW; x++) { for(size_t y = 0; y < scrH; y++) { nn_scrchr_t p = nn_getPixel(s, x, y); // fuck palettes - Color fgColor = ne_processColor(p.fg); - Color bgColor = ne_processColor(p.bg); + Color fgColor = ne_processColor(nn_mapDepth(p.fg, depth)); + Color bgColor = ne_processColor(nn_mapDepth(p.bg, depth)); DrawRectangle(x * pixelWidth, y * pixelHeight, pixelWidth, pixelHeight, bgColor); DrawTextCodepoint(unscii, p.codepoint, (Vector2) {x * pixelWidth, y * pixelHeight}, pixelHeight - 5, fgColor); } diff --git a/src/neonucleus.h b/src/neonucleus.h index 34eb7bb..ddd2e2a 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -620,6 +620,14 @@ int nn_getPaletteCount(nn_screen *screen); int nn_maxDepth(nn_screen *screen); int nn_getDepth(nn_screen *screen); void nn_setDepth(nn_screen *screen, int depth); +const char *nn_depthName(int depth); + +double nn_colorDistance(int colorA, int colorB); +int nn_mapColor(int color, int *palette, int paletteSize); + +int nn_mapDepth(int color, int depth); +void nn_getStd4BitPalette(int color[16]); +void nn_getStd8BitPalette(int color[256]); void nn_setPixel(nn_screen *screen, int x, int y, nn_scrchr_t pixel); nn_scrchr_t nn_getPixel(nn_screen *screen, int x, int y); @@ -636,41 +644,25 @@ void nn_setOn(nn_screen *buffer, bool on); nn_component *nn_addScreen(nn_computer *computer, nn_address address, int slot, nn_screen *screen); typedef struct nn_gpuControl { - // resolution and colors - int maxWidth; - int maxHeight; - int maxDepth; - // VRAM Buffers int totalVRAM; - // Energy costs - double bindEnergy; - double pixelChangeEnergy; - double pixelResetEnergy; - double colorChangeEnergy; - double vramByteChangeEnergy; - + // Calls per tick, only applicable to screens + double screenCopyPerTick; + double screenFillPerTick; + double screenSetsPerTick; + double screenColorChangesPerTick; + double bitbltPerTick; // for bitblit + // Heat - double bindHeat; - double pixelChangeHeat; - double pixelResetHeat; - double colorChangeHeat; - double vramByteChangeHeat; + double heatPerPixelChange; + double heatPerPixelReset; + double heatPerVRAMChange; - // Call budgets - size_t bindCost; - size_t pixelChangeCost; - size_t pixelResetCost; - size_t colorChangeCost; - size_t vramByteChangeCost; - - // Latencies - double bindLatency; - double pixelChangeLatency; - double pixelResetLatency; - double colorChangeLatency; - double vramByteChangeLatency; + // Energy + double energyPerPixelChange; + double energyPerPixelReset; + double energyPerVRAMChange; } nn_gpuControl; // the control is COPIED. diff --git a/src/utils.c b/src/utils.c index 1bea158..db621d9 100644 --- a/src/utils.c +++ b/src/utils.c @@ -114,3 +114,50 @@ double nn_realTimeClock(void *_) { return nn_realTime(); } +// TODO: use OKLAB the color space for more accurate results. + +typedef struct nn_rgbColor { + double r, g, b; +} nn_rgbColor; + +nn_rgbColor nni_splitColorToRgb(int color) { + double r = (color & 0xFF0000) >> 16; + double g = (color & 0x00FF00) >> 8; + double b = color & 0x0000FF; + + int max = 0xFF; + return (nn_rgbColor) { + .r = r / max, + .g = g / max, + .b = b / max, + }; +} + +double nn_colorDistance(int colorA, int colorB) { + if(colorA == colorB) return 0; + nn_rgbColor a = nni_splitColorToRgb(colorA); + nn_rgbColor b = nni_splitColorToRgb(colorB); + + nn_rgbColor delta; + delta.r = a.r - b.r; + delta.g = a.g - b.g; + delta.b = a.b - b.b; + + return delta.r*delta.r + delta.g*delta.g + delta.b*delta.b; +} + +int nn_mapColor(int color, int *palette, int paletteSize) { + if(paletteSize <= 0) return color; + int bestColor = palette[0]; + double fitness = nn_colorDistance(color, bestColor); + + for(int i = 1; i < paletteSize; i++) { + double dist = nn_colorDistance(color, palette[i]); + if(dist < fitness) { + bestColor = palette[i]; + fitness = dist; + } + if(bestColor == color) return color; + } + return bestColor; +}