Implement 16-color palette and averaging, for a more realistic NTSC display
authorNick Downing <nick@ndcode.org>
Wed, 25 May 2022 03:42:57 +0000 (13:42 +1000)
committerNick Downing <nick@ndcode.org>
Wed, 25 May 2022 03:42:57 +0000 (13:42 +1000)
hrcg/emu_65c02.c
hrcg/palette.py [new file with mode: 0755]

index 4464c84..8ee93c9 100644 (file)
 #define APPLE_HEIGHT 192
 #define APPLE_HIRES0 0x2000
 
+// includes cool down sequence
+#define PADDED_WIDTH (APPLE_WIDTH + 3)
+#define PADDED_HEIGHT APPLE_HEIGHT
+#define PADDED_WORDS ((PADDED_WIDTH + 31) >> 5)
+
 #define WINDOW_X_SCALE 2
 #define WINDOW_Y_SCALE 4
-#define WINDOW_WIDTH (APPLE_WIDTH * WINDOW_X_SCALE)
-#define WINDOW_HEIGHT (APPLE_HEIGHT * WINDOW_Y_SCALE)
+#define WINDOW_WIDTH (PADDED_WIDTH * WINDOW_X_SCALE)
+#define WINDOW_HEIGHT (PADDED_HEIGHT * WINDOW_Y_SCALE)
 
 #define INSTRS_PER_UPDATE 1500
 
@@ -55,35 +60,24 @@ SDL_Texture *texture;
 SDL_Surface *surface;
 uint32_t frame[WINDOW_HEIGHT][WINDOW_WIDTH];
 
