pandora: fix readme and pxml version
[picodrive.git] / platform / common / plat_sdl.c
index 3948cc4..4f8249f 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * PicoDrive
  * (C) notaz, 2013
+ * (C) irixxxx, 2020-2024
  *
  * This work is licensed under the terms of MAME license.
  * See COPYING file in the top-level directory.
 #include <stdio.h>
 
 #include "../libpicofe/input.h"
+#include "../libpicofe/plat.h"
 #include "../libpicofe/plat_sdl.h"
 #include "../libpicofe/in_sdl.h"
 #include "../libpicofe/gl.h"
 #include "emu.h"
 #include "menu_pico.h"
 #include "input_pico.h"
+#include "plat_sdl.h"
 #include "version.h"
 
-#include <pico/pico.h>
+#include <pico/pico_int.h>
 
 static void *shadow_fb;
+static int shadow_size;
+static struct area { int w, h; } area;
 
-const struct in_default_bind in_sdl_defbinds[] __attribute__((weak)) = {
-       { SDLK_UP,     IN_BINDTYPE_PLAYER12, GBTN_UP },
-       { SDLK_DOWN,   IN_BINDTYPE_PLAYER12, GBTN_DOWN },
-       { SDLK_LEFT,   IN_BINDTYPE_PLAYER12, GBTN_LEFT },
-       { SDLK_RIGHT,  IN_BINDTYPE_PLAYER12, GBTN_RIGHT },
-       { SDLK_z,      IN_BINDTYPE_PLAYER12, GBTN_A },
-       { SDLK_x,      IN_BINDTYPE_PLAYER12, GBTN_B },
-       { SDLK_c,      IN_BINDTYPE_PLAYER12, GBTN_C },
-       { SDLK_a,      IN_BINDTYPE_PLAYER12, GBTN_X },
-       { SDLK_s,      IN_BINDTYPE_PLAYER12, GBTN_Y },
-       { SDLK_d,      IN_BINDTYPE_PLAYER12, GBTN_Z },
-       { SDLK_RETURN, IN_BINDTYPE_PLAYER12, GBTN_START },
-       { SDLK_f,      IN_BINDTYPE_PLAYER12, GBTN_MODE },
-       { SDLK_ESCAPE, IN_BINDTYPE_EMU, PEVB_MENU },
-       { SDLK_TAB,    IN_BINDTYPE_EMU, PEVB_RESET },
-       { SDLK_F1,     IN_BINDTYPE_EMU, PEVB_STATE_SAVE },
-       { SDLK_F2,     IN_BINDTYPE_EMU, PEVB_STATE_LOAD },
-       { SDLK_F3,     IN_BINDTYPE_EMU, PEVB_SSLOT_PREV },
-       { SDLK_F4,     IN_BINDTYPE_EMU, PEVB_SSLOT_NEXT },
-       { SDLK_F5,     IN_BINDTYPE_EMU, PEVB_SWITCH_RND },
-       { SDLK_F6,     IN_BINDTYPE_EMU, PEVB_PICO_PPREV },
-       { SDLK_F7,     IN_BINDTYPE_EMU, PEVB_PICO_PNEXT },
-       { SDLK_F8,     IN_BINDTYPE_EMU, PEVB_PICO_SWINP },
-       { SDLK_BACKSPACE, IN_BINDTYPE_EMU, PEVB_FF },
-       { 0, 0, 0 }
-};
-
-const struct menu_keymap in_sdl_key_map[] __attribute__((weak)) =
+static struct in_pdata in_sdl_platform_data;
+
+static int hide_cursor;
+
+static int sound_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 53000, -1 };
+struct plat_target plat_target = { .sound_rates = sound_rates };
+
+#if defined __MIYOO__
+const char *plat_device = "miyoo";
+#elif defined __GCW0__
+const char *plat_device = "gcw0";
+#elif defined __RETROFW__
+const char *plat_device = "retrofw";
+#elif defined __DINGUX__
+const char *plat_device = "dingux";
+#else
+const char *plat_device = "";
+#endif
+
+int plat_parse_arg(int argc, char *argv[], int *x)
 {
-       { SDLK_UP,      PBTN_UP },
-       { SDLK_DOWN,    PBTN_DOWN },
-       { SDLK_LEFT,    PBTN_LEFT },
-       { SDLK_RIGHT,   PBTN_RIGHT },
-       { SDLK_RETURN,  PBTN_MOK },
-       { SDLK_ESCAPE,  PBTN_MBACK },
-       { SDLK_SEMICOLON,       PBTN_MA2 },
-       { SDLK_QUOTE,   PBTN_MA3 },
-       { SDLK_LEFTBRACKET,  PBTN_L },
-       { SDLK_RIGHTBRACKET, PBTN_R },
-};
-
-const struct menu_keymap in_sdl_joy_map[] __attribute__((weak)) =
+#if defined __OPENDINGUX__
+       if (*plat_device == '\0' && strcasecmp(argv[*x], "-device") == 0) {
+               plat_device = argv[++(*x)];
+               return 0;
+       }
+#endif
+       return 1;
+}
+
+void plat_early_init(void)
 {
-       { SDLK_UP,      PBTN_UP },
-       { SDLK_DOWN,    PBTN_DOWN },
-       { SDLK_LEFT,    PBTN_LEFT },
-       { SDLK_RIGHT,   PBTN_RIGHT },
-       /* joystick */
-       { SDLK_WORLD_0, PBTN_MOK },
-       { SDLK_WORLD_1, PBTN_MBACK },
-       { SDLK_WORLD_2, PBTN_MA2 },
-       { SDLK_WORLD_3, PBTN_MA3 },
-};
-
-extern const char * const in_sdl_key_names[] __attribute__((weak));
-
-static const struct in_pdata in_sdl_platform_data = {
-       .defbinds = in_sdl_defbinds,
-       .key_map = in_sdl_key_map,
-       .kmap_size = sizeof(in_sdl_key_map) / sizeof(in_sdl_key_map[0]),
-       .joy_map = in_sdl_joy_map,
-       .jmap_size = sizeof(in_sdl_joy_map) / sizeof(in_sdl_joy_map[0]),
-       .key_names = in_sdl_key_names,
-};
+}
+
+int plat_target_init(void)
+{
+#if defined __ODBETA__
+       if (*plat_device == '\0') {
+               /* ODbeta should always have a device tree, get the model info from there */
+               FILE *f = fopen("/proc/device-tree/compatible", "r");
+               if (f) {
+                       char buf[10];
+                       int c = fread(buf, 1, sizeof(buf), f);
+                       if (strncmp(buf, "gcw,", 4) == 0)
+                               plat_device = "gcw0";
+               }
+       }
+#endif
+       return 0;
+}
+
+void plat_target_finish(void)
+{
+}
 
 /* YUV stuff */
 static int yuv_ry[32], yuv_gy[32], yuv_by[32];
 static unsigned char yuv_u[32 * 2], yuv_v[32 * 2];
