sms, add ghosting for GG LCD (generic+libretro only)
authorkub <derkub@gmail.com>
Thu, 4 Nov 2021 19:33:28 +0000 (20:33 +0100)
committerkub <derkub@gmail.com>
Thu, 4 Nov 2021 19:33:28 +0000 (20:33 +0100)
platform/common/emu.h
platform/common/menu_pico.h
platform/common/upscale.h
platform/libretro/libretro.c
platform/libretro/libretro_core_options.h
platform/linux/emu.c
platform/linux/menu.c

index de91b9b..0be17f9 100644 (file)
@@ -90,6 +90,7 @@ typedef struct _currentConfig_t {
        int renderer;
        int renderer32x;
        int filter;  // EOPT_FILTER_* video filter
+       int ghosting;
        int analog_deadzone;
        int msh2_khz;
        int ssh2_khz;
index 73f3ec0..c74a96d 100644 (file)
@@ -89,6 +89,7 @@ typedef enum
        MA_32XOPT_SSH2_CYCLES,
        MA_SMSOPT_HARDWARE,
        MA_SMSOPT_MAPPER,
+       MA_SMSOPT_GHOSTING,
        MA_CTRL_PLAYER1,
        MA_CTRL_PLAYER2,
        MA_CTRL_EMU,
index 1cf0fd9..c233f84 100644 (file)
 #include <pico/pico_types.h>
 
 
-/* LSB of all colors in a pixel */
+/* LSB of all colors in 1 or 2 pixels */
 #if defined(USE_BGR555)
-#define PXLSB          0x0421
+#define PXLSB          0x04210421
 #else
-#define PXLSB          0x0821
+#define PXLSB          0x08210821
 #endif
 
 /* RGB565 pixel mixing, see https://www.compuphase.com/graphic/scale3.htm and
                            http://blargg.8bitalley.com/info/rgb_mixing.html */
-/* 2-level mixing */
+/* 2-level mixing. NB blargg version isn't 2-pixel-at-once safe for RGB565 */
 //#define p_05(d,p1,p2)        d=(((p1)+(p2)  + ( ((p1)^(p2))&PXLSB))>>1) // round up
 //#define p_05(d,p1,p2)        d=(((p1)+(p2)  - ( ((p1)^(p2))&PXLSB))>>1) // round down
 #define p_05(d,p1,p2)  d=(((p1)&(p2)) + ((((p1)^(p2))&~PXLSB)>>1))
@@ -379,7 +379,7 @@ scalers v:
 */
 
 #define v_mix(di,li,ri,w,p_mix,f) do {                 \
-       u16 i, t, u; (void)t, (void)u;                  \
+       int i; u32 t, u; (void)t, (void)u;              \
        for (i = 0; i < w; i += 4) {                    \
                p_mix((di)[i  ], f((li)[i  ]),f((ri)[i  ])); \
                p_mix((di)[i+1], f((li)[i+1]),f((ri)[i+1])); \
@@ -587,6 +587,35 @@ scalers v:
 } while (0)
 
 
+/* exponentially smoothing (for LCD ghosting): y[n] = x[n]*a + y[n-1]*(1-a) */
+
+#define PXLSBn (PXLSB*15) // using 4 LSBs of each subpixel for subtracting
+// NB implement rounding to x[n] by adding 1 to counter round down if y[n] is
+// smaller than x[n]: use some of the lower bits to implement subtraction on
+// subpixels, with an additional bit to detect borrow, then add the borrow.
+// It's doing the increment wrongly in a lot of cases, which doesn't matter
+// much since it will converge to x[n] in a few frames anyway if x[n] is static
+#define p_05_round(d,p1,p2)                            \
+       p_05(u, p1, p2);                                \
+       t=(u|~PXLSBn)-(p1&PXLSBn); d = u+(~(t>>4)&PXLSB)
+// Unfortunately this won't work for p_025, where adding 1 isn't enough and
+// adding 2 would be too much, so offer only p_075 here
+#define p_075_round(d,p1,p2)                           \
+       p_075(u, p1, p2);                               \
+       t=(u|~PXLSBn)-(p1&PXLSBn); d = u+(~(t>>4)&PXLSB)
+
+// this is essentially v_mix and v_copy combined
+#define v_blend(di,ri,w,p_mix) do {                    \
+       int i; u32 t, u; (void)t, (void)u;              \
+       for (i = 0; i < w; i += 4) {                    \
+               p_mix((ri)[i  ], (di)[i  ],(ri)[i  ]); (di)[i  ] = (ri)[i  ]; \
+               p_mix((ri)[i+1], (di)[i+1],(ri)[i+1]); (di)[i+1] = (ri)[i+1]; \
+               p_mix((ri)[i+2], (di)[i+2],(ri)[i+2]); (di)[i+2] = (ri)[i+2]; \
+               p_mix((ri)[i+3], (di)[i+3],(ri)[i+3]); (di)[i+3] = (ri)[i+3]; \
+       }                                               \
+} while (0)
+
+
 /* X x Y -> X*5/4 x Y, for X 256->320 */
 void upscale_rgb_nn_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
 void upscale_rgb_snn_x_4_5(u16 *__restrict di, int ds, u8 *__restrict si, int ss, int width, int height, u16 *pal);
