platform ps2, handle audio similar to psp
[picodrive.git] / platform / linux / emu.c
index b99c4a6..7417ca0 100644 (file)
@@ -1,6 +1,7 @@
 /*\r
  * PicoDrive\r
  * (C) notaz, 2006-2010\r
 /*\r
  * PicoDrive\r
  * (C) notaz, 2006-2010\r
+ * (C) irixxxx, 2019-2024\r
  *\r
  * This work is licensed under the terms of MAME license.\r
  * See COPYING file in the top-level directory.\r
  *\r
  * This work is licensed under the terms of MAME license.\r
  * See COPYING file in the top-level directory.\r
@@ -8,11 +9,13 @@
 \r
 #include <stdio.h>\r
 #include <unistd.h>\r
 \r
 #include <stdio.h>\r
 #include <unistd.h>\r
+#include <sys/mman.h>\r
 \r
 #include "../libpicofe/menu.h"\r
 #include "../libpicofe/plat.h"\r
 #include "../common/emu.h"\r
 #include "../common/arm_utils.h"\r
 \r
 #include "../libpicofe/menu.h"\r
 #include "../libpicofe/plat.h"\r
 #include "../common/emu.h"\r
 #include "../common/arm_utils.h"\r
+#include "../common/upscale.h"\r
 #include "../common/version.h"\r
 \r
 #include <pico/pico_int.h>\r
 #include "../common/version.h"\r
 \r
 #include <pico/pico_int.h>\r
@@ -22,6 +25,10 @@ const char *renderer_names[] = { "16bit accurate", " 8bit accurate", " 8bit fast
 const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL };\r
 enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT };\r
 \r
 const char *renderer_names32x[] = { "accurate", "faster", "fastest", NULL };\r
 enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT };\r
 \r
+static int out_x, out_y, out_w, out_h; // renderer output in render buffer\r
+static int screen_x, screen_y, screen_w, screen_h; // final render destination \r
+static int render_bg;                  // force 16bit mode for bg render\r
+static u16 *ghost_buf;                 // backbuffer to simulate LCD ghosting\r
 \r
 void pemu_prep_defconfig(void)\r
 {\r
 \r
 void pemu_prep_defconfig(void)\r
 {\r
@@ -29,44 +36,35 @@ void pemu_prep_defconfig(void)
 \r
 void pemu_validate_config(void)\r
 {\r
 \r
 void pemu_validate_config(void)\r
 {\r
-       extern int PicoOpt;\r
-//     PicoOpt &= ~POPT_EXT_FM;\r
-       PicoOpt &= ~POPT_EN_SVP_DRC;\r
+#if !defined(DRC_SH2)\r
+       PicoIn.opt &= ~POPT_EN_DRC;\r
+#endif\r
 }\r
 \r
 }\r
 \r
-// FIXME: dupes from GP2X, need cleanup\r
-static void (*osd_text)(int x, int y, const char *text);\r
+#define is_16bit_mode() \\r
+       (currentConfig.renderer == RT_16BIT || (PicoIn.AHW & PAHW_32X) || render_bg)\r
 \r
 \r
-/*\r
-static void osd_text8(int x, int y, const char *text)\r
+static int get_renderer(void)\r
 {\r
 {\r
-       int len = strlen(text)*8;\r
-       int *p, i, h, offs;\r
-\r
-       len = (len+3) >> 2;\r
-       for (h = 0; h < 8; h++) {\r
-               offs = (x + g_screen_width * (y+h)) & ~3;\r
-               p = (int *) ((char *)g_screen_ptr + offs);\r
-               for (i = len; i; i--, p++)\r
-                       *p = 0xe0e0e0e0;\r
-       }\r
-       emu_text_out8(x, y, text);\r
+       if (PicoIn.AHW & PAHW_32X)\r
+               return currentConfig.renderer32x;\r
+       else\r
+               return currentConfig.renderer;\r
 }\r
 }\r
-*/\r
 \r
 \r
