pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_config.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - Config semantics
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 * Portable half of the config block: the field descriptor table, the
12 * validation / defaults / migration driver, the pending mirror refresh, and
13 * the VDP-side apply. Host storage (flash), host detection (SCART, hardware
14 * version) and host-side apply effects (VGA scanlines) live in the host.
15 */
16
17#include "impl/pico9918_priv.h"
18
19#include <string.h>
20
21const pico9918_config_field_t pico9918_config_fields[] = {
22 {PICO9918_CONF_CRT_SCANLINES, 1, 0, PENDING_MIRROR_NONE, 0x1000},
23 {PICO9918_CONF_SCANLINE_SPRITES, 3, 0, PENDING_MIRROR_NONE, 0x1000},
24 {PICO9918_CONF_CLOCK_PRESET_ID, 2, 0, PICO9918_CONF_PENDING_CLOCK_PRESET, 0x1000},
25 {PICO9918_CONF_SCART_MODE, 1, 0, PICO9918_CONF_PENDING_SCART_MODE, 0x1200},
26 {PICO9918_CONF_VDP_DEVICE, 3, 0, PENDING_MIRROR_NONE, 0x1101},
27 {PICO9918_CONF_DISP_DRIVER_PREF, 2, 0, PICO9918_CONF_PENDING_DRIVER_PREF, 0x1200}, // 1.2.0
28 {PICO9918_CONF_VGA_MODE, 0, 0, PICO9918_CONF_PENDING_VGA_MODE, 0x1200}, // 0=480p60 (only)
29 {PICO9918_CONF_DIAG_REGISTERS, 1, 0, PENDING_MIRROR_NONE, 0x1000},
30 {PICO9918_CONF_DIAG_PERFORMANCE, 1, 0, PENDING_MIRROR_NONE, 0x1000},
31 {PICO9918_CONF_DIAG_PALETTE, 1, 0, PENDING_MIRROR_NONE, 0x1000},
32 {PICO9918_CONF_DIAG_ADDRESS, 1, 0, PENDING_MIRROR_NONE, 0x1000},
33 // WARNING: VR58/59 lets a host write any byte >= 8, so shipped units may carry residue in byte 15.
34 // migrateNewFields compares strictly, so the stamp must be the release that first CLAIMS the byte:
35 // an earlier one leaves a unit already on that version unswept, booting on residue.
36 {PICO9918_CONF_VDP_BASE, 1, PICO9918_BASE_TMS9918, PENDING_MIRROR_NONE, 0x1300},
37};
38
39const size_t pico9918_config_field_count = sizeof(pico9918_config_fields) / sizeof(pico9918_config_fields[0]);
40
41void pico9918_config_refresh_pending_mirror(uint8_t config[CONFIG_BYTES], uint8_t state)
42{
43 config[PICO9918_CONF_PENDING_STATE] = state;
44 for (size_t i = 0; i < pico9918_config_field_count; ++i)
45 {
46 if (pico9918_config_fields[i].pendingMirror == PENDING_MIRROR_NONE) continue;
47 config[pico9918_config_fields[i].pendingMirror] = config[pico9918_config_fields[i].offset];
48 }
49}
50
51void pico9918_config_pending_capture(const uint8_t config[CONFIG_BYTES], uint8_t* record)
52{
53 for (size_t i = 0; i < pico9918_config_field_count; ++i)
54 {
55 if (pico9918_config_fields[i].pendingMirror == PENDING_MIRROR_NONE) continue;
56 record[pico9918_config_fields[i].pendingMirror - PICO9918_CONF_PENDING_STATE] =
57 config[pico9918_config_fields[i].offset];
58 }
59}
60
61void pico9918_config_pending_restore(uint8_t config[CONFIG_BYTES], const uint8_t* record)
62{
63 for (size_t i = 0; i < pico9918_config_field_count; ++i)
64 {
65 if (pico9918_config_fields[i].pendingMirror == PENDING_MIRROR_NONE) continue;
66 config[pico9918_config_fields[i].offset] =
67 record[pico9918_config_fields[i].pendingMirror - PICO9918_CONF_PENDING_STATE];
68 }
69}
70
71PICO9918_INLINE_HOT uint16_t configStoredVersion(const uint8_t* config)
72{
73 return ((uint16_t)config[PICO9918_CONF_SW_VERSION] << 8) | config[PICO9918_CONF_SW_PATCH_VERSION];
74}
75
76static bool configOutOfRange(const uint8_t* config)
77{
78 for (size_t i = 0; i < pico9918_config_field_count; ++i)
79 {
80 if (config[pico9918_config_fields[i].offset] > pico9918_config_fields[i].max) return true;
81 }
82 return false;
83}
84
86{
87 memset(config, 0, CONFIG_BYTES);
88
89 for (size_t i = 0; i < pico9918_config_field_count; ++i)
90 {
91 config[pico9918_config_fields[i].offset] = pico9918_config_fields[i].defaultValue;
92 }
93
94 /* entry 0 stays zero; the rest carry the 0xf alpha the validator reads as "initialised" */
95 for (int i = 1; i < 16; ++i)
96 {
97 uint16_t rgb = 0xf000 | pico9918_default_palette(i);
98 config[PICO9918_CONF_PALETTE_IDX_0 + (i * 2)] = rgb >> 8;
99 config[PICO9918_CONF_PALETTE_IDX_0 + (i * 2) + 1] = rgb & 0xff;
100 }
101}
102
103
104// apply defaults only for fields introduced after storedVer
105static void migrateNewFields(uint8_t* config, uint16_t storedVer)
106{
107 for (size_t i = 0; i < pico9918_config_field_count; ++i)
108 {
109 if (pico9918_config_fields[i].introducedIn > storedVer)
110 {
111 config[pico9918_config_fields[i].offset] = pico9918_config_fields[i].defaultValue;
112 }
113 }
114}
115
117{
118 uint16_t storedVer = configStoredVersion(config);
119
120 if (config[PICO9918_CONF_PICO_MODEL] != id.picoModel || config[PICO9918_CONF_PALETTE_IDX_0] != 0x00 ||
121 (config[PICO9918_CONF_PALETTE_IDX_0 + 2] & 0xf0) != 0xf0 || // not initialised
122 configOutOfRange(config))
123 {
125
126 storedVer = 0; // a defaulted block stamps and saves like a version change
127 }
128
129 config[PICO9918_CONF_PICO_MODEL] = id.picoModel;
130 config[PICO9918_CONF_HW_VERSION] = id.hwVersion;
131
132 // the host persists all 256 bytes; clear command bytes read back from storage
133 config[PICO9918_CONF_SAVE_FORCED] = 0;
134 config[PICO9918_CONF_PENDING_CANCEL] = 0;
135 config[PICO9918_CONF_PENDING_CONFIRM] = 0;
136 config[PICO9918_CONF_SAVE_TO_FLASH] = 0;
137
138 if (storedVer == (((uint16_t)id.swVersion << 8) | id.swPatch)) return false;
139
140 migrateNewFields(config, storedVer);
141
142 config[PICO9918_CONF_SW_VERSION] = id.swVersion;
143 config[PICO9918_CONF_SW_PATCH_VERSION] = id.swPatch;
144
145 /* forced, not pending-split: a migration is not a display change the user chose */
146 config[PICO9918_CONF_SAVE_FORCED] = 1;
147 return true;
148}
149
151{
152 config[PICO9918_CONF_PICO_MODEL] = id.picoModel;
153 config[PICO9918_CONF_HW_VERSION] = id.hwVersion;
154 config[PICO9918_CONF_SW_VERSION] = id.swVersion;
155 config[PICO9918_CONF_SW_PATCH_VERSION] = id.swPatch;
156
157 /* the initialised marker: entry 0 always 0, the rest carrying alpha 0xf */
158 config[PICO9918_CONF_PALETTE_IDX_0] = 0;
159 config[PICO9918_CONF_PALETTE_IDX_0 + 1] = 0;
160 for (int i = 1; i < 16; ++i)
161 {
162 config[PICO9918_CONF_PALETTE_IDX_0 + (i * 2)] |= 0xf0;
163 }
164}
165
167{
168 return tms9918->config;
169}
170
171/* host config-applied hook - see the header for the contract, and pico9918.h for why only
172 the storage differs between the two builds */
173#if PICO9918_SINGLE_INSTANCE
174static struct
175{
176 pico9918_config_applied_fn fn;
177 void* userdata;
178} configApplied;
179#define CONFIG_APPLIED_CB configApplied
180#else
181#define CONFIG_APPLIED_CB tms9918->configApplied
182#endif
183
184void pico9918_config_set_applied_callback(PICO9918_INST_ARG pico9918_config_applied_fn cb, void* userdata)
185{
186 CONFIG_APPLIED_CB.fn = cb;
187 CONFIG_APPLIED_CB.userdata = userdata;
188}
189
190/* `tms9918` names the parameter in one build and the global instance in the other, so the
191 callback is handed its instance either way */
192static inline void configAppliedFire(PICO9918_INST_ONLY_ARG)
193{
194 if (CONFIG_APPLIED_CB.fn) CONFIG_APPLIED_CB.fn(tms9918, CONFIG_APPLIED_CB.userdata);
195}
196
198{
199 /* the overlays are the personality's: panels nothing will draw are not a summary */
200 tms9918->config[PICO9918_CONF_DIAG] =
201 PICO9918_HAS(tms9918, PICO9918_FEAT_OVERLAY) &&
202 (tms9918->config[PICO9918_CONF_DIAG_ADDRESS] || tms9918->config[PICO9918_CONF_DIAG_PALETTE] ||
203 tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE] || tms9918->config[PICO9918_CONF_DIAG_REGISTERS]);
204
205 if (!PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG))
206 {
207 /* an F18A's registers, palette and render base are not a settings block's */
208 tms9918->configVdpDirty = false;
209 tms9918->vdpBase = PICO9918_BASE_TMS9918;
210 }
211 else
212 {
213 if (tms9918->configVdpDirty)
214 {
215 tms9918->configVdpDirty = false;
216
217 if (tms9918->config[PICO9918_CONF_CRT_SCANLINES])
218 TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) |= PICO9918_R50_VSCANLINES;
219 else
220 TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) &= (uint8_t)~PICO9918_R50_VSCANLINES;
221
222 TMS_REGISTER(tms9918, PICO9918_REG_MAX_SCAN_SPRITES) =
223 1 << (tms9918->config[PICO9918_CONF_SCANLINE_SPRITES] + 2);
224
225 for (int i = 0; i < 16; ++i)
226 {
227 uint16_t rgb = (tms9918->config[PICO9918_CONF_PALETTE_IDX_0 + (i * 2)] << 8) |
228 tms9918->config[PICO9918_CONF_PALETTE_IDX_0 + (i * 2) + 1];
229 tms9918->vram.map.pram[i] = __builtin_bswap16(rgb);
230 }
231 tms9918->palDirty = 1;
232 }
233
234 tms9918->vdpBase = (tms9918->config[PICO9918_CONF_VDP_BASE] == PICO9918_BASE_V9938)
237 }
238
239 /* last, so the host derives its effects from registers this call may just have seeded */
240 configAppliedFire(PICO9918_INST_ONLY);
241}
242
244{
245 tms9918->configDirty = true;
246 if (applyVdpEffects) tms9918->configVdpDirty = true;
247}
248
250{
251 if (applyVdpEffects) tms9918->configVdpDirty = true;
253
254 /* the boundary must not apply the same block again */
255 tms9918->configDirty = false;
256}
uint16_t pico9918_default_palette(int index)
a default palette entry, 0xargb
Definition pico9918.c:3536
#define PICO9918_R50_VSCANLINES
F18A only: dim every second raster line.
Definition pico9918.h:360
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:71
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:74
@ PICO9918_REG_ENHANCED2
GPU triggers, per-position attributes, layer priority.
Definition pico9918.h:237
@ PICO9918_REG_MAX_SCAN_SPRITES
sprites drawn per scanline before the limit bites
Definition pico9918.h:227
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
void pico9918_config_apply(pico9918_t *tms9918)
apply the config block's VDP-side effects: registers 50 and 30, the palette unpack,...
uint8_t * pico9918_config(pico9918_t *tms9918)
the instance's CONFIG_BYTES settings block
bool pico9918_config_validate(uint8_t config[CONFIG_BYTES], pico9918_config_host_id_t id)
validate a config block just read from host storage, and stamp id into it
void pico9918_config_schedule_apply(pico9918_t *tms9918, bool applyVdpEffects)
ask for the block to be applied at the next end of frame
void pico9918_config_set_applied_callback(pico9918_t *tms9918, pico9918_config_applied_fn cb, void *userdata)
register the host's config-applied hook
void pico9918_config_prepare_save(uint8_t config[CONFIG_BYTES], pico9918_config_host_id_t id)
stamp id and the initialised marker into a block about to be persisted
void pico9918_config_pending_restore(uint8_t config[CONFIG_BYTES], const uint8_t *record)
copy a pending record's field slots back over the live config
void pico9918_config_pending_capture(const uint8_t config[CONFIG_BYTES], uint8_t *record)
copy the live tracked fields into a PICO9918_PENDING_RECORD_BYTES record
void pico9918_config_apply_now(pico9918_t *tms9918, bool applyVdpEffects)
apply the block now, and cancel any apply already owed
void pico9918_config_refresh_pending_mirror(uint8_t config[CONFIG_BYTES], uint8_t state)
copy live tracked fields into the in-RAM pending mirror with the given state
void pico9918_config_defaults(uint8_t config[CONFIG_BYTES])
write a complete, valid settings block: every field at its default
#define PICO9918_BASE_V9938
the V9938 base
#define CONFIG_BYTES
size of the config block, in bytes
#define PICO9918_BASE_TMS9918
vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE
#define PENDING_MIRROR_NONE
pendingMirror value for a field outside the confirmation flow
pico9918-core - the private instance layout
one config field's descriptor
uint8_t offset
the field's config byte index
uint8_t defaultValue
what a reset or a migration writes
uint8_t pendingMirror
PICO9918_CONF_PENDING_* offset, or PENDING_MIRROR_NONE.
the identity bytes at 0-3, which only the host knows