pandora: fix readme and pxml version
[picodrive.git] / platform / common / plat_sdl.c
... / ...
CommitLineData
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
25static void *shadow_fb;
26static int shadow_size;
27static struct area { int w, h; } area;
28
29static struct in_pdata in_sdl_platform_data;
30
31static int hide_cursor;
32
33static int sound_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 53000, -1 };
34struct plat_target plat_target = { .sound_rates = sound_rates };
35
36#if defined __MIYOO__
37const char *plat_device = "miyoo";
38#elif defined __GCW0__
39const char *plat_device = "gcw0";
40#elif defined __RETROFW__
41const char *plat_device = "retrofw";
42#elif defined __DINGUX__
43const char *plat_device = "dingux";
44#else
45const char *plat_device = "";
46#endif
47
48int 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
59void plat_early_init(void)
60{
61}
62
63int 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
80void plat_target_finish(void)
81{
82}
83
84/* YUV stuff */
85static int yuv_ry[32], yuv_gy[32], yuv_by[32];
86static unsigned char yuv_u[32 * 2], yuv_v[32 * 2];
87static unsigned char yuv_y[256];
88static struct uyvy { uint32_t y:8; uint32_t vyu:24; } yuv_uyvy[65536];
89
90void 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
136void 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
179void 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
203static int clear_buf_cnt, clear_stat_cnt;
204
205static 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
215void 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
261void 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
269void 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
341void plat_video_wait_vsync(void)
342{
343}
344
345void plat_video_clear_status(void)
346{
347 clear_stat_cnt = 3; // do it thrice in case of triple buffering
348}
349
350void 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
361void 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
384void plat_video_menu_enter(int is_rom_loaded)
385{
386 if (SDL_MUSTLOCK(plat_sdl_screen))
387 SDL_UnlockSurface(plat_sdl_screen);
388}
389
390void 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
403void 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
430void plat_video_menu_leave(void)
431{
432}
433
434void 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
457void plat_show_cursor(int on)
458{
459 SDL_ShowCursor(on && !hide_cursor);
460}
461
462int plat_grab_cursor(int on)
463{
464 SDL_WM_GrabInput(on ? SDL_GRAB_ON : SDL_GRAB_OFF);
465 return on;
466}
467
468int plat_has_wm(void)
469{
470 return plat_sdl_is_windowed();
471}
472
473static 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
507static void plat_sdl_quit(void)
508{
509 // for now..
510 exit(1);
511}
512
513void 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
578void 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}