index 8942d93..c9c38d2 100644 (file)
@@ -40,6 +40,8 @@
 #include <malloc.h>
 #include "libretro-common/include/libretro_gskit_ps2.h"
 #include "ps2/asm.h"
+#else
+#include <platform/common/upscale.h>
 #endif
 
 #ifdef _3DS
@@ -93,7 +95,6 @@ static const float VOUT_4_3 = (4.0f / 3.0f);
 static const float VOUT_CRT = (1.29911f);
 
 static bool show_overscan = false;
-static bool old_show_overscan = false;
 
 /* Required to allow on the fly changes to 'show overscan' */
 static int vm_current_start_line = -1;
@@ -103,9 +104,10 @@ static int vm_current_col_count = -1;
 
 static int vout_16bit = 1;
 static int vout_format = PDF_RGB555;
-static void *vout_buf;
+static void *vout_buf, *vout_ghosting_buf;
 static int vout_width, vout_height, vout_offset;
 static float vout_aspect = 0.0;
+static int vout_ghosting = 0;
 
 #if defined(RENDER_GSKIT_PS2)
 #define VOUT_8BIT_WIDTH 328
@@ -672,6 +674,12 @@ void emu_video_mode_change(int start_line, int line_count, int start_col, int co
          VOUT_MAX_HEIGHT : vout_height;
    vout_offset = (vout_offset > vout_width * (VOUT_MAX_HEIGHT - 1) * 2) ?
          vout_width * (VOUT_MAX_HEIGHT - 1) * 2 : vout_offset;
+
+   /* LCD ghosting */
+   if (vout_ghosting && vout_height == 144) {
+      vout_ghosting_buf = realloc(vout_ghosting_buf, VOUT_MAX_HEIGHT*vout_width*2);
+      memset(vout_ghosting_buf, 0, vout_width*vout_height*2);
+   }
 #endif
    Pico.m.dirtyPal = 1;
 
@@ -1443,6 +1451,7 @@ static void update_variables(bool first_run)
    double new_sound_rate;
    unsigned short old_snd_filter;
    int32_t old_snd_filter_range;
+   bool old_show_overscan;
 
    var.value = NULL;
    var.key = "picodrive_input1";
@@ -1506,6 +1515,17 @@ static void update_variables(bool first_run)
          PicoIn.hwSelect = PMS_MAP_SEGA;
    }
 
+   var.value = NULL;
+   var.key = "picodrive_ggghost";
+   if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
+      if (strcmp(var.value, "normal") == 0)
+         vout_ghosting = 2;
+      else if (strcmp(var.value, "weak") == 0)
+         vout_ghosting = 1;
+      else
+         vout_ghosting = 0;
+   }
+
    OldPicoRegionOverride = PicoIn.regionOverride;
    var.value = NULL;
    var.key = "picodrive_region";
@@ -1818,6 +1838,20 @@ void retro_run(void)
       }
    }
 
+   if (vout_ghosting && vout_height == 144) {
+      unsigned short *pd = (unsigned short *)vout_buf;
+      unsigned short *ps = (unsigned short *)vout_ghosting_buf;
+      int y;
+      for (y = 0; y < VOUT_MAX_HEIGHT; y++) {
+         if (vout_ghosting == 1)
+            v_blend(pd, ps, vout_width, p_075_round);
+         else
+            v_blend(pd, ps, vout_width, p_05_round);
+         pd += vout_width;
+         ps += vout_width;
+      }
+   }
+
    buff = (char*)vout_buf + vout_offset;
 #endif
 
@@ -1913,6 +1947,10 @@ void retro_deinit(void)
    free(vout_buf);
 #endif
    vout_buf = NULL;
