pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
diag.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - Diagnostics overlay
4 *
5 * Copyright (c) 2024 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 */
12
13#include "diag.h"
14
15#include "overlay/bmp_font.h"
16
17#include "impl/pico9918_priv.h"
18
19#include "gpu/gpu.h"
20
21#include <stdbool.h>
22#include <string.h>
23
24
25typedef struct
26{
27 union
28 {
29 uint32_t words[3];
30 char digits[sizeof(uint32_t) * 3];
31 };
32 int start;
33} IntString;
34
35static void clear(IntString* number)
36{
37 number->start = 0;
38 number->words[0] = 0;
39 number->words[1] = 0;
40 number->words[2] = 0;
41}
42
43
44/* for diagnostics / statistics */
45IntString frameTimeStr = {0};
46IntString renderTimePerScanlineStr = {0};
47IntString temperatureStr = {0};
48IntString gpuPctStr = {0};
49IntString clockMhzStr = {0};
50IntString modeStr = {0};
51IntString fpsStr = {0};
52#if PICO9918_DIAG_GPU_FRAME_COUNTER
53IntString gpuFrameStr = {0};
54#endif
55IntString hwVerStr = {0};
56IntString fwVerStr = {0};
57IntString outputStr = {0};
58
59/* host-pushed OUTPUT row units ("@60" / "@50"); the driver encoding is host
60 policy, so the label arrives rather than being derived here */
61static const char* outputUnitsStr = "";
62
63IntString nameTabStr = {0};
64IntString colorTabStr = {0};
65IntString pattTabStr = {0};
66IntString sprAttTabStr = {0};
67IntString sprPattTabStr = {0};
68
69uint32_t accumulatedRenderTime = 0;
70uint32_t accumulatedFrameTime = 0;
71uint32_t accumulatedScanlines = 0;
72uint32_t lastUpdateTime = 0;
73
74/*
75 * Panel colours, expressed through the pixel policy rather than as raw host
76 * words. The macro's input is a pram-order value (0xGB0R), so the canonical
77 * colour is byte-swapped to feed it: pale cyan 0x07ff -> 0xff07, white 0x0fff ->
78 * 0xff0f, grey 0x0888 -> 0x8808.
79 *
80 * The mask is load-bearing, not tidying: PICO9918_PIXEL_FROM_RGB12 replicates green
81 * into bits 15-12, which are dead at the pin boundary but NOT on the RP2040
82 * CRT-scanline path, where the whole word is shifted right by one UNMASKED and
83 * bit 12 lands in blue's MSB. Without the mask the panel text renders
84 * brighter-blue whenever CRT scanlines are enabled. Keep the top nibble clear.
85 *
86 * 0x0fff is a 12-bit mask on a >= 16-bit type, so this is well-defined at any
87 * pixel width, but the *values* only mean BGR12 under the Pico policy - the
88 * desktop RGBA8888 policy would need its own literals (no desktop consumer draws
89 * these panels today).
90 */
91#define DIAG_COLOR(pramOrder) ((PICO9918_PIXEL_T)(PICO9918_PIXEL_FROM_RGB12(pramOrder) & 0x0fff))
92
93const PICO9918_PIXEL_T labelColor = DIAG_COLOR(0xff07);
94const PICO9918_PIXEL_T valueColor = DIAG_COLOR(0xff0f);
95const PICO9918_PIXEL_T unitsColor = DIAG_COLOR(0x8808);
96
97static float precLookup[] = {1.0f, 10.0f, 100.0f, 1000.0f};
98
99/* convert a float to a string */
100static void flt2Str(float flt, int prec, IntString* out)
101{
102 if (prec > 3) prec = 3;
103 flt *= precLookup[prec];
104 uint32_t number = (uint32_t)(flt + 0.5f);
105
106 out->start = sizeof(out->digits) - 1;
107 out->digits[out->start] = '\0';
108 while (prec--)
109 {
110 out->digits[--out->start] = '0' + (number % 10);
111 number /= 10;
112 }
113 out->digits[--out->start] = '.';
114 if (!number)
115 {
116 out->digits[--out->start] = '0';
117 }
118 else
119 {
120 while (number && out->start)
121 {
122 out->digits[--out->start] = '0' + (number % 10);
123 number /= 10;
124 }
125 }
126}
127
128#if PICO9918_DIAG_GPU_FRAME_COUNTER
129/* convert an integer to string */
130static void uint2Str(uint32_t number, int width, IntString* out)
131{
132 out->start = sizeof(out->digits) - 1;
133 out->digits[out->start] = '\0';
134 while (number && out->start)
135 {
136 out->digits[--out->start] = '0' + (number % 10);
137 number /= 10;
138 --width;
139 }
140
141 while (width-- > 0)
142 {
143 out->digits[--out->start] = '0';
144 }
145}
146#endif
147
148
149/* convert an integer to hex string */
150static void uint2hexStr(uint32_t number, int width, IntString* out)
151{
152 out->start = sizeof(out->digits) - 1;
153 out->digits[out->start] = '\0';
154 while (number && out->start)
155 {
156 uint32_t nibble = number % 16;
157 number /= 16;
158 if (nibble < 10)
159 out->digits[--out->start] = '0' + nibble;
160 else
161 out->digits[--out->start] = 'A' - 10 + nibble;
162 --width;
163 }
164
165 while (width-- > 0)
166 {
167 out->digits[--out->start] = '0';
168 }
169}
170
171/* glyphs per row of the font image, and the first character it holds */
172#define FONT_CHARS 96
173#define FONT_FIRST 32
174
175/* A cell is PICO9918_DIAG_CHAR_WIDTH pixels, so this many ink words wide; the table's
176 stride is rounded up to a power of two so indexing it is a shift, not a multiply. */
177#define INK_WORDS (PICO9918_DIAG_CHAR_WIDTH / PICO9918_INK_PIXELS)
178#define INK_STRIDE (INK_WORDS <= 4 ? 4 : 8)
179
180/* which pixels of which ink word a glyph's ink lands in, indexed by pattern byte,
181 taken from the byte's bits 5..0 left to right */
182static PICO9918_INK_T glyphMask[64][INK_STRIDE];
183
184static void PICO9918_IN_FLASH_FUNC(glyphMaskInit)(void)
185{
186 for (uint32_t b = 0; b < 64; ++b)
187 {
188 for (uint32_t w = 0; w < INK_WORDS; ++w)
189 {
190 PICO9918_INK_T m = 0;
191 for (uint32_t k = 0; k < PICO9918_INK_PIXELS; ++k)
192 {
193 if (b & (0x20u >> (w * PICO9918_INK_PIXELS + k))) m |= PICO9918_INK_ONE(k);
194 }
195 glyphMask[b][w] = m;
196 }
197 }
198}
199
200void PICO9918_IN_FLASH_FUNC(pico9918_diag_init)(void)
201{
202 glyphMaskInit();
203
204 clear(&frameTimeStr);
205 clear(&gpuPctStr);
206#if PICO9918_DIAG_GPU_FRAME_COUNTER
207 clear(&gpuFrameStr);
208#endif
209 clear(&renderTimePerScanlineStr);
210 clear(&temperatureStr);
211 clear(&clockMhzStr);
212 clear(&modeStr);
213 clear(&fpsStr);
214 clear(&hwVerStr);
215 clear(&fwVerStr);
216
217 clear(&nameTabStr);
218 clear(&colorTabStr);
219 clear(&pattTabStr);
220 clear(&sprAttTabStr);
221 clear(&sprPattTabStr);
222 clear(&outputStr);
223
224}
225
226void pico9918_diag_set_version_info(const char* hwVersion, const char* fwVersion)
227{
228 if (hwVersion)
229 {
230 strncpy(hwVerStr.digits, hwVersion, sizeof(hwVerStr.digits) - 1);
231 hwVerStr.digits[sizeof(hwVerStr.digits) - 1] = '\0';
232 hwVerStr.start = 0;
233 }
234
235 if (fwVersion)
236 {
237 strncpy(fwVerStr.digits, fwVersion, sizeof(fwVerStr.digits) - 1);
238 fwVerStr.digits[sizeof(fwVerStr.digits) - 1] = '\0';
239 fwVerStr.start = 0;
240 }
241}
242
243void pico9918_diag_set_output_name(const char* name, const char* units)
244{
245 if (name)
246 {
247 strncpy(outputStr.digits, name, sizeof(outputStr.digits) - 1);
248 outputStr.digits[sizeof(outputStr.digits) - 1] = '\0';
249 outputStr.start = 0;
250 }
251
252 if (units) outputUnitsStr = units;
253}
254
255const char* modeNames[] = {
256 "GFX I", "GFX II", "TEXT", "MULTI", "80 COL",
257};
258
259/* set the temperature value to display */
261{
262 flt2Str(tempC, 2, &temperatureStr);
263}
264
265void pico9918_diag_set_clock_hz(float clockHz)
266{
267 flt2Str(clockHz / 1000000.0f, 1, &clockMhzStr);
268}
269
270/* host-pushed timing input. The dropped-frame count needs no push: the frame module
271 owns that counter, so the FPS row reads it directly below. */
272static float hostFrameRateHz = 0.0f;
273
274void pico9918_diag_set_frame_rate(float frameRateHz)
275{
276 hostFrameRateHz = frameRateHz;
277}
278
279/* update diagnostics values */
281{
282 const uint32_t framesPerUpdate = 1 << 2;
283
284 /* read off the count, so a panel's phase cannot depend on which others are enabled */
285 const uint32_t phase = frameCount & (framesPerUpdate - 1);
286 if (tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE])
287 {
288 if (phase == 0)
289 {
290 flt2Str((float)(accumulatedFrameTime / framesPerUpdate) / 1000.0f, 3, &frameTimeStr);
291
292 /* samples are whole microseconds, but their average resolves far finer */
293 if (accumulatedScanlines)
294 {
295 flt2Str((float)accumulatedRenderTime / accumulatedScanlines, 2, &renderTimePerScanlineStr);
296 }
297
298 accumulatedRenderTime = accumulatedFrameTime = accumulatedScanlines = 0;
299
300 uint32_t currentTime = PICO9918_HOST_TIME_US();
301
302 /* currentTime MUST be the later reading: reversed, this underflows and the row reads 0% or 100% */
303 uint32_t totalTime = currentTime - lastUpdateTime;
304
305 float gpuPct = (pico9918_gpu_time(totalTime) / (float)totalTime) * 100.0f;
306 flt2Str(gpuPct, 4, &gpuPctStr);
308
309#if PICO9918_DIAG_GPU_FRAME_COUNTER
310 uint2Str(pico9918_gpu_frame_count, 1, &gpuFrameStr);
311#endif
312
313 lastUpdateTime = currentTime;
314 }
315
316 if (phase == 3)
317 {
318 flt2Str((16.0f - pico9918_dropped_frames_count) * (hostFrameRateHz / 16.0f), 2, &fpsStr);
319 }
320 }
321
322 if (tms9918->config[PICO9918_CONF_DIAG_ADDRESS])
323 {
324 if (phase == 2)
325 {
326 uint2hexStr((TMS_REGISTER(tms9918, TMS_REG_NAME_TABLE) & 0x0f) << 10, 4, &nameTabStr);
327
328 uint8_t mask = (pico9918_display_mode(PICO9918_INST_ONLY) == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
329 uint2hexStr((TMS_REGISTER(tms9918, TMS_REG_COLOR_TABLE) & mask) << 6, 4, &colorTabStr);
330
331 mask = (pico9918_display_mode(PICO9918_INST_ONLY) == TMS_MODE_GRAPHICS_II) ? 0x04 : 0x07;
332 uint2hexStr(((TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & mask) << 11) & 0xffff, 4, &pattTabStr);
333
334 uint2hexStr((TMS_REGISTER(tms9918, TMS_REG_SPRITE_ATTR_TABLE) & 0x7f) << 7, 4, &sprAttTabStr);
335 uint2hexStr((TMS_REGISTER(tms9918, TMS_REG_SPRITE_PATT_TABLE) & 0x07) << 11, 4, &sprPattTabStr);
336
337 const char* s = modeNames[pico9918_display_mode(PICO9918_INST_ONLY)];
338 char* d = modeStr.digits;
339 while (*s)
340 {
341 *d++ = *s++;
342 }
343 *d = 0;
344 }
345 }
346}
347
348int pico9918_diag_render_text(uint16_t scanline, const char* text, uint16_t x, uint16_t y, PICO9918_PIXEL_T fg,
349 PICO9918_PIXEL_T* pixels)
350{
351 const int fontY = scanline - y;
352 if (fontY < 0 || fontY >= PICO9918_DIAG_CHAR_HEIGHT) return x;
353
354 /* biased by the first character the image holds, so the loop indexes it directly */
355 const uint8_t* __restrict fontRow = font + fontY * FONT_CHARS - FONT_FIRST;
356 const uint8_t* __restrict s = (const uint8_t*)text;
357 const PICO9918_INK_T ink = PICO9918_INK_FILL(fg);
358 PICO9918_INK_T* p = (PICO9918_INK_T*)(pixels + x);
359 uint32_t c;
360 while ((c = *s++) != 0)
361 {
362 /* the background comes from the one mask: half the table and one load fewer */
363 const PICO9918_INK_T* __restrict m = glyphMask[fontRow[c]];
364 for (int w = 0; w < INK_WORDS; ++w)
365 {
366 p[w] = (ink & m[w]) | (PICO9918_INK_DARKEN(p[w]) & ~m[w]);
367 }
368 p += INK_WORDS;
369 }
370 return (PICO9918_PIXEL_T*)p - pixels;
371}
372
373
374/* one row of a panel string, whose origin is the top of the screen */
375PICO9918_INLINE int renderRow(uint16_t row, const char* text, uint16_t x, PICO9918_PIXEL_T fg,
376 PICO9918_PIXEL_T* pixels)
377{
378 return pico9918_diag_render_text(row, text, x, 0, fg, pixels);
379}
380
381
382/* render a bcd value scanline
383 *
384 * PICO9918_INLINE, not a bare `inline`: there is no external definition of this
385 * anywhere, so a plain C99 inline makes a desktop -O0 build emit calls to a symbol
386 * that does not exist. TU-local - nothing outside this file names it. */
387PICO9918_INLINE int renderNum(uint16_t row, IntString* str, uint16_t x, PICO9918_PIXEL_T fg, PICO9918_PIXEL_T* pixels)
388{
389 return renderRow(row, str->digits + str->start, x, fg, pixels);
390}
391
392
393void pico9918_diag_update_render_time(uint32_t renderTime, uint32_t frameTime)
394{
395 ++accumulatedScanlines;
396 accumulatedRenderTime += renderTime;
397 accumulatedFrameTime += frameTime;
398}
399
400
401/* darken a run with no glyph over it, whose origin and count are both whole ink words */
402static int backgroundPixels(int xPos, int count, PICO9918_PIXEL_T* pixels)
403{
404 PICO9918_INK_T* p = (PICO9918_INK_T*)(pixels + xPos);
405 for (int i = count / PICO9918_INK_PIXELS; i > 0; --i)
406 {
407 *p = PICO9918_INK_DARKEN(*p);
408 ++p;
409 }
410 return xPos + count;
411}
412
413
414/* a nibble as four glyphs, fixed stride so the index is a shift not a load */
415static const char nibbleBinStr[16][8] = {
416 "((((", "((()", "(()(", "(())", "()((", "()()", "())(", "()))",
417 ")(((", ")(()", ")()(", ")())", "))((", "))()", ")))(", "))))",
418};
419
420// register numbers to render
421static const uint8_t extReg[] = {10, 11, 15, 19, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
422 35, 36, 37, 38, 48, 49, 50, 51, 54, 55, 56, 57, 58, 59, 63};
423
424const uint32_t leftXPos = 2;
425
426static void renderLeft(const char* label, IntString* val, const char* units, uint16_t row,
427 PICO9918_PIXEL_T* pixels)
428{
429 uint32_t xPos = leftXPos;
430 xPos = renderRow(row, label, xPos, labelColor, pixels);
431 xPos = renderNum(row, val, xPos, valueColor, pixels);
432 xPos = renderRow(row, units, xPos, unitsColor, pixels);
433 xPos = backgroundPixels(xPos, 102 - xPos, pixels);
434}
435
436static void diagHwVer(uint16_t row, PICO9918_PIXEL_T* pixels)
437{
438 renderLeft("HWVER : ", &hwVerStr, "", row, pixels);
439}
440
441static void diagFwVer(uint16_t row, PICO9918_PIXEL_T* pixels)
442{
443 renderLeft("FWVER : ", &fwVerStr, "", row, pixels);
444}
445
446static void diagRenderTime(uint16_t row, PICO9918_PIXEL_T* pixels)
447{
448 renderLeft("FRAME : ", &frameTimeStr, "&S", row, pixels);
449}
450
451static void diagScanlineRenderTime(uint16_t row, PICO9918_PIXEL_T* pixels)
452{
453 renderLeft("RENDER: ", &renderTimePerScanlineStr, "US", row, pixels);
454}
455
456static void diagGpuTime(uint16_t row, PICO9918_PIXEL_T* pixels)
457{
458 renderLeft("GPU : ", &gpuPctStr, "%", row, pixels);
459}
460
461#if PICO9918_DIAG_GPU_FRAME_COUNTER
462static void diagGpuFrames(uint16_t row, PICO9918_PIXEL_T* pixels)
463{
464 renderLeft("GPU FR: ", &gpuFrameStr, "", row, pixels);
465}
466#endif
467
468static void diagFPS(uint16_t row, PICO9918_PIXEL_T* pixels)
469{
470 renderLeft("FPS : ", &fpsStr, "FPS", row, pixels);
471}
472
473static void diagTemp(uint16_t row, PICO9918_PIXEL_T* pixels)
474{
475 renderLeft("TEMP : ", &temperatureStr, "^C", row, pixels);
476}
477
478static void diagOutput(uint16_t row, PICO9918_PIXEL_T* pixels)
479{
480 renderLeft("OUTPUT: ", &outputStr, outputUnitsStr, row, pixels);
481}
482
483static void diagClock(uint16_t row, PICO9918_PIXEL_T* pixels)
484{
485 renderLeft("CLOCK : ", &clockMhzStr, "MHZ", row, pixels);
486}
487
488static void diagNameTab(uint16_t row, PICO9918_PIXEL_T* pixels)
489{
490 renderLeft("NAME : >", &nameTabStr, "", row, pixels);
491}
492
493static void diagColorTab(uint16_t row, PICO9918_PIXEL_T* pixels)
494{
495 renderLeft("COLOR : >", &colorTabStr, "", row, pixels);
496}
497
498static void diagPattTab(uint16_t row, PICO9918_PIXEL_T* pixels)
499{
500 renderLeft("PATT : >", &pattTabStr, "", row, pixels);
501}
502
503static void diagSprAttrTab(uint16_t row, PICO9918_PIXEL_T* pixels)
504{
505 renderLeft("SP ATR: >", &sprAttTabStr, "", row, pixels);
506}
507
508static void diagSprPattTab(uint16_t row, PICO9918_PIXEL_T* pixels)
509{
510 renderLeft("SP PAT: >", &sprPattTabStr, "", row, pixels);
511}
512
513static void diagMode(uint16_t row, PICO9918_PIXEL_T* pixels)
514{
515 renderLeft("MODE : ", &modeStr, "", row, pixels);
516}
517
518typedef void (*DiagPtr)(uint16_t, PICO9918_PIXEL_T*);
519
520static DiagPtr const performanceDiags[] = {&diagHwVer,
521 &diagFwVer,
522 &diagClock,
523 &diagOutput,
524 &diagRenderTime,
525 &diagScanlineRenderTime,
526 &diagFPS,
527 &diagGpuTime,
528#if PICO9918_DIAG_GPU_FRAME_COUNTER
529 &diagGpuFrames,
530#endif
531 &diagTemp};
532
533static DiagPtr const addressDiags[] = {&diagMode, &diagNameTab, &diagColorTab,
534 &diagPattTab, &diagSprAttrTab, &diagSprPattTab};
535
536/* every row either group can claim, plus the blank one each leaves after it */
537static DiagPtr leftDiags[sizeof(performanceDiags) / sizeof(performanceDiags[0]) +
538 sizeof(addressDiags) / sizeof(addressDiags[0]) + 2] = {0};
539static int leftDiagRows = 0;
540
542{
543 memset(leftDiags, 0, sizeof(leftDiags));
544
545 leftDiagRows = 0;
546
547 if (tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE])
548 {
549 for (int j = 0; j < sizeof(performanceDiags) / sizeof(void*); ++j) leftDiags[leftDiagRows++] = performanceDiags[j];
550 leftDiagRows++;
551 }
552
553 if (tms9918->config[PICO9918_CONF_DIAG_ADDRESS])
554 {
555 for (int j = 0; j < sizeof(addressDiags) / sizeof(void*); ++j) leftDiags[leftDiagRows++] = addressDiags[j];
556 leftDiagRows++;
557 }
558}
559
560static void renderPalette(PICO9918_INST_ARG int y, uint32_t vVirtualPixels, PICO9918_PIXEL_T* pixels)
561{
562 int row = y % 6;
563
564 uint8_t palette = (y - (vVirtualPixels - 24)) / 6;
565 if (palette < 4)
566 {
567 char buf[] = "PALETTE 0:";
568 buf[8] = '0' + palette;
569 renderRow(row, buf, leftXPos, labelColor, pixels);
570 uint32_t xPos = 32;
571 if (row < 5)
572 {
573 for (int c = 0; c < 16; ++c)
574 {
575 /* WARNING: the trailing `& 0xfff` is load-bearing, so do not swap this for pico9918_palette_lut.
576 The LUT replicates green into bits 15-12, and the RP2040 CRT-dim path shifts the pixel pair
577 right UNMASKED, landing bit 12 in blue's MSB. */
578 uint32_t color = tms9918->vram.map.pram[palette * 16 + c] & 0xFF0F;
579 color |= (color & 0xf000) >> 8;
580 color &= 0xfff;
581
582 /* two 16-bit pixels per 32-bit store, so 15 stores cover the 30-pixel swatch */
583 color |= color << 16;
584 uint32_t* pix32 = (uint32_t*)pixels;
585 for (int i = 0; i < 15; ++i)
586 {
587 pix32[xPos++] = color;
588 }
589 xPos++;
590 }
591 }
592 }
593}
594
595
596void pico9918_diag_render(PICO9918_INST_ARG uint16_t y, uint32_t vVirtualPixels, PICO9918_PIXEL_T* pixels)
597{
598 /* line 0 has no row above it, and the subtraction below would wrap it to 65535 */
599 if (y == 0) return;
600 y -= 1; // vertical border
601
602 // palette
603 if (tms9918->config[PICO9918_CONF_DIAG_PALETTE] && (y > ((int)vVirtualPixels - 27)))
604 renderPalette(PICO9918_INST y + 2, vVirtualPixels, pixels);
605
606 const unsigned diagRow6 = (unsigned)y / 6u;
607 int diagRow = (int)diagRow6;
608 int row = (int)((unsigned)y - diagRow6 * 6u);
609
610 int maxReg = 8;
611 if (PICO9918_UNLOCKED(tms9918))
612 {
613 maxReg += sizeof(extReg) / sizeof(extReg[0]);
614 }
615
616 // left panels
617 if (diagRow < leftDiagRows && leftDiags[diagRow] != NULL)
618 {
619 leftDiags[diagRow](row, pixels);
620 }
621
622 // registers
623 if (tms9918->config[PICO9918_CONF_DIAG_REGISTERS] && (diagRow < maxReg))
624 {
625 if (diagRow >= 8)
626 {
627 diagRow = extReg[diagRow - 8];
628 }
629
630 int xPos = 636 - (PICO9918_DIAG_CHAR_WIDTH * 13);
631 const unsigned regTens = (unsigned)diagRow / 10u;
632 char buf[] = "R00:";
633 buf[1] = '0' + regTens;
634 buf[2] = '0' + ((unsigned)diagRow - regTens * 10u);
635 xPos = renderRow(row, buf, xPos, labelColor, pixels);
636 xPos = backgroundPixels(xPos, 2, pixels);
637 xPos = renderRow(row, nibbleBinStr[TMS_REGISTER(tms9918, diagRow) >> 4], xPos, valueColor, pixels);
638 xPos = backgroundPixels(xPos, 2, pixels);
639 xPos = renderRow(row, nibbleBinStr[TMS_REGISTER(tms9918, diagRow) & 0xf], xPos, valueColor, pixels);
640 }
641}
void pico9918_diag_set_clock_hz(float clockHz)
system clock, Hz
Definition diag.c:265
void pico9918_diag_init(void)
one-time initialisation of the panel value strings
Definition diag.c:200
void pico9918_diag_set_temperature(float tempC)
core temperature, degrees C
Definition diag.c:260
void pico9918_diag_set_version_info(const char *hwVersion, const char *fwVersion)
Version identity for the HWVER / FWVER rows.
Definition diag.c:226
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
int pico9918_diag_render_text(uint16_t scanline, const char *text, uint16_t x, uint16_t y, PICO9918_PIXEL_T fg, PICO9918_PIXEL_T *pixels)
render text into the scanline buffer, if row scanline falls in the glyph band starting at y.
Definition diag.c:348
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_output_name(const char *name, const char *units)
Display-mode label for the OUTPUT row, e.g.
Definition diag.c:243
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
#define PICO9918_DIAG_CHAR_WIDTH
glyph cell width, pixels
Definition diag.h:46
#define PICO9918_DIAG_CHAR_HEIGHT
glyph cell height, pixels
Definition diag.h:47
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
Definition gpu.c:421
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
Definition gpu.c:412
pico9918-core - GPU Interface
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
Definition pico9918.c:3499
#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_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:73
pico9918-core - the private instance layout