+static unsigned char yuv_y[256];
+static struct uyvy { uint32_t y:8; uint32_t vyu:24; } yuv_uyvy[65536];
 
 void bgr_to_uyvy_init(void)
 {
-  int i, v;
-
-  /* init yuv converter:
-    y0 = (int)((0.299f * r0) + (0.587f * g0) + (0.114f * b0));
-    y1 = (int)((0.299f * r1) + (0.587f * g1) + (0.114f * b1));
-    u = (int)(8 * 0.565f * (b0 - y0)) + 128;
-    v = (int)(8 * 0.713f * (r0 - y0)) + 128;
-  */
-  for (i = 0; i < 32; i++) {
-    yuv_ry[i] = (int)(0.299f * i * 65536.0f + 0.5f);
-    yuv_gy[i] = (int)(0.587f * i * 65536.0f + 0.5f);
-    yuv_by[i] = (int)(0.114f * i * 65536.0f + 0.5f);
-  }
-  for (i = -32; i < 32; i++) {
-    v = (int)(8 * 0.565f * i) + 128;
-    if (v < 0)
-      v = 0;
-    if (v > 255)
-      v = 255;
-    yuv_u[i + 32] = v;
-    v = (int)(8 * 0.713f * i) + 128;
-    if (v < 0)
-      v = 0;
-    if (v > 255)
-      v = 255;
-    yuv_v[i + 32] = v;
-  }
+       int i, v;
+
+       /* init yuv converter:
+           y0 = (int)((0.299f * r0) + (0.587f * g0) + (0.114f * b0));
+           y1 = (int)((0.299f * r1) + (0.587f * g1) + (0.114f * b1));
+           u = (int)(8 * 0.565f * (b0 - y0)) + 128;
+           v = (int)(8 * 0.713f * (r0 - y0)) + 128;
+       */
+       for (i = 0; i < 32; i++) {
+               yuv_ry[i] = (int)(0.299f * i * 65536.0f + 0.5f);
+               yuv_gy[i] = (int)(0.587f * i * 65536.0f + 0.5f);
+               yuv_by[i] = (int)(0.114f * i * 65536.0f + 0.5f);
+       }
+       for (i = -32; i < 32; i++) {
+               v = (int)(8 * 0.565f * i) + 128;
+               if (v < 0)
+                       v = 0;
+               if (v > 255)
+                       v = 255;
+               yuv_u[i + 32] = v;
+               v = (int)(8 * 0.713f * i) + 128;
+               if (v < 0)
+                       v = 0;
+               if (v > 255)
+                       v = 255;
+               yuv_v[i + 32] = v;
+       }
+       // valid Y range seems to be 16..235
+       for (i = 0; i < 256; i++) {
+               yuv_y[i] = 16 + 219 * i / 32;
+       }
+       // everything combined into one large array for speed
+       for (i = 0; i < 65536; i++) {
+               int r = (i >> 11) & 0x1f, g = (i >> 6) & 0x1f, b = (i >> 0) & 0x1f;
+               int y = (yuv_ry[r] + yuv_gy[g] + yuv_by[b]) >> 16;
+               yuv_uyvy[i].y = yuv_y[y];
+#if CPU_IS_LE
+               yuv_uyvy[i].vyu = (yuv_v[r-y + 32] << 16) | (yuv_y[y] << 8) | yuv_u[b-y + 32];
+#else
+               yuv_uyvy[i].vyu = (yuv_v[b-y + 32] << 16) | (yuv_y[y] << 8) | yuv_u[r-y + 32];
+#endif
+       }
+}
+
+void rgb565_to_uyvy(void *d, const void *s, int w, int h, int pitch, int dpitch, int x2)
+{
+       uint32_t *dst = d;
+       const uint16_t *src = s;
+       int i;
+
+       if (x2) while (h--) {
+               for (i = w; i >= 4; src += 4, dst += 4, i -= 4)
+               {
+                       struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
+                       struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
+#if CPU_IS_LE
+                       dst[0] = (uyvy0->y << 24) | uyvy0->vyu;
+                       dst[1] = (uyvy1->y << 24) | uyvy1->vyu;
+                       dst[2] = (uyvy2->y << 24) | uyvy2->vyu;
+                       dst[3] = (uyvy3->y << 24) | uyvy3->vyu;
+#else
+                       dst[0] = uyvy0->y | (uyvy0->vyu << 8);
+                       dst[1] = uyvy1->y | (uyvy1->vyu << 8);
+                       dst[2] = uyvy2->y | (uyvy2->vyu << 8);
+                       dst[3] = uyvy3->y | (uyvy3->vyu << 8);
+#endif
+               }
+               src += pitch - (w-i);
+               dst += (dpitch - 2*(w-i))/2;
+       } else while (h--) {
+               for (i = w; i >= 4; src += 4, dst += 2, i -= 4)
+               {
+                       struct uyvy *uyvy0 = yuv_uyvy + src[0], *uyvy1 = yuv_uyvy + src[1];
+                       struct uyvy *uyvy2 = yuv_uyvy + src[2], *uyvy3 = yuv_uyvy + src[3];
+#if CPU_IS_LE
+                       dst[0] = (uyvy1->y << 24) | uyvy0->vyu;
+                       dst[1] = (uyvy3->y << 24) | uyvy2->vyu;
+#else
+                       dst[0] = uyvy1->y | (uyvy0->vyu << 8);
+                       dst[1] = uyvy3->y | (uyvy2->vyu << 8);
+#endif
+               }
+               src += pitch - (w-i);
+               dst += (dpitch - (w-i))/2;
+       }
+}
+
+void copy_intscale(void *dst, int w, int h, int pp, void *src, int sw, int sh, int spp)
+{
+       int xf = w / sw, yf = h / sh, f = xf < yf ? xf : yf;
+       int wf = f * sw, hf = f * sh;
+       int x = (w - wf)/2, y = (h - hf)/2;
+       uint16_t *p = (uint16_t *)dst;
+       uint16_t *q = (uint16_t *)src;
+
+       // copy 16bit image with scaling by an integer factor
+       int i, j, k, l;
+       p += y * pp + x;
+       for (i = 0; i < sh; i++) {
+               for (j = 0; j < sw; j++, q++)
+                       for (l = 0; l < f; l++)
+                               *p++ = *q;
+               p += pp - wf;
+               q += spp - sw;
+               for (k = 1; k < f; k++) {
+                       memcpy(p, p-pp, wf*2);
+                       p += pp;
+               }
+       }
 }
 
