15#include "overlay/bmp_font.h"
30 char digits[
sizeof(uint32_t) * 3];
52#if PICO9918_DIAG_GPU_FRAME_COUNTER
61static const char* outputUnitsStr =
"";
69uint32_t accumulatedRenderTime = 0;
70uint32_t accumulatedFrameTime = 0;
71uint32_t accumulatedScanlines = 0;
72uint32_t lastUpdateTime = 0;
91#define DIAG_COLOR(pramOrder) ((PICO9918_PIXEL_T)(PICO9918_PIXEL_FROM_RGB12(pramOrder) & 0x0fff))
93const PICO9918_PIXEL_T labelColor = DIAG_COLOR(0xff07);
94const PICO9918_PIXEL_T valueColor = DIAG_COLOR(0xff0f);
95const PICO9918_PIXEL_T unitsColor = DIAG_COLOR(0x8808);
97static float precLookup[] = {1.0f, 10.0f, 100.0f, 1000.0f};
100static void flt2Str(
float flt,
int prec,
IntString* out)
102 if (prec > 3) prec = 3;
103 flt *= precLookup[prec];
104 uint32_t number = (uint32_t)(flt + 0.5f);
106 out->start =
sizeof(out->digits) - 1;
107 out->digits[out->start] =
'\0';
110 out->digits[--out->start] =
'0' + (number % 10);
113 out->digits[--out->start] =
'.';
116 out->digits[--out->start] =
'0';
120 while (number && out->start)
122 out->digits[--out->start] =
'0' + (number % 10);
128#if PICO9918_DIAG_GPU_FRAME_COUNTER
130static void uint2Str(uint32_t number,
int width,
IntString* out)
132 out->start =
sizeof(out->digits) - 1;
133 out->digits[out->start] =
'\0';
134 while (number && out->start)
136 out->digits[--out->start] =
'0' + (number % 10);
143 out->digits[--out->start] =
'0';
150static void uint2hexStr(uint32_t number,
int width,
IntString* out)
152 out->start =
sizeof(out->digits) - 1;
153 out->digits[out->start] =
'\0';
154 while (number && out->start)
156 uint32_t nibble = number % 16;
159 out->digits[--out->start] =
'0' + nibble;
161 out->digits[--out->start] =
'A' - 10 + nibble;
167 out->digits[--out->start] =
'0';
177#define INK_WORDS (PICO9918_DIAG_CHAR_WIDTH / PICO9918_INK_PIXELS)
178#define INK_STRIDE (INK_WORDS <= 4 ? 4 : 8)
182static PICO9918_INK_T glyphMask[64][INK_STRIDE];
184static void PICO9918_IN_FLASH_FUNC(glyphMaskInit)(void)
186 for (uint32_t b = 0; b < 64; ++b)
188 for (uint32_t w = 0; w < INK_WORDS; ++w)
190 PICO9918_INK_T m = 0;
191 for (uint32_t k = 0; k < PICO9918_INK_PIXELS; ++k)
193 if (b & (0x20u >> (w * PICO9918_INK_PIXELS + k))) m |= PICO9918_INK_ONE(k);
204 clear(&frameTimeStr);
206#if PICO9918_DIAG_GPU_FRAME_COUNTER
209 clear(&renderTimePerScanlineStr);
210 clear(&temperatureStr);
220 clear(&sprAttTabStr);
221 clear(&sprPattTabStr);
230 strncpy(hwVerStr.digits, hwVersion,
sizeof(hwVerStr.digits) - 1);
231 hwVerStr.digits[
sizeof(hwVerStr.digits) - 1] =
'\0';
237 strncpy(fwVerStr.digits, fwVersion,
sizeof(fwVerStr.digits) - 1);
238 fwVerStr.digits[
sizeof(fwVerStr.digits) - 1] =
'\0';
247 strncpy(outputStr.digits, name,
sizeof(outputStr.digits) - 1);
248 outputStr.digits[
sizeof(outputStr.digits) - 1] =
'\0';
252 if (units) outputUnitsStr = units;
255const char* modeNames[] = {
256 "GFX I",
"GFX II",
"TEXT",
"MULTI",
"80 COL",
262 flt2Str(tempC, 2, &temperatureStr);
267 flt2Str(clockHz / 1000000.0f, 1, &clockMhzStr);
272static float hostFrameRateHz = 0.0f;
276 hostFrameRateHz = frameRateHz;
282 const uint32_t framesPerUpdate = 1 << 2;
285 const uint32_t phase = frameCount & (framesPerUpdate - 1);
286 if (tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE])
290 flt2Str((
float)(accumulatedFrameTime / framesPerUpdate) / 1000.0f, 3, &frameTimeStr);
293 if (accumulatedScanlines)
295 flt2Str((
float)accumulatedRenderTime / accumulatedScanlines, 2, &renderTimePerScanlineStr);
298 accumulatedRenderTime = accumulatedFrameTime = accumulatedScanlines = 0;
300 uint32_t currentTime = PICO9918_HOST_TIME_US();
303 uint32_t totalTime = currentTime - lastUpdateTime;
306 flt2Str(gpuPct, 4, &gpuPctStr);
309#if PICO9918_DIAG_GPU_FRAME_COUNTER
310 uint2Str(pico9918_gpu_frame_count, 1, &gpuFrameStr);
313 lastUpdateTime = currentTime;
318 flt2Str((16.0f - pico9918_dropped_frames_count) * (hostFrameRateHz / 16.0f), 2, &fpsStr);
322 if (tms9918->config[PICO9918_CONF_DIAG_ADDRESS])
326 uint2hexStr((TMS_REGISTER(tms9918, TMS_REG_NAME_TABLE) & 0x0f) << 10, 4, &nameTabStr);
329 uint2hexStr((TMS_REGISTER(tms9918, TMS_REG_COLOR_TABLE) & mask) << 6, 4, &colorTabStr);
332 uint2hexStr(((TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & mask) << 11) & 0xffff, 4, &pattTabStr);
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);
338 char* d = modeStr.digits;
349 PICO9918_PIXEL_T* pixels)
351 const int fontY = scanline - y;
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);
360 while ((c = *s++) != 0)
363 const PICO9918_INK_T* __restrict m = glyphMask[fontRow[c]];
364 for (
int w = 0; w < INK_WORDS; ++w)
366 p[w] = (ink & m[w]) | (PICO9918_INK_DARKEN(p[w]) & ~m[w]);
370 return (PICO9918_PIXEL_T*)p - pixels;
375PICO9918_INLINE
int renderRow(uint16_t row,
const char* text, uint16_t x, PICO9918_PIXEL_T fg,
376 PICO9918_PIXEL_T* pixels)
387PICO9918_INLINE
int renderNum(uint16_t row,
IntString* str, uint16_t x, PICO9918_PIXEL_T fg, PICO9918_PIXEL_T* pixels)
389 return renderRow(row, str->digits + str->start, x, fg, pixels);
395 ++accumulatedScanlines;
396 accumulatedRenderTime += renderTime;
397 accumulatedFrameTime += frameTime;
402static int backgroundPixels(
int xPos,
int count, PICO9918_PIXEL_T* pixels)
404 PICO9918_INK_T* p = (PICO9918_INK_T*)(pixels + xPos);
405 for (
int i = count / PICO9918_INK_PIXELS; i > 0; --i)
407 *p = PICO9918_INK_DARKEN(*p);
415static const char nibbleBinStr[16][8] = {
416 "((((",
"((()",
"(()(",
"(())",
"()((",
"()()",
"())(",
"()))",
417 ")(((",
")(()",
")()(",
")())",
"))((",
"))()",
")))(",
"))))",
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};
424const uint32_t leftXPos = 2;
426static void renderLeft(
const char* label,
IntString* val,
const char* units, uint16_t row,
427 PICO9918_PIXEL_T* pixels)
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);
436static void diagHwVer(uint16_t row, PICO9918_PIXEL_T* pixels)
438 renderLeft(
"HWVER : ", &hwVerStr,
"", row, pixels);
441static void diagFwVer(uint16_t row, PICO9918_PIXEL_T* pixels)
443 renderLeft(
"FWVER : ", &fwVerStr,
"", row, pixels);
446static void diagRenderTime(uint16_t row, PICO9918_PIXEL_T* pixels)
448 renderLeft(
"FRAME : ", &frameTimeStr,
"&S", row, pixels);
451static void diagScanlineRenderTime(uint16_t row, PICO9918_PIXEL_T* pixels)
453 renderLeft(
"RENDER: ", &renderTimePerScanlineStr,
"US", row, pixels);
456static void diagGpuTime(uint16_t row, PICO9918_PIXEL_T* pixels)
458 renderLeft(
"GPU : ", &gpuPctStr,
"%", row, pixels);
461#if PICO9918_DIAG_GPU_FRAME_COUNTER
462static void diagGpuFrames(uint16_t row, PICO9918_PIXEL_T* pixels)
464 renderLeft(
"GPU FR: ", &gpuFrameStr,
"", row, pixels);
468static void diagFPS(uint16_t row, PICO9918_PIXEL_T* pixels)
470 renderLeft(
"FPS : ", &fpsStr,
"FPS", row, pixels);
473static void diagTemp(uint16_t row, PICO9918_PIXEL_T* pixels)
475 renderLeft(
"TEMP : ", &temperatureStr,
"^C", row, pixels);
478static void diagOutput(uint16_t row, PICO9918_PIXEL_T* pixels)
480 renderLeft(
"OUTPUT: ", &outputStr, outputUnitsStr, row, pixels);
483static void diagClock(uint16_t row, PICO9918_PIXEL_T* pixels)
485 renderLeft(
"CLOCK : ", &clockMhzStr,
"MHZ", row, pixels);
488static void diagNameTab(uint16_t row, PICO9918_PIXEL_T* pixels)
490 renderLeft(
"NAME : >", &nameTabStr,
"", row, pixels);
493static void diagColorTab(uint16_t row, PICO9918_PIXEL_T* pixels)
495 renderLeft(
"COLOR : >", &colorTabStr,
"", row, pixels);
498static void diagPattTab(uint16_t row, PICO9918_PIXEL_T* pixels)
500 renderLeft(
"PATT : >", &pattTabStr,
"", row, pixels);
503static void diagSprAttrTab(uint16_t row, PICO9918_PIXEL_T* pixels)
505 renderLeft(
"SP ATR: >", &sprAttTabStr,
"", row, pixels);
508static void diagSprPattTab(uint16_t row, PICO9918_PIXEL_T* pixels)
510 renderLeft(
"SP PAT: >", &sprPattTabStr,
"", row, pixels);
513static void diagMode(uint16_t row, PICO9918_PIXEL_T* pixels)
515 renderLeft(
"MODE : ", &modeStr,
"", row, pixels);
518typedef void (*DiagPtr)(uint16_t, PICO9918_PIXEL_T*);
520static DiagPtr
const performanceDiags[] = {&diagHwVer,
525 &diagScanlineRenderTime,
528#if PICO9918_DIAG_GPU_FRAME_COUNTER
533static DiagPtr
const addressDiags[] = {&diagMode, &diagNameTab, &diagColorTab,
534 &diagPattTab, &diagSprAttrTab, &diagSprPattTab};
537static DiagPtr leftDiags[
sizeof(performanceDiags) /
sizeof(performanceDiags[0]) +
538 sizeof(addressDiags) /
sizeof(addressDiags[0]) + 2] = {0};
539static int leftDiagRows = 0;
543 memset(leftDiags, 0,
sizeof(leftDiags));
547 if (tms9918->config[PICO9918_CONF_DIAG_PERFORMANCE])
549 for (
int j = 0; j <
sizeof(performanceDiags) /
sizeof(
void*); ++j) leftDiags[leftDiagRows++] = performanceDiags[j];
553 if (tms9918->config[PICO9918_CONF_DIAG_ADDRESS])
555 for (
int j = 0; j <
sizeof(addressDiags) /
sizeof(
void*); ++j) leftDiags[leftDiagRows++] = addressDiags[j];
560static void renderPalette(
PICO9918_INST_ARG int y, uint32_t vVirtualPixels, PICO9918_PIXEL_T* pixels)
564 uint8_t palette = (y - (vVirtualPixels - 24)) / 6;
567 char buf[] =
"PALETTE 0:";
568 buf[8] =
'0' + palette;
569 renderRow(row, buf, leftXPos, labelColor, pixels);
573 for (
int c = 0; c < 16; ++c)
578 uint32_t color = tms9918->vram.map.pram[palette * 16 + c] & 0xFF0F;
579 color |= (color & 0xf000) >> 8;
583 color |= color << 16;
584 uint32_t* pix32 = (uint32_t*)pixels;
585 for (
int i = 0; i < 15; ++i)
587 pix32[xPos++] = color;
603 if (tms9918->config[PICO9918_CONF_DIAG_PALETTE] && (y > ((
int)vVirtualPixels - 27)))
606 const unsigned diagRow6 = (unsigned)y / 6u;
607 int diagRow = (int)diagRow6;
608 int row = (int)((
unsigned)y - diagRow6 * 6u);
611 if (PICO9918_UNLOCKED(tms9918))
613 maxReg +=
sizeof(extReg) /
sizeof(extReg[0]);
617 if (diagRow < leftDiagRows && leftDiags[diagRow] != NULL)
619 leftDiags[diagRow](row, pixels);
623 if (tms9918->config[PICO9918_CONF_DIAG_REGISTERS] && (diagRow < maxReg))
627 diagRow = extReg[diagRow - 8];
631 const unsigned regTens = (unsigned)diagRow / 10u;
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);
void pico9918_diag_set_clock_hz(float clockHz)
system clock, Hz
void pico9918_diag_init(void)
one-time initialisation of the panel value strings
void pico9918_diag_set_temperature(float tempC)
core temperature, degrees C
void pico9918_diag_set_version_info(const char *hwVersion, const char *fwVersion)
Version identity for the HWVER / FWVER rows.
void pico9918_diag_update(pico9918_t *tms9918, uint32_t frameCount)
recompute the panel values - call once per frame
void pico9918_diag_config_updated(pico9918_t *tms9918)
rebuild the panel row table - call whenever the PICO9918_CONF_DIAG* bytes change
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.
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
void pico9918_diag_set_output_name(const char *name, const char *units)
Display-mode label for the OUTPUT row, e.g.
void pico9918_diag_set_frame_rate(float frameRateHz)
Host display timing, Hz.
void pico9918_diag_update_render_time(uint32_t renderTime, uint32_t frameTime)
accumulate one scanline's render and total time, in microseconds
pico9918-core - Diagnostics overlay
#define PICO9918_DIAG_CHAR_WIDTH
glyph cell width, pixels
#define PICO9918_DIAG_CHAR_HEIGHT
glyph cell height, pixels
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
pico9918-core - GPU Interface
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
#define PICO9918_INST_ONLY
pass the instance as the only argument
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
#define PICO9918_INST
pass the instance ahead of other arguments
pico9918-core - the private instance layout