From: kub Date: Thu, 4 Nov 2021 19:33:28 +0000 (+0100) Subject: sms, add ghosting for GG LCD (generic+libretro only) X-Git-Tag: v2.00~433 X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f55ce7bf474622f1368b88ef6a9c856fa657616f;p=picodrive.git sms, add ghosting for GG LCD (generic+libretro only) --- diff --git a/platform/common/emu.h b/platform/common/emu.h index de91b9b7..0be17f97 100644 --- a/platform/common/emu.h +++ b/platform/common/emu.h @@ -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; diff --git a/platform/common/menu_pico.h b/platform/common/menu_pico.h index 73f3ec05..c74a96d7 100644 --- a/platform/common/menu_pico.h +++ b/platform/common/menu_pico.h @@ -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, diff --git a/platform/common/upscale.h b/platform/common/upscale.h index 1cf0fd9c..c233f847 100644 --- a/platform/common/upscale.h +++ b/platform/common/upscale.h @@ -36,16 +36,16 @@ #include -/* 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); diff --git a/platform/libretro/libretro.c b/platform/libretro/libretro.c index 8942d939..c9c38d24 100644 --- a/platform/libretro/libretro.c +++ b/platform/libretro/libretro.c @@ -40,6 +40,8 @@ #include #include "libretro-common/include/libretro_gskit_ps2.h" #include "ps2/asm.h" +#else +#include #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++) { diff --git a/platform/libretro/libretro_core_options.h b/platform/libretro/libretro_core_options.h index 80fdc11d..01bca340 100644 --- a/platform/libretro/libretro_core_options.h +++ b/platform/libretro/libretro_core_options.h @@ -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", diff --git a/platform/linux/emu.c b/platform/linux/emu.c index 1b228e91..1dbd146a 100644 --- a/platform/linux/emu.c +++ b/platform/linux/emu.c @@ -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 static int screen_x, screen_y, screen_w, screen_h; // final render destination static int render_bg; // force 16bit mode for bg render +static u16 *ghost_buf; // backbuffer to simulate LCD ghosting void pemu_prep_defconfig(void) { @@ -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); } + if (currentConfig.ghosting && out_h == 144) { + // GG LCD ghosting emulation + u16 *pd = screen_buffer(g_screen_ptr) + + out_y * g_screen_ppitch + out_x; + u16 *ps = ghost_buf; + int y, h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h; + int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w; + for (y = 0; y < h; y++) { + if (currentConfig.ghosting == 1) + v_blend((u32 *)pd, (u32 *)ps, w/2, p_075_round); + else + v_blend((u32 *)pd, (u32 *)ps, w/2, p_05_round); + pd += g_screen_ppitch; + ps += w; + } + } + if (notice) emu_osd_text16(4, g_screen_height - 8, notice); if (currentConfig.EmuOpt & EOPT_SHOW_FPS) @@ -312,27 +330,30 @@ static int cb_vscaling_nop(unsigned int line) static int cb_vscaling_end(unsigned int line) { - u16 *dest = Pico.est.DrawLineDest; + u16 *dest = (u16 *)Pico.est.DrawLineDest + out_x; + // helpers for 32 bit operation (2 pixels at once): + u32 *dest32 = (u32 *)dest; + int pp = g_screen_ppitch; if (out_h == 144) switch (currentConfig.filter) { - case 0: v_upscale_nn_3_5(dest, g_screen_ppitch, 320, vscale_state); + case 0: v_upscale_nn_3_5(dest32, pp/2, out_w/2, vscale_state); break; - default: v_upscale_snn_3_5(dest, g_screen_ppitch, 320, vscale_state); + default: v_upscale_snn_3_5(dest32, pp/2, out_w/2, vscale_state); break; } else switch (currentConfig.filter) { - case 3: v_upscale_bl4_16_17(dest, g_screen_ppitch, 320, vscale_state); + case 3: v_upscale_bl4_16_17(dest32, pp/2, out_w/2, vscale_state); break; - case 2: v_upscale_bl2_16_17(dest, g_screen_ppitch, 320, vscale_state); + case 2: v_upscale_bl2_16_17(dest32, pp/2, out_w/2, vscale_state); break; - case 1: v_upscale_snn_16_17(dest, g_screen_ppitch, 320, vscale_state); + case 1: v_upscale_snn_16_17(dest32, pp/2, out_w/2, vscale_state); break; - default: v_upscale_nn_16_17(dest, g_screen_ppitch, 320, vscale_state); + default: v_upscale_nn_16_17(dest32, pp/2, out_w/2, vscale_state); break; } - Pico.est.DrawLineDest = dest; + Pico.est.DrawLineDest = (u16 *)dest32 - out_x; return 0; } @@ -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); plat_video_set_buffer(g_screen_ptr); + // create a backing buffer for emulating the bad GG lcd display + if (currentConfig.ghosting && out_h == 144) { + int h = currentConfig.vscaling == EOPT_SCALE_SW ? 240:out_h; + int w = currentConfig.scaling == EOPT_SCALE_SW ? 320:out_w; + ghost_buf = realloc(ghost_buf, w * h * 2); + memset(ghost_buf, 0, w * h * 2); + } + // clear whole screen in all buffers if (!is_16bit_mode()) memset32(Pico.est.Draw2FB, 0xe0e0e0e0, (320+8) * (8+240+8) / 4); @@ -398,6 +427,10 @@ void pemu_loop_end(void) { /* do one more frame for menu bg */ pemu_forced_frame(0, 1); + if (ghost_buf) { + free(ghost_buf); + ghost_buf = NULL; + } } void plat_wait_till_us(unsigned int us_to) diff --git a/platform/linux/menu.c b/platform/linux/menu.c index 236ebd92..ac24bc1d 100644 --- a/platform/linux/menu.c +++ b/platform/linux/menu.c @@ -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