-void rgb565_to_uyvy(void *d, const void *s, int pixels)
+static int clear_buf_cnt, clear_stat_cnt;
+
+static void resize_buffers(void)
 {
-  unsigned int *dst = d;
-  const unsigned short *src = s;
-  const unsigned char *yu = yuv_u + 32;
-  const unsigned char *yv = yuv_v + 32;
-  int r0, g0, b0, r1, g1, b1;
-  int y0, y1, u, v;
-
-  for (; pixels > 0; src += 2, dst++, pixels -= 2)
-  {
-    r0 = (src[0] >> 11) & 0x1f;
-    g0 = (src[0] >> 6) & 0x1f;
-    b0 =  src[0] & 0x1f;
-    r1 = (src[1] >> 11) & 0x1f;
-    g1 = (src[1] >> 6) & 0x1f;
-    b1 =  src[1] & 0x1f;
-    y0 = (yuv_ry[r0] + yuv_gy[g0] + yuv_by[b0]) >> 16;
-    y1 = (yuv_ry[r1] + yuv_gy[g1] + yuv_by[b1]) >> 16;
-    u = yu[b0 - y0];
-    v = yv[r0 - y0];
-    // valid Y range seems to be 16..235
-    y0 = 16 + 219 * y0 / 31;
-    y1 = 16 + 219 * y1 / 31;
-
-    *dst = (y1 << 24) | (v << 16) | (y0 << 8) | u;
-  }
+       // make sure the shadow buffers are big enough in case of resize
+       if (shadow_size < g_menuscreen_w * g_menuscreen_h * 2) {
+               shadow_size = g_menuscreen_w * g_menuscreen_h * 2;
+               shadow_fb = realloc(shadow_fb, shadow_size);
+               g_menubg_ptr = realloc(g_menubg_ptr, shadow_size);
+       }
+}
+
+void plat_video_set_size(int w, int h)
+{
+       if ((plat_sdl_overlay || plat_sdl_gl_active) && w <= 320 && h <= 240) {
+               // scale to the window, but mind aspect ratio (scaled to 4:3):
+               // w *= win_aspect / 4:3_aspect or h *= 4:3_aspect / win_aspect
+               if (g_menuscreen_w * 3/4 >= g_menuscreen_h)
+                       w = (w * 3 * g_menuscreen_w/g_menuscreen_h)/4 & ~1;
+               else
+                       h = (h * 4 * g_menuscreen_h/g_menuscreen_w)/3 & ~1;
+       }
+
+       if (area.w != w || area.h != h) {
+               area = (struct area) { w, h };
+
+               if (plat_sdl_overlay || plat_sdl_gl_active || !plat_sdl_is_windowed()) {
+                       // create surface for overlays, or try using a hw scaler
+                       if (plat_sdl_change_video_mode(w, h, 0) < 0) {
+                               // failed, revert to original resolution
+                               area = (struct area) { g_screen_width,g_screen_height };
+                               plat_sdl_change_video_mode(g_screen_width, g_screen_height, 0);
+                       }
+               }
+               if (plat_sdl_overlay || plat_sdl_gl_active) {
+                       // use shadow buffer for overlays
+                       g_screen_width = area.w;
+                       g_screen_height = area.h;
+                       g_screen_ppitch = area.w;
+                       g_screen_ptr = shadow_fb;
+               } else if (plat_sdl_is_windowed() &&
+                   (plat_sdl_screen->w >= 320*2 || plat_sdl_screen->h >= 240*2 ||
+                    plat_sdl_screen->w < 320 || plat_sdl_screen->h < 240)) {
+                       // shadow buffer for integer scaling
+                       g_screen_width = 320;
+                       g_screen_height = 240;
+                       g_screen_ppitch = 320;
+                       g_screen_ptr = shadow_fb;
+               } else {
+                       // unscaled SDL window buffer can be used directly
+                       g_screen_width = plat_sdl_screen->w;
+                       g_screen_height = plat_sdl_screen->h;
+                       g_screen_ppitch = plat_sdl_screen->pitch/2;
+                       g_screen_ptr = plat_sdl_screen->pixels;
+               }
+       }
+}
+
+void plat_video_set_shadow(int w, int h)
+{
+       g_screen_width = w;
+       g_screen_height = h;
+       g_screen_ppitch = w;
+       g_screen_ptr = shadow_fb;
 }
 
 void plat_video_flip(void)
 {
+       resize_buffers();
+
        if (plat_sdl_overlay != NULL) {
                SDL_Rect dstrect =
                        { 0, 0, plat_sdl_screen->w, plat_sdl_screen->h };
-
                SDL_LockYUVOverlay(plat_sdl_overlay);
-               rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
-                               g_screen_ppitch * g_screen_height);
+               if (area.w <= plat_sdl_overlay->w && area.h <= plat_sdl_overlay->h)
+                       rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
+                                       area.w, area.h, g_screen_ppitch,
+                                       plat_sdl_overlay->pitches[0]/2,
+                                       plat_sdl_overlay->w >= 2*area.w);
                SDL_UnlockYUVOverlay(plat_sdl_overlay);
                SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
        }