-static void osd_text16(int x, int y, const char *text)\r
+static void change_renderer(int diff)\r
 {\r
 {\r
-       int len = strlen(text)*8;\r
-       int *p, i, h, offs;\r
-\r
-       len = (len+1) >> 1;\r
-       for (h = 0; h < 8; h++) {\r
-               offs = (x + g_screen_width * (y+h)) & ~1;\r
-               p = (int *) ((short *)g_screen_ptr + offs);\r
-               for (i = len; i; i--, p++)\r
-                       *p = (*p >> 2) & 0x39e7;\r
-       }\r
-       emu_text_out16(x, y, text);\r
+       int *r;\r
+       if (PicoIn.AHW & PAHW_32X)\r
+               r = &currentConfig.renderer32x;\r
+       else\r
+               r = &currentConfig.renderer;\r
+       *r += diff;\r
+\r
+       if      (*r >= RT_COUNT)\r
+               *r = 0;\r
+       else if (*r < 0)\r
+               *r = RT_COUNT - 1;\r
 }\r
 \r
 static void draw_cd_leds(void)\r
 }\r
 \r
 static void draw_cd_leds(void)\r
@@ -74,97 +72,227 @@ static void draw_cd_leds(void)
        int led_reg, pitch, scr_offs, led_offs;\r
        led_reg = Pico_mcd->s68k_regs[0];\r
 \r
        int led_reg, pitch, scr_offs, led_offs;\r
        led_reg = Pico_mcd->s68k_regs[0];\r
 \r
-       pitch = 320;\r
+       pitch = g_screen_ppitch;\r
        led_offs = 4;\r
        scr_offs = pitch * 2 + 4;\r
 \r
        led_offs = 4;\r
        scr_offs = pitch * 2 + 4;\r
 \r
