int renderer;
int renderer32x;
int filter; // EOPT_FILTER_* video filter
+ int ghosting;
int analog_deadzone;
int msh2_khz;
int ssh2_khz;
MA_32XOPT_SSH2_CYCLES,
MA_SMSOPT_HARDWARE,
MA_SMSOPT_MAPPER,
+ MA_SMSOPT_GHOSTING,
MA_CTRL_PLAYER1,
MA_CTRL_PLAYER2,
MA_CTRL_EMU,
#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))
*/
#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])); \
} 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);
#include <malloc.h>
#include "libretro-common/include/libretro_gskit_ps2.h"
#include "ps2/asm.h"
+#else
+#include <platform/common/upscale.h>
#endif
#ifdef _3DS
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;
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
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;
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";
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";
}
}
+ 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
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++) {
},
"Auto"
},
+ {
+ "picodrive_ggghost",
+ "Game Gear LCD ghosting",
+ "Enable LCD ghosting emulation.",
+ {
+ { "off", NULL },
+ { "weak", NULL },
+ { "normal", NULL },
+ { NULL, NULL },
+ },
+ "off"
+ },
{
"picodrive_region",
"Region",
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
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
\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
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
{\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
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