@@ -166,11 +286,55 @@ void plat_video_flip(void)
                gl_flip(shadow_fb, g_screen_ppitch, g_screen_height);
        }
        else {
+               int copy = g_screen_ptr != plat_sdl_screen->pixels;
+               if (copy)
+                       copy_intscale(plat_sdl_screen->pixels, plat_sdl_screen->w,
+                               plat_sdl_screen->h, plat_sdl_screen->pitch/2,
+                               shadow_fb, g_screen_width, g_screen_height, g_screen_ppitch);
+
                if (SDL_MUSTLOCK(plat_sdl_screen))
                        SDL_UnlockSurface(plat_sdl_screen);
                SDL_Flip(plat_sdl_screen);
-               g_screen_ptr = plat_sdl_screen->pixels;
-               PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2);
+
+               // take over resized settings for the physical SDL surface
+               if ((plat_sdl_screen->w != g_menuscreen_w ||
+                   plat_sdl_screen->h != g_menuscreen_h)  && plat_sdl_is_windowed() &&
+                   SDL_WM_GrabInput(SDL_GRAB_ON) == SDL_GRAB_ON) {
+                       plat_sdl_change_video_mode(g_menuscreen_w, g_menuscreen_h, -1);
+                       SDL_WM_GrabInput(SDL_GRAB_OFF);
+                       g_menuscreen_pp = plat_sdl_screen->pitch/2;
+
+                       // force upper layer to use new dimensions
+                       plat_video_set_shadow(g_screen_width, g_screen_height);
+                       plat_video_set_buffer(g_screen_ptr);
+                       rendstatus_old = -1;
+               } else if (!copy) {
+                       g_screen_ppitch = plat_sdl_screen->pitch/2;
+                       g_screen_ptr = plat_sdl_screen->pixels;
+                       plat_video_set_buffer(g_screen_ptr);
+               }
+
+               if (SDL_MUSTLOCK(plat_sdl_screen))
+                       SDL_LockSurface(plat_sdl_screen);
+
+               if (clear_buf_cnt) {
+                       memset(plat_sdl_screen->pixels, 0, plat_sdl_screen->pitch*plat_sdl_screen->h);
+                       clear_buf_cnt--;
+               }
+       }
+
+       // for overlay/gl modes buffer ptr may change on resize
+       if ((plat_sdl_overlay || plat_sdl_gl_active) &&
+           (g_screen_ptr != shadow_fb || g_screen_ppitch != g_screen_width)) {
+               g_screen_ppitch = g_screen_width;
+               g_screen_ptr = shadow_fb;
+               plat_video_set_buffer(g_screen_ptr);
+       }
+       if (clear_stat_cnt) {
+               unsigned short *d = (unsigned short *)g_screen_ptr + g_screen_ppitch * g_screen_height;
+               int l = g_screen_ppitch * 8;
+               memset((int *)(d - l), 0, l * 2);
+               clear_stat_cnt--;
        }
 }
 