-       if (currentConfig.renderer != RT_16BIT) {\r
-       #define p(x) px[(x) >> 2]\r
-               // 8-bit modes\r
-               unsigned int *px = (unsigned int *)((char *)g_screen_ptr + scr_offs);\r
-               unsigned int col_g = (led_reg & 2) ? 0xc0c0c0c0 : 0xe0e0e0e0;\r
-               unsigned int col_r = (led_reg & 1) ? 0xd0d0d0d0 : 0xe0e0e0e0;\r
-               p(pitch*0) = p(pitch*1) = p(pitch*2) = col_g;\r
-               p(pitch*0 + led_offs) = p(pitch*1 + led_offs) = p(pitch*2 + led_offs) = col_r;\r
-       #undef p\r
-       } else {\r
-       #define p(x) px[(x)*2 >> 2] = px[((x)*2 >> 2) + 1]\r
-               // 16-bit modes\r
-               unsigned int *px = (unsigned int *)((short *)g_screen_ptr + scr_offs);\r
-               unsigned int col_g = (led_reg & 2) ? 0x06000600 : 0;\r
-               unsigned int col_r = (led_reg & 1) ? 0xc000c000 : 0;\r
-               p(pitch*0) = p(pitch*1) = p(pitch*2) = col_g;\r
-               p(pitch*0 + led_offs) = p(pitch*1 + led_offs) = p(pitch*2 + led_offs) = col_r;\r
-       #undef p\r
+#define p(x) px[(x)*2 >> 2] = px[((x)*2 >> 2) + 1]\r
+       // 16-bit modes\r
+       uint32_t *px = (uint32_t *)((short *)g_screen_ptr + scr_offs);\r
+       uint32_t col_g = (led_reg & 2) ? 0x06000600 : 0;\r
+       uint32_t col_r = (led_reg & 1) ? 0xc000c000 : 0;\r
+       p(pitch*0) = p(pitch*1) = p(pitch*2) = col_g;\r
+       p(pitch*0 + led_offs) = p(pitch*1 + led_offs) = p(pitch*2 + led_offs) = col_r;\r
+#undef p\r
+}\r
+\r
+static void draw_pico_ptr(void)\r
+{\r
+       int up = (PicoPicohw.pen_pos[0]|PicoPicohw.pen_pos[1]) & 0x8000;\r
+       int o = (up ? 0x0000 : 0xffff), _ = (up ? 0xffff : 0x0000);\r
+       int pitch = g_screen_ppitch;\r
+       u16 *p = g_screen_ptr;\r
+       int x = pico_pen_x, y = pico_pen_y;\r
+       // storyware pages are actually squished, 2:1\r
+       int h = (pico_inp_mode == 1 ? 160 : out_h);\r
+       if (h < 224) y++;\r
+\r
+       x = (x * out_w * ((1ULL<<32) / 320 + 1)) >> 32;\r
+       y = (y *     h * ((1ULL<<32) / 224 + 1)) >> 32;\r
+       p += (screen_y+y)*pitch + (screen_x+x);\r
+\r
+       p[-pitch-1] ^= o; p[-pitch] ^= _; p[-pitch+1] ^= _; p[-pitch+2] ^= o;\r
+       p[-1]       ^= _; p[0]      ^= o; p[1]        ^= o; p[2]        ^= _;\r
+       p[pitch-1]  ^= _; p[pitch]  ^= o; p[pitch+1]  ^= o; p[pitch+2]  ^= _;\r
+       p[2*pitch-1]^= o; p[2*pitch]^= _; p[2*pitch+1]^= _; p[2*pitch+2]^= o;\r
+}\r
+\r
+/* render/screen buffer handling:\r
+ * In 16 bit mode, render output is directly placed in the screen buffer.\r
+ * SW scaling is handled in renderer (x) and in vscaling callbacks here (y).\r
+ * In 8 bit modes, output goes to the internal Draw2FB buffer in alternate\r
+ * renderer format (8 pix overscan at left/top/bottom), left aligned (DIS_32C).\r
+ * It is converted to 16 bit and SW scaled in pemu_finalize_frame.\r
+ *\r
+ * HW scaling always aligns the image to the left/top, since selecting an area\r
+ * for display isn't always possible.\r
+ */\r
+\r
+static inline u16 *screen_buffer(u16 *buf)\r
+{\r
+       return buf + screen_y * g_screen_ppitch + screen_x -\r
+                       (out_y * g_screen_ppitch + out_x);\r
+}\r
+\r
+void screen_blit(u16 *pd, int pp, u8* ps, int ss, u16 *pal)\r
+{\r
+       typedef void (*upscale_t)\r
+                       (u16 *di,int ds, u8 *si,int ss, int w,int h, u16 *pal);\r
+       static const upscale_t upscale_256_224_hv[] = {\r
+               upscale_rgb_nn_x_4_5_y_16_17,   upscale_rgb_snn_x_4_5_y_16_17,\r
+               upscale_rgb_bl2_x_4_5_y_16_17,  upscale_rgb_bl4_x_4_5_y_16_17,\r
+       };\r
+       static const upscale_t upscale_256_____h[] = {\r
+               upscale_rgb_nn_x_4_5,           upscale_rgb_snn_x_4_5,\r
+               upscale_rgb_bl2_x_4_5,          upscale_rgb_bl4_x_4_5,\r
+       };\r
+       static const upscale_t upscale_____224_v[] = {\r
+               upscale_rgb_nn_y_16_17,         upscale_rgb_snn_y_16_17,\r
+               upscale_rgb_bl2_y_16_17,        upscale_rgb_bl4_y_16_17,\r
+       };\r
+       static const upscale_t upscale_160_144_hv[] = {\r
+               upscale_rgb_nn_x_1_2_y_3_5,     upscale_rgb_nn_x_1_2_y_3_5,\r
+               upscale_rgb_bl2_x_1_2_y_3_5,    upscale_rgb_bl4_x_1_2_y_3_5,\r
+       };\r
+       static const upscale_t upscale_160_____h[] = {\r
+               upscale_rgb_nn_x_1_2,           upscale_rgb_nn_x_1_2,\r
+               upscale_rgb_bl2_x_1_2,          upscale_rgb_bl2_x_1_2,\r
+       };\r
+       static const upscale_t upscale_____144_v[] = {\r
+               upscale_rgb_nn_y_3_5,           upscale_rgb_nn_y_3_5,\r
+               upscale_rgb_bl2_y_3_5,          upscale_rgb_bl4_y_3_5,\r
+       };\r
+       const upscale_t *upscale;\r
+       int y;\r
+\r
+       // handle software upscaling\r
+       upscale = NULL;\r
+       if (currentConfig.scaling == EOPT_SCALE_SW && out_w <= 256) {\r
+           if (currentConfig.vscaling == EOPT_SCALE_SW && out_h <= 224)\r
+               // h+v scaling\r
+               upscale = out_w >= 240 ? upscale_256_224_hv: upscale_160_144_hv;\r
+           else\r
+               // h scaling\r
+               upscale = out_w >= 240 ? upscale_256_____h : upscale_160_____h;\r
+       } else if (currentConfig.vscaling == EOPT_SCALE_SW && out_h <= 224)\r
+               // v scaling\r
+               upscale = out_w >= 240 ? upscale_____224_v : upscale_____144_v;\r
+       if (!upscale) {\r
+               // no scaling\r
+               for (y = 0; y < out_h; y++)\r
+                       h_copy(pd, pp, ps, 328, out_w, f_pal);\r
+               return;\r
        }\r
        }\r