-// linear 0.250000 -> gamma encoded 0.537099 -> 0x89
-// linear 0.500000 -> gamma encoded 0.735357 -> 0xbc
-// linear 0.750000 -> gamma encoded 0.880825 -> 0xe1
-uint32_t argb[WINDOW_X_SCALE * 4] = {
-               // hue     R    G    B
-  0xffff8900,  //  15  1.00 0.25 0.00
-  //0xffff0000,        //   0  1.00 0.00 0.00
-  //0xffff0089,        // 345  1.00 0.00 0.25
-  0xffff00bc,  // 330  1.00 0.00 0.50
-  //0xffff00e1,        // 315  1.00 0.00 0.75
-  //0xffff00ff,        // 300  1.00 0.00 1.00
-  0xffe100ff,  // 285  0.75 0.00 1.00
-  //0xffbc00ff,        // 270  0.50 0.00 1.00
-  //0xff8900ff,        // 255  0.25 0.00 1.00
-  0xff0000ff,  // 289  0.00 0.00 1.00
-  //0xff0089ff,        // 225  0.00 0.25 1.00
-  //0xff00bcff,        // 210  0.00 0.50 1.00
-  0xff00e1ff,  // 195  0.00 0.75 1.00
-  //0xff00ffff,        // 1bc  0.00 1.00 1.00
-  //0xff00ffe1,        // 165  0.00 1.00 0.75
-  0xff00ffbc,  // 150  0.00 1.00 0.50
-  //0xff00ff89,        // 135  0.00 1.00 0.25
-  //0xff00ff00,        // 120  0.00 1.00 0.00
-  0xff89ff00,  // 105  0.25 1.00 0.00
-  //0xffbcff00,        //  90  0.50 1.00 0.00
-  //0xffe1ff00,        //  75  0.75 1.00 0.00
-  0xffffff00,  //  60  1.00 1.00 0.00
-  //0xffffe100,        //  45  1.00 0.75 0.00
-  //0xffffbc00,        //  30  1.00 0.50 0.00
+// see palette.py
+uint32_t palette[0x10] = {
+  0xff000000,
+  0xffbc0089,
+  0xff0000bc,
+  0xffbc00e1,
+  0xff00bc89,
+  0xffbcbcbc,
+  0xff00bce1,
+  0xffbcbcff,
+  0xffbcbc00,
+  0xffffbc89,
+  0xffbcbcbc,
+  0xffffbce1,
+  0xffbcff89,
+  0xffffffbc,
+  0xffbcffe1,
+  0xffffffff,
 };
 
 VrEmu6502 *cpu;
@@ -418,28 +412,37 @@ int main(int argc, char **argv) {
     // draw
     memset(frame, 0, WINDOW_HEIGHT * WINDOW_WIDTH * sizeof(uint32_t));
     for (int i = 0; i < APPLE_HEIGHT; ++i) {
-      int y = i * WINDOW_Y_SCALE;
       int line =
         APPLE_HIRES0 |
         (i >> 6) * 40 |
         ((i & 0x38) << 4) |
         ((i & 7) << 10);
+      uint32_t buf[PADDED_WORDS];
+      memset(buf, 0, PADDED_WORDS * sizeof(uint32_t));
       for (int j = 0; j < 40; ++j) {
         int data = mem[line + j];
         int hibit = data >> 7;
         for (int k = 0; k < 7; ++k) {
           if (data & 1) {
-            int x = (((j * 7 + k) << 1) + hibit) * WINDOW_X_SCALE;
-            for (int l = 0; l < WINDOW_X_SCALE * 2; ++l) {
-              uint32_t v = argb[x & (WINDOW_X_SCALE * 4 - 1)];
-              for (int m = 0; m < WINDOW_Y_SCALE; ++m)
-                frame[y + m][x] = v;
-              ++x;
-            }
+            int l = ((j * 7 + k) << 1) + hibit;
+            buf[l >> 5] |= 1 << (l & 0x1f);
+            ++l;
+            buf[l >> 5] |= 1 << (l & 0x1f);
           }
           data >>= 1;
         }
       }
+      int k = 0, x = 0, y = i * WINDOW_Y_SCALE;
+      for (int j = 0; j < PADDED_WIDTH; ++j) {
+        int mask = 1 << (j & 3);
+        k = (k & ~mask) | ((buf[j >> 5] >> (j & 0x1c)) & mask);
+        uint32_t v = palette[k];
+        for (int l = 0; l < WINDOW_X_SCALE; ++l) {
+          for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+            frame[y + m][x] = v;
+          ++x;
+        }
+      }
     }
     SDL_UpdateTexture(texture, NULL, frame, WINDOW_WIDTH * sizeof(uint32_t));
     SDL_RenderCopy(renderer, texture, NULL, NULL);
diff --git a/hrcg/palette.py b/hrcg/palette.py
new file mode 100755 (executable)
index 0000000..2393015
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+import math
+import numpy
+import sys
+
+hues = numpy.array(
+  [
+    [1., 1., 0., 0., 0., 1., 1.],
+    [0., 1., 1., 1., 0., 0., 0.],
+    [0., 0., 0., 1., 1., 1., 0.],
+  ],
+  numpy.double
+)
+
+
+hue = 330.
+primaries = []
+for i in range(4):
+  j = hue / 60.
+  k = math.floor(j)
+  j -= k
+  primaries.append(hues[:, k] + j * (hues[:, k + 1] - hues[:, k]))
+  hue = (hue - 90.) % 360.
+primaries = numpy.stack(primaries, 1)
+
+palette = []
+for i in range(0x10):
+  bits = (i >> numpy.arange(4, dtype = numpy.int32) & 1).astype(bool)
+  palette.append(primaries @ bits)
+palette = numpy.stack(palette, 1)
+palette /= numpy.max(palette)
+#print('linear', palette.transpose())
+
+# SRGB gamma encode
+mask = palette < .0031308
+palette[mask] *= 12.92
+palette[~mask] = (1.055 * palette[~mask] ** (1. / 2.4)) - .055
+#print('encoded', palette.transpose())
+
+palette = numpy.sum(
+  numpy.round(palette * 255.).astype(numpy.int32) <<
+    numpy.arange(16, -8, -8, numpy.int32)[:, numpy.newaxis],
+  0
+) + 0xff000000
+sys.stdout.write(
+  '''uint32_t palette[0x10] = {{
+{0:s}}};
+'''.format(
+    ''.join([f'  0x{palette[i]:08x},\n' for i in range(0x10)])
+  )
+)