@@ -178,17 +342,57 @@ void plat_video_wait_vsync(void)
 {
 }
 
+void plat_video_clear_status(void)
+{
+       clear_stat_cnt = 3; // do it thrice in case of triple buffering
+}
+
+void plat_video_clear_buffers(void)
+{
+       int count = g_menuscreen_w * g_menuscreen_h;
+       if (count < area.w * area.h) count = area.w * area.h;
+       memset(shadow_fb, 0, count * 2);
+       if (plat_sdl_overlay)
+               plat_sdl_overlay_clear();
+       memset(plat_sdl_screen->pixels, 0, plat_sdl_screen->pitch*plat_sdl_screen->h);
+       clear_buf_cnt = 3; // do it thrice in case of triple buffering
+}
+
+void plat_video_menu_update(void)
+{
+       // WM may grab input while resizing the window; our own window resizing
+       // is only safe if the WM isn't active anymore, so try to grab input.
+       if (plat_sdl_is_windowed() && SDL_WM_GrabInput(SDL_GRAB_ON) == SDL_GRAB_ON) {
+               // w/h might change in resize callback
+               int w, h;
+               do {
+                       w = g_menuscreen_w, h = g_menuscreen_h;
+                       plat_sdl_change_video_mode(w, h, -1);
+               } while (w != g_menuscreen_w || h != g_menuscreen_h);
+               SDL_WM_GrabInput(SDL_GRAB_OFF);
+       }
+
+       // update pitch as it is needed by the menu bg scaler
+       if (plat_sdl_overlay || plat_sdl_gl_active)
+               g_menuscreen_pp = g_menuscreen_w;
+       else
+               g_menuscreen_pp = plat_sdl_screen->pitch / 2;
+
+       resize_buffers();
+}
+
 void plat_video_menu_enter(int is_rom_loaded)
 {
-       plat_sdl_change_video_mode(g_menuscreen_w, g_menuscreen_h, 0);
-       g_screen_ptr = shadow_fb;
+       if (SDL_MUSTLOCK(plat_sdl_screen))
+               SDL_UnlockSurface(plat_sdl_screen);
 }
 
 void plat_video_menu_begin(void)
 {
-       if (plat_sdl_overlay != NULL || plat_sdl_gl_active) {
+       plat_video_menu_update(); // just in case
+
+       if (plat_sdl_overlay || plat_sdl_gl_active)
                g_menuscreen_ptr = shadow_fb;
-       }
        else {
                if (SDL_MUSTLOCK(plat_sdl_screen))
                        SDL_LockSurface(plat_sdl_screen);
@@ -203,8 +407,11 @@ void plat_video_menu_end(void)
                        { 0, 0, plat_sdl_screen->w, plat_sdl_screen->h };
 
                SDL_LockYUVOverlay(plat_sdl_overlay);
-               rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
-                               g_menuscreen_pp * g_menuscreen_h);
+               if (g_menuscreen_w <= plat_sdl_overlay->w && g_menuscreen_h <= plat_sdl_overlay->h)
+                       rgb565_to_uyvy(plat_sdl_overlay->pixels[0], shadow_fb,
+                               g_menuscreen_w, g_menuscreen_h, g_menuscreen_pp,
+                               plat_sdl_overlay->pitches[0]/2,
+                               plat_sdl_overlay->w >= 2 * g_menuscreen_w);
                SDL_UnlockYUVOverlay(plat_sdl_overlay);
 
                SDL_DisplayYUVOverlay(plat_sdl_overlay, &dstrect);
@@ -218,7 +425,6 @@ void plat_video_menu_end(void)
                SDL_Flip(plat_sdl_screen);
        }
        g_menuscreen_ptr = NULL;