+\r
+       upscale[currentConfig.filter & 0x3](pd, pp, ps, ss, out_w, out_h, pal);\r
 }\r
 \r
 void pemu_finalize_frame(const char *fps, const char *notice)\r
 {\r
 }\r
 \r
 void pemu_finalize_frame(const char *fps, const char *notice)\r
 {\r
-       if (currentConfig.renderer != RT_16BIT && !(PicoAHW & PAHW_32X)) {\r
-               unsigned short *pd = (unsigned short *)g_screen_ptr + 8 * g_screen_width;\r
-               unsigned char *ps = PicoDraw2FB + 328*8 + 8;\r
-               unsigned short *pal = HighPal;\r
-               int i, x;\r
-               if (Pico.m.dirtyPal)\r
-                       PicoDrawUpdateHighPal();\r
-               for (i = 0; i < 224; i++, ps += 8)\r
-                       for (x = 0; x < 320; x++)\r
-                               *pd++ = pal[*ps++];\r
+       if (!is_16bit_mode()) {\r
+               // convert the 8 bit CLUT output to 16 bit RGB\r
+               u16 *pd = screen_buffer(g_screen_ptr) +\r
+                               out_y * g_screen_ppitch + out_x;\r
+               u8  *ps = Pico.est.Draw2FB + out_y * 328 + out_x + 8;\r
+\r
+               PicoDrawUpdateHighPal();\r
+\r
+               if (out_w == 248 && currentConfig.scaling == EOPT_SCALE_SW)\r
+                       pd += (320 - out_w*320/256) / 2; // SMS with 1st tile blanked, recenter\r
+               screen_blit(pd, g_screen_ppitch, ps, 328, Pico.est.HighPal);\r
        }\r
 \r
        }\r
 \r
-       if (notice || (currentConfig.EmuOpt & EOPT_SHOW_FPS)) {\r
-               if (notice)\r
-                       osd_text(4, g_screen_height - 8, notice);\r
-               if (currentConfig.EmuOpt & EOPT_SHOW_FPS)\r
-                       osd_text(g_screen_width - 60, g_screen_height - 8, fps);\r
+       if (currentConfig.ghosting && out_h == 144) {\r
+               // GG LCD ghosting emulation\r
+               u16 *pd = screen_buffer(g_screen_ptr) +\r
+                               out_y * g_screen_ppitch + out_x;\r
+               u16 *ps = ghost_buf;\r
+               int y, h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h;\r
+               int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w;\r
+\r
+               if (currentConfig.ghosting == 1)\r
+                       for (y = 0; y < h; y++) {\r
+                               v_blend((u32 *)pd, (u32 *)ps, w/2, p_075_round);\r
+                               pd += g_screen_ppitch;\r
+                               ps += w;\r
+                       }\r
+               else\r
+                       for (y = 0; y < h; y++) {\r
+                               v_blend((u32 *)pd, (u32 *)ps, w/2, p_05_round);\r
+                               pd += g_screen_ppitch;\r
+                               ps += w;\r
+                       }\r
        }\r
        }\r
-       if ((PicoAHW & PAHW_MCD) && (currentConfig.EmuOpt & EOPT_EN_CD_LEDS))\r
+\r
+       if (PicoIn.AHW & PAHW_PICO) {\r
+               int h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h;\r
+               int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w;\r
+               u16 *pd = screen_buffer(g_screen_ptr) + out_y*g_screen_ppitch + out_x;\r
+\r
+               if (pico_inp_mode)\r
+                       emu_pico_overlay(pd, w, h, g_screen_ppitch);\r
+               if (pico_inp_mode /*== 2 || overlay*/)\r
+                       draw_pico_ptr();\r
+       }\r
+\r
+       if (notice)\r
+               emu_osd_text16(4, g_screen_height - 8, notice);\r
+       if (currentConfig.EmuOpt & EOPT_SHOW_FPS)\r
+               emu_osd_text16(g_screen_width - 60, g_screen_height - 8, fps);\r
+       if ((PicoIn.AHW & PAHW_MCD) && (currentConfig.EmuOpt & EOPT_EN_CD_LEDS))\r
                draw_cd_leds();\r
 }\r
 \r
                draw_cd_leds();\r
 }\r
 \r
