|
5 | 5 | #include <ogc/lwp_watchdog.h> |
6 | 6 |
|
7 | 7 | #include "../common/common_utils.h" |
| 8 | +#include "gen_logo.h" |
8 | 9 | #include "n64.h" |
9 | 10 | #include "ppc_utils.h" |
10 | 11 |
|
| 12 | +// XFB is YUY2-packed: each 32-bit word holds [Y0 Cb Y1 Cr] for a 2-pixel |
| 13 | +// horizontal pair. RGB→YUV (BT.601 limited range) per ANSI cycle color. |
| 14 | +typedef struct { |
| 15 | + unsigned char y, cb, cr; |
| 16 | +} yuv_t; |
| 17 | + |
| 18 | +static const yuv_t cycle_yuv[] = { |
| 19 | + { 81, 90, 240}, // red |
| 20 | + {145, 54, 34}, // green |
| 21 | + {210, 16, 146}, // yellow |
| 22 | + { 41, 240, 110}, // blue |
| 23 | + {106, 202, 222}, // magenta |
| 24 | + {170, 166, 16}, // cyan |
| 25 | + {235, 128, 128}, // white |
| 26 | +}; |
| 27 | +#define CYCLE_LEN ((int)(sizeof(cycle_yuv) / sizeof(cycle_yuv[0]))) |
| 28 | + |
| 29 | +#define BLACK_Y 16 |
| 30 | +#define NEUTRAL_C 128 |
| 31 | + |
| 32 | +static inline u32 yuv_pair(u8 y0, u8 y1, u8 cb, u8 cr) { |
| 33 | + return ((u32)y0 << 24) | ((u32)cb << 16) | ((u32)y1 << 8) | cr; |
| 34 | +} |
| 35 | + |
| 36 | +static void xfb_clear_box(u32 *fb_words, int fb_pitch_words, int x_px, |
| 37 | + int y_px, int w, int h) { |
| 38 | + u32 black = yuv_pair(BLACK_Y, BLACK_Y, NEUTRAL_C, NEUTRAL_C); |
| 39 | + int x_pair = x_px / 2; |
| 40 | + int w_pair = w / 2; |
| 41 | + for (int row = 0; row < h; row++) { |
| 42 | + u32 *line = fb_words + (y_px + row) * fb_pitch_words + x_pair; |
| 43 | + for (int col = 0; col < w_pair; col++) line[col] = black; |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +static void xfb_draw_logo(u32 *fb_words, int fb_pitch_words, int x_px, |
| 48 | + int y_px, const yuv_t *col) { |
| 49 | + int x_pair = x_px / 2; |
| 50 | + for (int row = 0; row < LOGO_H; row++) { |
| 51 | + u32 *line = fb_words + (y_px + row) * fb_pitch_words + x_pair; |
| 52 | + const u8 *mask_row = &logo_mask[row * LOGO_BYTES_PER_ROW]; |
| 53 | + for (int col_pair = 0; col_pair < LOGO_W / 2; col_pair++) { |
| 54 | + int idx_a = col_pair * 2; |
| 55 | + int idx_b = idx_a + 1; |
| 56 | + bool lit_a = mask_row[idx_a / 8] & (0x80 >> (idx_a % 8)); |
| 57 | + bool lit_b = mask_row[idx_b / 8] & (0x80 >> (idx_b % 8)); |
| 58 | + u8 y0 = lit_a ? col->y : BLACK_Y; |
| 59 | + u8 y1 = lit_b ? col->y : BLACK_Y; |
| 60 | + // Cb/Cr are shared between the pair (4:2:2). Use logo color when at |
| 61 | + // least one of the pair is lit, neutral otherwise. |
| 62 | + bool any_lit = lit_a || lit_b; |
| 63 | + line[col_pair] = yuv_pair(y0, y1, any_lit ? col->cb : NEUTRAL_C, |
| 64 | + any_lit ? col->cr : NEUTRAL_C); |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
11 | 69 | #define CONSOLE_START_POS 20 |
12 | 70 |
|
13 | 71 | static void *xfb = NULL; |
@@ -324,53 +382,44 @@ int main(int argc, char **argv) { |
324 | 382 | diff_msec(last_activity, gettime()) >= IDLE_THRESHOLD_MS; |
325 | 383 |
|
326 | 384 | if (idle) { |
327 | | - // === Screensaver: clear once on entry, then bounce a color-cycling |
328 | | - // ASCII rendition of the joypad logo (logo_solid.svg in branding/) |
329 | | - // around the screen, erasing the prior frame each step. |
330 | | - static const char *logo[] = { |
331 | | - " _________ ", |
332 | | - " / + o o\\ ", |
333 | | - " ( +++ ^_^ )", |
334 | | - " \\ + o o / ", |
335 | | - " '---------' ", |
336 | | - }; |
337 | | - const int LOGO_W = 15; |
338 | | - const int LOGO_H = 5; |
339 | | - const char *blank_row = " "; // LOGO_W + 1 spaces |
| 385 | + // === Screensaver: bitmap-rendered joypad logo bounces around the XFB |
| 386 | + // directly. Color cycles through cycle_yuv[] on each wall hit. |
| 387 | + const int FB_W = rMode->fbWidth; |
| 388 | + const int FB_H = rMode->xfbHeight; |
| 389 | + const int FB_PITCH = FB_W / 2; // 32-bit words per scanline |
| 390 | + u32 *fb_words = (u32 *)xfb; |
340 | 391 |
|
341 | 392 | if (!screensaver_on) { |
342 | | - printf("\x1b[2J"); |
| 393 | + // Clear the entire framebuffer to black. |
| 394 | + u32 black = yuv_pair(BLACK_Y, BLACK_Y, NEUTRAL_C, NEUTRAL_C); |
| 395 | + for (int i = 0; i < FB_PITCH * FB_H; i++) fb_words[i] = black; |
343 | 396 | screensaver_on = true; |
344 | 397 | ss_prev_x = -1; |
| 398 | + ss_x = 80; // pixel coords now |
| 399 | + ss_y = 80; |
345 | 400 | } |
346 | 401 | ss_frame++; |
347 | | - // Move every 4th frame for a slower glide. Two LongWait(2) per loop |
348 | | - // iteration ≈ 33ms/frame, so this updates ~7-8 times per second. |
349 | 402 | if ((ss_frame & 3) == 0) { |
| 403 | + // Erase prior bounding box. |
350 | 404 | if (ss_prev_x >= 0) { |
351 | | - for (int row = 0; row < LOGO_H; row++) { |
352 | | - SetPosition(ss_prev_x, ss_prev_y + row); |
353 | | - printf("%s", blank_row); |
354 | | - } |
355 | | - } |
356 | | - ss_x += ss_dx; |
357 | | - ss_y += ss_dy; |
358 | | - // Bounds for 480p NTSC console: ~77 chars wide × ~28 tall. |
359 | | - // Logo's left edge can travel up to (width - LOGO_W) so its right |
360 | | - // edge reaches the screen's right side. |
361 | | - const int max_x = 77 - LOGO_W; |
362 | | - const int max_y = 23; |
363 | | - if (ss_x <= 0) { ss_x = 0; ss_dx = -ss_dx; ss_color = (ss_color % 7) + 1; } |
364 | | - if (ss_x >= max_x) { ss_x = max_x; ss_dx = -ss_dx; ss_color = (ss_color % 7) + 1; } |
365 | | - if (ss_y <= 1) { ss_y = 1; ss_dy = -ss_dy; ss_color = (ss_color % 7) + 1; } |
366 | | - if (ss_y >= max_y) { ss_y = max_y; ss_dy = -ss_dy; ss_color = (ss_color % 7) + 1; } |
367 | | - SetFgColor(ss_color, 2); |
368 | | - for (int row = 0; row < LOGO_H; row++) { |
369 | | - SetPosition(ss_x, ss_y + row); |
370 | | - printf("%s", logo[row]); |
| 405 | + xfb_clear_box(fb_words, FB_PITCH, ss_prev_x, ss_prev_y, LOGO_W, LOGO_H); |
371 | 406 | } |
372 | | - fflush(stdout); |
373 | | - ss_prev_x = ss_x; |
| 407 | + // Step + bounce. Move 4 px/update so the glide is visible without |
| 408 | + // racing across the screen. |
| 409 | + ss_x += ss_dx * 4; |
| 410 | + ss_y += ss_dy * 2; |
| 411 | + const int max_x = FB_W - LOGO_W - 24; // leave overscan margin |
| 412 | + const int max_y = FB_H - LOGO_H - 24; |
| 413 | + const int min_x = 24; |
| 414 | + const int min_y = 24; |
| 415 | + if (ss_x <= min_x) { ss_x = min_x; ss_dx = -ss_dx; ss_color = (ss_color + 1) % CYCLE_LEN; } |
| 416 | + if (ss_x >= max_x) { ss_x = max_x; ss_dx = -ss_dx; ss_color = (ss_color + 1) % CYCLE_LEN; } |
| 417 | + if (ss_y <= min_y) { ss_y = min_y; ss_dy = -ss_dy; ss_color = (ss_color + 1) % CYCLE_LEN; } |
| 418 | + if (ss_y >= max_y) { ss_y = max_y; ss_dy = -ss_dy; ss_color = (ss_color + 1) % CYCLE_LEN; } |
| 419 | + // Pixel positions must be even (XFB packs 2 px/word). |
| 420 | + int draw_x = ss_x & ~1; |
| 421 | + xfb_draw_logo(fb_words, FB_PITCH, draw_x, ss_y, &cycle_yuv[ss_color]); |
| 422 | + ss_prev_x = draw_x; |
374 | 423 | ss_prev_y = ss_y; |
375 | 424 | } |
376 | 425 | LongWait(2); |
|
0 commit comments