1 //#define BENCHMARK_GFX
2 /*
3  * ============================================================================
4  *  Title:    Graphics Interface Routines
5  *  Author:   J. Zbiciak, J. Tanner
6  * ============================================================================
7  *  GFX_INIT         -- Initializes a gfx_t object.
8  *  GFX_TICK         -- Services a gfx_t tick.
9  *  GFX_VID_ENABLE   -- Alert gfx that video has been enabled or blanked
10  *  GFX_SET_BORD     -- Set the border / offset parameters for the display
11  * ============================================================================
12  *  GFX_T            -- Graphics subsystem object.
13  *  GFX_PVT_T        -- Private internal state to gfx_t structure.
14  * ============================================================================
15  *  The graphics subsystem provides an abstraction layer between the
16  *  emulator and the graphics library being used.  Theoretically, this
17  *  should allow easy porting to other graphics libraries.
18  *
19  *  TODO:
20  *   -- Make use of dirty rectangle updating for speed.
21  * ============================================================================
22  */
23 
24 #include "sdl_jzintv.h"
25 #include "config.h"
26 #include "periph/periph.h"
27 #include "gfx/gfx.h"
28 #include "gfx/gfx_prescale.h"
29 #include "gfx/gfx_scale.h"
30 //#include "file/file.h"
31 #include "mvi/mvi.h"
32 #include "avi/avi.h"
33 #include "lzoe/lzoe.h"
34 #include "file/file.h"
35 
36 #ifdef PLAT_MACOS
37 #include "gfx/gfx_sdl2_osx.h"
38 #endif
39 
40 const double frame_delta = 0.0166;  /* Slightly faster than 60Hz.           */
41 
42 /*
43  * ============================================================================
44  *  GFX_PVT_T        -- Private internal state to gfx_t structure.
45  * ============================================================================
46  */
47 typedef struct gfx_pvt_t
48 {
49     SDL_Window      *wind;          /*  Main window.                        */
50     SDL_Renderer    *rend;          /*  Renders texture onto surface.       */
51     SDL_PixelFormat *pixf;          /*  Screen surface.                     */
52     SDL_Texture     *text;          /*  Main surface texture.               */
53     SDL_Color   pal_on [32];        /*  Palette when video is enabled.      */
54     SDL_Color   pal_off[32];        /*  Palette when video is blanked.      */
55     int         vid_enable;         /*  Video enable flag.                  */
56     int         border_x, border_y; /*  X/Y border padding.                 */
57     int         dim_x, dim_y;       /*  X/Y dimensions of window.           */
58     int         ofs_x, ofs_y;       /*  X/Y offsets for centering img.      */
59     int         bpp;                /*  Actual color depth.                 */
60     int         flags;              /*  Flags for current display window.   */
61 
62     /* For GFX_DROP_EXTRA only: */
63     double      last_frame;         /*  Wallclock time of next frame.       */
64 
65     uint8_t *RESTRICT inter_vid;    /*  Intermediate video after prescaler  */
66     uint8_t *RESTRICT prev;         /*  previous frame for dirty-rect       */
67 
68     gfx_prescaler_t      *prescaler; /* Scale 160x200 to an intermediate    */
69     gfx_prescaler_dtor_t *ps_dtor;   /* Destructor for prescaler, if any.   */
70     void                 *ps_opaque; /* Prescaler opaque structure          */
71     gfx_scale_spec_t     scaler;
72 
73     gfx_dirtyrect_spec  dr_spec;    /*  Dirty-rectangle control spec.       */
74 
75 
76     uint32_t    *dirty_rows;        /*  dirty-row bitmap for scaler         */
77     int         dirty_rows_sz;
78 
79     int         num_rects;
80     SDL_Rect    *dirty_rects;
81 } gfx_pvt_t;
82 
83 LOCAL void gfx_dtor(periph_t *const p);
84 LOCAL void gfx_tick(gfx_t *gfx);
85 LOCAL void gfx_find_dirty_rects(gfx_t *gfx);
86 
87 /* ======================================================================== */
88 /*  GFX_SDL_ABORT    -- Abort due to SDL errors.                            */
89 /* ======================================================================== */
gfx_sdl_abort(const char * context)90 LOCAL void gfx_sdl_abort(const char *context)
91 {
92     fprintf(stderr, "gfx: %s\ngfx/SDL Error:%s\n", context, SDL_GetError());
93     exit(1);
94 }
95 
96 /* ======================================================================== */
97 /*  GFX_SET_SCALER_PALETTE                                                  */
98 /* ======================================================================== */
gfx_set_scaler_palette(gfx_scale_spec_t * const scaler,const SDL_PixelFormat * const pix_fmt,const SDL_Color * const pal)99 LOCAL void gfx_set_scaler_palette
100 (
101     gfx_scale_spec_t       *const scaler,
102     const SDL_PixelFormat  *const pix_fmt,
103     const SDL_Color        *const pal
104 )
105 {
106     for (int i = 0; i < 32; i++)
107         gfx_scale_set_palette(scaler, i,
108             SDL_MapRGB(pix_fmt, pal[i].r, pal[i].g, pal[i].b));
109 }
110 
111 /* ======================================================================== */
112 /*  GFX_TEARDOWN_SDL_DISPLAY                                                */
113 /*  Tears down the existing surface, renderer, texture, and window, if any. */
114 /* ======================================================================== */
gfx_teardown_sdl_display(gfx_t * gfx)115 LOCAL void gfx_teardown_sdl_display(gfx_t *gfx)
116 {
117     if (gfx->pvt->text) SDL_DestroyTexture(gfx->pvt->text);
118     if (gfx->pvt->pixf) SDL_FreeFormat(gfx->pvt->pixf);
119     if (gfx->pvt->rend) SDL_DestroyRenderer(gfx->pvt->rend);
120     if (gfx->pvt->wind) SDL_DestroyWindow(gfx->pvt->wind);
121 
122     gfx->pvt->text = NULL;
123     gfx->pvt->pixf = NULL;
124     gfx->pvt->rend = NULL;
125     gfx->pvt->wind = NULL;
126 }
127 
128 /* ======================================================================== */
129 /*  GFX_SETUP_SDL_DISPLAY:  Do all the dirty SDL dirty work for setting up  */
130 /*                          the display.  This gets called during init, or  */
131 /*                          when toggling between full-screen and windowed  */
132 /* ======================================================================== */
gfx_setup_sdl_display(gfx_t * gfx,uint32_t gfx_flags,int quiet)133 LOCAL int gfx_setup_sdl_display
134 (
135     gfx_t *gfx, uint32_t gfx_flags, int quiet
136 )
137 {
138     /* Target width / height of the border.  Actual border may be larger.   */
139     const int bord_x = gfx->pvt->border_x;
140     const int bord_y = gfx->pvt->border_y;
141 
142     /* Actual dims of the scaler output, and thus our texture dimensions.   */
143     const int text_x = gfx->pvt->scaler.actual_x;
144     const int text_y = gfx->pvt->scaler.actual_y;
145 
146     /* Our desired window size / physical display mode.                     */
147     const int tgt_wind_x = text_x + bord_x;
148     const int tgt_wind_y = text_y + bord_y;
149     //const unsigned tgt_wind_bpp = gfx->pvt->scaler.bpp;
150     const unsigned tgt_wind_bpp = 32;  /* For now, force to 32bpp. */
151 
152     /* The actual window size / physical display mode.                      */
153     int wind_x = tgt_wind_x, wind_y = tgt_wind_y;
154 
155     /* -------------------------------------------------------------------- */
156     /*  Force dirty-rectangles off.  It's not clear we can support them in  */
157     /*  SDL2:  Streaming textures aren't guaranteed to persist between      */
158     /*  locks.  Likewise, the renderer backdrop is also not guaranteed to   */
159     /*  persist after SDL_RenderPresent().                                  */
160     /* -------------------------------------------------------------------- */
161     gfx_flags &= ~GFX_DRECTS;
162 
163     /* -------------------------------------------------------------------- */
164     /*  Set up the SDL video flags from our flags.                          */
165     /* -------------------------------------------------------------------- */
166     if (tgt_wind_bpp == 32)
167         gfx_flags &= ~GFX_HWPAL;    /*  No hardware palette in 32-bpp.      */
168 
169     if ((gfx_flags & GFX_DRECTS) != 0)
170         gfx_flags &= ~GFX_DBLBUF;   /*  No double-buffering w/dirty rects.  */
171 
172     const bool request_fullscreen = gfx_flags & GFX_FULLSC;
173     uint32_t wind_flags =
174         SDL_WINDOW_INPUT_FOCUS      /*  Should we or shouldn't we?          */
175       | SDL_WINDOW_SHOWN
176       | (request_fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
177 
178     /*  The code currently is not "high-DPI" aware, and so does the wrong   */
179     /*  thing if you turn on High DPI mode.  We don't need it anyway.       */
180     //| SDL_WINDOW_ALLOW_HIGHDPI
181 
182     const bool software_surface = gfx_flags & GFX_SWSURF;
183     const bool disable_vsync    = gfx_flags & GFX_ASYNCB;
184     uint32_t rend_flags =
185         (software_surface ? SDL_RENDERER_SOFTWARE : SDL_RENDERER_ACCELERATED)
186       | (disable_vsync    ? 0 : SDL_RENDERER_PRESENTVSYNC);
187 
188     /* -------------------------------------------------------------------- */
189     /*  Try to allocate a screen surface at the desired size, etc.          */
190     /* -------------------------------------------------------------------- */
191     if (!quiet)
192     {
193         jzp_printf("gfx:  Searching for video modes near %dx%dx%d, %s:\n",
194            tgt_wind_x, tgt_wind_y, tgt_wind_bpp,
195            request_fullscreen ? "Full screen" : "Windowed");
196 
197         jzp_flush();
198     }
199 
200     /* -------------------------------------------------------------------- */
201     /*  Work in progress for SDL2.                                          */
202     /*  For now, just work with display 0 and let SDL2 pick window size.    */
203     /* -------------------------------------------------------------------- */
204 #if 0
205     if (!request_fullscreen)
206     {
207         /* In windowed mode, just go with the requested dimensions. */
208         wind_x = tgt_wind_x;
209         wind_y = tgt_wind_y;
210     } else
211     {
212         const int curr_disp = 0;
213         const int num_modes = SDL_GetNumDisplayModes(curr_disp);
214         SDL_DisplayMode disp_mode;
215 
216         /* ---------------------------------------------------------------- */
217         /*  SDL_GetDisplayMode returns a list sorted largest to smallest.   */
218         /*  Find the smallest mode >= the size requested.                   */
219         /* ---------------------------------------------------------------- */
220         const int tgt_wind_area = tgt_wind_x * tgt_wind_y;
221         int best = -1, area_diff, best_area_diff = INT_MAX;
222 
223         for (int i = 0; i < num_modes; ++i)
224         {
225             if (SDL_GetDisplayMode(curr_disp, i, &disp_mode))
226             {
227                 jzp_printf("gfx:  Warning, SDL2 returned %s when querying "
228                            "disp mode %d of %d\n", SDL_GetError(), i,
229                            num_modes);
230                 break;
231             }
232 
233             if (SDL_BITSPERPIXEL(disp_mode.format) != tgt_wind_bpp)
234                 continue;
235 
236             if (!quiet)
237                 jzp_printf("gfx:  Considering %dx%d... ",
238                            disp_mode.w, disp_mode.h);
239             if (disp_mode.w >= tgt_wind_x && disp_mode.h >= tgt_wind_y)
240             {
241                 area_diff = disp_mode.w * disp_mode.h - tgt_wind_area;
242 
243                 if (best_area_diff > area_diff)
244                 {
245                     best_area_diff = area_diff;
246                     best = i;
247 
248                     if (!quiet)
249                         jzp_printf("New best fit.  Diff = %d\n", area_diff);
250 
251                     if (!best_area_diff)
252                         break;
253                 } else
254                     if (!quiet)
255                         jzp_printf("Poorer fit.    Diff = %d\n", area_diff);
256             } else
257             {
258                 if (!quiet)
259                     jzp_printf("Too small.\n");
260             }
261         }
262 
263         /* No suitable mode available. */
264         if (best == -1)
265             gfx_sdl_abort("No suitable video mode.");
266 
267         SDL_GetDisplayMode(curr_disp, best, &disp_mode);
268         wind_x = disp_mode.w;
269         wind_y = disp_mode.h;
270     }
271 #endif
272 
273     /* -------------------------------------------------------------------- */
274     /*  Destroy the old window/surface/texture/renderer, if any.            */
275     /* -------------------------------------------------------------------- */
276     gfx_teardown_sdl_display(gfx);
277 
278     /* -------------------------------------------------------------------- */
279     /*  Set up the new window/surface/texture/renderer.                     */
280     /* -------------------------------------------------------------------- */
281     SDL_Window *const wind =
282         SDL_CreateWindow("jzintv",
283             SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
284             tgt_wind_x, tgt_wind_y, wind_flags);
285 
286     if (!wind) gfx_sdl_abort("Could not create window");
287 
288     SDL_GetWindowSize(wind, &wind_x, &wind_y);
289 
290     const uint32_t wind_pix_fmt = SDL_GetWindowPixelFormat(wind);
291     jzp_printf("gfx:  Window pix format: %s\n",
292         SDL_GetPixelFormatName(wind_pix_fmt));
293 
294     SDL_Renderer *rend = SDL_CreateRenderer(wind, -1, rend_flags);
295 
296     if (!rend && rend_flags != SDL_RENDERER_SOFTWARE)
297     {
298         jzp_printf("gfx: Could not create renderer with requested flags: %s\n"
299                    "     Trying again with software renderer, no VSync.\n",
300                    SDL_GetError());
301 
302         /* Try again with software renderer. */
303         rend_flags = SDL_RENDERER_SOFTWARE;
304         rend = SDL_CreateRenderer(wind, -1, rend_flags);
305         if (!rend) gfx_sdl_abort("Could not create renderer");
306     }
307 
308     /* Note: We only keep the surface around for the pixel format pointer.  */
309     SDL_PixelFormat *pixf = SDL_AllocFormat(wind_pix_fmt);
310 
311     SDL_Texture *text =
312         SDL_CreateTexture(rend, wind_pix_fmt, SDL_TEXTUREACCESS_STREAMING,
313                           text_x, text_y);
314 
315     uint32_t text_pix_fmt;
316     SDL_QueryTexture(text, &text_pix_fmt, NULL, NULL, NULL);
317 
318     /* Sanity check. */
319     if (text_pix_fmt != wind_pix_fmt)
320     {
321         jzp_printf("gfx: Texture pixel format doesn't match window's format. "
322                    "%u vs %u\n", text_pix_fmt, wind_pix_fmt);
323         exit(-1);
324     }
325 
326     /* Snapshot the final wind/rend flags, in case SDL2 overrode us. */
327     SDL_RendererInfo rend_info;
328     SDL_GetRendererInfo(rend, &rend_info);
329     const uint32_t act_wind_flags = SDL_GetWindowFlags(wind);
330     const uint32_t act_rend_flags = rend_info.flags;
331 
332     gfx->pvt->wind  = wind;
333     gfx->pvt->rend  = rend;
334     gfx->pvt->pixf  = pixf;
335     gfx->pvt->text  = text;
336     gfx->pvt->dim_x = wind_x;
337     gfx->pvt->dim_y = wind_y;
338     gfx->pvt->ofs_x = ((wind_x - text_x) >> 1) & (~3);
339     gfx->pvt->ofs_y =  (wind_y - text_y) >> 1;
340     gfx->pvt->bpp   = SDL_BYTESPERPIXEL(text_pix_fmt) * 8;
341     gfx->pvt->flags = gfx_flags;
342 
343     gfx->pvt->last_frame = get_time();
344 
345     if (!quiet)
346     {
347         jzp_printf("gfx:  Selected:  %dx%dx%d with:\n"
348            "gfx:      VSync: %s, Rend: %s, Windowed: %s\n"
349            "gfx:      Video Driver: '%s', Render Driver: '%s'\n",
350            wind_x, wind_y, gfx->pvt->bpp,
351            act_rend_flags & SDL_RENDERER_PRESENTVSYNC ? "Yes"      : "No",
352            act_rend_flags & SDL_RENDERER_SOFTWARE     ? "Software" : "Hardware",
353            act_wind_flags & SDL_WINDOW_FULLSCREEN     ? "No"       : "Yes",
354            SDL_GetCurrentVideoDriver(), rend_info.name);
355     }
356 
357 #if defined(PLAT_MACOS) && defined(USE_SDL2)
358     /* -------------------------------------------------------------------- */
359     /*  SDL2 2.0.12 and prior do not set a colorspace.  This occasionally   */
360     /*  causes problems with dragging a window between desktops on OS/X.    */
361     /* -------------------------------------------------------------------- */
362     void *metal_layer = SDL_RenderGetMetalLayer(rend);
363     if (metal_layer &&
364         gfx_set_srgb_colorspace(metal_layer))
365     {
366         jzp_printf("gfx:  Manually set sRGB colorspace on Metal layer.\n");
367     }
368 #endif
369 
370     /* -------------------------------------------------------------------- */
371     /*  TEMPORARY: Verify that the surface's format is as we expect.  This  */
372     /*  is just a temporary bit of paranoia to ensure that scr->pixels      */
373     /*  is in the format I _think_ it's in.                                 */
374     /* -------------------------------------------------------------------- */
375 #if 0
376     if ((tgt_wind_bpp == 8 && (SDL_BITSPERPIXEL(text_pix_fmt)  !=  8   ||
377                                SDL_BYTESPERPIXEL(text_pix_fmt) !=  1))   ||
378         (tgt_wind_bpp ==16 && (SDL_BITSPERPIXEL(text_pix_fmt)  != 16   ||
379                                SDL_BYTESPERPIXEL(text_pix_fmt) !=  2))   ||
380         (tgt_wind_bpp ==32 && (SDL_BITSPERPIXEL(text_pix_fmt)  != 32   ||
381                                SDL_BYTESPERPIXEL(text_pix_fmt) !=  4)))
382     {
383         fprintf(stderr,"gfx panic: BitsPerPixel = %d, BytesPerPixel = %d\n",
384                 SDL_BITSPERPIXEL(text_pix_fmt),
385                 SDL_BYTESPERPIXEL(text_pix_fmt));
386         return -1;
387     }
388 #else
389     if (tgt_wind_bpp ==32 && SDL_BYTESPERPIXEL(text_pix_fmt) != 4)
390     {
391         fprintf(stderr,"gfx panic: BitsPerPixel = %d, BytesPerPixel = %d\n",
392                 SDL_BITSPERPIXEL(text_pix_fmt),
393                 SDL_BYTESPERPIXEL(text_pix_fmt));
394         return -1;
395     }
396 #endif
397 
398     /* -------------------------------------------------------------------- */
399     /*  New surface will may need palette initialization.                   */
400     /* -------------------------------------------------------------------- */
401     if (gfx->pvt->bpp != 32)
402     {
403         fprintf(stderr, "gfx panic: SDL2 is 32bpp only for now.\n");
404         return -1;
405     } else
406     {
407         gfx_set_scaler_palette(&gfx->pvt->scaler,
408                                 gfx->pvt->pixf,
409                                 gfx->pvt->vid_enable ? gfx->pvt->pal_on
410                                                      : gfx->pvt->pal_off);
411     }
412 
413     /* -------------------------------------------------------------------- */
414     /*  Start the display off as "completely dirty."                        */
415     /* -------------------------------------------------------------------- */
416     gfx->dirty = 3;
417     gfx->b_dirty = 3;
418 
419     /* -------------------------------------------------------------------- */
420     /*  Hide the mouse if full screen.                                      */
421     /* -------------------------------------------------------------------- */
422     SDL_ShowCursor(
423         SDL_GetNumVideoDisplays() == 1 &&
424         (act_wind_flags & SDL_WINDOW_FULLSCREEN) ? SDL_DISABLE : SDL_ENABLE);
425 
426     SDL_PumpEvents();
427     SDL_ShowWindow(gfx->pvt->wind);
428     SDL_RaiseWindow(gfx->pvt->wind);
429 
430     if (!(act_wind_flags & SDL_WINDOW_FULLSCREEN))
431         SDL_SetWindowBordered(gfx->pvt->wind, SDL_TRUE);
432 
433     return 0;
434 }
435 
436 #ifdef BENCHMARK_GFX
437 LOCAL int dr_hist[244];   /* histogram of number of dirty rects   */
438 LOCAL int drw_hist[21];   /* histogram of dirty rectangle widths  */
439 
440 LOCAL void gfx_dr_hist_dump(void);
441 #endif
442 
443 /* ======================================================================== */
444 /*  GFX_CHECK        -- Validates gfx parameters                            */
445 /* ======================================================================== */
gfx_check(int desire_x,int desire_y,int desire_bpp,int prescaler)446 int gfx_check(int desire_x, int desire_y, int desire_bpp, int prescaler)
447 {
448     int i;
449 
450     desire_bpp = 32;  // TODO:  Support other bit depths eventually?
451 
452     if (desire_x < 320)
453     {
454         fprintf(stderr, "Minimum X resolution is 320\n");
455         return -1;
456     }
457 
458     if (desire_y < 200)
459     {
460         fprintf(stderr, "Minimum Y resolution is 200\n");
461         return -1;
462     }
463 
464     if (!(desire_bpp == 8 || desire_bpp == 16 ||
465           desire_bpp == 24 || desire_bpp == 32))
466     {
467         fprintf(stderr, "Bits per pixel must be 8, 16, 24 or 32\n");
468         return -1;
469     }
470 
471     if (prescaler < 0 || prescaler > gfx_prescaler_registry_size)
472     {
473         if (prescaler > gfx_prescaler_registry_size)
474         {
475             fprintf(stderr, "gfx:  Prescaler number %d out of range\n",
476                     prescaler);
477         }
478         fprintf(stderr, "Supported prescalers:\n");
479 
480         for (i = 0; i < gfx_prescaler_registry_size; i++)
481             jzp_printf("    %d: %s\n", i, gfx_prescaler_registry[i].name);
482 
483         return -1;
484     }
485 
486     return 0;
487 }
488 
489 /* ======================================================================== */
490 /*  GFX_FLIP         -- Copy the texture to the display.                    */
491 /* ======================================================================== */
gfx_flip(const gfx_t * const gfx)492 LOCAL int gfx_flip(const gfx_t *const gfx)
493 {
494     const gfx_pvt_t *const pvt = gfx->pvt;
495     SDL_Renderer *const rend = pvt->rend;
496     SDL_Texture *const text = pvt->text;
497     const SDL_Color bord_color = pvt->pal_on[gfx->b_color];
498     const SDL_Rect dest = {
499         .x = pvt->ofs_x, .y = pvt->ofs_y,
500         .w = pvt->scaler.actual_x, .h = pvt->scaler.actual_y
501     };
502 
503     /* -------------------------------------------------------------------- */
504     /*  The docs for SDL_RenderPresent indicate that the back-buffer        */
505     /*  contents may not be preserved between calls to Present.  Thus, we   */
506     /*  need to clear the backdrop to our border color before copying the   */
507     /*  texture to the display.                                             */
508     /* -------------------------------------------------------------------- */
509     SDL_SetRenderDrawColor(rend, bord_color.r, bord_color.g, bord_color.b, 255);
510     SDL_RenderClear(rend);
511     if (pvt->vid_enable || gfx->debug_blank)
512         SDL_RenderCopy(rend, text, NULL,
513                        gfx->scrshot & GFX_RESET ? NULL : &dest);
514     SDL_RenderPresent(rend);
515 
516     return 0;
517 }
518 
519 /* ======================================================================== */
520 /*  GFX_INIT         -- Initializes a gfx_t object.                         */
521 /* ======================================================================== */
gfx_init(gfx_t * gfx,int desire_x,int desire_y,int desire_bpp,int flags,int verbose,int prescaler,int border_x,int border_y,int pal_mode,struct avi_writer_t * const avi,int audio_rate,const palette_t * const palette)522 int gfx_init(gfx_t *gfx, int desire_x, int desire_y, int desire_bpp,
523                          int flags,    int verbose,  int prescaler,
524                          int border_x, int border_y, int pal_mode,
525                          struct avi_writer_t *const avi, int audio_rate,
526                          const palette_t *const palette)
527 {
528     int  inter_x = 160, inter_y = 200;
529     int  i, need_inter_vid = 0;
530     void *prescaler_opaque;
531     gfx_dirtyrect_spec dr_spec;
532 
533     /* -------------------------------------------------------------------- */
534     /*  Set up prescaler (ie. Scale2X/3X/4X or similar)                     */
535     /* -------------------------------------------------------------------- */
536     if (prescaler > 0)
537     {
538         jzp_printf("gfx:  Configuring prescaler %s\n",
539                     gfx_prescaler_registry[prescaler].name);
540     }
541 
542     prescaler_opaque = gfx_prescaler_registry[prescaler].prescaler_init
543                        (
544                             160,      200,
545                             &inter_x, &inter_y, &need_inter_vid,
546                             &dr_spec
547                        );
548 
549     /* -------------------------------------------------------------------- */
550     /*  Sanity checks and cleanups.                                         */
551     /* -------------------------------------------------------------------- */
552     assert(gfx);
553     memset((void*)gfx, 0, sizeof(gfx_t));
554 
555     /* -------------------------------------------------------------------- */
556     /*  Allocate memory for the gfx_t.                                      */
557     /* -------------------------------------------------------------------- */
558     gfx->vid = CALLOC(uint8_t,   160 * 200);
559     gfx->pvt = CALLOC(gfx_pvt_t, 1);
560 
561     if (gfx->pvt)
562     {
563         int dr_count, dr_x_dim, dr_y_dim;
564 
565         dr_x_dim = (dr_spec.active_last_x - dr_spec.active_first_x + 1);
566         dr_y_dim = (dr_spec.active_last_y - dr_spec.active_first_y + 1);
567 
568         dr_count = ((dr_x_dim + dr_spec.x_step - 1) / dr_spec.x_step) *
569                    ((dr_y_dim + dr_spec.y_step - 1) / dr_spec.y_step);
570 
571         jzp_printf("active x: %d, %d, %d active y: %d %d, %d\n",
572             dr_spec.active_first_x, dr_spec.active_last_x, dr_spec.x_step,
573             dr_spec.active_first_y, dr_spec.active_last_y, dr_spec.y_step);
574 
575         if (need_inter_vid)
576             gfx->pvt->inter_vid = CALLOC(uint8_t, inter_x * inter_y);
577         else
578             gfx->pvt->inter_vid = gfx->vid;
579 
580         gfx->pvt->prescaler = gfx_prescaler_registry[prescaler].prescaler;
581         gfx->pvt->ps_opaque = prescaler_opaque;
582         gfx->pvt->ps_dtor   = gfx_prescaler_registry[prescaler].prescaler_dtor;
583 
584         gfx->pvt->prev          = CALLOC(uint8_t,   inter_x * inter_y);
585         gfx->pvt->dirty_rects   = CALLOC(SDL_Rect, dr_count);
586 
587         gfx->pvt->dirty_rows    = CALLOC(uint32_t,  ((inter_y+31) >> 5));
588         gfx->pvt->dirty_rows_sz = 4 * ((inter_y+31) >> 5);
589 
590         gfx->pvt->dr_spec       = dr_spec;
591 
592         gfx->pvt->border_x      = border_x;
593         gfx->pvt->border_y      = border_y;
594     }
595 
596     if (!gfx->vid || !gfx->pvt || !gfx->pvt->prev || !gfx->pvt->dirty_rows ||
597         !gfx->pvt->dirty_rects || !gfx->pvt->inter_vid)
598     {
599 
600         fprintf(stderr, "gfx:  Panic:  Could not allocate memory.\n");
601 
602         goto die;
603     }
604 
605     /* -------------------------------------------------------------------- */
606     /*  Configure the scaler.                                               */
607     /* -------------------------------------------------------------------- */
608     //if (desire_bpp == 24)
609         desire_bpp = 32;  // TODO: Support other bit-depths someday?
610 
611     if (gfx_scale_init_spec(&(gfx->pvt->scaler),
612                              inter_x,  inter_y,
613                              desire_x, desire_y, desire_bpp))
614     {
615         fprintf(stderr,
616                 "Could not configure scaler for %d x %d @ %d bpp\n",
617                 desire_x, desire_y, desire_bpp);
618         goto die;
619     }
620 
621     /* -------------------------------------------------------------------- */
622     /*  Set up our color palette.  We start with video blanked.             */
623     /* -------------------------------------------------------------------- */
624     gfx->palette = *palette;
625     for (i = 0; i < 16; i++)
626     {
627         gfx->pvt->pal_on [i].r = palette->color[i][0];
628         gfx->pvt->pal_on [i].g = palette->color[i][1];
629         gfx->pvt->pal_on [i].b = palette->color[i][2];
630         gfx->pvt->pal_off[i].r = palette->color[i][0] >> 1;
631         gfx->pvt->pal_off[i].g = palette->color[i][1] >> 1;
632         gfx->pvt->pal_off[i].b = palette->color[i][2] >> 1;
633     }
634     for (i = 16; i < 32; i++)
635     {
636         gfx->pvt->pal_on [i].r = util_palette.color[i - 16][0];
637         gfx->pvt->pal_on [i].g = util_palette.color[i - 16][1];
638         gfx->pvt->pal_on [i].b = util_palette.color[i - 16][2];
639         gfx->pvt->pal_off[i].r = util_palette.color[i - 16][0];
640         gfx->pvt->pal_off[i].g = util_palette.color[i - 16][1];
641         gfx->pvt->pal_off[i].b = util_palette.color[i - 16][2];
642     }
643     gfx->pvt->vid_enable = 0;
644     gfx->dirty      = 3;
645     gfx->b_dirty    = 3;
646 
647     gfx->fps        = pal_mode ? 50 : 60;
648     gfx->avi        = avi;
649     gfx->audio_rate = audio_rate;  // ugh
650 
651     /* -------------------------------------------------------------------- */
652     /*  Set up initial graphics mode.                                       */
653     /* -------------------------------------------------------------------- */
654     if (gfx_setup_sdl_display(gfx, flags, !verbose) < 0)
655         gfx_sdl_abort("Could not initialize video display");
656 
657     /* -------------------------------------------------------------------- */
658     /*  Ok, see if we succeeded in setting our initial video mode, and do   */
659     /*  some minor tidying.                                                 */
660     /* -------------------------------------------------------------------- */
661     gfx_flip(gfx);
662 
663     /* -------------------------------------------------------------------- */
664     /*  Set up the gfx_t's internal structures.                             */
665     /* -------------------------------------------------------------------- */
666     gfx->periph.read        = NULL;
667     gfx->periph.write       = NULL;
668     gfx->periph.peek        = NULL;
669     gfx->periph.poke        = NULL;
670     gfx->periph.tick        = NULL;  /* STIC ticks us directly */
671     gfx->periph.min_tick    = 0;
672     gfx->periph.max_tick    = INT_MAX;
673     gfx->periph.addr_base   = 0;
674     gfx->periph.addr_mask   = 0;
675     gfx->periph.dtor        = gfx_dtor;
676 
677 #ifdef BENCHMARK_GFX
678     atexit(gfx_dr_hist_dump);
679 #endif
680 
681     return 0;
682 
683 die:
684     if (gfx->pvt)
685     {
686         CONDFREE(gfx->pvt->dirty_rows);
687         CONDFREE(gfx->pvt->dirty_rects);
688         CONDFREE(gfx->pvt->prev);
689         if (gfx->pvt->inter_vid != gfx->vid) CONDFREE(gfx->pvt->inter_vid);
690     }
691     CONDFREE(gfx->pvt);
692     CONDFREE(gfx->vid);
693     return -1;
694 }
695 
696 /* ======================================================================== */
697 /*  GFX_DTOR     -- Tear down the gfx_t                                     */
698 /* ======================================================================== */
gfx_dtor(periph_t * const p)699 LOCAL void gfx_dtor(periph_t *const p)
700 {
701     gfx_t *const gfx = PERIPH_AS(gfx_t, p);
702 
703     if (gfx->movie)
704     {
705         if (gfx->movie->f)
706             fclose(gfx->movie->f);
707 
708         CONDFREE(gfx->movie);
709     }
710 
711     if (avi_is_active(gfx->avi))
712         avi_end_video(gfx->avi);
713 
714     if (gfx->pvt)
715     {
716         gfx_teardown_sdl_display(gfx);
717 
718 
719         /* destruct the prescaler;
720            prescaler should also free opaque struct if needed */
721         if (gfx->pvt->ps_dtor)
722             gfx->pvt->ps_dtor(gfx->pvt->ps_opaque);
723 
724         /* destruct the scaler */
725         gfx_scale_dtor(&(gfx->pvt->scaler));
726 
727         CONDFREE(gfx->pvt->dirty_rows);
728         CONDFREE(gfx->pvt->dirty_rects);
729         CONDFREE(gfx->pvt->prev);
730         if (gfx->pvt->inter_vid != gfx->vid) CONDFREE(gfx->pvt->inter_vid);
731     }
732     CONDFREE(gfx->pvt);
733     CONDFREE(gfx->vid);
734 }
735 
736 /* ======================================================================== */
737 /*  GFX_TOGGLE_WINDOWED -- Try to toggle windowed vs. full-screen.          */
738 /* ======================================================================== */
gfx_toggle_windowed(gfx_t * gfx,int quiet)739 bool gfx_toggle_windowed(gfx_t *gfx, int quiet)
740 {
741     gfx_pvt_t *const pvt = gfx->pvt;
742     const uint32_t new_flags = pvt->flags ^ GFX_FULLSC;
743     const uint32_t wind_flags =
744         new_flags & GFX_FULLSC ? SDL_WINDOW_FULLSCREEN : 0;
745     const bool new_fullsc = new_flags != 0;
746     const int toggle = gfx->toggle;
747     bool flipped = false;
748     gfx->toggle = 0;
749 
750     switch (toggle)
751     {
752         case GFX_WIND_TOG: break;
753         case GFX_WIND_ON:  if (new_fullsc == true ) return false; else break;
754         case GFX_WIND_OFF: if (new_fullsc == false) return false; else break;
755     }
756 
757     if (!quiet)
758         jzp_printf("\n");
759 
760     if (SDL_SetWindowFullscreen(pvt->wind, wind_flags) == 0)
761     {
762         flipped = true;
763         gfx->req_pause = true;
764         pvt->flags = new_flags;
765 
766         SDL_ShowCursor(
767             SDL_GetNumVideoDisplays() == 1 &&
768             new_fullsc ? SDL_DISABLE : SDL_ENABLE);
769 
770         SDL_PumpEvents();
771         SDL_ShowWindow(pvt->wind);
772         SDL_RaiseWindow(pvt->wind);
773         if (!new_fullsc)    /* Needed on w32 SDL2, apparently? */
774             SDL_SetWindowBordered(pvt->wind, SDL_TRUE);
775 
776         const int text_x = pvt->scaler.actual_x;
777         const int text_y = pvt->scaler.actual_y;
778         int wind_x, wind_y;
779         SDL_GetWindowSize(pvt->wind, &wind_x, &wind_y);
780 
781         pvt->dim_x = wind_x;
782         pvt->dim_y = wind_y;
783         pvt->ofs_x = ((wind_x - text_x) >> 1) & (~3);
784         pvt->ofs_y =  (wind_y - text_y) >> 1;
785     }
786 
787     gfx->b_dirty |= 2;
788     gfx->dirty   |= 2;
789     gfx->drop_frame = 0;
790     return flipped;
791 }
792 
793 /* ======================================================================== */
794 /*  GFX_FORCE_WINDOWED -- Force display to be windowed mode; Returns 1 if   */
795 /*                        display was previously full-screen.               */
796 /* ======================================================================== */
gfx_force_windowed(gfx_t * gfx,int quiet)797 int gfx_force_windowed(gfx_t *gfx, int quiet)
798 {
799     if (gfx->pvt->flags & GFX_FULLSC)
800     {
801         gfx_toggle_windowed(gfx, quiet);
802         return 1;
803     }
804 
805     return 0;
806 }
807 
808 /* ======================================================================== */
809 /*  GFX_SET_TITLE    -- Sets the window title                               */
810 /* ======================================================================== */
gfx_set_title(gfx_t * gfx,const char * title)811 int gfx_set_title(gfx_t *gfx, const char *title)
812 {
813     SDL_Window *const wind = gfx->pvt->wind;
814     SDL_SetWindowTitle(wind, title);
815     return 0;
816 }
817 
818 /* ======================================================================== */
819 /*  GFX_REFRESH      -- Core graphics refresh.                              */
820 /* ======================================================================== */
gfx_refresh(gfx_t * const gfx)821 void gfx_refresh(gfx_t *const gfx)
822 {
823     /* -------------------------------------------------------------------- */
824     /*  Every ~0.5 second, force a dirty frame, in case there is a static   */
825     /*  image.  On some systems (OS X in my case), the window will not      */
826     /*  refresh properly unless we send *something* occasionally.           */
827     /*                                                                      */
828     /*  Where I saw it:  Dragging a window from the Retina display to an    */
829     /*  external monitor caused the window to go all white.                 */
830     /* -------------------------------------------------------------------- */
831     if ((gfx->tot_frames++ & 31) == 0)
832     {
833         gfx->dirty |= 3;
834         gfx->b_dirty |= 3;
835     }
836 
837     /* -------------------------------------------------------------------- */
838     /*  Don't bother if display isn't dirty or if we're iconified.          */
839     /* -------------------------------------------------------------------- */
840     if (!gfx->scrshot && (!gfx->dirty || gfx->hidden))
841     {
842         return;
843     }
844 
845     /* -------------------------------------------------------------------- */
846     /*  DEBUG: Report blocks of dropped frames.                             */
847     /* -------------------------------------------------------------------- */
848     if (gfx->dropped_frames)
849     {
850 #if 0
851         jzp_printf("Dropped %d frames.\n", gfx->dropped_frames);
852         jzp_flush();
853 #endif
854         gfx->tot_dropped_frames += gfx->dropped_frames;
855         gfx->dropped_frames = 0;
856     }
857 
858     /* -------------------------------------------------------------------- */
859     /*  Update the palette if there's been a change in blanking state or    */
860     /*  border color.                                                       */
861     /* -------------------------------------------------------------------- */
862     if ((gfx->pvt->vid_enable & 2) || gfx->b_dirty)
863     {
864         if (gfx->pvt->vid_enable & 2)
865         {
866             gfx->pvt->vid_enable &= 1;
867             gfx->pvt->vid_enable ^= 1;
868             gfx->b_dirty = 3;
869         }
870 
871         gfx_set_scaler_palette(&gfx->pvt->scaler,
872                                 gfx->pvt->pixf,
873                                 gfx->pvt->vid_enable ? gfx->pvt->pal_on
874                                                      : gfx->pvt->pal_off);
875 
876         gfx->dirty |= 2;
877     }
878 
879     /* -------------------------------------------------------------------- */
880     /*  If dirty-rectangle disabled, force a dirty frame to a full flip.    */
881     /* -------------------------------------------------------------------- */
882     if ((gfx->pvt->flags & GFX_DRECTS) == 0 &&
883         (gfx->dirty || gfx->b_dirty))
884     {
885         gfx->dirty |= 3;
886     }
887 
888     /* -------------------------------------------------------------------- */
889     /*  Run the prescaler if any part of the frame is dirty.                */
890     /* -------------------------------------------------------------------- */
891     if (gfx->dirty)
892         gfx->pvt->prescaler(gfx->vid, gfx->pvt->inter_vid, gfx->pvt->ps_opaque);
893 
894     /* -------------------------------------------------------------------- */
895     /*  Push whole frame if dirty == 2, else do dirty-rectangle update.     */
896     /* -------------------------------------------------------------------- */
897     if (gfx->dirty >= 2)
898     {
899         memset(gfx->pvt->dirty_rows, 0xFF, gfx->pvt->dirty_rows_sz);
900         gfx_tick(gfx);
901         gfx_flip(gfx);
902     } else if (gfx->dirty || gfx->b_dirty)
903     {
904         gfx_find_dirty_rects(gfx);
905 
906         if (gfx->pvt->num_rects > 0)
907             gfx_tick(gfx);
908 
909         if (gfx->pvt->num_rects > 0 || gfx->b_dirty)
910             gfx_flip(gfx);
911     }
912 
913     gfx->dirty = 0;
914     gfx->b_dirty = 0;
915 }
916 
917 #ifdef BENCHMARK_GFX
918 LOCAL double bm_max = 0, bm_min = 1e30, bm_tot = 0;
919 LOCAL int bm_cnt = 0;
920 #endif
921 /* ======================================================================== */
922 /*  GFX_STIC_TICK    -- Get ticked directly by STIC to fix gfx pipeline.    */
923 /* ======================================================================== */
gfx_stic_tick(gfx_t * const gfx)924 void gfx_stic_tick(gfx_t *const gfx)
925 {
926 #ifdef BENCHMARK_GFX
927     double start, end, diff;
928 
929     start = get_time();
930 #endif
931 
932 
933     /* -------------------------------------------------------------------- */
934     /*  Update a movie if one's active, or user requested toggle in movie   */
935     /*  state.  We do this prior to dropping frames so that movies always   */
936     /*  have a consistent frame rate.                                       */
937     /* -------------------------------------------------------------------- */
938     if (gfx->scrshot & (GFX_MOVIE | GFX_MVTOG))
939         gfx_movieupd(gfx);
940 
941     /* -------------------------------------------------------------------- */
942     /*  Update an AVI if one's active, or if user requested a toggle.       */
943     /* -------------------------------------------------------------------- */
944     if (gfx->scrshot & (GFX_AVI | GFX_AVTOG))
945         gfx_aviupd(gfx);
946 
947     /* -------------------------------------------------------------------- */
948     /*  Toggle full-screen/windowed if requested.  Pause for a short time   */
949     /*  if we do toggle between windowed and full-screen.                   */
950     /* -------------------------------------------------------------------- */
951     if (gfx->toggle)
952         gfx_toggle_windowed(gfx, 0);
953 
954     /* -------------------------------------------------------------------- */
955     /*  If we've been asked to drop 'extra' frames (ie. limit to max 60Hz   */
956     /*  according to wall-clock), do so.                                    */
957     /* -------------------------------------------------------------------- */
958     if (gfx->dirty && !gfx->drop_frame &&
959         (gfx->pvt->flags & GFX_SKIP_EXTRA) != 0)
960     {
961         const double now  = get_time();
962         const double elapsed = now - gfx->pvt->last_frame;
963 
964         if (elapsed < frame_delta)
965         {
966             gfx->drop_frame = 1;
967             gfx->dropped_frames--;  /* Don't count this dropped frame */
968         }
969     }
970 
971     /* -------------------------------------------------------------------- */
972     /*  Drop a frame if we need to.                                         */
973     /* -------------------------------------------------------------------- */
974     if (gfx->drop_frame)
975     {
976         gfx->drop_frame--;
977         gfx->tot_frames++;
978         if (gfx->dirty) gfx->dropped_frames++;
979         return;
980     }
981 
982     gfx_refresh(gfx);
983 
984     /* -------------------------------------------------------------------- */
985     /*  If a screen-shot was requested, go write out a GIF file of the      */
986     /*  screen right now.  Screen-shot GIFs are always 320x200.             */
987     /* -------------------------------------------------------------------- */
988     if (gfx->scrshot & GFX_SHOT)
989     {
990         gfx_scrshot(gfx);
991         gfx->scrshot &= ~GFX_SHOT;
992     }
993 
994     /* -------------------------------------------------------------------- */
995     /*  If rate-limiting our display, record the time /after/ the flip, as  */
996     /*  some OSes *cough*OSX*cough* wait for vertical retrace, while other  */
997     /*  OSes *cough*Linux*cough* do not.                                    */
998     /* -------------------------------------------------------------------- */
999     if (gfx->pvt->flags & GFX_SKIP_EXTRA)
1000     {
1001         gfx->pvt->last_frame = get_time();
1002     }
1003 
1004 #ifdef BENCHMARK_GFX
1005     end = get_time();
1006     diff = end - start;
1007     if (diff > bm_max) bm_max = diff;
1008     if (diff < bm_min) bm_min = diff;
1009     bm_tot += diff;
1010 
1011     if (++bm_cnt == 120)
1012     {
1013         jzp_printf("gfx_tick: min = %8.3f max = %8.3f avg = %8.3f\n",
1014                    bm_min * 1000., bm_max * 1000., bm_tot * 1000. / 120);
1015         bm_max = bm_tot = 0;
1016         bm_cnt = 0;
1017         bm_min = 1e30;
1018     }
1019 #endif
1020 }
1021 
1022 /* ======================================================================== */
1023 /*  GFX_TICK         -- Services a gfx_t tick in any graphics format        */
1024 /* ======================================================================== */
gfx_tick(gfx_t * gfx)1025 LOCAL void gfx_tick(gfx_t *gfx)
1026 {
1027     void    *pixels;
1028     int      pitch;
1029 
1030     if (SDL_LockTexture(gfx->pvt->text, NULL, &pixels, &pitch))
1031         gfx_sdl_abort("Could not lock texture");
1032 
1033     gfx_scale
1034     (
1035         &gfx->pvt->scaler,
1036         gfx->pvt->inter_vid,
1037         (uint8_t *)pixels, pitch,
1038         gfx->pvt->dirty_rows
1039     );
1040 
1041     SDL_UnlockTexture(gfx->pvt->text);
1042 }
1043 
1044 /* ======================================================================== */
1045 /*  GFX_VID_ENABLE   -- Alert gfx that video has been enabled or blanked    */
1046 /* ======================================================================== */
gfx_vid_enable(gfx_t * gfx,int enabled)1047 void gfx_vid_enable(gfx_t *gfx, int enabled)
1048 {
1049     /* -------------------------------------------------------------------- */
1050     /*  Force 'enabled' to be 0 or 1.                                       */
1051     /* -------------------------------------------------------------------- */
1052     enabled = enabled == VID_ENABLED;
1053 
1054     /* -------------------------------------------------------------------- */
1055     /*  If enabled state changed, schedule a palette update.                */
1056     /* -------------------------------------------------------------------- */
1057     if ((gfx->pvt->vid_enable ^ enabled) & 1)
1058     {
1059         gfx->pvt->vid_enable |= 2;
1060         gfx->dirty |= 2;
1061     } else
1062     {
1063         gfx->pvt->vid_enable = enabled;
1064     }
1065 }
1066 
1067 /* ======================================================================== */
1068 /*  GFX_SET_BORD     -- Set the border color for the display                */
1069 /* ======================================================================== */
gfx_set_bord(gfx_t * gfx,int b_color)1070 void gfx_set_bord
1071 (
1072     gfx_t *gfx,         /*  Graphics object.                        */
1073     int b_color
1074 )
1075 {
1076     int dirty = 0;
1077 
1078     /* -------------------------------------------------------------------- */
1079     /*  Set up the display parameters.                                      */
1080     /* -------------------------------------------------------------------- */
1081     if (gfx->b_color != b_color)
1082     {
1083         gfx->b_color = b_color;
1084         gfx->b_dirty = 1;
1085     }
1086 
1087     /* -------------------------------------------------------------------- */
1088     /*  If we're using the normal STIC blanking behavior, set our "off"     */
1089     /*  colors to the currently selected border color.  The alternate mode  */
1090     /*  (which is useful for debugging) sets the blanked colors to be       */
1091     /*  dimmed versions of the normal palette.                              */
1092     /* -------------------------------------------------------------------- */
1093     if (!gfx->debug_blank)
1094     {
1095         int i;
1096 
1097         for (i = 0; i < 16; i++)
1098             gfx->pvt->pal_off[i] = gfx->pvt->pal_on[b_color];
1099     }
1100 
1101     if (dirty)     { gfx->dirty   |= 1; }
1102     if (dirty & 2) { gfx->b_dirty |= 2; }
1103 }
1104 
1105 /* ======================================================================== */
1106 /*  GFX_FIND_DIRTY_RECTS -- Finds dirty rectangles in the current image.    */
1107 /*                                                                          */
1108 /*  Current algorithm just divides the display into 240 8x16 tiles aligned  */
1109 /*  with the STIC's cards.  A tile is considered either clean or dirty      */
1110 /*  in its entirety for now.  A tile can be merged with tiles to its        */
1111 /*  right if they're contiguous, or there's a gap of at most one tile.      */
1112 /*                                                                          */
1113 /*  The algorithm is also responsible for copying the new image into the    */
1114 /*  reference image, and constructing a bitmap of which rows need to be     */
1115 /*  expanded by the scaler code.                                            */
1116 /* ======================================================================== */
gfx_find_dirty_rects(gfx_t * gfx)1117 LOCAL void gfx_find_dirty_rects(gfx_t *gfx)
1118 {
1119     int x, y, xx, yy, i, j, t;
1120     int nr = 0, row_start;
1121     uint32_t *RESTRICT old_pix = (uint32_t *)(void *)gfx->pvt->prev;
1122     uint32_t *RESTRICT new_pix = (uint32_t *)(void *)gfx->pvt->inter_vid;
1123     uint32_t is_dirty;
1124     SDL_Rect *rect = gfx->pvt->dirty_rects;
1125 
1126     int wpitch = gfx->pvt->dr_spec.pitch >> 2;
1127     int y0 = gfx->pvt->dr_spec.active_first_y;
1128     int y1 = gfx->pvt->dr_spec.active_last_y + 1;
1129     int ys = gfx->pvt->dr_spec.y_step;
1130 
1131     int x0 = (gfx->pvt->dr_spec.active_first_x >> 3);
1132     int x1 = (gfx->pvt->dr_spec.active_last_x  >> 3) + 1;
1133     int xs = (gfx->pvt->dr_spec.x_step         >> 3);
1134 
1135     int bo = (gfx->pvt->dr_spec.bord_first_x >> 2) +
1136              (gfx->pvt->dr_spec.bord_first_y * wpitch);
1137 
1138     /* -------------------------------------------------------------------- */
1139     /*  Set our merge threshold based on whether we're allowed to include   */
1140     /*  a clean rectangle between two dirty rectangles when coalescing.     */
1141     /* -------------------------------------------------------------------- */
1142     t = gfx->pvt->flags & GFX_DRCMRG ? 1 : 0;
1143 
1144     /* -------------------------------------------------------------------- */
1145     /*  Initally mark all rows clean.                                       */
1146     /* -------------------------------------------------------------------- */
1147     memset((void *)gfx->pvt->dirty_rows, 0, gfx->pvt->dirty_rows_sz);
1148 
1149     /* -------------------------------------------------------------------- */
1150     /*  Scan the source image tile-row-wise looking for differences.        */
1151     /* -------------------------------------------------------------------- */
1152     for (y = y0; y < y1; y += ys)
1153     {
1154         row_start = nr;
1155 
1156         /* ---------------------------------------------------------------- */
1157         /*  Find dirty rectangles in this row of cards.                     */
1158         /* ---------------------------------------------------------------- */
1159         for (x  = x0; x < x1; x += xs)
1160         {
1161             is_dirty = 0;
1162             switch (xs)
1163             {
1164                 case 1:
1165                 {
1166                     for (yy = y; yy < y + ys; yy++)
1167                         is_dirty  |= (old_pix[yy * wpitch + x*2 + 0] !=
1168                                       new_pix[yy * wpitch + x*2 + 0])
1169                                   |  (old_pix[yy * wpitch + x*2 + 1] !=
1170                                       new_pix[yy * wpitch + x*2 + 1]);
1171                     break;
1172                 }
1173 
1174                 case 2:
1175                 {
1176                     for (yy = y; yy < y + ys; yy++)
1177                         is_dirty  |= (old_pix[yy * wpitch + x*2 + 0] !=
1178                                       new_pix[yy * wpitch + x*2 + 0])
1179                                   |  (old_pix[yy * wpitch + x*2 + 1] !=
1180                                       new_pix[yy * wpitch + x*2 + 1])
1181                                   |  (old_pix[yy * wpitch + x*2 + 2] !=
1182                                       new_pix[yy * wpitch + x*2 + 2])
1183                                   |  (old_pix[yy * wpitch + x*2 + 3] !=
1184                                       new_pix[yy * wpitch + x*2 + 3]);
1185                     break;
1186                 }
1187 
1188                 case 3:
1189                 {
1190                     for (yy = y; yy < y + ys; yy++)
1191                         is_dirty  |= (old_pix[yy * wpitch + x*2 + 0] !=
1192                                       new_pix[yy * wpitch + x*2 + 0])
1193                                   |  (old_pix[yy * wpitch + x*2 + 1] !=
1194                                       new_pix[yy * wpitch + x*2 + 1])
1195                                   |  (old_pix[yy * wpitch + x*2 + 2] !=
1196                                       new_pix[yy * wpitch + x*2 + 2])
1197                                   |  (old_pix[yy * wpitch + x*2 + 3] !=
1198                                       new_pix[yy * wpitch + x*2 + 3])
1199                                   |  (old_pix[yy * wpitch + x*2 + 4] !=
1200                                       new_pix[yy * wpitch + x*2 + 4])
1201                                   |  (old_pix[yy * wpitch + x*2 + 5] !=
1202                                       new_pix[yy * wpitch + x*2 + 5]);
1203                     break;
1204                 }
1205 
1206                 case 4:
1207                 {
1208                     for (yy = y; yy < y + ys; yy++)
1209                         is_dirty  |= (old_pix[yy * wpitch + x*2 + 0] !=
1210                                       new_pix[yy * wpitch + x*2 + 0])
1211                                   |  (old_pix[yy * wpitch + x*2 + 1] !=
1212                                       new_pix[yy * wpitch + x*2 + 1])
1213                                   |  (old_pix[yy * wpitch + x*2 + 2] !=
1214                                       new_pix[yy * wpitch + x*2 + 2])
1215                                   |  (old_pix[yy * wpitch + x*2 + 3] !=
1216                                       new_pix[yy * wpitch + x*2 + 3])
1217                                   |  (old_pix[yy * wpitch + x*2 + 4] !=
1218                                       new_pix[yy * wpitch + x*2 + 4])
1219                                   |  (old_pix[yy * wpitch + x*2 + 5] !=
1220                                       new_pix[yy * wpitch + x*2 + 5])
1221                                   |  (old_pix[yy * wpitch + x*2 + 6] !=
1222                                       new_pix[yy * wpitch + x*2 + 6])
1223                                   |  (old_pix[yy * wpitch + x*2 + 7] !=
1224                                       new_pix[yy * wpitch + x*2 + 7]);
1225                     break;
1226                 }
1227 
1228                 default:
1229                 {
1230                     for (yy = y; yy < y + ys; yy++)
1231                         for (xx = x; xx < x + xs; xx++)
1232                             is_dirty |= (old_pix[yy * wpitch + xx*2 + 0] !=
1233                                          new_pix[yy * wpitch + xx*2 + 0])
1234                                      |  (old_pix[yy * wpitch + xx*2 + 1] !=
1235                                          new_pix[yy * wpitch + xx*2 + 1]);
1236 
1237                     break;
1238                 }
1239             }
1240 
1241             if (is_dirty)
1242             {
1243                 rect[nr].x = x;
1244                 rect[nr].y = y;
1245                 rect[nr].w = xs;
1246                 rect[nr].h = ys;
1247                 nr++;
1248             }
1249 /*fprintf(stderr, "%3d %3d %3d\n", x, y, nr); */
1250         }
1251 
1252         /* ---------------------------------------------------------------- */
1253         /*  While it's still hot in the cache, copy "new" to "old"          */
1254         /* ---------------------------------------------------------------- */
1255         memcpy((void *)&old_pix[y * wpitch],
1256                (void *)&new_pix[y * wpitch],
1257                sizeof(uint32_t) * wpitch * ys);
1258 
1259         /* ---------------------------------------------------------------- */
1260         /*  Mark these rows as dirty in the dirty_row bitmap                */
1261         /* ---------------------------------------------------------------- */
1262         if (nr > row_start)
1263             for (yy = y; yy < y + ys; yy++)
1264                 gfx->pvt->dirty_rows[yy >> 5] |= 1u << (yy & 31);
1265 
1266         /* ---------------------------------------------------------------- */
1267         /*  Coalesce rectangles if they're adjacent or separated by at      */
1268         /*  most one clean rectangle.                                       */
1269         /* ---------------------------------------------------------------- */
1270         if (nr - row_start < 2)
1271             continue;
1272 
1273         for (i = row_start, j = row_start + 1; j < nr; j++)
1274         {
1275             if (rect[i].x + rect[i].w + t >= rect[j].x)
1276             {
1277                 rect[i].w = rect[j].x - rect[i].x + rect[j].w;
1278                 continue;
1279             } else
1280             {
1281                 rect[++i] = rect[j];
1282             }
1283         }
1284 
1285         nr = i + 1;
1286     }
1287 
1288     /* -------------------------------------------------------------------- */
1289     /*  If border areas changed color, update those too.                    */
1290     /*  XXX:  This needs to get fixed when I fix scaler's border handler.   */
1291     /* -------------------------------------------------------------------- */
1292     if (old_pix[bo] != new_pix[bo])
1293     {
1294         int x0l, x0h, y0l, y0h;     /* upper rectangle */
1295         int x1l, x1h, y1l, y1h;     /* lower rectangle */
1296 
1297         old_pix[bo] =  new_pix[bo];
1298 
1299         x0l = x1l = gfx->pvt->dr_spec.bord_first_x >> 3;    /* in dwords */
1300         x0h = x1h = gfx->pvt->dr_spec.bord_last_x  >> 3;    /* in dwords */
1301 
1302         y0l = gfx->pvt->dr_spec.bord_first_y;               /* in pixels */
1303         y0h = gfx->pvt->dr_spec.active_first_y - 1;         /* in pixels */
1304 
1305         y1l = gfx->pvt->dr_spec.active_last_y + 1;          /* in pixels */
1306         y1h = gfx->pvt->dr_spec.bord_last_y;                /* in pixels */
1307 
1308         rect[nr].x = x0l;
1309         rect[nr].y = y0l;
1310         rect[nr].w = x0h - x0l + 1;
1311         rect[nr].h = y0h - y0l + 1;
1312         nr++;
1313 
1314         rect[nr].x = x1l;
1315         rect[nr].y = y1l;
1316         rect[nr].w = x1h - x1l + 1;
1317         rect[nr].h = y1h - y1l + 1;
1318         nr++;
1319 
1320         for (yy = y0l; yy <= y0h; yy++)
1321             gfx->pvt->dirty_rows[yy >> 5] |= 1u << (yy & 31);
1322 
1323         for (yy = y1l; yy <= y1h; yy++)
1324             gfx->pvt->dirty_rows[yy >> 5] |= 1u << (yy & 31);
1325     }
1326 
1327     /* -------------------------------------------------------------------- */
1328     /*  Convert the rectangles to display coordinates.  Ick.                */
1329     /* -------------------------------------------------------------------- */
1330     for (i = 0; i < nr; i++)
1331     {
1332         int w, h;
1333 #ifdef BENCHMARK_GFX
1334         drw_hist[rect[i].w]++;
1335 #endif
1336         x = rect[i].x * 8;
1337         y = rect[i].y;
1338         w = rect[i].w * 8;
1339         h = rect[i].h;
1340 
1341         rect[i].x  = gfx->pvt->scaler.scaled_x[x];
1342         rect[i].y  = gfx->pvt->scaler.scaled_y[y];
1343         rect[i].w  = gfx->pvt->scaler.scaled_x[x + w] - rect[i].x;
1344         rect[i].h  = gfx->pvt->scaler.scaled_y[y + h] - rect[i].y;
1345 
1346         rect[i].x += gfx->pvt->ofs_x;
1347         rect[i].y += gfx->pvt->ofs_y;
1348     }
1349 
1350     gfx->pvt->num_rects = nr;
1351 
1352 #ifdef BENCHMARK_GFX
1353     dr_hist[nr]++;
1354 #endif
1355 
1356     return;
1357 }
1358 
1359 #ifdef BENCHMARK_GFX
gfx_dr_hist_dump(void)1360 LOCAL void gfx_dr_hist_dump(void)
1361 {
1362     int i;
1363 
1364     jzp_printf("Dirty rectangle counts:\n");
1365     for (i = 0; i <= 244; i++)
1366         if (dr_hist[i])
1367             jzp_printf("%4d: %7d\n", i, dr_hist[i]);
1368 
1369     jzp_printf("Dirty rectangle width counts:\n");
1370     for (i = 0; i <= 20; i++)
1371         if (drw_hist[i])
1372             jzp_printf("%4d: %7d\n", i, drw_hist[i]);
1373 }
1374 #endif
1375 
1376 /* ======================================================================== */
1377 /*  This program is free software; you can redistribute it and/or modify    */
1378 /*  it under the terms of the GNU General Public License as published by    */
1379 /*  the Free Software Foundation; either version 2 of the License, or       */
1380 /*  (at your option) any later version.                                     */
1381 /*                                                                          */
1382 /*  This program is distributed in the hope that it will be useful,         */
1383 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of          */
1384 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       */
1385 /*  General Public License for more details.                                */
1386 /*                                                                          */
1387 /*  You should have received a copy of the GNU General Public License along */
1388 /*  with this program; if not, write to the Free Software Foundation, Inc., */
1389 /*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             */
1390 /* ======================================================================== */
1391 /*          Copyright (c) 1998-2020, Joseph Zbiciak, John Tanner            */
1392 /* ======================================================================== */
1393