+void plat_video_set_buffer(void *buf)\r
+{\r
+       if (is_16bit_mode())\r
+               PicoDrawSetOutBuf(screen_buffer(buf), g_screen_ppitch * 2);\r
+}\r
+\r
 static void apply_renderer(void)\r
 {\r
 static void apply_renderer(void)\r
 {\r
-       switch (currentConfig.renderer) {\r
+       PicoIn.opt |= POPT_DIS_32C_BORDER;\r
+       PicoIn.opt &= ~(POPT_ALT_RENDERER|POPT_EN_SOFTSCALE);\r
+       if (is_16bit_mode()) {\r
+               if (currentConfig.scaling == EOPT_SCALE_SW)\r
+                       PicoIn.opt |= POPT_EN_SOFTSCALE;\r
+               PicoIn.filter = currentConfig.filter;\r
+       }\r
+\r
+       switch (get_renderer()) {\r
        case RT_16BIT:\r
        case RT_16BIT:\r
-               PicoOpt &= ~POPT_ALT_RENDERER;\r
-               PicoDrawSetOutFormat(PDF_RGB555, 0);\r
-               PicoDrawSetOutBuf(g_screen_ptr, g_screen_width * 2);\r
+               // 32X uses line mode for vscaling with accurate renderer, since\r
+               // the MD VDP layer must be unscaled and merging the scaled 32X\r
+               // image data will fail.\r
+               PicoDrawSetOutFormat(PDF_RGB555,\r
+                       (PicoIn.AHW & PAHW_32X) && currentConfig.vscaling);\r
+               PicoDrawSetOutBuf(screen_buffer(g_screen_ptr), g_screen_ppitch * 2);\r
                break;\r
        case RT_8BIT_ACC:\r
                break;\r
        case RT_8BIT_ACC:\r
-               PicoOpt &= ~POPT_ALT_RENDERER;\r
+               // for simplification the 8 bit accurate renderer uses the same\r
+               // storage format as the fast renderer\r
                PicoDrawSetOutFormat(PDF_8BIT, 0);\r
                PicoDrawSetOutFormat(PDF_8BIT, 0);\r
-               PicoDrawSetOutBuf(PicoDraw2FB + 8, 328);\r
+               PicoDrawSetOutBuf(Pico.est.Draw2FB, 328);\r
                break;\r
        case RT_8BIT_FAST:\r
                break;\r
        case RT_8BIT_FAST:\r
-               PicoOpt |=  POPT_ALT_RENDERER;\r
+               PicoIn.opt |=  POPT_ALT_RENDERER;\r
                PicoDrawSetOutFormat(PDF_NONE, 0);\r
                break;\r
        }\r
 \r
                PicoDrawSetOutFormat(PDF_NONE, 0);\r
                break;\r
        }\r
 \r
-       if (PicoAHW & PAHW_32X)\r
-               PicoDrawSetOutBuf(g_screen_ptr, g_screen_width * 2);\r
+       if (PicoIn.AHW & PAHW_32X)\r
+               PicoDrawSetOutBuf(screen_buffer(g_screen_ptr), g_screen_ppitch * 2);\r
+       Pico.m.dirtyPal = 1;\r
 }\r
 \r
 void plat_video_toggle_renderer(int change, int is_menu)\r
 {\r
 }\r
 \r
 void plat_video_toggle_renderer(int change, int is_menu)\r
 {\r
-       currentConfig.renderer += change;\r
-       if      (currentConfig.renderer >= RT_COUNT)\r
-               currentConfig.renderer = 0;\r
-       else if (currentConfig.renderer < 0)\r
-               currentConfig.renderer = RT_COUNT - 1;\r
+       change_renderer(change);\r
+       plat_video_clear_buffers();\r
 \r
 \r
-       if (!is_menu)\r
+       if (!is_menu) {\r
                apply_renderer();\r
 \r
                apply_renderer();\r
 \r
-       emu_status_msg(renderer_names[currentConfig.renderer]);\r
+               if (PicoIn.AHW & PAHW_32X)\r
+                       emu_status_msg(renderer_names32x[get_renderer()]);\r
+               else\r
+                       emu_status_msg(renderer_names[get_renderer()]);\r
+       }\r
 }\r
 \r
 void plat_status_msg_clear(void)\r
 {\r
 }\r
 \r
 void plat_status_msg_clear(void)\r
 {\r
-       unsigned short *d = (unsigned short *)g_screen_ptr + g_screen_width * g_screen_height;\r
-       int l = g_screen_width * 8;\r
-       memset32((int *)(d - l), 0, l * 2 / 4);\r
+       plat_video_clear_status();\r
 }\r
 \r
 void plat_status_msg_busy_next(const char *msg)\r
 }\r
 \r
 void plat_status_msg_busy_next(const char *msg)\r
