@@ -33,6 +33,7 @@ static inline u32 yuv_pair(u8 y0, u8 y1, u8 cb, u8 cr) {
3333 return ((u32 )y0 << 24 ) | ((u32 )cb << 16 ) | ((u32 )y1 << 8 ) | cr ;
3434}
3535
36+ __attribute__((unused ))
3637static void xfb_clear_box (u32 * fb_words , int fb_pitch_words , int x_px ,
3738 int y_px , int w , int h ) {
3839 u32 black = yuv_pair (BLACK_Y , BLACK_Y , NEUTRAL_C , NEUTRAL_C );
@@ -71,6 +72,51 @@ static void xfb_draw_logo(u32 *fb_words, int fb_pitch_words, int x_px,
7172 LOGO_H , LOGO_BYTES_PER_ROW , col );
7273}
7374
75+ // Single-pass screensaver composer: walks the union rect of (new logo,
76+ // old logo), writing each pixel pair exactly once. Pixels inside the new
77+ // logo bbox use mask + color, pixels outside that bbox (including where
78+ // the old logo was) get plain black. Eliminates the brief
79+ // just-erased/not-yet-drawn window that the old erase+draw two-step
80+ // created — that window was the source of the screensaver flicker on
81+ // scanouts that landed inside it.
82+ static void xfb_compose_logo (u32 * fb_words , int fb_pitch_words ,
83+ int new_x , int new_y , int prev_x , int prev_y ,
84+ const yuv_t * col ) {
85+ // Even-aligned union bbox (XFB packs 2 px / word).
86+ int ux1 = (new_x < prev_x ? new_x : prev_x ) & ~1 ;
87+ int uy1 = new_y < prev_y ? new_y : prev_y ;
88+ int rmax = (new_x + LOGO_W > prev_x + LOGO_W ? new_x : prev_x ) + LOGO_W ;
89+ int ux2 = (rmax + 1 ) & ~1 ; // round up to even
90+ int uy2 = (new_y + LOGO_H > prev_y + LOGO_H ? new_y : prev_y ) + LOGO_H ;
91+ if (ux1 < 0 ) ux1 = 0 ;
92+ if (uy1 < 0 ) uy1 = 0 ;
93+ const u8 black_y = BLACK_Y , neutral = NEUTRAL_C ;
94+
95+ for (int y = uy1 ; y < uy2 ; y ++ ) {
96+ int rel_y = y - new_y ;
97+ bool y_in_logo = rel_y >= 0 && rel_y < LOGO_H ;
98+ const u8 * mask_row = y_in_logo ? & logo_mask [rel_y * LOGO_BYTES_PER_ROW ]
99+ : NULL ;
100+ u32 * line = fb_words + y * fb_pitch_words ;
101+ for (int x = ux1 ; x < ux2 ; x += 2 ) {
102+ bool lit_a = false, lit_b = false;
103+ if (mask_row ) {
104+ int rel_a = x - new_x ;
105+ int rel_b = rel_a + 1 ;
106+ if (rel_a >= 0 && rel_a < LOGO_W )
107+ lit_a = mask_row [rel_a / 8 ] & (0x80 >> (rel_a % 8 ));
108+ if (rel_b >= 0 && rel_b < LOGO_W )
109+ lit_b = mask_row [rel_b / 8 ] & (0x80 >> (rel_b % 8 ));
110+ }
111+ u8 ya = lit_a ? col -> y : black_y ;
112+ u8 yb = lit_b ? col -> y : black_y ;
113+ bool any = lit_a || lit_b ;
114+ line [x / 2 ] = yuv_pair (ya , yb , any ? col -> cb : neutral ,
115+ any ? col -> cr : neutral );
116+ }
117+ }
118+ }
119+
74120// Console column 0 sits at pixel x=20 (CONSOLE_START_POS, defined below).
75121// With cropped masks (no internal padding) the bounding box left edge IS
76122// the silhouette's visible edge — so placing the logo at x=20 makes its
@@ -424,36 +470,28 @@ int main(int argc, char **argv) {
424470 ss_x = 80 ; // pixel coords now
425471 ss_y = 80 ;
426472 }
427- // Update every frame with small increments → smooth glide.
428- if (ss_prev_x >= 0 ) {
429- xfb_clear_box (fb_words , FB_PITCH , ss_prev_x , ss_prev_y , LOGO_W , LOGO_H );
430- }
431- // 4px / 3px per frame at 30Hz → ~120/90 px/sec (interlaced display
432- // shows two fields per frame; updating once per pair keeps both
433- // fields displaying the SAME logo position so we don't get the
434- // even/odd-line flicker that line-doubled sprites create when
435- // their content shifts mid-frame).
473+ // Step + bounce.
436474 ss_x += ss_dx * 4 ;
437475 ss_y += ss_dy * 3 ;
438- // Bounce against the actual framebuffer edges. Whatever the TV
439- // overscan eats happens at the same outer ring regardless, so
440- // letting the sprite touch x=0 / x=FB_W-LOGO_W maximizes visible
441- // travel on CRTs with conservative overscan.
442476 const int max_x = FB_W - LOGO_W ;
443477 const int max_y = FB_H - LOGO_H ;
444- const int min_x = 0 ;
445- const int min_y = 0 ;
446- if (ss_x <= min_x ) { ss_x = min_x ; ss_dx = - ss_dx ; ss_color = (ss_color + 1 ) % CYCLE_LEN ; }
478+ if (ss_x <= 0 ) { ss_x = 0 ; ss_dx = - ss_dx ; ss_color = (ss_color + 1 ) % CYCLE_LEN ; }
447479 if (ss_x >= max_x ) { ss_x = max_x ; ss_dx = - ss_dx ; ss_color = (ss_color + 1 ) % CYCLE_LEN ; }
448- if (ss_y <= min_y ) { ss_y = min_y ; ss_dy = - ss_dy ; ss_color = (ss_color + 1 ) % CYCLE_LEN ; }
480+ if (ss_y <= 0 ) { ss_y = 0 ; ss_dy = - ss_dy ; ss_color = (ss_color + 1 ) % CYCLE_LEN ; }
449481 if (ss_y >= max_y ) { ss_y = max_y ; ss_dy = - ss_dy ; ss_color = (ss_color + 1 ) % CYCLE_LEN ; }
450- // Pixel positions must be even (XFB packs 2 px/word).
451- int draw_x = ss_x & ~1 ;
452- xfb_draw_logo (fb_words , FB_PITCH , draw_x , ss_y , & cycle_yuv [ss_color ]);
482+ int draw_x = ss_x & ~1 ; // even alignment for XFB pair packing
483+ int prev_x = ss_prev_x >= 0 ? ss_prev_x : draw_x ;
484+ int prev_y = ss_prev_x >= 0 ? ss_prev_y : ss_y ;
485+ // One pass over the union of (new, old) bbox: each pixel-pair gets
486+ // its final value (logo or black) written exactly once. No
487+ // intermediate just-cleared-not-yet-drawn frames for scanout to
488+ // catch → no flicker.
489+ xfb_compose_logo (fb_words , FB_PITCH , draw_x , ss_y , prev_x , prev_y ,
490+ & cycle_yuv [ss_color ]);
453491 ss_prev_x = draw_x ;
454492 ss_prev_y = ss_y ;
455- LongWait (2 ); // 30 Hz update — pairs an even+odd field on the
456- // same logo position, eliminating interlace flicker .
493+ LongWait (2 ); // 30 Hz update — both interlaced fields show the
494+ // same logo position, so even/odd lines agree .
457495 continue ;
458496 }
459497
0 commit comments