-
 }
 
 void plat_video_menu_leave(void)
@@ -227,21 +433,75 @@ void plat_video_menu_leave(void)
 
 void plat_video_loop_prepare(void)
 {
-       plat_sdl_change_video_mode(g_screen_width, g_screen_height, 0);
+       int w = g_menuscreen_w, h = g_menuscreen_h;
 
-       if (plat_sdl_overlay != NULL || plat_sdl_gl_active) {
-               g_screen_ptr = shadow_fb;
-       }
-       else {
+       // take over any new vout settings
+       area = (struct area) { 0, 0 };
+       plat_sdl_change_video_mode(0, 0, 0);
+       resize_buffers();
+
+       // switch over to scaled output if available, but keep the aspect ratio
+       if (plat_sdl_overlay || plat_sdl_gl_active)
+               w = 320, h = 240;
+
+       g_screen_width = w, g_screen_height = h;
+       plat_video_set_size(w, h);
+
+       if (!(plat_sdl_overlay || plat_sdl_gl_active))
                if (SDL_MUSTLOCK(plat_sdl_screen))
                        SDL_LockSurface(plat_sdl_screen);
-               g_screen_ptr = plat_sdl_screen->pixels;
-       }
-       PicoDrawSetOutBuf(g_screen_ptr, g_screen_ppitch * 2);
+
+       plat_video_set_buffer(g_screen_ptr);
 }
 