@@ -178,7 +306,6 @@ void plat_status_msg_busy_next(const char *msg)
 \r
 void plat_status_msg_busy_first(const char *msg)\r
 {\r
 \r
 void plat_status_msg_busy_first(const char *msg)\r
 {\r
-//     memset32(g_screen_ptr, 0, g_screen_width * g_screen_height * 2 / 4);\r
        plat_status_msg_busy_next(msg);\r
 }\r
 \r
        plat_status_msg_busy_next(msg);\r
 }\r
 \r
@@ -186,42 +313,178 @@ void plat_update_volume(int has_changed, int is_up)
 {\r
 }\r
 \r
 {\r
 }\r
 \r
+void pemu_sound_start(void)\r
+{\r
+       emu_sound_start();\r
+}\r
+\r
+void plat_debug_cat(char *str)\r
+{\r
+}\r
+\r
 void pemu_forced_frame(int no_scale, int do_emu)\r
 {\r
 void pemu_forced_frame(int no_scale, int do_emu)\r
 {\r
-       PicoDrawSetOutBuf(g_screen_ptr, g_screen_width * 2);\r
-       PicoDrawSetCallbacks(NULL, NULL);\r
+       int hs = currentConfig.scaling, vs = currentConfig.vscaling;\r
+\r
+       // create centered and sw scaled (if scaling enabled) 16 bit output\r
+       PicoIn.opt &= ~POPT_DIS_32C_BORDER;\r
        Pico.m.dirtyPal = 1;\r
        Pico.m.dirtyPal = 1;\r
+       if (currentConfig.scaling)  currentConfig.scaling  = EOPT_SCALE_SW;\r
+       if (currentConfig.vscaling) currentConfig.vscaling = EOPT_SCALE_SW;\r
 \r
 \r
-       emu_cmn_forced_frame(no_scale, do_emu);\r
+       // render a frame in 16 bit mode\r
+       render_bg = 1;\r
+       emu_cmn_forced_frame(no_scale, do_emu, screen_buffer(g_screen_ptr));\r
+       render_bg = 0;\r
 \r
 \r
-       g_menubg_src_ptr = g_screen_ptr;\r
+       g_menubg_src_ptr = realloc(g_menubg_src_ptr, g_screen_height * g_screen_ppitch * 2);\r
+       memcpy(g_menubg_src_ptr, g_screen_ptr, g_screen_height * g_screen_ppitch * 2);\r
+       currentConfig.scaling = hs, currentConfig.vscaling = vs;\r
 }\r
 \r
 }\r
 \r
