pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_frame.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - frame module
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 * Per-frame / per-scanline sequencing: the interrupt/status latch merge, the
12 * vertical porch, the end-of-frame interrupt and trigger line, the true end of
13 * frame, and the vertical geometry it derives.
14 *
15 * It lands in a .c rather than on the Impl inline surface because a real function
16 * here costs nothing extra: every call site already reaches it with a `bl`.
17 * Inlining it into its three callers - one of them the per-scanline path - would
18 * ADD per-scanline code, not remove a call. The entries the host reaches through a
19 * VGA function pointer are calls for the same reason.
20 */
21
22#include "impl/pico9918_priv.h"
23#include "pico9918_frame.h"
24/* pico9918_gpu_trigger, the ONLY GPU entry this module uses - so gpu/gpu.h is not
25 included here; the GPU time accessors it declares belong to the diagnostics
26 overlay. */
28#include "overlay/diag.h"
29#include "overlay/splash.h"
30
31/* Frame counter and dropped-frame accounting - declared on the Impl surface, which
32 carries the ownership and placement rationale. The window itself is a file static
33 because nothing outside this module reads it; only the running total is exposed. */
34int pico9918_frame_count = 0;
35int pico9918_dropped_frames_count = 0;
36static bool dropped_frames[16] = {0};
37
38/* Vertical geometry and the display-enable latch. Also declared on the Impl
39 surface - same rationale, same measurement. */
40int pico9918_v_pixels = 192;
41uint32_t pico9918_v_border = 0;
42bool pico9918_valid_writes = false;
43
44/* What pico9918_frame_output_line maps an output line through. Seeded progressive. */
45uint8_t pico9918_v_scale = 2;
46uint16_t pico9918_v_virtual = 240;
47
48#if PICO9918_DIAG_GPU_FRAME_COUNTER
49/* GPU frames observed at end of frame. Opt-in, and it sits with the accumulation
50 that feeds it: the increment is per-frame and the reset is on console reset,
51 both in this module. */
52uint32_t pico9918_gpu_frame_count = 0;
53#endif
54
55/* Host late-config-reload hook - see the header for the contract, and for why this
56 is a pointer rather than a tier-1 op. pico9918.h carries why only the storage
57 differs between the two builds. */
58#if PICO9918_SINGLE_INSTANCE
59static struct
60{
61 pico9918_config_reload_fn fn;
62 void* userdata;
63} configReload;
64#define CONFIG_RELOAD_CB configReload
65#else
66#define CONFIG_RELOAD_CB tms9918->configReload
67#endif
68
69/* The border colour the border-fill DMA reads. THE SCRATCH PLACEMENT IS
70 LOAD-BEARING: `.scratch_y` is one of the RP2040's two single-cycle SRAM banks,
71 and a silent fallthrough to striped SRAM is a real per-scanline loss that no
72 functional test can see.
73
74 Not static, because the fill instance that reads it is configured in
75 pico9918.c's initLookups(), alongside the library's own fill - one init site
76 for both, never lazily. Declared on the Impl surface with the module's other
77 globals rather than reached for by an extern at the use site. */
78PICO9918_SECTION_SCRATCH_Y(buffer) uint32_t pico9918_border_bg;
79
80void pico9918_frame_set_config_reload_callback(PICO9918_INST_ARG pico9918_config_reload_fn cb, void* userdata)
81{
82 CONFIG_RELOAD_CB.fn = cb;
83 CONFIG_RELOAD_CB.userdata = userdata;
84}
85
86static inline void configReloadFire(PICO9918_INST_ONLY_ARG)
87{
88 if (CONFIG_RELOAD_CB.fn) CONFIG_RELOAD_CB.fn(tms9918, CONFIG_RELOAD_CB.userdata);
89}
90
91/**
92 * \brief merge newly raised status flags into the SR0 latch, publish it, and bring the
93 * /INT pin into agreement. See the header for the parameter contract.
94 *
95 * The internal sequence is load-bearing at two points and must not be reordered:
96 *
97 * - the three-way merge below is the v1.2.0 semantics, not a simplification of
98 * it. Each branch differs in what it lets through and what it preserves;
99 * - the status publish (PICO9918_HOST_STATUS_VISIBLE) happens BEFORE the pin sync,
100 * so a host CPU that takes the interrupt cannot read a stale status.
101 */
103{
104 PICO9918_HOST_ENTER_CRITICAL();
105 uint8_t currentStatus = pico9918_frame_status_impl(PICO9918_INST_ONLY);
106 if ((currentStatus & PICO9918_SR0_INT) == 0)
107 {
108 if (currentStatus & PICO9918_SR0_5S)
109 {
110 // 5S already latched - preserve existing ID, OR in any new flags (INT, 5S, COL)
111 currentStatus |= (tempStatus & 0xe0);
112 }
113 else
114 {
115 currentStatus = (currentStatus & 0xe0) | tempStatus;
116 }
117 }
118 else
119 {
120 currentStatus |= (tempStatus & PICO9918_SR0_COLLISION);
121 }
122
124 PICO9918_HOST_STATUS_VISIBLE();
125
127 PICO9918_HOST_EXIT_CRITICAL();
128}
129
130/** \brief see the header. */
132{
133 tms9918->vram.map.blanking = 1; // V
134 tms9918->vram.map.scanline = 255; // F18A value for vsync
135 TMS_STATUS(tms9918, PICO9918_SR_RASTER_LINE) = 255;
136}
137
138/**
139 * \brief see the header.
140 *
141 * The configDirty consumption is a CHECK-THEN-CLEAR on a non-volatile flag written
142 * from the other core (the register path sets it on core 1, the host's config load
143 * on core 0) and consumed here. Deliberately left alone: the worst case is a config
144 * apply deferred by one frame.
145 */
147{
148 pico9918_set_frame_done_int_impl(PICO9918_INST true);
149 TMS_STATUS(tms9918, PICO9918_SR_IDENT) |= PICO9918_SR1_BLANK;
150 if (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_GPU_VSYNC)
151 {
152 pico9918_gpu_trigger(PICO9918_INST_ONLY);
153 }
154
155 if (tms9918->configDirty)
156 {
157 tms9918->configDirty = false;
158
159 /* invokes the config-applied callback, which owns the VGA-side write */
162 }
163
165}
166
167/** \brief see the header. It takes no display line: the body never reads one. */
169{
170 if (!pico9918_frame_done_int_impl(PICO9918_INST_ONLY))
171 {
172 bool droppedFrame = pico9918_frame_status_impl(PICO9918_INST_ONLY) & PICO9918_SR0_INT;
173 pico9918_dropped_frames_count += droppedFrame - dropped_frames[pico9918_frame_count & 0xf];
174 dropped_frames[pico9918_frame_count & 0xf] = droppedFrame;
175
177 }
178}
179
180/**
181 * \brief see the header.
182 *
183 * Three things about this expression are deliberately NOT tidied:
184 *
185 * - the yScale-conditional structure. vPixelScale and vVirtualPixels are
186 * rewritten only when yScale > 1, and the vPixels doubling is gated on the SAME
187 * condition. Under interlace the host owns the first two and vPixels must NOT
188 * double - the two fields already supply the second set of lines;
189 * - the shift-by-bool algebra (`yScale - (bool)doubleRows`, `<< (bool)doubleRows`).
190 * The golden frame surface's reference deliberately decomposes this into
191 * explicit cases so the two do not share the algebra; rewriting it here to look
192 * like the reference would destroy that independence;
193 * - the SIGNED intermediate for the border. SCART NTSC in row-30 mode makes it
194 * negative and the narrowing to uint32_t is the shipping behaviour, pinned at
195 * geom-scart-ntsc-row30. A known defect, ruled won't-fix: the host's unsigned
196 * border test then sends all 220 lines down the border path. Do not "fix" it
197 * here.
198 *
199 * yScale is derived from `interlaced` rather than from a build-time DISPLAY_YSCALE.
200 * A non-SCART build's DISPLAY_YSCALE is always 2 with interlaced false, so the
201 * interlace-derived form covers both cases exactly and the library needs no
202 * build-time host macro.
203 */
205{
206 const int yScale = display->interlaced ? 1 : 2;
207
208 if (yScale > 1)
209 {
210 bool doubleRows = TMS_REGISTER(tms9918, TMS_REG_0) & TMS_R0_DOUBLE_ROWS;
211 display->vPixelScale = yScale - (bool)doubleRows;
212 display->vVirtualPixels = (display->displayPixels / yScale) << (bool)doubleRows;
213 }
214
215 /* Outside the conditional so both arms publish; under interlace these are the host's. */
216 pico9918_v_scale = display->vPixelScale;
217 pico9918_v_virtual = display->vVirtualPixels;
218
219 int baseRows = (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_ROW30) ? 30 : 24;
220 pico9918_v_pixels = baseRows << 3;
221 if (yScale > 1 && (TMS_REGISTER(tms9918, TMS_REG_0) & TMS_R0_DOUBLE_ROWS)) pico9918_v_pixels <<= 1;
222 pico9918_v_border = (display->vVirtualPixels - pico9918_v_pixels) / 2;
223
225 g.vPixels = pico9918_v_pixels;
226 g.vBorder = pico9918_v_border;
227 g.triggerScanline = pico9918_v_border + pico9918_v_pixels;
228 return g;
229}
230
231/**
232 * \brief see the header. It takes no frame number: the cadence and the thresholds all run
233 * off this module's own frame counter, which - unlike the VGA layer's frame number -
234 * resets on console reset.
235 *
236 * The frame count is re-READ at each use rather than cached in a local. That is not
237 * a style choice: the host's tier-1 critical section deliberately does not mask the
238 * reset GPIO IRQ, which zeroes the counter, so a console reset landing mid-function
239 * is observable and a cached copy would hide it.
240 */
243{
244 ++pico9918_frame_count;
245#if PICO9918_DIAG_GPU_FRAME_COUNTER
246 pico9918_gpu_frame_count += (TMS_STATUS(tms9918, PICO9918_SR_GPU) & 0x80) != 0;
247#endif
248
249 /* The slice is per scanline, so it moves with the line count and the refresh. */
250 pico9918_gpu_note_frame(PICO9918_INST display->vVirtualPixels, frameRateHz);
251
252 {
253 static float tempAccum = 0.0f;
254 tempAccum += tempC;
255 if ((pico9918_frame_count & 0x3f) == 0) // every 64th frame
256 {
257 tempAccum /= 64.0f;
259 uint8_t t4 = (uint8_t)(tempAccum * 4.0f + 0.5f);
260 TMS_STATUS(tms9918, PICO9918_SR_TEMPERATURE) = t4;
261 tempAccum = 0.0f;
262 }
263 }
264
265 if (!pico9918_valid_writes)
266 {
267 // has the display been enabled?
268 if ((pico9918_valid_writes = (TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_DISP_ACTIVE)) != 0)
269 {
271 if (pico9918_frame_count > PICO9918_FRAME_STARTUP_FRAMES)
272 {
273 // reset diagnostics and other settings back to defaults
274 configReloadFire(PICO9918_INST_ONLY);
275 }
276 }
277 }
278
279 if (tms9918->config[PICO9918_CONF_DIAG] && PICO9918_HAS(tms9918, PICO9918_FEAT_OVERLAY))
280 {
281 pico9918_diag_set_frame_rate(frameRateHz);
282 pico9918_diag_update(PICO9918_INST pico9918_frame_count);
283 }
284
285 // a missed last row leaves the end-of-frame interrupt owing
286 if (!pico9918_frame_done_int_impl(PICO9918_INST_ONLY))
287 {
289 }
290
292}
293
294/**
295 * \brief see the header. The per-scanline path - the function every gate in this project
296 * exists to protect.
297 *
298 * The host's pending-display banner is deliberately not here. It is host code - host flash
299 * state, a host trigger byte, centring against the host's own buffer width - and its only
300 * ordering constraints are pixel ones: after the border fill and the splash, before the
301 * diagnostics overlay. The host's overlay tail already sits there, and what runs between
302 * touches no pixels, so the framebuffer is bit-identical either way.
303 *
304 * The return value is the border flag, and the host needs it to place that banner. "y is
305 * small" is not the same test: in row-30 progressive mode vBorder is 0, so the rows the
306 * banner occupies are active ones and a host that guessed would paint over the display.
307 *
308 * FIVE THINGS HERE MUST NOT BE TIDIED:
309 *
310 * - the `bg` store BEFORE the border WAIT. The previous line's right-border fill may
311 * still be reading the source word when this line overwrites it. Benign - the
312 * value is the same on all but the frame a background register changes - and
313 * "fixing" it by waiting first adds a per-scanline stall the goldens cannot see;
314 * - the row-30 border test. The `TRAP:` below is a recorded, user-ruled won't-fix;
315 * - the SCART-NTSC negative-border underflow. vBorder is unsigned and row-30 on that
316 * timing makes it 4294967286, so this test sends all 220 lines down the border
317 * path and renders none. Pinned deliberately by the golden frame surface at
318 * geom-scart-ntsc-row30. Do not fix it here;
319 * - the frame count re-READ at each use rather than cached, for the reason
320 * pico9918_frame_end above states: the reset GPIO IRQ is not masked and zeroes it;
321 * - the `y -= vBorder` in BOTH arms. It looks like it belongs after the branch, but
322 * the border arm's own body reads the unadjusted y (the bottom-border scanline
323 * register, the palette-regenerate trigger and the splash all do), so hoisting it
324 * would change all three.
325 */
326bool __time_critical_func(pico9918_frame_scanline)(PICO9918_INST_ARG uint16_t y,
327 const pico9918_scanline_params_t* params, PICO9918_PIXEL_T* pixels)
328{
329 const uint32_t halfHBorder = (params->hVirtualPixels - TMS9918_PIXELS_X * 2) / 4;
330
331 // for interlaced modes, bit 12 of y carries the field number (0=Field1, 1=Field2)
332 const uint8_t field = (y >> 12) & 1;
333 y = y & 0x0fff; // virtual line within the field (0..N-1)
334
335 uint32_t* dPixels = (uint32_t*)pixels;
336
337 /* 512 bytes for 80 columns on a board with the 8bpp tier, 256 everywhere else */
338 const uint32_t lineBytes = pico9918_line_bytes_impl(PICO9918_INST_ONLY);
339 const bool packedNibbles =
340 pico9918_display_mode_impl(PICO9918_INST_ONLY) == TMS_MODE_TEXT80 && lineBytes == TMS9918_PIXELS_X;
341 pico9918_border_bg = pico9918_palette_lut
342 [(TMS_REGISTER(tms9918, TMS_REG_FG_BG_COLOR) & 0x0f) |
343 (packedNibbles ? 0 : (TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_SELECT) & PICO9918_R24_TILE1_PS) << 4)];
344
345 if (y == 0)
346 {
347 pico9918_set_frame_done_int_impl(PICO9918_INST false);
348 }
349
350 PICO9918_FILL32_WAIT(PICO9918_FILL_BORDER);
351
352 /*** top and bottom borders ***/
353 // TRAP: none of this runs in ROW30 mode. A ruled won't-fix, not an oversight - see
354 // the note above this function, which lists it among what must not be tidied.
355 if (y < pico9918_v_border_impl(PICO9918_INST_ONLY) ||
356 y >= (pico9918_v_border_impl(PICO9918_INST_ONLY) + pico9918_v_pixels_impl(PICO9918_INST_ONLY)))
357 {
358 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_BORDER, params->hVirtualPixels / 2);
359 PICO9918_FILL32_TRIGGER(PICO9918_FILL_BORDER, dPixels);
360 tms9918->vram.map.blanking = 1; // V
361 if ((y >= pico9918_v_border_impl(PICO9918_INST_ONLY) + pico9918_v_pixels_impl(PICO9918_INST_ONLY)))
362 {
363 tms9918->vram.map.scanline = y - pico9918_v_border_impl(PICO9918_INST_ONLY);
364 TMS_STATUS(tms9918, PICO9918_SR_RASTER_LINE) = tms9918->vram.map.scanline;
365 }
366
367 if (PICO9918_HAS(tms9918, PICO9918_FEAT_OVERLAY) &&
368 (!pico9918_valid_writes_impl(PICO9918_INST_ONLY) ||
369 (pico9918_frame_count_impl(PICO9918_INST_ONLY) < 600)))
370 {
371 PICO9918_FILL32_WAIT(PICO9918_FILL_BORDER);
372
373 pico9918_splash_render(y, pico9918_frame_count_impl(PICO9918_INST_ONLY),
374 pico9918_v_border_impl(PICO9918_INST_ONLY),
375 pico9918_v_pixels_impl(PICO9918_INST_ONLY), params->vVirtualPixels, pixels);
376
377 if (pico9918_frame_count_impl(PICO9918_INST_ONLY) > PICO9918_FRAME_STARTUP_FRAMES)
378 {
379 tms9918->config[PICO9918_CONF_DIAG] = true;
380 tms9918->config[PICO9918_CONF_DIAG_REGISTERS] = true;
381 tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE] = true;
382 tms9918->config[PICO9918_CONF_DIAG_PALETTE] = true;
383 tms9918->config[PICO9918_CONF_DIAG_ADDRESS] = true;
384 }
385 }
386
387 if ((y == pico9918_v_border_impl(PICO9918_INST_ONLY) - 1) && pico9918_palette_dirty(PICO9918_INST_ONLY))
388 {
389 pico9918_palette_regenerate(PICO9918_INST_ONLY);
390 }
391
392 if (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_GPU_HSYNC)
393 {
394 pico9918_gpu_trigger(PICO9918_INST_ONLY);
395 }
396
397 /* Border lines too: a program paging in the vertical blank waits on exactly these. */
398 pico9918_gpu_service(PICO9918_INST_ONLY);
399
400 return true;
401 }
402
403 uint32_t lineStart = PICO9918_HOST_TIME_US();
404
405 y -= pico9918_v_border_impl(PICO9918_INST_ONLY);
406 tms9918->vram.map.blanking = 0;
407 tms9918->vram.map.scanline = y;
408 TMS_STATUS(tms9918, PICO9918_SR_RASTER_LINE) = y;
409
410 /*** left border ***/
411 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_BORDER, halfHBorder);
412 PICO9918_FILL32_TRIGGER(PICO9918_FILL_BORDER, dPixels);
413
414 /*** main display region ***/
415 if (pico9918_palette_dirty(PICO9918_INST_ONLY)) pico9918_palette_regenerate(PICO9918_INST_ONLY);
416
417 uint16_t tmsY =
418 pico9918_frame_map_line_impl(PICO9918_INST y, field, params->interlaced, params->interlacedFieldOrder);
419
420 /* the previous line's capture reads the buffer this render is about to overwrite */
421 PICO9918_LINE_CAPTURE_WAIT();
422
423 uint32_t renderTime = PICO9918_HOST_TIME_US();
424 uint8_t tempStatus = pico9918_scan_line(PICO9918_INST tmsY);
425 renderTime = PICO9918_HOST_TIME_US() - renderTime;
426
427 const uint8_t* lineSource = pico9918_line_source_impl(PICO9918_INST_ONLY);
428 PICO9918_LINE_CAPTURE(y, pico9918_v_pixels_impl(PICO9918_INST_ONLY), lineBytes, lineSource);
429
430 /*** F18A status register updates ***/
431 TMS_STATUS(tms9918, PICO9918_SR_IDENT) &= (uint8_t)~PICO9918_SR1_BLANK;
432
433 if (tms9918->vram.map.scanline &&
434 (TMS_REGISTER(tms9918, PICO9918_REG_HORZ_INT_LINE) == tms9918->vram.map.scanline))
435 {
436 TMS_STATUS(tms9918, PICO9918_SR_IDENT) |= PICO9918_SR1_HF;
437 }
438
439 if (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_GPU_HSYNC)
440 {
441 pico9918_gpu_trigger(PICO9918_INST_ONLY);
442 }
443
445
446 PICO9918_FILL32_WAIT(PICO9918_FILL_BORDER);
447
448 tms9918->vram.map.blanking = 1; // H
449
450 // convert all pixel data from color index to the host pixel format
451#if PICO9918_TEXT80_8BPP
452 if (lineBytes != TMS9918_PIXELS_X)
453 PICO9918_EXPAND_INDEXED_WIDE(dPixels + halfHBorder, lineSource, lineBytes, pico9918_palette_lut);
454 else
455#endif
456 PICO9918_EXPAND_INDEXED(dPixels + halfHBorder, lineSource, TMS9918_PIXELS_X, pico9918_palette_lut);
457
458 // right border
459 PICO9918_FILL32_TRIGGER(PICO9918_FILL_BORDER, dPixels + halfHBorder + TMS9918_PIXELS_X);
460
461 if (tms9918->config[PICO9918_CONF_DIAG] && PICO9918_HAS(tms9918, PICO9918_FEAT_OVERLAY))
462 {
463 PICO9918_FILL32_WAIT(PICO9918_FILL_BORDER);
464 pico9918_diag_render(PICO9918_INST y + pico9918_v_border_impl(PICO9918_INST_ONLY),
465 params->vVirtualPixels, pixels);
466 }
467
468 PICO9918_LINE_NOTE_TIME(y, PICO9918_HOST_TIME_US() - lineStart);
469
470 if (tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE])
471 pico9918_diag_update_render_time(renderTime, PICO9918_HOST_TIME_US() - lineStart);
472
473 /* A long program's slice for this line; the arming write already ran the short ones. */
474 pico9918_gpu_service(PICO9918_INST_ONLY);
475
476 return false;
477}
478
479/* One stop over a whole line, borders included. Parity by output line, not repeat index -
480 that is stuck at zero when vPixelScale is 1. */
481static bool dimLine(PICO9918_INST_ARG PICO9918_PIXEL_T* pixels, uint32_t count,
482 uint32_t outputLine)
483{
484 if (!(outputLine & 1) || !(TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_VSCANLINES))
485 return false;
486
487 uint32_t* pairs = (uint32_t*)pixels;
488 for (uint32_t i = 0; i < count / 2; ++i) pairs[i] = PICO9918_PIXEL_PAIR_DIM(pairs[i]);
489 return true;
490}
491
492/** \brief see the header. A repeat re-reads the host's buffer rather than re-rendering. */
495 pico9918_scanline_params_t* params, PICO9918_PIXEL_T* pixels)
496{
497 const uint32_t scale = pico9918_v_scale ? pico9918_v_scale : 1;
498 bool fresh = false;
499
500 params->vVirtualPixels = pico9918_v_virtual;
501
502 if (outputLine % scale == 0)
503 {
504 const uint16_t y = (uint16_t)(outputLine / scale);
505
506 if (pico9918_frame_scanline(PICO9918_INST y, params, pixels) &&
507 tms9918->config[PICO9918_CONF_DIAG] && PICO9918_HAS(tms9918, PICO9918_FEAT_OVERLAY))
509
510 fresh = true;
511 }
512
513 bool changed = dimLine(PICO9918_INST pixels, params->hVirtualPixels, outputLine) || fresh;
514
515#if PICO9918_BUILD_RUNTIME_CHIP
516 if (tms9918->chip == PICO9918_CHIP_F18A)
517 changed |= pico9918_f18a_badge_render((uint16_t)outputLine,
518 pico9918_frame_count_impl(PICO9918_INST_ONLY), pixels);
519#endif
520
521 return changed;
522}
void pico9918_diag_set_temperature(float tempC)
core temperature, degrees C
Definition diag.c:260
void pico9918_diag_update(pico9918_t *tms9918, uint32_t frameCount)
recompute the panel values - call once per frame
Definition diag.c:280
void pico9918_diag_config_updated(pico9918_t *tms9918)
rebuild the panel row table - call whenever the PICO9918_CONF_DIAG* bytes change
Definition diag.c:541
void pico9918_diag_render(pico9918_t *tms9918, uint16_t y, uint32_t vVirtualPixels, PICO9918_PIXEL_T *pixels)
render the diagnostics panels for border row y
Definition diag.c:596
void pico9918_diag_set_frame_rate(float frameRateHz)
Host display timing, Hz.
Definition diag.c:274
void pico9918_diag_update_render_time(uint32_t renderTime, uint32_t frameTime)
accumulate one scanline's render and total time, in microseconds
Definition diag.c:393
pico9918-core - Diagnostics overlay
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
Definition pico9918.c:3256
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
Definition pico9918.h:276
#define PICO9918_R50_GPU_HSYNC
trigger the GPU every scanline
Definition pico9918.h:356
#define PICO9918_R50_VSCANLINES
F18A only: dim every second raster line.
Definition pico9918.h:360
#define PICO9918_SR1_BLANK
the raster is in blanking
Definition pico9918.h:282
#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
#define PICO9918_R24_TILE1_PS
tile layer 1 palette select
Definition pico9918.h:319
#define TMS_R1_DISP_ACTIVE
render the active display
Definition pico9918.h:300
#define PICO9918_R50_GPU_VSYNC
trigger the GPU every frame
Definition pico9918.h:357
#define PICO9918_SR0_INT
status register 0 bits.
Definition pico9918.h:275
@ PICO9918_REG_ENHANCED1
tile layer 2, 30-row mode, ECM levels, real Y
Definition pico9918.h:236
@ PICO9918_REG_ENHANCED2
GPU triggers, per-position attributes, layer priority.
Definition pico9918.h:237
@ PICO9918_REG_HORZ_INT_LINE
scanline the horizontal interrupt fires on
Definition pico9918.h:220
@ PICO9918_REG_PALETTE_SELECT
sub-palette for sprites and each tile layer
Definition pico9918.h:221
@ PICO9918_SR_RASTER_LINE
the line currently being drawn
Definition pico9918.h:259
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
Definition pico9918.h:257
@ PICO9918_SR_TEMPERATURE
PICO9918 only: core temperature, as degrees C times four.
Definition pico9918.h:269
@ PICO9918_SR_GPU
GPU running and its status byte.
Definition pico9918.h:258
#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
#define TMS_R0_DOUBLE_ROWS
PICO9918 only: twice the rows, drawn interlaced.
Definition pico9918.h:293
#define PICO9918_SR1_HF
status register 1 bits.
Definition pico9918.h:281
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
Definition pico9918.h:277
#define PICO9918_R49_ROW30
30 rows of tiles rather than 24
Definition pico9918.h:343
@ PICO9918_CHIP_F18A
an F18A: unlock, enhanced renderer, GPU
Definition pico9918.h:153
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:73
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:41
void pico9918_config_apply(pico9918_t *tms9918)
apply the config block's VDP-side effects: registers 50 and 30, the palette unpack,...
bool pico9918_frame_output_line(pico9918_t *tms9918, uint32_t outputLine, pico9918_scanline_params_t *params, PICO9918_PIXEL_T *pixels)
see the header.
void pico9918_frame_porch(pico9918_t *tms9918)
see the header.
bool pico9918_frame_scanline(pico9918_t *tms9918, uint16_t y, const pico9918_scanline_params_t *params, PICO9918_PIXEL_T *pixels)
see the header.
void pico9918_frame_raise_end_of_frame_int(pico9918_t *tms9918)
see the header.
pico9918_frame_geometry_t pico9918_frame_end(pico9918_t *tms9918, float tempC, float frameRateHz, pico9918_frame_display_t *display)
see the header.
void pico9918_frame_end_of_scanline(pico9918_t *tms9918)
see the header.
void pico9918_frame_update_interrupts(pico9918_t *tms9918, uint8_t tempStatus)
merge newly raised status flags into the SR0 latch, publish it, and bring the /INT pin into agreement...
pico9918_frame_geometry_t pico9918_frame_geometry(pico9918_t *tms9918, pico9918_frame_display_t *display)
see the header.
pico9918-core - frame module
#define PICO9918_FRAME_STARTUP_FRAMES
frames of startup grace before an un-enabled display is taken to mean "nothing is driving this VDP",...
void pico9918_frame_set_config_reload_callback(pico9918_t *tms9918, pico9918_config_reload_fn cb, void *userdata)
register the host's late-config-reload hook
pico9918-core - GPU privileged surface
pico9918-core - the private instance layout
PICO9918_INLINE void pico9918_frame_sync_int_impl(pico9918_t *tms9918)
recompute the interrupt state and, only if it changed, drive the pin.
PICO9918_INLINE_HOT void pico9918_set_status_impl(pico9918_t *tms9918, uint8_t status)
set status flag
void pico9918_splash_allow_hide(void)
allow the splash to animate back out - the host calls this once the display has been enabled
Definition splash.c:64
void pico9918_splash_render(uint16_t y, uint32_t frameCount, uint32_t vBorder, uint32_t vPixels, uint32_t vVirtualPixels, PICO9918_PIXEL_T *pixels)
render the splash logo into the scanline buffer, if row y falls in the logo band.
Definition splash.c:74
pico9918-core - Splash overlay
the host's mutable vertical display parameters, as the end-of-frame geometry sees them
uint16_t vVirtualPixels
(in, and out when yScale > 1)
the vertical geometry the end of frame derives
uint32_t vBorder
top border offset, in virtual lines
uint32_t triggerScanline
vBorder + vPixels
int vPixels
active VDP display lines
the host's per-call display parameters, as the scanline sees them
uint16_t vVirtualPixels
virtual lines per field
uint16_t hVirtualPixels
full scanline width, guard pixels excluded