-void plat_early_init(void)
+void plat_show_cursor(int on)
+{
+       SDL_ShowCursor(on && !hide_cursor);
+}
+
+int plat_grab_cursor(int on)
 {
+       SDL_WM_GrabInput(on ? SDL_GRAB_ON : SDL_GRAB_OFF);
+       return on;
+}
+
+int plat_has_wm(void)
+{
+       return plat_sdl_is_windowed();
+}
+
+static void plat_sdl_resize(int w, int h)
+{
+       // take over new settings
+#if defined(__OPENDINGUX__)
+       if (currentConfig.vscaling != EOPT_SCALE_HW &&
+           plat_sdl_screen->w == 320 && plat_sdl_screen->h == 480) {
+               g_menuscreen_h = 240;
+               g_menuscreen_w = 320;
+       } else
+#endif
+       {
+               g_menuscreen_h = plat_sdl_screen->h;
+               g_menuscreen_w = plat_sdl_screen->w;
+#if 0 // auto resizing may be nice, but creates problems on some SDL platforms
+               if (!plat_sdl_overlay && !plat_sdl_gl_active &&
+                   plat_sdl_is_windowed() && !plat_sdl_is_fullscreen()) {
+                       // in SDL window mode, adapt window to integer scaling
+                       if (g_menuscreen_w * 3/4 >= g_menuscreen_h)
+                               g_menuscreen_w = g_menuscreen_h * 4/3;
+                       else
+                               g_menuscreen_h = g_menuscreen_w * 3/4;
+                       g_menuscreen_w = g_menuscreen_w/320*320;
+                       g_menuscreen_h = g_menuscreen_h/240*240;
+                       if (g_menuscreen_w == 0) {
+                               g_menuscreen_w = 320;
+                               g_menuscreen_h = 240;
+                       }
+               }
+#endif
+       }
+
+       rendstatus_old = -1;
 }
 
 static void plat_sdl_quit(void)