-void pemu_sound_start(void)\r
+/* vertical sw scaling, 16 bit mode */\r
+static int vscale_state;\r
+\r
+static int cb_vscaling_begin(unsigned int line)\r
 {\r
 {\r
-       emu_sound_start();\r
+       // at start of new frame?\r
+       if (line <= out_y) {\r
+               // set y frame offset (see emu_video_mode_change)\r
+               Pico.est.DrawLineDest = screen_buffer(g_screen_ptr) +\r
+                               (out_y * g_screen_ppitch /*+ out_x*/);\r
+               vscale_state = 0;\r
+               return out_y - line;\r
+       } else if (line > out_y + out_h)\r
+               return 1;\r
+\r
+       return 0;\r
 }\r
 \r
 }\r
 \r
-void plat_debug_cat(char *str)\r
+static int cb_vscaling_nop(unsigned int line)\r
+{\r
+       return 0;\r
+}\r
+\r
+static int cb_vscaling_end(unsigned int line)\r
 {\r
 {\r
+       u16 *dest = (u16 *)Pico.est.DrawLineDest + out_x;\r
+       // helpers for 32 bit operation (2 pixels at once):\r
+       u32 *dest32 = (u32 *)dest;\r
+       int pp = g_screen_ppitch;\r
+\r
+       if (out_h == 144)\r
+         switch (currentConfig.filter) {\r
+         case 0: v_upscale_nn_3_5(dest32, pp/2, out_w/2, vscale_state);\r
+                 break;\r
+         default: v_upscale_snn_3_5(dest32, pp/2, out_w/2, vscale_state);\r
+                 break;\r
+         }\r
+       else\r
+         switch (currentConfig.filter) {\r
+         case 3: v_upscale_bl4_16_17(dest32, pp/2, out_w/2, vscale_state);\r
+                 break;\r
+         case 2: v_upscale_bl2_16_17(dest32, pp/2, out_w/2, vscale_state);\r
+                 break;\r
+         case 1: v_upscale_snn_16_17(dest32, pp/2, out_w/2, vscale_state);\r
+                 break;\r
+         default: v_upscale_nn_16_17(dest32, pp/2, out_w/2, vscale_state);\r
+                 break;\r
+         }\r
+       Pico.est.DrawLineDest = (u16 *)dest32 - out_x;\r
+       return 0;\r
 }\r
 \r
 }\r
 \r
