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