@@ -252,19 +512,28 @@ static void plat_sdl_quit(void)
 
 void plat_init(void)
 {
-       int shadow_size;
        int ret;
 
        ret = plat_sdl_init();
        if (ret != 0)
                exit(1);
 
+#if defined(__OPENDINGUX__)
+       // opendingux on JZ47x0 may falsely report a HW overlay, fix to window
+       plat_target.vout_method = 0;
+#elif !defined(__MIYOO__) && !defined(__RETROFW__) && !defined(__DINGUX__)
+       if (! plat_sdl_is_windowed())
+#endif
+       {
+               hide_cursor = 1;
+               SDL_ShowCursor(0);
+       }
+
        plat_sdl_quit_cb = plat_sdl_quit;
+       plat_sdl_resize_cb = plat_sdl_resize;
 
        SDL_WM_SetCaption("PicoDrive " VERSION, NULL);
 
-       g_menuscreen_w = plat_sdl_screen->w;
-       g_menuscreen_h = plat_sdl_screen->h;
        g_menuscreen_pp = g_menuscreen_w;
        g_menuscreen_ptr = NULL;
 
@@ -272,8 +541,8 @@ void plat_init(void)
        if (shadow_size < 320 * 480 * 2)
                shadow_size = 320 * 480 * 2;
 
-       shadow_fb = malloc(shadow_size);
-       g_menubg_ptr = malloc(shadow_size);
+       shadow_fb = calloc(1, shadow_size);
+       g_menubg_ptr = calloc(1, shadow_size);
        if (shadow_fb == NULL || g_menubg_ptr == NULL) {
                fprintf(stderr, "OOM\n");
                exit(1);
@@ -284,10 +553,26 @@ void plat_init(void)
        g_screen_ppitch = 320;
        g_screen_ptr = shadow_fb;
 
+       plat_target_setup_input();
+       in_sdl_platform_data.defbinds = in_sdl_defbinds,
+       in_sdl_platform_data.kmap_size = in_sdl_key_map_sz,
+       in_sdl_platform_data.key_map = in_sdl_key_map,
+       in_sdl_platform_data.jmap_size = in_sdl_joy_map_sz,
+       in_sdl_platform_data.joy_map = in_sdl_joy_map,
+       in_sdl_platform_data.key_names = in_sdl_key_names,
+       in_sdl_platform_data.kbd_map = in_sdl_kbd_map,
        in_sdl_init(&in_sdl_platform_data, plat_sdl_event_handler);
        in_probe();
 
+       // create an artificial resize event to initialize mouse scaling
+       SDL_Event ev;
+       ev.resize.type = SDL_VIDEORESIZE;
+       ev.resize.w = g_menuscreen_w;
+       ev.resize.h = g_menuscreen_h;
+       SDL_PeepEvents(&ev, 1, SDL_ADDEVENT, SDL_ALLEVENTS);
+
        bgr_to_uyvy_init();
+       linux_menu_init();
 }
 
 void plat_finish(void)