-void emu_video_mode_change(int start_line, int line_count, int is_32cols)\r
+void emu_video_mode_change(int start_line, int line_count, int start_col, int col_count)\r
 {\r
 {\r
+       // relative position in core fb and screen fb\r
+       out_y = start_line; out_x = start_col;\r
+       out_h = line_count; out_w = col_count;\r
+\r
+       if (! render_bg)\r
+               plat_video_loop_prepare(); // recalculates g_screen_w/h\r
+       PicoDrawSetCallbacks(NULL, NULL);\r
+       // center output in screen\r
+       screen_w = g_screen_width,  screen_x = (screen_w - out_w)/2;\r
+       screen_h = g_screen_height, screen_y = (screen_h - out_h)/2;\r
+\r
+       switch (currentConfig.scaling) {\r
+       case EOPT_SCALE_HW:\r
+               // mind aspect ratio for SMS with 1st column blanked\r
+               screen_w = (out_w == 248 ? 256 : out_w);\r
+               screen_x = (screen_w - out_w)/2;\r
+               break;\r
+       case EOPT_SCALE_SW:\r
+               screen_x = (screen_w - 320)/2;\r
+               break;\r
+       }\r
+       switch (currentConfig.vscaling) {\r
+       case EOPT_SCALE_HW:\r
+               screen_h = (out_h < 224 && out_h > 144 ? 224 : out_h);\r
+               screen_y = 0;\r
+               // NTSC always has 224 visible lines, anything smaller has bars\r
+               if (out_h < 224 && out_h > 144)\r
+                       screen_y += (224 - out_h)/2;\r
+               // handle vertical centering for 16 bit mode\r
+               if (is_16bit_mode())\r
+                       PicoDrawSetCallbacks(cb_vscaling_begin,cb_vscaling_nop);\r
+               break;\r
+       case EOPT_SCALE_SW:\r
+               screen_y = (screen_h - 240)/2 + (out_h < 240 && out_h > 144);\r
+               // NTSC always has 224 visible lines, anything smaller has bars\r
+               if (out_h < 224 && out_h > 144)\r
+                       screen_y += (224 - out_h)/2;\r
+               // in 16 bit mode sw scaling is divided between core and platform\r
+               if (is_16bit_mode() && out_h < 240)\r
+                       PicoDrawSetCallbacks(cb_vscaling_begin,cb_vscaling_end);\r
+               break;\r
+       }\r
+\r
+       if (! render_bg)\r
+               plat_video_set_size(screen_w, screen_h);\r
+\r
+       if (screen_w < g_screen_width)\r
+               screen_x = (g_screen_width  - screen_w)/2;\r
+       if (screen_h < g_screen_height) {\r
+               screen_y = (g_screen_height - screen_h)/2;\r
+               // NTSC always has 224 visible lines, anything smaller has bars\r
+               if (out_h < 224 && out_h > 144)\r
+                       screen_y += (224 - out_h)/2;\r
+       }\r
+\r
+       plat_video_set_buffer(g_screen_ptr);\r
+\r
+       // create a backing buffer for emulating the bad GG lcd display\r
+       if (currentConfig.ghosting && out_h == 144) {\r
+               int h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h;\r
+               int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w;\r
+               ghost_buf = realloc(ghost_buf, w * h * 2);\r
+               memset(ghost_buf, 0, w * h * 2);\r
+       }\r
+\r
        // clear whole screen in all buffers\r
        // clear whole screen in all buffers\r
-       memset32(g_screen_ptr, 0, g_screen_width * g_screen_height * 2 / 4);\r
+       if (!is_16bit_mode())\r
+               memset32(Pico.est.Draw2FB, 0xe0e0e0e0, (320+8) * (8+240+8) / 4);\r
+       plat_video_clear_buffers();\r
 }\r
 \r
 void pemu_loop_prep(void)\r
 {\r
        apply_renderer();\r
 }\r
 \r
 void pemu_loop_prep(void)\r
 {\r
        apply_renderer();\r
-       osd_text = osd_text16;\r
+       plat_video_clear_buffers();\r
 }\r
 \r
 void pemu_loop_end(void)\r
 {\r
        /* do one more frame for menu bg */\r
 }\r
 \r
 void pemu_loop_end(void)\r
 {\r
        /* do one more frame for menu bg */\r
+       plat_video_set_shadow(320, 240);\r
        pemu_forced_frame(0, 1);\r
        pemu_forced_frame(0, 1);\r
+       g_menubg_src_w = g_screen_width;\r
+       g_menubg_src_h = g_screen_height;\r
+       g_menubg_src_pp = g_screen_ppitch;\r
+       if (ghost_buf) {\r
+               free(ghost_buf);\r
+               ghost_buf = NULL;\r
+       }\r
 }\r
 \r
 void plat_wait_till_us(unsigned int us_to)\r
 }\r
 \r
 void plat_wait_till_us(unsigned int us_to)\r
@@ -237,3 +500,12 @@ void plat_wait_till_us(unsigned int us_to)
        }\r
 }\r
 \r
        }\r
 }\r
 \r
+void *plat_mem_get_for_drc(size_t size)\r
+{\r
+#ifdef MAP_JIT\r
+       // newer versions of OSX, IOS or TvOS need this\r
+       return plat_mmap(0, size, 1, 0);\r
+#else\r
+       return NULL;\r
+#endif\r
+}\r