pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_palette.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - Palette to pixel-pair LUT
4 *
5 * Copyright (c) 2021 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 * Builds the 256-entry LUT that the scanline expansion consumes. The indexed
12 * scanline buffer is always a 256-byte stream; only the meaning of a byte
13 * changes per mode, so the build is selected once per rebuild and never per
14 * pixel.
15 *
16 * The colour transform itself is host policy - PICO9918_PIXEL_FROM_RGB12 - so
17 * this TU is portable and the Pico build still emits the BGR12 pair trick.
18 * (A pram entry is byte-swapped RGB444, 0xGB0R; the macro's input contract is
19 * documented in platform/pico/platform_pico.h.)
20 */
21
22#include "impl/pico9918_priv.h"
23
24/*
25 * The pixel LUT. Regular SRAM, NOT a scratch bank - preserved from the
26 * firmware exactly; scratch-placing it is a separate measured experiment.
27 */
28PICO9918_PALETTE_LUT_T __aligned(4) pico9918_palette_lut[256];
29
30#if !PICO9918_SINGLE_INSTANCE
31const pico9918_t* pico9918_palette_owner = 0;
32#endif
33
34/* Word reads over the uint16_t palette, so the two-entry load is not a strict-aliasing
35 violation. Both entries are converted from that one load - see
36 PICO9918_PIXEL_FROM_RGB12_PAIR, which is the same policy applied to each half. */
37typedef uint32_t PICO9918_MAY_ALIAS pico9918_aliasing_u32_t;
38_Static_assert(_Alignof(pico9918_t) >= 4, "palette LUT build requires word alignment");
39_Static_assert(offsetof(pico9918_t, vram.map.pram) % 4 == 0, "palette LUT build must be word aligned");
40
41/*
42 * Raw PRAM as of the last rebuild, and the layout it was built for: 0 means there is no
43 * shadow yet, otherwise pixelsDoubled + 1, so .bss zero-init reads as "rebuild".
44 *
45 * The GPU-busy status bit is a suspicion, not an announcement - a running GPU program
46 * could have written the palette - so it asks for a rebuild on every active line of every
47 * frame the GPU runs on. Comparing the 32 words the rebuild would read costs a fraction of
48 * converting them, and the answer is almost always that nothing changed.
49 */
50static uint32_t paletteShadow[32];
51static uint8_t paletteShadowLayout;
52
53/* Convert two adjacent palette entries as doubled pixels, and return the converted
54 pair packed into a word for the paired build below to reuse. */
55static inline uint32_t cachePixelPair(PICO9918_PALETTE_LUT_T* dest, const uint16_t* source)
56{
57 const pico9918_aliasing_u32_t* alignedSource =
58 (const pico9918_aliasing_u32_t*)PICO9918_ASSUME_ALIGNED(source, 4);
59 const uint32_t packed = PICO9918_PIXEL_FROM_RGB12_PAIR(*alignedSource);
60 dest[0] = PICO9918_PIXEL_PAIR(PICO9918_LOW16(packed));
61 dest[1] = PICO9918_PIXEL_PAIR(packed >> 16);
62 return packed;
63}
64
65PICO9918_NOINLINE void pico9918_palette_regenerate(PICO9918_INST_ONLY_ARG)
66{
67 const bool pixelsDoubled = pico9918_display_mode(PICO9918_INST_ONLY) != TMS_MODE_TEXT80 ||
69 const uint16_t* source = tms9918->vram.map.pram;
70 const pico9918_aliasing_u32_t* words =
71 (const pico9918_aliasing_u32_t*)PICO9918_ASSUME_ALIGNED(source, 4);
72
73 /* LOAD-BEARING: only the GPU-busy term is overridden here. Every explicit palDirty
74 * setter still forces the rebuild, so a caller that dirties the LUT for a reason the
75 * source words cannot show - a conversion policy change, a reset to the same colours -
76 * keeps the behaviour it had. */
77 if (!tms9918->palDirty && paletteShadowLayout == (uint8_t)(pixelsDoubled + 1)
78#if !PICO9918_SINGLE_INSTANCE
79 && pico9918_palette_owner == tms9918
80#endif
81 )
82 {
83 uint32_t diff = 0;
84 for (int i = 0; i < 32; ++i) diff |= words[i] ^ paletteShadow[i];
85 if (diff == 0) return;
86 }
87
88 /* LOAD-BEARING: the clear precedes every read of PRAM below, shadow included. A GPU
89 * palette write landing after it faults the guard and sets the flag again, so the line
90 * after this one rebuilds; clearing last would swallow that write for good. */
91 tms9918->palDirty = 0;
92#if !PICO9918_SINGLE_INSTANCE
93 pico9918_palette_owner = tms9918;
94#endif
95
96 for (int i = 0; i < 32; ++i) paletteShadow[i] = words[i];
97 paletteShadowLayout = (uint8_t)(pixelsDoubled + 1);
98
99 if (pixelsDoubled)
100 {
101 for (int i = 0; i < 64; i += 8)
102 {
103 cachePixelPair(pico9918_palette_lut + i + 0, source + i + 0);
104 cachePixelPair(pico9918_palette_lut + i + 2, source + i + 2);
105 cachePixelPair(pico9918_palette_lut + i + 4, source + i + 4);
106 cachePixelPair(pico9918_palette_lut + i + 6, source + i + 6);
107 }
108 }
109 else
110 {
111 uint16_t __aligned(4) tmpPal[16];
112 for (int i = 0; i < 16; i += 2)
113 {
114 const uint32_t packed = cachePixelPair(pico9918_palette_lut + i, source + i);
115 pico9918_aliasing_u32_t* dest = (pico9918_aliasing_u32_t*)PICO9918_ASSUME_ALIGNED(tmpPal + i, 4);
116 *dest = packed;
117 }
118 for (int high = 1; high < 16; ++high)
119 {
120 const uint32_t highPixel = tmpPal[high];
121 PICO9918_PALETTE_LUT_T* dest = pico9918_palette_lut + (high << 4);
122#if defined(__GNUC__)
123#pragma GCC unroll 1
124#endif
125 for (int low = 0; low < 16; low += 2)
126 {
127 const pico9918_aliasing_u32_t* pairSource =
128 (const pico9918_aliasing_u32_t*)PICO9918_ASSUME_ALIGNED(tmpPal + low, 4);
129 uint32_t pair = *pairSource;
130 dest[low] = (pair << 16) | highPixel;
131 dest[low + 1] = (pair & 0xFFFF0000) | highPixel;
132 }
133 }
134 }
135}
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
Definition pico9918.c:3499
uint32_t pico9918_line_bytes(pico9918_t *tms9918)
how many bytes of pixels[] this mode fills.
Definition pico9918.c:3518
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:74
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
#define TMS9918_PIXELS_X
active display width, every mode
Definition pico9918.h:376
pico9918-core - the private instance layout