+   if (vout_ghosting_buf)
+      free(vout_ghosting_buf);
+   vout_ghosting_buf = NULL;
+
    PicoExit();
 
    for (i = 0; i < sizeof(disks) / sizeof(disks[0]); i++) {
index 80fdc11..01bca34 100644 (file)
@@ -125,6 +125,18 @@ struct retro_core_option_definition option_defs_us[] = {
        },
       "Auto"
    },
+   {
+      "picodrive_ggghost",
+      "Game Gear LCD ghosting",
+      "Enable LCD ghosting emulation.",
+      {
+         { "off", NULL },
+         { "weak",  NULL },
+         { "normal",  NULL },
+         { NULL, NULL },
+      },
+      "off"
+   },
    {
       "picodrive_region",
       "Region",
index 1b228e9..1dbd146 100644 (file)
@@ -27,6 +27,7 @@ enum renderer_types { RT_16BIT, RT_8BIT_ACC, RT_8BIT_FAST, RT_COUNT };
 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
@@ -167,6 +168,23 @@ void pemu_finalize_frame(const char *fps, const char *notice)
                screen_blit(pd, g_screen_ppitch, ps, 328, Pico.est.HighPal);\r
        }\r
 \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
+               for (y = 0; y < h; y++) {\r
+                       if (currentConfig.ghosting == 1)\r
+                               v_blend((u32 *)pd, (u32 *)ps, w/2, p_075_round);\r
+                       else\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 (notice)\r
                emu_osd_text16(4, g_screen_height - 8, notice);\r
        if (currentConfig.EmuOpt & EOPT_SHOW_FPS)\r
@@ -312,27 +330,30 @@ static int cb_vscaling_nop(unsigned int line)
 \r
 static int cb_vscaling_end(unsigned int line)\r
 {\r
-       u16 *dest = Pico.est.DrawLineDest;\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(dest, g_screen_ppitch, 320, vscale_state);\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(dest, g_screen_ppitch, 320, vscale_state);\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(dest, g_screen_ppitch, 320, vscale_state);\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(dest, g_screen_ppitch, 320, vscale_state);\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(dest, g_screen_ppitch, 320, vscale_state);\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(dest, g_screen_ppitch, 320, vscale_state);\r
+         default: v_upscale_nn_16_17(dest32, pp/2, out_w/2, vscale_state);\r
                  break;\r
          }\r
-       Pico.est.DrawLineDest = dest;\r
+       Pico.est.DrawLineDest = (u16 *)dest32 - out_x;\r
        return 0;\r
 }\r
 \r
@@ -382,6 +403,14 @@ void emu_video_mode_change(int start_line, int line_count, int start_col, int co
                plat_video_set_size(screen_w, screen_h);\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
        if (!is_16bit_mode())\r
                memset32(Pico.est.Draw2FB, 0xe0e0e0e0, (320+8) * (8+240+8) / 4);\r
@@ -398,6 +427,10 @@ void pemu_loop_end(void)
 {\r
        /* do one more frame for menu bg */\r
        pemu_forced_frame(0, 1);\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
index 236ebd9..ac24bc1 100644 (file)
@@ -2,13 +2,16 @@
 
 static const char *men_scaling_opts[] = { "OFF", "software", "hardware", NULL };
 static const char *men_filter_opts[] = { "nearest", "smoother", "bilinear 1", "bilinear 2", NULL };
+static const char *men_ghosting_opts[] = { "OFF", "weak", "normal", NULL };
 
 static const char h_scale[] = "hardware scaling may not be working on some devices";
+static const char h_ghost[] = "when active simulates inertia of the GG LCD display";
 
 #define MENU_OPTIONS_GFX \
-       mee_enum_h    ("Horizontal scaling", MA_OPT_SCALING, currentConfig.scaling, men_scaling_opts, h_scale), \
-       mee_enum_h    ("Vertical scaling",  MA_OPT_VSCALING, currentConfig.vscaling, men_scaling_opts, h_scale), \
-       mee_enum_h    ("Scaler type", MA_OPT3_FILTERING, currentConfig.filter, men_filter_opts, NULL), \
+       mee_enum_h    ("Horizontal scaling",     MA_OPT_SCALING, currentConfig.scaling, men_scaling_opts, h_scale), \
+       mee_enum_h    ("Vertical scaling",       MA_OPT_VSCALING, currentConfig.vscaling, men_scaling_opts, h_scale), \
+       mee_enum_h    ("Scaler type",            MA_OPT3_FILTERING, currentConfig.filter, men_filter_opts, NULL), \
+       mee_enum_h    ("Game Gear LCD ghosting", MA_SMSOPT_GHOSTING, currentConfig.ghosting, men_ghosting_opts, h_ghost), \
 
 #define MENU_OPTIONS_ADV