4f8249f6d95489b9390c4d9817aba56faa920f63
[picodrive.git] / platform / common / plat_sdl.c
1 /*
2  * PicoDrive
3  * (C) notaz, 2013
4  * (C) irixxxx, 2020-2024
5  *
6  * This work is licensed under the terms of MAME license.
7  * See COPYING file in the top-level directory.
8  */
9
10 #include <stdio.h>
11
12 #include "../libpicofe/input.h"
13 #include "../libpicofe/plat.h"
14 #include "../libpicofe/plat_sdl.h"
15 #include "../libpicofe/in_sdl.h"
16 #include "../libpicofe/gl.h"
17 #include "emu.h"
18 #include "menu_pico.h"
19 #include "input_pico.h"
20 #include "plat_sdl.h"
21 #include "version.h"
22
23 #include <pico/pico_int.h>
24
25 static void *shadow_fb;
26 static int shadow_size;
27 static struct area { int w, h; } area;
28
29 static struct in_pdata in_sdl_platform_data;
30
31 static int hide_cursor;
32
33 static int sound_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 53000, -1 };
34 struct plat_target plat_target = { .sound_rates = sound_rates };
35
36 #if defined __MIYOO__
37 const char *plat_device = "miyoo";
38 #elif defined __GCW0__
39 const char *plat_device = "gcw0";
40 #elif defined __RETROFW__
41 const char *plat_device = "retrofw";
42 #elif defined __DINGUX__
43 const char *plat_device = "dingux";
44 #else
45 const char *plat_device = "";
46 #endif
47
48 int plat_parse_arg(int argc, char *argv[], int *x)
49 {
50 #if defined __OPENDINGUX__
51         if (*plat_device == '\0' && strcasecmp(argv[*x], "-device") == 0) {
52                 plat_device = argv[++(*x)];
53                 return 0;
54         }
55 #endif
56         return 1;
57 }
58
59 void plat_early_init(void)
60 {
61 }
62
63 int plat_target_init(void)
64 {
65 #if defined __ODBETA__
66         if (*plat_device == '\0') {
67                 /* ODbeta should always have a device tree, get the model info from there */
68                 FILE *f = fopen("/proc/device-tree/compatible", "r");
69                 if (f) {
70                         char buf[10];
71                         int c = fread(buf, 1, sizeof(buf), f);
72                         if (strncmp(buf, "gcw,", 4) == 0)
73                                 plat_device = "gcw0";
74                 }
75         }
76 #endif
77         return 0;
78 }
79
80 void plat_target_finish(void)
81 {
82 }
83
84 /* YUV stuff */
85 static int yuv_ry[32], yuv_gy[32], yuv_by[32];
86 static unsigned char yuv_u[32 * 2], yuv_v[32 * 2];
87 static unsigned char yuv_y[256];
88 static struct uyvy { uint32_t y:8; uint32_t vyu:24; } yuv_uyvy[65536];
89
90 void bgr_to_uyvy_init(void)
91 {
92         int i, v;
93
94         /* init yuv converter:
95             y0 = (int)((0.299f * r0) + (0.587f * g0) + (0.114f * b0));
96             y1 = (int)((0.299f * r1) + (0.587f * g1) + (0.114f * b1));
97             u = (int)(8 * 0.565f * (b0 - y0)) + 128;
98             v = (int)(8 * 0.713f * (r0 - y0)) + 128;
99         */
100         for (i = 0; i < 32; i++) {
101                 yuv_ry[i] = (int)(0.299f * i * 65536.0f + 0.5f);
102                 yuv_gy[i] = (int)(0.587f * i * 65536.0f + 0.5f);
103                 yuv_by[i] = (int)(0.114f * i * 65536.0f + 0.5f);
104         }
105         for (i = -32; i < 32; i++) {
106                 v = (int)(8 * 0.565f * i) + 128;
107                 if (v < 0)
108                         v = 0;
109                 if (v > 255)
110                         v = 255;
111                 yuv_u[i + 32] = v;
112                 v = (int)(8 * 0.713f * i) + 128;
113                 if (v < 0)
114                         v = 0;
115                 if (v > 255)
116                         v = 255;
117                 yuv_v[i + 32] = v;
118         }
119         // valid Y range seems to be 16..235
120         for (i = 0; i < 256; i++) {
121                 yuv_y[i] = 16 + 219 * i / 32;
122         }
123         // everything combined into one large array for speed
124         for (i = 0; i < 65536; i++) {
125                 int r = (i >> 11) & 0x1f, g = (i >> 6) & 0x1f, b = (i >> 0) & 0x1f;
126                 int y = (yuv_ry[r] + yuv_gy[g] + yuv_by[b]) >> 16;
127                 yuv_uyvy[i].y = yuv_y[y];
128 #if CPU_IS_LE
129                 yuv_uyvy[i].vyu = (yuv_v[r-y + 32] << 16) | (yuv_y[y] << 8) | yuv_u[b-y + 32];
130 #else
131                 yuv_uyvy[i].vyu = (yuv_v[b-y + 32] << 16) | (yuv_y[y] << 8) | yuv_u[r-y + 32];
132 #endif
133         }
134 }
135
136 void rgb565_to_uyvy(void *d, const void *s, int w, int h, int pitch, int dpitch, int x2)
137 {
138         uint32_t *dst = d;
139         const uint16_t *src = s;
140         int i;
141
142         if (x2) while (h--) {
143                 for (i = w; i >= 4; src += 4, dst += 4, i -= 4)
144                 {
145                         struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
146                         struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
147 #if CPU_IS_LE
148                         dst[0] = (uyvy0->y << 24) | uyvy0->vyu;
149                         dst[1] = (uyvy1->y << 24) | uyvy1->vyu;
150                         dst[2] = (uyvy2->y << 24) | uyvy2->vyu;
151                         dst[3] = (uyvy3->y << 24) | uyvy3->vyu;
152 #else
153                         dst[0] = uyvy0->y | (uyvy0->vyu << 8);
154                         dst[1] = uyvy1->y | (uyvy1->vyu << 8);
155                         dst[2] = uyvy2->y | (uyvy2->vyu << 8);
156                         dst[3] = uyvy3->y | (uyvy3->vyu << 8);
157 #endif
158                 }
159                 src += pitch - (w-i);
160                 dst += (dpitch - 2*(w-i))/2;
161         } else while (h--) {
162                 for (i = w; i >= 4; src += 4, dst += 2, i -= 4)
163                 {
164                         struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
165                         struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
166 #if CPU_IS_LE
167                         dst[0] = (uyvy1->y << 24) | uyvy0->vyu;
168                         dst[1] = (uyvy3->y << 24) | uyvy2->vyu;
169 #else
170                         dst[0] = uyvy1->y | (uyvy0->vyu << 8);
171                         dst[1] = uyvy3->y | (uyvy2->vyu << 8);
172 #endif
173                 }
174                 src += pitch - (w-i);
175                 dst += (dpitch - (w-i))/2;
176         }
177 }
178
179 void copy_intscale(void *dst, int w, int h, int pp, void *src, int sw, int sh, int spp)
180 {
181         int xf = w / sw, yf = h / sh, f = xf < yf ? xf : yf;
182         int wf = f * sw, hf = f * sh;
183         int x = (w - wf)/2, y = (h - hf)/2;
184         uint16_t *p = (uint16_t *)dst;
185         uint16_t *q = (uint16_t *)src;
186
187         // copy 16bit image with scaling by an integer factor
188         int i, j, k, l;
189         p += y * pp + x;
190         for (i = 0; i < sh; i++) {
191                 for (j = 0; j < sw; j++, q++)
192                         for (l = 0; l < f; l++)
193                                 *p++ = *q;
194                 p += pp - wf;
195                 q += spp - sw;
196                 for (k = 1; k < f; k++) {
197                         memcpy(p, p-pp, wf*2);
198                         p += pp;
199                 }
200         }
201 }
202
203 static int clear_buf_cnt, clear_stat_cnt;
204
205 static void resize_buffers(void)
206 {
207         // make sure the shadow buffers are big enough in case of resize
208         if (shadow_size < g_menuscreen_w * g_menuscreen_h * 2) {
209                 shadow_size = g_menuscreen_w * g_menuscreen_h * 2;
210                 shadow_fb = realloc(shadow_fb, shadow_size);
211                 g_menubg_ptr = realloc(g_menubg_ptr, shadow_size);
212         }
213 }
214
215 void plat_video_set_size(int w, int h)
216 {
217         if ((plat_sdl_overlay || plat_sdl_gl_active) && w <= 320 && h <= 240) {
218                 // scale to the window, but mind aspect ratio (scaled to 4:3):
219                 // w *= win_aspect / 4:3_aspect or h *= 4:3_aspect / win_aspect
220                 if (g_menuscreen_w * 3/4 >= g_menuscreen_h)
221                         w = (w * 3 * g_menuscreen_w/g_menuscreen_h)/4 & ~1;
222                 else
223                         h = (h * 4 * g_menuscreen_h/g_menuscreen_w)/3 & ~1;
224         }
225
226         if (area.w != w || area.h != h) {
227                 area = (struct area) { w, h };
228
229                 if (plat_sdl_overlay || plat_sdl_gl_active || !plat_sdl_is_windowed()) {
230                         // create surface for overlays, or try using a hw scaler
231                         if (plat_sdl_change_video_mode(w, h, 0) < 0) {
232                                 // failed, revert to original resolution
233                                 area = (struct area) { g_screen_width,g_screen_height };
234                                 plat_sdl_change_video_mode(g_screen_width, g_screen_height, 0);
235                         }
236                 }
237                 if (plat_sdl_overlay || plat_sdl_gl_active) {
238                         // use shadow buffer for overlays
239                         g_screen_width = area.w;
240                         g_screen_height = area.h;
241                         g_screen_ppitch = area.w;
242                         g_screen_ptr = shadow_fb;
243                 } else if (plat_sdl_is_windowed() &&
244                     (plat_sdl_screen->w >= 320*2 || plat_sdl_screen->h >= 240*2 ||
245                      plat_sdl_screen->w < 320 || plat_sdl_screen->h < 240)) {
246                         // shadow buffer for integer scaling
247                         g_screen_width = 320;
248                         g_screen_height = 240;
249                         g_screen_ppitch = 320;
250                         g_screen_ptr = shadow_fb;
251                 } else {
252                         // unscaled SDL window buffer can be used directly
253                         g_screen_width = plat_sdl_screen->w;
254                         g_screen_height = plat_sdl_screen->h;
255                         g_screen_ppitch = plat_sdl_screen->pitch/2;
256                         g_screen_ptr = plat_sdl_screen->pixels;
257                 }
258         }
259 }
260
261 void plat_video_set_shadow(int w, int h)
262 {
263         g_screen_width = w;
264         g_screen_height = h;
265         g_screen_ppitch = w;
266         g_screen_ptr = shadow_fb;
267 }
268
269 void plat_video_flip(void)
270 {
271         resize_buffers();
272
273         if (plat_sdl_overlay != NULL) {
274                 SDL_Rect dstrect =
275                         { 0, 0, plat_sdl_screen->w, plat_sdl_screen->h };
276                 SDL_LockYUVOverlay(plat_sdl_overlay);
277                 if (area.w <= plat_sdl_overlay->w && area.h <= plat_sdl_overlay->h)
278                         rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
279                                         area.w, area.h, g_screen_ppitch,
280                                         plat_sdl_overlay->pitches[0]/2,
281                                         plat_sdl_overlay->w >= 2*area.w);
282                 SDL_UnlockYUVOverlay(plat_sdl_overlay);
283                 SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
284         }
285         else if (plat_sdl_gl_active) {
286                 gl_flip(shadow_fb, g_screen_ppitch, g_screen_height);
287         }
288         else {
289                 int copy = g_screen_ptr != plat_sdl_screen->pixels;
290                 if (copy)
291                         copy_intscale(plat_sdl_screen->pixels, plat_sdl_screen->w,
292                                 plat_sdl_screen->h, plat_sdl_screen->pitch/2,
293                                 shadow_fb, g_screen_width, g_screen_height, g_screen_ppitch);
294
295                 if (SDL_MUSTLOCK(plat_sdl_screen))
296                         SDL_UnlockSurface(plat_sdl_screen);
297                 SDL_Flip(plat_sdl_screen);
298
299                 // take over resized settings for the physical SDL surface
300                 if ((plat_sdl_screen->w != g_menuscreen_w ||
301                     plat_sdl_screen->h != g_menuscreen_h)  && plat_sdl_is_windowed() &&
302                     SDL_WM_GrabInput(SDL_GRAB_ON) == SDL_GRAB_ON) {
303                         plat_sdl_change_video_mode(g_menuscreen_w, g_menuscreen_h, -1);
304                         SDL_WM_GrabInput(SDL_GRAB_OFF);
305                         g_menuscreen_pp = plat_sdl_screen->pitch/2;
306
307                         // force upper layer to use new dimensions
308                         plat_video_set_shadow(g_screen_width, g_screen_height);
309                         plat_video_set_buffer(g_screen_ptr);
310                         rendstatus_old = -1;
311                 } else if (!copy) {
312                         g_screen_ppitch = plat_sdl_screen->pitch/2;
313                         g_screen_ptr = plat_sdl_screen->pixels;
314                         plat_video_set_buffer(g_screen_ptr);
315                 }
316
317                 if (SDL_MUSTLOCK(plat_sdl_screen))
318                         SDL_LockSurface(plat_sdl_screen);
319
320                 if (clear_buf_cnt) {
321                         memset(plat_sdl_screen->pixels, 0, plat_sdl_screen->pitch*plat_sdl_screen->h);
322                         clear_buf_cnt--;
323                 }
324         }
325
326         // for overlay/gl modes buffer ptr may change on resize
327         if ((plat_sdl_overlay || plat_sdl_gl_active) &&
328             (g_screen_ptr != shadow_fb || g_screen_ppitch != g_screen_width)) {
329                 g_screen_ppitch = g_screen_width;
330                 g_screen_ptr = shadow_fb;
331                 plat_video_set_buffer(g_screen_ptr);
332         }
333         if (clear_stat_cnt) {
334                 unsigned short *d = (unsigned short *)g_screen_ptr + g_screen_ppitch * g_screen_height;
335                 int l = g_screen_ppitch * 8;
336                 memset((int *)(d - l), 0, l * 2);
337                 clear_stat_cnt--;
338         }
339 }
340
341 void plat_video_wait_vsync(void)
342 {
343 }
344
345 void plat_video_clear_status(void)
346 {
347         clear_stat_cnt = 3; // do it thrice in case of triple buffering
348 }
349
350 void plat_video_clear_buffers(void)
351 {
352         int count = g_menuscreen_w * g_menuscreen_h;
353         if (count < area.w * area.h) count = area.w * area.h;
354         memset(shadow_fb, 0, count * 2);
355         if (plat_sdl_overlay)
356                 plat_sdl_overlay_clear();
357         memset(plat_sdl_screen->pixels, 0, plat_sdl_screen->pitch*plat_sdl_screen->h);
358         clear_buf_cnt = 3; // do it thrice in case of triple buffering
359 }
360
361 void plat_video_menu_update(void)
362 {
363         // WM may grab input while resizing the window; our own window resizing
364         // is only safe if the WM isn't active anymore, so try to grab input.
365         if (plat_sdl_is_windowed() && SDL_WM_GrabInput(SDL_GRAB_ON) == SDL_GRAB_ON) {
366                 // w/h might change in resize callback
367                 int w, h;
368                 do {
369                         w = g_menuscreen_w, h = g_menuscreen_h;
370                         plat_sdl_change_video_mode(w, h, -1);
371                 } while (w != g_menuscreen_w || h != g_menuscreen_h);
372                 SDL_WM_GrabInput(SDL_GRAB_OFF);
373         }
374
375         // update pitch as it is needed by the menu bg scaler
376         if (plat_sdl_overlay || plat_sdl_gl_active)
377                 g_menuscreen_pp = g_menuscreen_w;
378         else
379                 g_menuscreen_pp = plat_sdl_screen->pitch / 2;
380
381         resize_buffers();
382 }
383
384 void plat_video_menu_enter(int is_rom_loaded)
385 {
386         if (SDL_MUSTLOCK(plat_sdl_screen))
387                 SDL_UnlockSurface(plat_sdl_screen);
388 }
389
390 void plat_video_menu_begin(void)
391 {
392         plat_video_menu_update(); // just in case
393
394         if (plat_sdl_overlay || plat_sdl_gl_active)
395                 g_menuscreen_ptr = shadow_fb;
396         else {
397                 if (SDL_MUSTLOCK(plat_sdl_screen))
398                         SDL_LockSurface(plat_sdl_screen);
399                 g_menuscreen_ptr = plat_sdl_screen->pixels;
400         }
401 }
402
403 void plat_video_menu_end(void)
404 {
405         if (plat_sdl_overlay != NULL) {
406                 SDL_Rect dstrect =
407                         { 0, 0, plat_sdl_screen->w, plat_sdl_screen->h };
408
409                 SDL_LockYUVOverlay(plat_sdl_overlay);
410                 if (g_menuscreen_w <= plat_sdl_overlay->w && g_menuscreen_h <= plat_sdl_overlay->h)
411                         rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
412                                 g_menuscreen_w, g_menuscreen_h, g_menuscreen_pp,
413                                 plat_sdl_overlay->pitches[0]/2,
414                                 plat_sdl_overlay->w >= 2 * g_menuscreen_w);
415                 SDL_UnlockYUVOverlay(plat_sdl_overlay);
416
417                 SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
418         }
419         else if (plat_sdl_gl_active) {
420                 gl_flip(g_menuscreen_ptr, g_menuscreen_pp, g_menuscreen_h);
421         }
422         else {
423                 if (SDL_MUSTLOCK(plat_sdl_screen))
424                         SDL_UnlockSurface(plat_sdl_screen);
425                 SDL_Flip(plat_sdl_screen);
426         }
427         g_menuscreen_ptr = NULL;
428 }
429
430 void plat_video_menu_leave(void)
431 {
432 }
433
434 void plat_video_loop_prepare(void)
435 {
436         int w = g_menuscreen_w, h = g_menuscreen_h;
437
438         // take over any new vout settings
439         area = (struct area) { 0, 0 };
440         plat_sdl_change_video_mode(0, 0, 0);
441         resize_buffers();
442
443         // switch over to scaled output if available, but keep the aspect ratio
444         if (plat_sdl_overlay || plat_sdl_gl_active)
445                 w = 320, h = 240;
446
447         g_screen_width = w, g_screen_height = h;
448         plat_video_set_size(w, h);
449
450         if (!(plat_sdl_overlay || plat_sdl_gl_active))
451                 if (SDL_MUSTLOCK(plat_sdl_screen))
452                         SDL_LockSurface(plat_sdl_screen);
453
454         plat_video_set_buffer(g_screen_ptr);
455 }
456
457 void plat_show_cursor(int on)
458 {
459         SDL_ShowCursor(on && !hide_cursor);
460 }
461
462 int plat_grab_cursor(int on)
463 {
464         SDL_WM_GrabInput(on ? SDL_GRAB_ON : SDL_GRAB_OFF);
465         return on;
466 }
467
468 int plat_has_wm(void)
469 {
470         return plat_sdl_is_windowed();
471 }
472
473 static void plat_sdl_resize(int w, int h)
474 {
475         // take over new settings
476 #if defined(__OPENDINGUX__)
477         if (currentConfig.vscaling != EOPT_SCALE_HW &&
478             plat_sdl_screen->w == 320 && plat_sdl_screen->h == 480) {
479                 g_menuscreen_h = 240;
480                 g_menuscreen_w = 320;
481         } else
482 #endif
483         {
484                 g_menuscreen_h = plat_sdl_screen->h;
485                 g_menuscreen_w = plat_sdl_screen->w;
486 #if 0 // auto resizing may be nice, but creates problems on some SDL platforms
487                 if (!plat_sdl_overlay && !plat_sdl_gl_active &&
488                     plat_sdl_is_windowed() && !plat_sdl_is_fullscreen()) {
489                         // in SDL window mode, adapt window to integer scaling
490                         if (g_menuscreen_w * 3/4 >= g_menuscreen_h)
491                                 g_menuscreen_w = g_menuscreen_h * 4/3;
492                         else
493                                 g_menuscreen_h = g_menuscreen_w * 3/4;
494                         g_menuscreen_w = g_menuscreen_w/320*320;
495                         g_menuscreen_h = g_menuscreen_h/240*240;
496                         if (g_menuscreen_w == 0) {
497                                 g_menuscreen_w = 320;
498                                 g_menuscreen_h = 240;
499                         }
500                 }
501 #endif
502         }
503
504         rendstatus_old = -1;
505 }
506
507 static void plat_sdl_quit(void)
508 {
509         // for now..
510         exit(1);
511 }
512
513 void plat_init(void)
514 {
515         int ret;
516
517         ret = plat_sdl_init();
518         if (ret != 0)
519                 exit(1);
520
521 #if defined(__OPENDINGUX__)
522         // opendingux on JZ47x0 may falsely report a HW overlay, fix to window
523         plat_target.vout_method = 0;
524 #elif !defined(__MIYOO__) && !defined(__RETROFW__) && !defined(__DINGUX__)
525         if (! plat_sdl_is_windowed())
526 #endif
527         {
528                 hide_cursor = 1;
529                 SDL_ShowCursor(0);
530         }
531
532         plat_sdl_quit_cb = plat_sdl_quit;
533         plat_sdl_resize_cb = plat_sdl_resize;
534
535         SDL_WM_SetCaption("PicoDrive " VERSION, NULL);
536
537         g_menuscreen_pp = g_menuscreen_w;
538         g_menuscreen_ptr = NULL;
539
540         shadow_size = g_menuscreen_w * g_menuscreen_h * 2;
541         if (shadow_size < 320 * 480 * 2)
542                 shadow_size = 320 * 480 * 2;
543
544         shadow_fb = calloc(1, shadow_size);
545         g_menubg_ptr = calloc(1, shadow_size);
546         if (shadow_fb == NULL || g_menubg_ptr == NULL) {
547                 fprintf(stderr, "OOM\n");
548                 exit(1);
549         }
550
551         g_screen_width = 320;
552         g_screen_height = 240;
553         g_screen_ppitch = 320;
554         g_screen_ptr = shadow_fb;
555
556         plat_target_setup_input();
557         in_sdl_platform_data.defbinds = in_sdl_defbinds,
558         in_sdl_platform_data.kmap_size = in_sdl_key_map_sz,
559         in_sdl_platform_data.key_map = in_sdl_key_map,
560         in_sdl_platform_data.jmap_size = in_sdl_joy_map_sz,
561         in_sdl_platform_data.joy_map = in_sdl_joy_map,
562         in_sdl_platform_data.key_names = in_sdl_key_names,
563         in_sdl_platform_data.kbd_map = in_sdl_kbd_map,
564         in_sdl_init(&in_sdl_platform_data, plat_sdl_event_handler);
565         in_probe();
566
567         // create an artificial resize event to initialize mouse scaling
568         SDL_Event ev;
569         ev.resize.type = SDL_VIDEORESIZE;
570         ev.resize.w = g_menuscreen_w;
571         ev.resize.h = g_menuscreen_h;
572         SDL_PeepEvents(&ev, 1, SDL_ADDEVENT, SDL_ALLEVENTS);
573
574         bgr_to_uyvy_init();
575         linux_menu_init();
576 }
577
578 void plat_finish(void)
579 {
580         free(shadow_fb);
581         shadow_fb = NULL;
582         free(g_menubg_ptr);
583         g_menubg_ptr = NULL;
584         plat_sdl_finish();
585 }