4 * (C) irixxxx, 2020-2024
6 * This work is licensed under the terms of MAME license.
7 * See COPYING file in the top-level directory.
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"
18 #include "menu_pico.h"
19 #include "input_pico.h"
23 #include <pico/pico_int.h>
25 static void *shadow_fb;
26 static int shadow_size;
27 static struct area { int w, h; } area;
29 static struct in_pdata in_sdl_platform_data;
31 static int hide_cursor;
33 static int sound_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 53000, -1 };
34 struct plat_target plat_target = { .sound_rates = sound_rates };
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";
45 const char *plat_device = "";
48 int plat_parse_arg(int argc, char *argv[], int *x)
50 #if defined __OPENDINGUX__
51 if (*plat_device == '\0' && strcasecmp(argv[*x], "-device") == 0) {
52 plat_device = argv[++(*x)];
59 void plat_early_init(void)
63 int plat_target_init(void)
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");
71 int c = fread(buf, 1, sizeof(buf), f);
72 if (strncmp(buf, "gcw,", 4) == 0)
80 void plat_target_finish(void)
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];
90 void bgr_to_uyvy_init(void)
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;
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);
105 for (i = -32; i < 32; i++) {
106 v = (int)(8 * 0.565f * i) + 128;
112 v = (int)(8 * 0.713f * i) + 128;
119 // valid Y range seems to be 16..235
120 for (i = 0; i < 256; i++) {
121 yuv_y[i] = 16 + 219 * i / 32;
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];
129 yuv_uyvy[i].vyu = (yuv_v[r-y + 32] << 16) | (yuv_y[y] << 8) | yuv_u[b-y + 32];
131 yuv_uyvy[i].vyu = (yuv_v[b-y + 32] << 16) | (yuv_y[y] << 8) | yuv_u[r-y + 32];
136 void rgb565_to_uyvy(void *d, const void *s, int w, int h, int pitch, int dpitch, int x2)
139 const uint16_t *src = s;
142 if (x2) while (h--) {
143 for (i = w; i >= 4; src += 4, dst += 4, i -= 4)
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];
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;
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);
159 src += pitch - (w-i);
160 dst += (dpitch - 2*(w-i))/2;
162 for (i = w; i >= 4; src += 4, dst += 2, i -= 4)
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];
167 dst[0] = (uyvy1->y << 24) | uyvy0->vyu;
168 dst[1] = (uyvy3->y << 24) | uyvy2->vyu;
170 dst[0] = uyvy1->y | (uyvy0->vyu << 8);
171 dst[1] = uyvy3->y | (uyvy2->vyu << 8);
174 src += pitch - (w-i);
175 dst += (dpitch - (w-i))/2;
179 void copy_intscale(void *dst, int w, int h, int pp, void *src, int sw, int sh, int spp)
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;
187 // copy 16bit image with scaling by an integer factor
190 for (i = 0; i < sh; i++) {
191 for (j = 0; j < sw; j++, q++)
192 for (l = 0; l < f; l++)
196 for (k = 1; k < f; k++) {
197 memcpy(p, p-pp, wf*2);
203 static int clear_buf_cnt, clear_stat_cnt;
205 static void resize_buffers(void)
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);
215 void plat_video_set_size(int w, int h)
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;
223 h = (h * 4 * g_menuscreen_h/g_menuscreen_w)/3 & ~1;
226 if (area.w != w || area.h != h) {
227 area = (struct area) { w, h };
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);
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;
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;
261 void plat_video_set_shadow(int w, int h)
266 g_screen_ptr = shadow_fb;
269 void plat_video_flip(void)
273 if (plat_sdl_overlay != NULL) {
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);
285 else if (plat_sdl_gl_active) {
286 gl_flip(shadow_fb, g_screen_ppitch, g_screen_height);
289 int copy = g_screen_ptr != plat_sdl_screen->pixels;
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);
295 if (SDL_MUSTLOCK(plat_sdl_screen))
296 SDL_UnlockSurface(plat_sdl_screen);
297 SDL_Flip(plat_sdl_screen);
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;
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);
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);
317 if (SDL_MUSTLOCK(plat_sdl_screen))
318 SDL_LockSurface(plat_sdl_screen);
321 memset(plat_sdl_screen->pixels, 0, plat_sdl_screen->pitch*plat_sdl_screen->h);
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);
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);
341 void plat_video_wait_vsync(void)
345 void plat_video_clear_status(void)
347 clear_stat_cnt = 3; // do it thrice in case of triple buffering
350 void plat_video_clear_buffers(void)
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
361 void plat_video_menu_update(void)
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
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);
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;
379 g_menuscreen_pp = plat_sdl_screen->pitch / 2;
384 void plat_video_menu_enter(int is_rom_loaded)
386 if (SDL_MUSTLOCK(plat_sdl_screen))
387 SDL_UnlockSurface(plat_sdl_screen);
390 void plat_video_menu_begin(void)
392 plat_video_menu_update(); // just in case
394 if (plat_sdl_overlay || plat_sdl_gl_active)
395 g_menuscreen_ptr = shadow_fb;
397 if (SDL_MUSTLOCK(plat_sdl_screen))
398 SDL_LockSurface(plat_sdl_screen);
399 g_menuscreen_ptr = plat_sdl_screen->pixels;
403 void plat_video_menu_end(void)
405 if (plat_sdl_overlay != NULL) {
407 { 0, 0, plat_sdl_screen->w, plat_sdl_screen->h };
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);
417 SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
419 else if (plat_sdl_gl_active) {
420 gl_flip(g_menuscreen_ptr, g_menuscreen_pp, g_menuscreen_h);
423 if (SDL_MUSTLOCK(plat_sdl_screen))
424 SDL_UnlockSurface(plat_sdl_screen);
425 SDL_Flip(plat_sdl_screen);
427 g_menuscreen_ptr = NULL;
430 void plat_video_menu_leave(void)
434 void plat_video_loop_prepare(void)
436 int w = g_menuscreen_w, h = g_menuscreen_h;
438 // take over any new vout settings
439 area = (struct area) { 0, 0 };
440 plat_sdl_change_video_mode(0, 0, 0);
443 // switch over to scaled output if available, but keep the aspect ratio
444 if (plat_sdl_overlay || plat_sdl_gl_active)
447 g_screen_width = w, g_screen_height = h;
448 plat_video_set_size(w, h);
450 if (!(plat_sdl_overlay || plat_sdl_gl_active))
451 if (SDL_MUSTLOCK(plat_sdl_screen))
452 SDL_LockSurface(plat_sdl_screen);
454 plat_video_set_buffer(g_screen_ptr);
457 void plat_show_cursor(int on)
459 SDL_ShowCursor(on && !hide_cursor);
462 int plat_grab_cursor(int on)
464 SDL_WM_GrabInput(on ? SDL_GRAB_ON : SDL_GRAB_OFF);
468 int plat_has_wm(void)
470 return plat_sdl_is_windowed();
473 static void plat_sdl_resize(int w, int h)
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;
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;
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;
507 static void plat_sdl_quit(void)
517 ret = plat_sdl_init();
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())
532 plat_sdl_quit_cb = plat_sdl_quit;
533 plat_sdl_resize_cb = plat_sdl_resize;
535 SDL_WM_SetCaption("PicoDrive " VERSION, NULL);
537 g_menuscreen_pp = g_menuscreen_w;
538 g_menuscreen_ptr = NULL;
540 shadow_size = g_menuscreen_w * g_menuscreen_h * 2;
541 if (shadow_size < 320 * 480 * 2)
542 shadow_size = 320 * 480 * 2;
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");
551 g_screen_width = 320;
552 g_screen_height = 240;
553 g_screen_ppitch = 320;
554 g_screen_ptr = shadow_fb;
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);
567 // create an artificial resize event to initialize mouse scaling
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);
578 void plat_finish(void)