snes9x/unix/x11.cpp

1874 lines
52 KiB
C++

/*****************************************************************************\
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
This file is licensed under the Snes9x License.
For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>
#ifdef USE_XVIDEO
#include <X11/extensions/Xvlib.h>
#ifdef USE_LIBYUV
#include <libyuv.h>
#endif
#define FOURCC_YUY2 0x32595559
#define FOURCC_I420 0x30323449
#endif
#ifdef USE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#ifdef MITSHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif
#include "snes9x.h"
#include "memmap.h"
#include "ppu.h"
#include "controls.h"
#include "movie.h"
#include "logger.h"
#include "conffile.h"
#include "blit.h"
#include "display.h"
// Wrapper struct to make generic XvImage vs XImage
struct Image
{
#ifdef USE_XVIDEO
union
{
XvImage* xvimage;
#endif
XImage* ximage;
#ifdef USE_XVIDEO
};
#endif
char *data;
uint32 height;
uint32 data_size;
uint32 bits_per_pixel;
uint32 bytes_per_line;
};
struct GUIData
{
Display *display;
Screen *screen;
Visual *visual;
GC gc;
int screen_num;
int depth;
int pixel_format;
int bytes_per_pixel;
uint32 red_shift;
uint32 blue_shift;
uint32 green_shift;
uint32 red_size;
uint32 green_size;
uint32 blue_size;
Window window;
Image *image;
uint8 *snes_buffer;
uint8 *filter_buffer;
uint8 *blit_screen;
uint32 blit_screen_pitch;
bool8 need_convert;
Cursor point_cursor;
Cursor cross_hair_cursor;
int video_mode;
int mouse_x;
int mouse_y;
bool8 mod1_pressed;
bool8 no_repeat;
bool8 fullscreen;
bool8 js_event_latch;
int x_offset;
int y_offset;
#ifdef USE_XVIDEO
bool8 use_xvideo;
int xv_port;
int scale_w;
int scale_h;
bool8 maxaspect;
int imageHeight;
int xv_format;
int xv_bpp;
unsigned char y_table[1 << 15];
unsigned char u_table[1 << 15];
unsigned char v_table[1 << 15];
#endif
#ifdef USE_XINERAMA
uint32 xinerama_head;
#endif
#ifdef MITSHM
XShmSegmentInfo sm_info;
bool8 use_shared_memory;
#endif
};
static struct GUIData GUI;
typedef std::pair<std::string, std::string> strpair_t;
extern std::vector<strpair_t> keymaps;
typedef void (* Blitter) (uint8 *, int, uint8 *, int, int, int);
#ifdef __linux
// Select seems to be broken in 2.x.x kernels - if a signal interrupts a
// select system call with a zero timeout, the select call is restarted but
// with an infinite timeout! The call will block until data arrives on the
// selected fd(s).
//
// The workaround is to stop the X library calling select in the first
// place! Replace XPending - which polls for data from the X server using
// select - with an ioctl call to poll for data and then only call the blocking
// XNextEvent if data is waiting.
#define SELECT_BROKEN_FOR_SIGNALS
#endif
enum
{
VIDEOMODE_BLOCKY = 1,
VIDEOMODE_TV,
VIDEOMODE_SMOOTH,
VIDEOMODE_SUPEREAGLE,
VIDEOMODE_2XSAI,
VIDEOMODE_SUPER2XSAI,
VIDEOMODE_EPX,
VIDEOMODE_HQ2X
};
static int ErrorHandler (Display *, XErrorEvent *);
static bool8 CheckForPendingXEvents (Display *);
static void SetXRepeat (bool8);
static void SetupImage (void);
static void TakedownImage (void);
static void SetupXImage (void);
static void TakedownXImage (void);
#ifdef USE_XVIDEO
static void SetupXvImage (void);
static void TakedownXvImage (void);
#endif
static void Repaint (bool8);
static void Convert16To24 (int, int);
static void Convert16To24Packed (int, int);
void S9xExtraDisplayUsage (void)
{
/* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
S9xMessage(S9X_INFO, S9X_USAGE, "-setrepeat Allow altering keyboard auto-repeat");
S9xMessage(S9X_INFO, S9X_USAGE, "");
S9xMessage(S9X_INFO, S9X_USAGE, "-fullscreen Switch to full-screen on start");
#ifdef USE_XVIDEO
S9xMessage(S9X_INFO, S9X_USAGE, "-xvideo Hardware accelerated scaling");
S9xMessage(S9X_INFO, S9X_USAGE, "-maxaspect Try to fill the display, in fullscreen");
#endif
#ifdef USE_XINERAMA
S9xMessage(S9X_INFO, S9X_USAGE, "-xineramahead Xinerama head number for multi-monitor setups");
#endif
S9xMessage(S9X_INFO, S9X_USAGE, "");
S9xMessage(S9X_INFO, S9X_USAGE, "-v1 Video mode: Blocky (default)");
S9xMessage(S9X_INFO, S9X_USAGE, "-v2 Video mode: TV");
S9xMessage(S9X_INFO, S9X_USAGE, "-v3 Video mode: Smooth");
S9xMessage(S9X_INFO, S9X_USAGE, "-v4 Video mode: SuperEagle");
S9xMessage(S9X_INFO, S9X_USAGE, "-v5 Video mode: 2xSaI");
S9xMessage(S9X_INFO, S9X_USAGE, "-v6 Video mode: Super2xSaI");
S9xMessage(S9X_INFO, S9X_USAGE, "-v7 Video mode: EPX");
S9xMessage(S9X_INFO, S9X_USAGE, "-v8 Video mode: hq2x");
S9xMessage(S9X_INFO, S9X_USAGE, "");
}
void S9xParseDisplayArg (char **argv, int &i, int argc)
{
if (!strcasecmp(argv[i], "-setrepeat"))
GUI.no_repeat = FALSE;
else
if (!strcasecmp(argv[i], "-fullscreen"))
GUI.fullscreen = TRUE;
else
#ifdef USE_XVIDEO
if (!strcasecmp(argv[i], "-xvideo"))
GUI.use_xvideo = TRUE;
else
if (!strcasecmp(argv[i], "-maxaspect"))
GUI.maxaspect = TRUE;
else
#endif
#ifdef USE_XINERAMA
if (!strcasecmp(argv[i], "-xineramahead"))
{
if (i + 1 < argc)
GUI.xinerama_head = atoi(argv[++i]);
else
S9xUsage();
}
else
#endif
if (!strncasecmp(argv[i], "-v", 2))
{
switch (argv[i][2])
{
case '1': GUI.video_mode = VIDEOMODE_BLOCKY; break;
case '2': GUI.video_mode = VIDEOMODE_TV; break;
case '3': GUI.video_mode = VIDEOMODE_SMOOTH; break;
case '4': GUI.video_mode = VIDEOMODE_SUPEREAGLE; break;
case '5': GUI.video_mode = VIDEOMODE_2XSAI; break;
case '6': GUI.video_mode = VIDEOMODE_SUPER2XSAI; break;
case '7': GUI.video_mode = VIDEOMODE_EPX; break;
case '8': GUI.video_mode = VIDEOMODE_HQ2X; break;
}
}
else
S9xUsage();
}
const char * S9xParseDisplayConfig (ConfigFile &conf, int pass)
{
if (pass != 1)
return ("Unix/X11");
if (!conf.GetBool("Unix::ClearAllControls", false))
{
keymaps.push_back(strpair_t("K00:k", "Joypad1 Right"));
keymaps.push_back(strpair_t("K00:Right", "Joypad1 Right"));
keymaps.push_back(strpair_t("K00:h", "Joypad1 Left"));
keymaps.push_back(strpair_t("K00:Left", "Joypad1 Left"));
keymaps.push_back(strpair_t("K00:j", "Joypad1 Down"));
keymaps.push_back(strpair_t("K00:n", "Joypad1 Down"));
keymaps.push_back(strpair_t("K00:Down", "Joypad1 Down"));
keymaps.push_back(strpair_t("K00:u", "Joypad1 Up"));
keymaps.push_back(strpair_t("K00:Up", "Joypad1 Up"));
keymaps.push_back(strpair_t("K00:Return", "Joypad1 Start"));
keymaps.push_back(strpair_t("K00:space", "Joypad1 Select"));
keymaps.push_back(strpair_t("K00:S+d", "Joypad1 ToggleTurbo A"));
keymaps.push_back(strpair_t("K00:C+d", "Joypad1 ToggleSticky A"));
keymaps.push_back(strpair_t("K00:d", "Joypad1 A"));
keymaps.push_back(strpair_t("K00:S+c", "Joypad1 ToggleTurbo B"));
keymaps.push_back(strpair_t("K00:C+c", "Joypad1 ToggleSticky B"));
keymaps.push_back(strpair_t("K00:c", "Joypad1 B"));
keymaps.push_back(strpair_t("K00:S+s", "Joypad1 ToggleTurbo X"));
keymaps.push_back(strpair_t("K00:C+s", "Joypad1 ToggleSticky X"));
keymaps.push_back(strpair_t("K00:s", "Joypad1 X"));
keymaps.push_back(strpair_t("K00:S+x", "Joypad1 ToggleTurbo Y"));
keymaps.push_back(strpair_t("K00:C+x", "Joypad1 ToggleSticky Y"));
keymaps.push_back(strpair_t("K00:x", "Joypad1 Y"));
keymaps.push_back(strpair_t("K00:S+a", "Joypad1 ToggleTurbo L"));
keymaps.push_back(strpair_t("K00:S+v", "Joypad1 ToggleTurbo L"));
keymaps.push_back(strpair_t("K00:C+a", "Joypad1 ToggleSticky L"));
keymaps.push_back(strpair_t("K00:C+v", "Joypad1 ToggleSticky L"));
keymaps.push_back(strpair_t("K00:a", "Joypad1 L"));
keymaps.push_back(strpair_t("K00:v", "Joypad1 L"));
keymaps.push_back(strpair_t("K00:S+z", "Joypad1 ToggleTurbo R"));
keymaps.push_back(strpair_t("K00:C+z", "Joypad1 ToggleSticky R"));
keymaps.push_back(strpair_t("K00:z", "Joypad1 R"));
keymaps.push_back(strpair_t("K00:KP_Left", "Joypad2 Left"));
keymaps.push_back(strpair_t("K00:KP_Right", "Joypad2 Right"));
keymaps.push_back(strpair_t("K00:KP_Up", "Joypad2 Up"));
keymaps.push_back(strpair_t("K00:KP_Down", "Joypad2 Down"));
keymaps.push_back(strpair_t("K00:KP_Enter", "Joypad2 Start"));
keymaps.push_back(strpair_t("K00:KP_Add", "Joypad2 Select"));
keymaps.push_back(strpair_t("K00:Prior", "Joypad2 A"));
keymaps.push_back(strpair_t("K00:Next", "Joypad2 B"));
keymaps.push_back(strpair_t("K00:Home", "Joypad2 X"));
keymaps.push_back(strpair_t("K00:End", "Joypad2 Y"));
keymaps.push_back(strpair_t("K00:Insert", "Joypad2 L"));
keymaps.push_back(strpair_t("K00:Delete", "Joypad2 R"));
keymaps.push_back(strpair_t("K00:A+F4", "SoundChannel0"));
keymaps.push_back(strpair_t("K00:C+F4", "SoundChannel0"));
keymaps.push_back(strpair_t("K00:A+F5", "SoundChannel1"));
keymaps.push_back(strpair_t("K00:C+F5", "SoundChannel1"));
keymaps.push_back(strpair_t("K00:A+F6", "SoundChannel2"));
keymaps.push_back(strpair_t("K00:C+F6", "SoundChannel2"));
keymaps.push_back(strpair_t("K00:A+F7", "SoundChannel3"));
keymaps.push_back(strpair_t("K00:C+F7", "SoundChannel3"));
keymaps.push_back(strpair_t("K00:A+F8", "SoundChannel4"));
keymaps.push_back(strpair_t("K00:C+F8", "SoundChannel4"));
keymaps.push_back(strpair_t("K00:A+F9", "SoundChannel5"));
keymaps.push_back(strpair_t("K00:C+F9", "SoundChannel5"));
keymaps.push_back(strpair_t("K00:A+F10", "SoundChannel6"));
keymaps.push_back(strpair_t("K00:C+F10", "SoundChannel6"));
keymaps.push_back(strpair_t("K00:A+F11", "SoundChannel7"));
keymaps.push_back(strpair_t("K00:C+F11", "SoundChannel7"));
keymaps.push_back(strpair_t("K00:A+F12", "SoundChannelsOn"));
keymaps.push_back(strpair_t("K00:C+F12", "SoundChannelsOn"));
keymaps.push_back(strpair_t("K00:S+1", "BeginRecordingMovie"));
keymaps.push_back(strpair_t("K00:S+2", "EndRecordingMovie"));
keymaps.push_back(strpair_t("K00:S+3", "LoadMovie"));
keymaps.push_back(strpair_t("K00:A+F1", "SaveSPC"));
keymaps.push_back(strpair_t("K00:C+F1", "SaveSPC"));
keymaps.push_back(strpair_t("K00:F10", "LoadOopsFile"));
keymaps.push_back(strpair_t("K00:A+F2", "LoadFreezeFile"));
keymaps.push_back(strpair_t("K00:C+F2", "LoadFreezeFile"));
keymaps.push_back(strpair_t("K00:F11", "LoadFreezeFile"));
keymaps.push_back(strpair_t("K00:A+F3", "SaveFreezeFile"));
keymaps.push_back(strpair_t("K00:C+F3", "SaveFreezeFile"));
keymaps.push_back(strpair_t("K00:F12", "SaveFreezeFile"));
keymaps.push_back(strpair_t("K00:F1", "QuickLoad000"));
keymaps.push_back(strpair_t("K00:F2", "QuickLoad001"));
keymaps.push_back(strpair_t("K00:F3", "QuickLoad002"));
keymaps.push_back(strpair_t("K00:F4", "QuickLoad003"));
keymaps.push_back(strpair_t("K00:F5", "QuickLoad004"));
keymaps.push_back(strpair_t("K00:F6", "QuickLoad005"));
keymaps.push_back(strpair_t("K00:F7", "QuickLoad006"));
keymaps.push_back(strpair_t("K00:F8", "QuickLoad007"));
keymaps.push_back(strpair_t("K00:F9", "QuickLoad008"));
keymaps.push_back(strpair_t("K00:S+F1", "QuickSave000"));
keymaps.push_back(strpair_t("K00:S+F2", "QuickSave001"));
keymaps.push_back(strpair_t("K00:S+F3", "QuickSave002"));
keymaps.push_back(strpair_t("K00:S+F4", "QuickSave003"));
keymaps.push_back(strpair_t("K00:S+F5", "QuickSave004"));
keymaps.push_back(strpair_t("K00:S+F6", "QuickSave005"));
keymaps.push_back(strpair_t("K00:S+F7", "QuickSave006"));
keymaps.push_back(strpair_t("K00:S+F8", "QuickSave007"));
keymaps.push_back(strpair_t("K00:S+F9", "QuickSave008"));
keymaps.push_back(strpair_t("K00:Scroll_Lock", "Pause"));
keymaps.push_back(strpair_t("K00:CS+Escape", "Reset"));
keymaps.push_back(strpair_t("K00:S+Escape", "SoftReset"));
keymaps.push_back(strpair_t("K00:Escape", "ExitEmu"));
keymaps.push_back(strpair_t("K00:Tab", "EmuTurbo"));
keymaps.push_back(strpair_t("K00:S+Tab", "ToggleEmuTurbo"));
keymaps.push_back(strpair_t("K00:A+equal", "IncEmuTurbo"));
keymaps.push_back(strpair_t("K00:A+minus", "DecEmuTurbo"));
keymaps.push_back(strpair_t("K00:C+equal", "IncTurboSpeed"));
keymaps.push_back(strpair_t("K00:C+minus", "DecTurboSpeed"));
keymaps.push_back(strpair_t("K00:equal", "IncFrameRate"));
keymaps.push_back(strpair_t("K00:minus", "DecFrameRate"));
keymaps.push_back(strpair_t("K00:S+equal", "IncFrameTime"));
keymaps.push_back(strpair_t("K00:S+minus", "DecFrameTime"));
keymaps.push_back(strpair_t("K00:6", "SwapJoypads"));
keymaps.push_back(strpair_t("K00:Print", "Screenshot"));
keymaps.push_back(strpair_t("K00:1", "ToggleBG0"));
keymaps.push_back(strpair_t("K00:2", "ToggleBG1"));
keymaps.push_back(strpair_t("K00:3", "ToggleBG2"));
keymaps.push_back(strpair_t("K00:4", "ToggleBG3"));
keymaps.push_back(strpair_t("K00:5", "ToggleSprites"));
keymaps.push_back(strpair_t("K00:9", "ToggleTransparency"));
keymaps.push_back(strpair_t("K00:BackSpace", "ClipWindows"));
keymaps.push_back(strpair_t("K00:A+Escape", "Debugger"));
keymaps.push_back(strpair_t("M00:B0", "{Mouse1 L,Superscope Fire,Justifier1 Trigger}"));
keymaps.push_back(strpair_t("M00:B1", "{Justifier1 AimOffscreen Trigger,Superscope AimOffscreen}"));
keymaps.push_back(strpair_t("M00:B2", "{Mouse1 R,Superscope Cursor,Justifier1 Start}"));
keymaps.push_back(strpair_t("M00:Pointer", "Pointer Mouse1+Superscope+Justifier1"));
keymaps.push_back(strpair_t("K00:grave", "Superscope ToggleTurbo"));
keymaps.push_back(strpair_t("K00:slash", "Superscope Pause"));
keymaps.push_back(strpair_t("K00:r", "Rewind"));
keymaps.push_back(strpair_t("K00:l", "Advance"));
}
GUI.no_repeat = !conf.GetBool("Unix/X11::SetKeyRepeat", TRUE);
GUI.fullscreen = conf.GetBool("Unix/X11::Fullscreen", FALSE);
#ifdef USE_XVIDEO
GUI.use_xvideo = conf.GetBool("Unix/X11::Xvideo", FALSE);
GUI.maxaspect = conf.GetBool("Unix/X11::MaxAspect", FALSE);
#endif
#ifdef USE_XINERAMA
GUI.xinerama_head = conf.GetUInt("Unix/X11::XineramaHead", 0);
#endif
if (conf.Exists("Unix/X11::VideoMode"))
{
GUI.video_mode = conf.GetUInt("Unix/X11::VideoMode", VIDEOMODE_BLOCKY);
if (GUI.video_mode < 1 || GUI.video_mode > 8)
GUI.video_mode = VIDEOMODE_BLOCKY;
}
else
GUI.video_mode = VIDEOMODE_BLOCKY;
return ("Unix/X11");
}
static void FatalError (const char *str)
{
fprintf(stderr, "%s\n", str);
S9xExit();
}
static int ErrorHandler (Display *display, XErrorEvent *event)
{
#ifdef MITSHM
GUI.use_shared_memory = FALSE;
#endif
return (0);
}
#ifdef USE_XVIDEO
static int get_inv_shift (uint32 mask, int bpp)
{
int i;
// Find mask
for (i = 0; (i < bpp) && !(mask & (1 << i)); i++) {};
// Find start of mask
for (; (i < bpp) && (mask & (1 << i)); i++) {};
return (bpp - i);
}
static unsigned char CLAMP (int v, int min, int max)
{
if (v < min) return min;
if (v > max) return max;
return v;
}
static bool8 SetupXvideo()
{
int ret;
// Init xv_port
GUI.xv_port = -1;
/////////////////////
// Check that Xvideo extension seems OK
unsigned int p_version, p_release, p_request_base, p_event_base, p_error_base;
ret = XvQueryExtension(GUI.display,
&p_version, &p_release, &p_request_base,
&p_event_base, &p_error_base);
if (ret != Success) { fprintf(stderr,"XvQueryExtension error\n"); return FALSE; }
printf("XvExtension version %i.%i\n",p_version,p_release);
/////////////////////
// Get info about the Adaptors available for this window
unsigned int p_num_adaptors;
XvAdaptorInfo* ai;
ret = XvQueryAdaptors(GUI.display, GUI.window, &p_num_adaptors, &ai);
if (ret != Success || p_num_adaptors == 0) {
fprintf(stderr,"XvQueryAdaptors error.");
return FALSE;
}
printf("XvQueryAdaptors: %d adaptor(s) found.\n",p_num_adaptors);
unsigned int minAdaptor = 0, maxAdaptor = p_num_adaptors;
// Allow user to force adaptor choice
/* if (adaptor >= 0 && adaptor < p_num_adaptors)
{
if (verbose) std::cout << "Forcing adaptor " << adaptor << ", '" << ai[adaptor].name << "'" << std::endl;
minAdaptor = adaptor;
maxAdaptor = adaptor + 1;
} */
/////////////////////
// Iterate through list of available adaptors.
// Grab a port if we can.
for (unsigned int i = minAdaptor; i < maxAdaptor && GUI.xv_port < 0; i++)
{
// We need to find one supporting XvInputMask and XvImageMask.
if (! (ai[i].type & XvImageMask)) continue;
if (! (ai[i].type & XvInputMask)) continue;
printf("\tAdaptor #%d: [%s]: %ld port(s) available.\n", i, ai[i].name, ai[i].num_ports);
// Get encodings available here
// AFAIK all ports on an adapter share the same encodings info.
unsigned int encodings;
XvEncodingInfo *ei;
ret = XvQueryEncodings(GUI.display, ai[i].base_id, &encodings, &ei);
if (ret != Success || encodings == 0) {
fprintf(stderr,"XvQueryEncodings error.");
continue;
}
// Ensure the XV_IMAGE encoding available has sufficient width/height for us.
bool8 can_fit = FALSE;
for (unsigned int j = 0; j < encodings; j++)
{
if (strcmp(ei[j].name,"XV_IMAGE")) continue;
if (ei[j].width >= SNES_WIDTH * 2 &&
ei[j].height >= SNES_HEIGHT_EXTENDED * 2)
{
can_fit = TRUE;
break;
}
}
XvFreeEncodingInfo(ei);
if (can_fit == FALSE)
{
fprintf(stderr,"\tDid not find XV_IMAGE encoding with enough max size\n");
continue;
}
// Phew. If we've made it this far, we can try to choose it for our output port.
for (unsigned int p = ai[i].base_id; p < ai[i].base_id+ai[i].num_ports; p++)
{
ret = XvGrabPort(GUI.display, p, CurrentTime);
if (ret == Success)
{
printf("\tSuccessfully bound to Xv port %d\n",p);
GUI.xv_port = p;
break;
} else {
fprintf(stderr,"\tXvGrabPort port %d fail.\n",p);
}
}
}
XvFreeAdaptorInfo(ai);
/////////////////////
// Bail out here if we haven't managed to bind to any port.
if (GUI.xv_port < 0)
{
fprintf(stderr,"No suitable xv_port found in any Adaptors.\n");
return FALSE;
}
// Xv ports can have Attributes (hue, saturation, etc)
/* Set XV_AUTOPAINT_COLORKEY _only_ if available */
int num_attrs;
XvAttribute* port_attr;
port_attr = XvQueryPortAttributes (GUI.display, GUI.xv_port, &num_attrs);
for (int i = 0; i < num_attrs; i++)
{
if (!strcmp (port_attr[i].name, "XV_AUTOPAINT_COLORKEY"))
{
Atom colorkey;
colorkey = XInternAtom (GUI.display, "XV_AUTOPAINT_COLORKEY", True);
if (colorkey != None)
{
XvSetPortAttribute (GUI.display, GUI.xv_port, colorkey, 1);
printf("\tSet XV_AUTOPAINT_COLORKEY.\n");
}
}
}
XFree(port_attr);
// Now we need to find to find the image format to use for output.
// There are two steps to this:
// Prefer an XvRGB version of lowest bitdepth.
// If that's not available use YUY2
int formats;
XvImageFormatValues* fo;
fo = XvListImageFormats(GUI.display, GUI.xv_port, &formats);
if (formats == 0)
{
fprintf(stderr,"No valid image formats for Xv port!");
return FALSE;
}
/* Ok time to search for a good Format */
GUI.xv_format = FOURCC_I420;
GUI.xv_bpp = 0x7FFFFFFF;
for (int i = 0; i < formats; i++)
{
//prefer I420
if (fo[i].id == FOURCC_YUY2) {
GUI.xv_format = FOURCC_YUY2;
}
else if (fo[i].id == 0x3 || fo[i].type == XvRGB)
{
if (fo[i].bits_per_pixel < GUI.xv_bpp)
{
GUI.xv_format = fo[i].id;
GUI.xv_bpp = fo[i].bits_per_pixel;
GUI.bytes_per_pixel = (GUI.xv_bpp == 15) ? 2 : GUI.xv_bpp >> 3;
GUI.depth = fo[i].depth;
GUI.red_shift = get_inv_shift (fo[i].red_mask, GUI.xv_bpp);
GUI.green_shift = get_inv_shift (fo[i].green_mask, GUI.xv_bpp);
GUI.blue_shift = get_inv_shift (fo[i].blue_mask, GUI.xv_bpp);
/* Check for red-blue inversion on SiliconMotion drivers */
if (fo[i].red_mask == 0x001f &&
fo[i].blue_mask == 0x7c00)
{
int copy = GUI.red_shift;
GUI.red_shift = GUI.blue_shift;
GUI.blue_shift = copy;
}
}
}
}
free (fo);
if (GUI.xv_format != FOURCC_YUY2 && GUI.xv_format != FOURCC_I420) {
printf("Selected XvRGB format: %d bpp\n",GUI.xv_bpp);
} else {
// use I420 or YUY2
if(GUI.xv_format == FOURCC_I420) {
printf("Xvideo I420 image format.\n");
} else {
printf("Xvideo YUY2 image format.\n");
GUI.depth = 15;
/* Build a table for yuv conversion */
for (unsigned int color = 0; color < (1 << 15); color++)
{
int r, g, b;
int y, u, v;
r = (color & 0x7c00) >> 7;
g = (color & 0x03e0) >> 2;
b = (color & 0x001F) << 3;
y = (int) ((0.257 * ((double) r)) + (0.504 * ((double) g)) + (0.098 * ((double) b)) + 16.0);
u = (int) ((-0.148 * ((double) r)) + (-0.291 * ((double) g)) + (0.439 * ((double) b)) + 128.0);
v = (int) ((0.439 * ((double) r)) + (-0.368 * ((double) g)) + (-0.071 * ((double) b)) + 128.0);
GUI.y_table[color] = CLAMP (y, 0, 255);
GUI.u_table[color] = CLAMP (u, 0, 255);
GUI.v_table[color] = CLAMP (v, 0, 255);
}
}
}
return TRUE;
}
#endif
void S9xInitDisplay (int argc, char **argv)
{
GUI.display = XOpenDisplay(NULL);
if (GUI.display == NULL)
FatalError("Failed to connect to X server.");
GUI.screen = DefaultScreenOfDisplay(GUI.display);
GUI.screen_num = XScreenNumberOfScreen(GUI.screen);
GUI.visual = DefaultVisualOfScreen(GUI.screen);
XVisualInfo plate, *matches;
int count;
plate.visualid = XVisualIDFromVisual(GUI.visual);
matches = XGetVisualInfo(GUI.display, VisualIDMask, &plate, &count);
if (!count)
FatalError("Your X Window System server is unwell!");
GUI.depth = matches[0].depth;
if ((GUI.depth != 15 && GUI.depth != 16 && GUI.depth != 24) || (matches[0].c_class != TrueColor))
FatalError("Requiers 15, 16, 24 or 32-bit color depth supporting TrueColor.");
GUI.red_shift = ffs(matches[0].red_mask) - 1;
GUI.green_shift = ffs(matches[0].green_mask) - 1;
GUI.blue_shift = ffs(matches[0].blue_mask) - 1;
GUI.red_size = matches[0].red_mask >> GUI.red_shift;
GUI.green_size = matches[0].green_mask >> GUI.green_shift;
GUI.blue_size = matches[0].blue_mask >> GUI.blue_shift;
if (GUI.depth == 16 && GUI.green_size == 63)
GUI.green_shift++;
XFree(matches);
// Init various scale-filters
S9xBlitFilterInit();
S9xBlit2xSaIFilterInit();
S9xBlitHQ2xFilterInit();
/* Set up parameters for creating the window */
XSetWindowAttributes attrib;
memset(&attrib, 0, sizeof(attrib));
attrib.background_pixel = BlackPixelOfScreen(GUI.screen);
attrib.colormap = XCreateColormap(GUI.display, RootWindowOfScreen(GUI.screen), GUI.visual, AllocNone);
int screen_left = 0, screen_top = 0;
int screen_w = WidthOfScreen(GUI.screen), screen_h = HeightOfScreen(GUI.screen);
#ifdef USE_XINERAMA
int heads = 0;
XineramaScreenInfo* si = 0;
int useless1, useless2;
if (!XineramaQueryExtension(GUI.display, &useless1, &useless2)) {
puts("Xinerama is not available");
goto xinerama_end;
}
if (!XineramaIsActive(GUI.display)) {
puts("Xinerama is not active");
goto xinerama_end;
}
si = XineramaQueryScreens(GUI.display, &heads);
if (!si) {
puts("XineramaQueryScreens failed");
goto xinerama_end;
}
if (GUI.xinerama_head >= heads) {
printf("Invalid xinerama head id (expected 0-%d, got %u)\n", heads - 1, GUI.xinerama_head);
goto xinerama_end;
}
si = &si[GUI.xinerama_head];
screen_left = si->x_org;
screen_top = si->y_org;
screen_w = si->width;
screen_h = si->height;
printf("Selected xinerama head %u (%d,%d %dx%d)\n", GUI.xinerama_head, screen_left, screen_top, screen_w, screen_h);
xinerama_end:
#endif
XSizeHints Hints;
memset((void *) &Hints, 0, sizeof(XSizeHints));
/* Try to switch to Fullscreen. */
if (GUI.fullscreen == TRUE)
{
Hints.flags = PPosition;
Hints.x = screen_left;
Hints.y = screen_top;
/* Create the window with maximum screen width,height positioned at 0,0. */
GUI.window = XCreateWindow(GUI.display, RootWindowOfScreen(GUI.screen),
Hints.x, Hints.y,
screen_w, screen_h, 0,
GUI.depth, InputOutput, GUI.visual, CWBackPixel | CWColormap, &attrib);
/* Try to tell the Window Manager not to decorate this window. */
Atom wm_state = XInternAtom (GUI.display, "_NET_WM_STATE", true );
Atom wm_fullscreen = XInternAtom (GUI.display, "_NET_WM_STATE_FULLSCREEN", true );
XChangeProperty(GUI.display, GUI.window, wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_fullscreen, 1);
#ifdef USE_XVIDEO
if (GUI.use_xvideo)
{
// Set some defaults
GUI.scale_w = screen_w;
GUI.scale_h = screen_h;
GUI.imageHeight = SNES_HEIGHT_EXTENDED * 2;
if (! GUI.maxaspect)
{
// Compute the maximum screen size for scaling xvideo window.
double screenAspect = (double)screen_w / screen_h;
double snesAspect = (double)SNES_WIDTH / SNES_HEIGHT_EXTENDED;
double ratio = screenAspect / snesAspect;
printf("\tScreen (%dx%d) aspect %f vs SNES (%dx%d) aspect %f (ratio: %f)\n",
screen_w,screen_h,screenAspect,
SNES_WIDTH,SNES_HEIGHT_EXTENDED,snesAspect,
ratio);
// Correct aspect ratio
if (screenAspect > snesAspect)
{
// widescreen monitor, 4:3 snes
// match height, scale width
GUI.scale_w /= ratio;
GUI.x_offset = (screen_w - GUI.scale_w) / 2;
} else {
// narrow monitor, 4:3 snes
// match width, scale height
GUI.scale_h *= ratio;
GUI.y_offset = (screen_h - GUI.scale_h) / 2;
}
}
printf("\tUsing size %dx%d with offset (%d,%d)\n",GUI.scale_w,GUI.scale_h,GUI.x_offset,GUI.y_offset);
}
else
#endif
{
/* Last: position the output window in the center of the screen. */
GUI.x_offset = (screen_w - SNES_WIDTH * 2) / 2;
GUI.y_offset = (screen_h - SNES_HEIGHT_EXTENDED * 2) / 2;
}
} else {
/* Tell the Window Manager that we do not wish to be resizable */
Hints.flags = PSize | PMinSize | PMaxSize | PPosition;
Hints.x = screen_left + (screen_w - SNES_WIDTH * 2) / 2;
Hints.y = screen_top + (screen_h - SNES_HEIGHT_EXTENDED * 2) / 2;
Hints.min_width = Hints.max_width = Hints.base_width = SNES_WIDTH * 2;
Hints.min_height = Hints.max_height = Hints.base_height = SNES_HEIGHT_EXTENDED * 2;
/* Create the window. */
GUI.window = XCreateWindow(GUI.display, RootWindowOfScreen(GUI.screen),
Hints.x, Hints.y,
SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, 0, GUI.depth, InputOutput, GUI.visual, CWBackPixel | CWColormap, &attrib);
/* Last: Windowed SNES is not drawn with any offsets. */
GUI.x_offset = GUI.y_offset = 0;
#ifdef USE_XVIDEO
GUI.scale_w = SNES_WIDTH * 2;
GUI.scale_h = SNES_HEIGHT_EXTENDED * 2;
#endif
}
XSetWMNormalHints(GUI.display, GUI.window, &Hints);
/* Load UI cursors */
static XColor bg, fg;
static char data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
Pixmap bitmap;
bitmap = XCreateBitmapFromData(GUI.display, GUI.window, data, 8, 8);
GUI.point_cursor = XCreatePixmapCursor(GUI.display, bitmap, bitmap, &fg, &bg, 0, 0);
XDefineCursor(GUI.display, GUI.window, GUI.point_cursor);
GUI.cross_hair_cursor = XCreateFontCursor(GUI.display, XC_crosshair);
GUI.gc = DefaultGCOfScreen(GUI.screen);
/* Other window-manager hints */
XWMHints WMHints;
memset((void *) &WMHints, 0, sizeof(XWMHints));
/* Rely on the Window Manager to provide us with keyboard input */
WMHints.input = True;
WMHints.flags = InputHint;
XSetWMHints(GUI.display, GUI.window, &WMHints);
XSelectInput(GUI.display, GUI.window, FocusChangeMask | ExposureMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | ButtonPressMask | ButtonReleaseMask);
/* Bring up our window (and put it in foreground) */
XMapRaised(GUI.display, GUI.window);
XClearWindow(GUI.display, GUI.window);
// Wait for map
XEvent event;
do {
XNextEvent(GUI.display, &event);
} while (event.type != MapNotify || event.xmap.event != GUI.window);
#ifdef USE_XVIDEO
if (GUI.use_xvideo)
{
GUI.use_xvideo = SetupXvideo();
}
#endif
GUI.pixel_format = 565;
SetupImage();
switch (GUI.depth)
{
default:
case 32:
GUI.bytes_per_pixel = 4;
break;
case 24:
if (GUI.image->bits_per_pixel == 24)
GUI.bytes_per_pixel = 3;
else
GUI.bytes_per_pixel = 4;
break;
case 15:
case 16:
GUI.bytes_per_pixel = 2;
break;
}
printf("Using internal pixel format %d\n",GUI.pixel_format);
}
void S9xDeinitDisplay (void)
{
TakedownImage();
if (GUI.display != NULL)
{
#ifdef USE_XVIDEO
if (GUI.use_xvideo)
{
XvUngrabPort(GUI.display,GUI.xv_port,CurrentTime);
}
#endif
S9xTextMode();
XSync(GUI.display, False);
XCloseDisplay(GUI.display);
}
S9xBlitFilterDeinit();
S9xBlit2xSaIFilterDeinit();
S9xBlitHQ2xFilterDeinit();
}
static void SetupImage (void)
{
TakedownImage();
// Create new image struct
GUI.image = (Image *) calloc(sizeof(Image), 1);
#ifdef USE_XVIDEO
if (GUI.use_xvideo)
SetupXvImage();
if (!GUI.use_xvideo)
#endif
SetupXImage();
// Setup SNES buffers
GFX.Pitch = SNES_WIDTH * 2 * 2;
GUI.snes_buffer = (uint8 *) calloc(GFX.Pitch * ((SNES_HEIGHT_EXTENDED + 4) * 2), 1);
if (!GUI.snes_buffer)
FatalError("Failed to allocate GUI.snes_buffer.");
GFX.Screen = (uint16 *) (GUI.snes_buffer + (GFX.Pitch * 2 * 2));
GUI.filter_buffer = (uint8 *) calloc((SNES_WIDTH * 2) * 2 * (SNES_HEIGHT_EXTENDED * 2), 1);
if (!GUI.filter_buffer)
FatalError("Failed to allocate GUI.filter_buffer.");
#ifdef USE_XVIDEO
if ((GUI.depth == 15 || GUI.depth == 16) && GUI.xv_format != FOURCC_YUY2 && GUI.xv_format != FOURCC_I420)
#else
if (GUI.depth == 15 || GUI.depth == 16)
#endif
{
GUI.blit_screen_pitch = GUI.image->bytes_per_line;
GUI.blit_screen = (uint8 *) GUI.image->data;
GUI.need_convert = FALSE;
}
else
{
GUI.blit_screen_pitch = (SNES_WIDTH * 2) * 2;
GUI.blit_screen = GUI.filter_buffer;
GUI.need_convert = TRUE;
}
if (GUI.need_convert) { printf("\tImage conversion needed before blit.\n"); }
S9xGraphicsInit();
}
static void TakedownImage (void)
{
if (GUI.snes_buffer)
{
free(GUI.snes_buffer);
GUI.snes_buffer = NULL;
}
if (GUI.filter_buffer)
{
free(GUI.filter_buffer);
GUI.filter_buffer = NULL;
}
if (GUI.image)
{
#ifdef USE_XVIDEO
if (GUI.use_xvideo)
TakedownXvImage();
else
#endif
TakedownXImage();
free(GUI.image);
GUI.image = NULL;
}
S9xGraphicsDeinit();
}
static void SetupXImage (void)
{
#ifdef MITSHM
GUI.use_shared_memory = TRUE;
int major, minor;
Bool shared;
if (!XShmQueryVersion(GUI.display, &major, &minor, &shared) || !shared)
GUI.image->ximage = NULL;
else
GUI.image->ximage = XShmCreateImage(GUI.display, GUI.visual, GUI.depth, ZPixmap, NULL, &GUI.sm_info, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
if (!GUI.image->ximage)
GUI.use_shared_memory = FALSE;
else
{
// set main Image struct vars
GUI.image->height = GUI.image->ximage->height;
GUI.image->bytes_per_line = GUI.image->ximage->bytes_per_line;
GUI.image->data_size = GUI.image->bytes_per_line * GUI.image->height;
GUI.sm_info.shmid = shmget(IPC_PRIVATE, GUI.image->data_size, IPC_CREAT | 0777);
if (GUI.sm_info.shmid < 0)
{
XDestroyImage(GUI.image->ximage);
GUI.use_shared_memory = FALSE;
}
else
{
GUI.image->ximage->data = GUI.sm_info.shmaddr = (char *) shmat(GUI.sm_info.shmid, 0, 0);
if (!GUI.image->ximage->data)
{
XDestroyImage(GUI.image->ximage);
shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
GUI.use_shared_memory = FALSE;
}
else
{
GUI.sm_info.readOnly = False;
XSetErrorHandler(ErrorHandler);
XShmAttach(GUI.display, &GUI.sm_info);
XSync(GUI.display, False);
// X Error handler might clear GUI.use_shared_memory if XShmAttach failed.
if (!GUI.use_shared_memory)
{
XDestroyImage(GUI.image->ximage);
shmdt(GUI.sm_info.shmaddr);
shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
} else
printf("Created XShmImage, size %d\n",GUI.image->data_size);
}
}
}
if (!GUI.use_shared_memory)
{
fprintf(stderr, "use_shared_memory failed, switching to XPutImage.\n");
#endif
GUI.image->ximage = XCreateImage(GUI.display, GUI.visual, GUI.depth, ZPixmap, 0, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, BitmapUnit(GUI.display), 0);
// set main Image struct vars
GUI.image->height = GUI.image->ximage->height;
GUI.image->bytes_per_line = GUI.image->ximage->bytes_per_line;
GUI.image->data_size = GUI.image->bytes_per_line * GUI.image->height;
GUI.image->ximage->data = (char *) malloc(GUI.image->data_size);
if (!GUI.image->ximage || !GUI.image->ximage->data)
FatalError("XCreateImage failed.");
printf("Created XImage, size %d\n",GUI.image->data_size);
#ifdef MITSHM
}
#endif
// Set final values
GUI.image->bits_per_pixel = GUI.image->ximage->bits_per_pixel;
GUI.image->data = GUI.image->ximage->data;
#ifdef LSB_FIRST
GUI.image->ximage->byte_order = LSBFirst;
#else
GUI.image->ximage->byte_order = MSBFirst;
#endif
}
static void TakedownXImage (void)
{
if (GUI.image->ximage)
{
#ifdef MITSHM
if (GUI.use_shared_memory)
{
XShmDetach(GUI.display, &GUI.sm_info);
GUI.image->ximage->data = NULL;
XDestroyImage(GUI.image->ximage);
if (GUI.sm_info.shmaddr)
shmdt(GUI.sm_info.shmaddr);
if (GUI.sm_info.shmid >= 0)
shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
GUI.image->ximage = NULL;
}
else
#endif
{
XDestroyImage(GUI.image->ximage);
GUI.image->ximage = NULL;
}
}
}
#ifdef USE_XVIDEO
static void SetupXvImage (void)
{
#ifdef MITSHM
GUI.use_shared_memory = TRUE;
int major, minor;
Bool shared;
if (!XShmQueryVersion(GUI.display, &major, &minor, &shared) || !shared)
GUI.image->xvimage = NULL;
else
GUI.image->xvimage = XvShmCreateImage(GUI.display, GUI.xv_port, GUI.xv_format, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, &GUI.sm_info);
if (!GUI.image->xvimage)
GUI.use_shared_memory = FALSE;
else
{
GUI.image->height = SNES_HEIGHT_EXTENDED * 2;
GUI.image->data_size = GUI.image->xvimage->data_size;
GUI.image->bytes_per_line = GUI.image->data_size / GUI.image->height;
GUI.sm_info.shmid = shmget(IPC_PRIVATE, GUI.image->data_size, IPC_CREAT | 0777);
if (GUI.sm_info.shmid < 0)
{
XFree(GUI.image->xvimage);
GUI.use_shared_memory = FALSE;
}
else
{
GUI.image->xvimage->data = GUI.sm_info.shmaddr = (char *) shmat(GUI.sm_info.shmid, 0, 0);
if (!GUI.image->xvimage->data)
{
XFree(GUI.image->xvimage);
shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
GUI.use_shared_memory = FALSE;
}
else
{
GUI.sm_info.readOnly = False;
XSetErrorHandler(ErrorHandler);
XShmAttach(GUI.display, &GUI.sm_info);
XSync(GUI.display, False);
// X Error handler might clear GUI.use_shared_memory if XShmAttach failed.
if (!GUI.use_shared_memory)
{
XFree(GUI.image->xvimage);
shmdt(GUI.sm_info.shmaddr);
shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
} else
printf("Created XvShmImage, size %d\n",GUI.image->data_size);
}
}
}
if (!GUI.use_shared_memory)
{
fprintf(stderr, "use_shared_memory failed, switching to XvPutImage.\n");
#endif
GUI.image->xvimage = XvCreateImage(GUI.display, GUI.xv_port, GUI.xv_format, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
GUI.image->height = SNES_HEIGHT_EXTENDED * 2;
GUI.image->data_size = GUI.image->xvimage->data_size;
GUI.image->bytes_per_line = GUI.image->data_size / GUI.image->height;
GUI.image->xvimage->data = (char *) malloc(GUI.image->data_size);
if (!GUI.image->xvimage || !GUI.image->xvimage->data)
{
fprintf(stderr, "XvCreateImage failed, falling back to software blit.\n");
GUI.use_xvideo = FALSE;
return;
}
printf("Created XvImage, size %d\n",GUI.image->data_size);
#ifdef MITSHM
}
#endif
// Set final values
GUI.image->bits_per_pixel = GUI.xv_bpp;
GUI.image->data = GUI.image->xvimage->data;
}
static void TakedownXvImage (void)
{
if (GUI.image->xvimage)
{
#ifdef MITSHM
if (GUI.use_shared_memory)
{
XShmDetach(GUI.display, &GUI.sm_info);
GUI.image->xvimage->data = NULL;
XFree(GUI.image->xvimage);
if (GUI.sm_info.shmaddr)
shmdt(GUI.sm_info.shmaddr);
if (GUI.sm_info.shmid >= 0)
shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
GUI.image->xvimage = NULL;
}
else
#endif
{
free(GUI.image->xvimage->data);
//GUI.image->xvimage->data = NULL;
XFree(GUI.image->xvimage);
GUI.image->xvimage = NULL;
}
}
}
#endif
#if defined(USE_XVIDEO) && !defined(USE_LIBYUV)
void rgb565ToRgb24(uint8 *dest, uint8 *s, int width, int height) {
for (int i=0, j=0; i< width * height * 2 ; i+=2) {
uint16 c = s[i] + (s[i+1]<<8);
uint8 r = uint8(((c & 0xF800) >> 11) << 3);
uint8 g = uint8(((c & 0x7E0) >> 5) << 2);
uint8 b = uint8(((c & 0x1F)) << 3);
dest[j++]=r;
dest[j++]=g;
dest[j++]=b;
}
}
void rgb24ToI420(uint8 *dest, uint8 *rgb, int width, int height) {
int image_size = width * height;
int upos = image_size;
int vpos = upos + upos / 4;
int i = 0;
for (int line = 0; line < height; ++line) {
if( !(line % 2) ) {
for ( int x = 0; x < width; x += 2 ) {
uint8 r = rgb[3 * i];
uint8 g = rgb[3 * i + 1];
uint8 b = rgb[3 * i + 2];
dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
dest[upos++] = ((-38*r + -74*g + 112*b) >> 8) + 128;
dest[vpos++] = ((112*r + -94*g + -18*b) >> 8) + 128;
r = rgb[3 * i];
g = rgb[3 * i + 1];
b = rgb[3 * i + 2];
dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
}
} else {
for (int x = 0; x < width; x += 1) {
uint8 r = rgb[3 * i];
uint8 g = rgb[3 * i + 1];
uint8 b = rgb[3 * i + 2];
dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
}
}
}
}
#endif
void S9xPutImage (int width, int height)
{
static int prevWidth = 0, prevHeight = 0;
int copyWidth, copyHeight;
Blitter blitFn = NULL;
if (GUI.video_mode == VIDEOMODE_BLOCKY || GUI.video_mode == VIDEOMODE_TV || GUI.video_mode == VIDEOMODE_SMOOTH)
if ((width <= SNES_WIDTH) && ((prevWidth != width) || (prevHeight != height)))
S9xBlitClearDelta();
if (width <= SNES_WIDTH)
{
if (height > SNES_HEIGHT_EXTENDED)
{
copyWidth = width * 2;
copyHeight = height;
blitFn = S9xBlitPixSimple2x1;
}
else
{
copyWidth = width * 2;
copyHeight = height * 2;
switch (GUI.video_mode)
{
case VIDEOMODE_BLOCKY: blitFn = S9xBlitPixSimple2x2; break;
case VIDEOMODE_TV: blitFn = S9xBlitPixTV2x2; break;
case VIDEOMODE_SMOOTH: blitFn = S9xBlitPixSmooth2x2; break;
case VIDEOMODE_SUPEREAGLE: blitFn = S9xBlitPixSuperEagle16; break;
case VIDEOMODE_2XSAI: blitFn = S9xBlitPix2xSaI16; break;
case VIDEOMODE_SUPER2XSAI: blitFn = S9xBlitPixSuper2xSaI16; break;
case VIDEOMODE_EPX: blitFn = S9xBlitPixEPX16; break;
case VIDEOMODE_HQ2X: blitFn = S9xBlitPixHQ2x16; break;
}
}
}
else
if (height <= SNES_HEIGHT_EXTENDED)
{
copyWidth = width;
copyHeight = height * 2;
switch (GUI.video_mode)
{
default: blitFn = S9xBlitPixSimple1x2; break;
case VIDEOMODE_TV: blitFn = S9xBlitPixTV1x2; break;
}
}
else
{
copyWidth = width;
copyHeight = height;
blitFn = S9xBlitPixSimple1x1;
}
blitFn((uint8 *) GFX.Screen, GFX.Pitch, GUI.blit_screen, GUI.blit_screen_pitch, width, height);
if (height < prevHeight)
{
int p = GUI.blit_screen_pitch >> 2;
for (int y = SNES_HEIGHT * 2; y < SNES_HEIGHT_EXTENDED * 2; y++)
{
uint32 *d = (uint32 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
for (int x = 0; x < p; x++)
*d++ = 0;
}
}
#ifdef USE_XVIDEO
// Adjust source blit region if SNES would only fill half the screen.
if (height <= SNES_HEIGHT_EXTENDED)
GUI.imageHeight = height * 2;
if (GUI.use_xvideo && (GUI.xv_format == FOURCC_YUY2))
{
uint16 *s = (uint16 *)GUI.blit_screen;
uint8 *d = (uint8 *)GUI.image->data;
// convert GUI.blit_screen and copy to XV image
for (int y = 0; y < copyHeight; y++)
{
for (int x = 0; x < SNES_WIDTH * 2; x += 2)
{
// Read two RGB pxls
// Now that we are RGB565 throughout, need to drop the Green LSB
unsigned short rgb1 = (*s & 0xFFC0) >> 1 | (*s & 0x001F); s++;
unsigned short rgb2 = (*s & 0xFFC0) >> 1 | (*s & 0x001F); s++;
// put two YUYV pxls
// lum1
*d = GUI.y_table[rgb1]; d++;
// U
*d = (GUI.u_table[rgb1] + GUI.u_table[rgb2]) / 2; d++;
// lum2
*d = GUI.y_table[rgb2]; d++;
// V
*d = (GUI.v_table[rgb1] + GUI.v_table[rgb2]) / 2; d++;
}
}
} else if (GUI.use_xvideo && (GUI.xv_format == FOURCC_I420)) {
uint8 *s = (uint8 *)GUI.blit_screen;
uint8 *d = (uint8 *)GUI.image->data;
int tw = SNES_WIDTH * 2;
int th = SNES_HEIGHT_EXTENDED * 2;
#ifdef USE_LIBYUV
libyuv::RGB565ToI420(
s, tw*2,
d, tw,
d + tw*th, tw/2,
d + tw*th + tw*th/4, tw/2,
tw, th);
#else
uint8 *ns = (uint8 *) calloc(tw*th*3, 1);
//convert to RGB24
rgb565ToRgb24(ns, s, tw, th);
//convert to I420
rgb24ToI420(d, ns, tw, th);
free(ns);
#endif
GUI.need_convert =0;
}
else
#endif
if (GUI.need_convert)
{
if (GUI.bytes_per_pixel == 3)
Convert16To24Packed(copyWidth, copyHeight);
else
Convert16To24(copyWidth, copyHeight);
}
Repaint(TRUE);
prevWidth = width;
prevHeight = height;
}
static void Convert16To24 (int width, int height)
{
if (GUI.pixel_format == 565)
{
for (int y = 0; y < height; y++)
{
uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
uint32 *d = (uint32 *) (GUI.image->data + y * GUI.image->bytes_per_line);
for (int x = 0; x < width; x++)
{
uint32 pixel = *s++;
*d++ = (((pixel >> 11) & 0x1f) << (GUI.red_shift + 3)) | (((pixel >> 6) & 0x1f) << (GUI.green_shift + 3)) | ((pixel & 0x1f) << (GUI.blue_shift + 3));
}
}
}
else
{
for (int y = 0; y < height; y++)
{
uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
uint32 *d = (uint32 *) (GUI.image->data + y * GUI.image->bytes_per_line);
for (int x = 0; x < width; x++)
{
uint32 pixel = *s++;
*d++ = (((pixel >> 10) & 0x1f) << (GUI.red_shift + 3)) | (((pixel >> 5) & 0x1f) << (GUI.green_shift + 3)) | ((pixel & 0x1f) << (GUI.blue_shift + 3));
}
}
}
}
static void Convert16To24Packed (int width, int height)
{
if (GUI.pixel_format == 565)
{
for (int y = 0; y < height; y++)
{
uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
uint8 *d = (uint8 *) (GUI.image->data + y * GUI.image->bytes_per_line);
#ifdef LSB_FIRST
if (GUI.red_shift < GUI.blue_shift)
#else
if (GUI.red_shift > GUI.blue_shift)
#endif
{
for (int x = 0; x < width; x++)
{
uint32 pixel = *s++;
*d++ = (pixel >> (11 - 3)) & 0xf8;
*d++ = (pixel >> (6 - 3)) & 0xf8;
*d++ = (pixel & 0x1f) << 3;
}
}
else
{
for (int x = 0; x < width; x++)
{
uint32 pixel = *s++;
*d++ = (pixel & 0x1f) << 3;
*d++ = (pixel >> (6 - 3)) & 0xf8;
*d++ = (pixel >> (11 - 3)) & 0xf8;
}
}
}
}
else
{
for (int y = 0; y < height; y++)
{
uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
uint8 *d = (uint8 *) (GUI.image->data + y * GUI.image->bytes_per_line);
#ifdef LSB_FIRST
if (GUI.red_shift < GUI.blue_shift)
#else
if (GUI.red_shift > GUI.blue_shift)
#endif
{
for (int x = 0; x < width; x++)
{
uint32 pixel = *s++;
*d++ = (pixel >> (10 - 3)) & 0xf8;
*d++ = (pixel >> (5 - 3)) & 0xf8;
*d++ = (pixel & 0x1f) << 3;
}
}
else
{
for (int x = 0; x < width; x++)
{
uint32 pixel = *s++;
*d++ = (pixel & 0x1f) << 3;
*d++ = (pixel >> (5 - 3)) & 0xf8;
*d++ = (pixel >> (10 - 3)) & 0xf8;
}
}
}
}
}
static void Repaint (bool8 isFrameBoundry)
{
#ifdef USE_XVIDEO
if (GUI.use_xvideo)
{
#ifdef MITSHM
if (GUI.use_shared_memory)
{
XvShmPutImage(GUI.display, GUI.xv_port, GUI.window, GUI.gc, GUI.image->xvimage,
0, 0, SNES_WIDTH * 2, GUI.imageHeight,
GUI.x_offset, GUI.y_offset, GUI.scale_w, GUI.scale_h, False);
}
else
#endif
XvPutImage(GUI.display, GUI.xv_port, GUI.window, GUI.gc, GUI.image->xvimage,
0, 0, SNES_WIDTH * 2, GUI.imageHeight,
GUI.x_offset, GUI.y_offset, GUI.scale_w, GUI.scale_h);
}
else
#endif
#ifdef MITSHM
if (GUI.use_shared_memory)
{
XShmPutImage(GUI.display, GUI.window, GUI.gc, GUI.image->ximage, 0, 0, GUI.x_offset, GUI.y_offset, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, False);
XSync(GUI.display, False); // Is this double-sync? See XQueryPointer below...
}
else
#endif
XPutImage(GUI.display, GUI.window, GUI.gc, GUI.image->ximage, 0, 0, GUI.x_offset, GUI.y_offset, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
Window root, child;
int root_x, root_y, x, y;
unsigned int mask;
// Use QueryPointer to sync X server and as a side effect also gets current pointer position for SNES mouse emulation.
XQueryPointer(GUI.display, GUI.window, &root, &child, &root_x, &root_y, &x, &y, &mask);
if (x >= 0 && y >= 0 && x < SNES_WIDTH * 2 && y < SNES_HEIGHT_EXTENDED * 2)
{
GUI.mouse_x = x >> 1;
GUI.mouse_y = y >> 1;
if (mask & Mod1Mask)
{
if (!GUI.mod1_pressed)
{
GUI.mod1_pressed = TRUE;
XDefineCursor(GUI.display, GUI.window, GUI.cross_hair_cursor);
}
}
else
if (GUI.mod1_pressed)
{
GUI.mod1_pressed = FALSE;
XDefineCursor(GUI.display, GUI.window, GUI.point_cursor);
}
}
if (Settings.DumpStreams && isFrameBoundry)
S9xVideoLogger(GUI.image->data, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, GUI.bytes_per_pixel, GUI.image->bytes_per_line);
}
void S9xTextMode (void)
{
SetXRepeat(TRUE);
}
void S9xGraphicsMode (void)
{
SetXRepeat(FALSE);
}
static bool8 CheckForPendingXEvents (Display *display)
{
#ifdef SELECT_BROKEN_FOR_SIGNALS
int arg = 0;
return (XEventsQueued(display, QueuedAlready) || (ioctl(ConnectionNumber(display), FIONREAD, &arg) == 0 && arg));
#else
return (XPending(display));
#endif
}
void S9xLatchJSEvent ()
{
// record that a JS event happened and was reported to the engine
GUI.js_event_latch = TRUE;
}
void S9xProcessEvents (bool8 block)
{
// Kick the screensaver if a joystick event occurred
if (GUI.js_event_latch == TRUE) {
XWarpPointer(GUI.display, None, None, 0, 0, 0, 0, 0, 0);
GUI.js_event_latch = FALSE;
}
// Process all other X events
while (block || CheckForPendingXEvents(GUI.display))
{
XEvent event;
XNextEvent(GUI.display, &event);
block = FALSE;
switch (event.type)
{
case KeyPress:
case KeyRelease:
S9xReportButton(((event.xkey.state & (ShiftMask | Mod1Mask | ControlMask | Mod4Mask)) << 8) | event.xkey.keycode, event.type == KeyPress);
#if 1
{
KeyCode kc = XKeysymToKeycode(GUI.display, XKeycodeToKeysym(GUI.display, event.xkey.keycode, 0));
if (event.xkey.keycode != kc)
S9xReportButton(((event.xkey.state & (ShiftMask | Mod1Mask | ControlMask | Mod4Mask)) << 8) | kc, event.type == KeyPress);
}
#endif
break;
case FocusIn:
SetXRepeat(FALSE);
XFlush(GUI.display);
break;
case FocusOut:
SetXRepeat(TRUE);
XFlush(GUI.display);
break;
case ConfigureNotify:
break;
case ButtonPress:
case ButtonRelease:
S9xReportButton(0x40000000 | (event.xbutton.button - 1), event.type == ButtonPress);
break;
case Expose:
Repaint(FALSE);
break;
}
}
}
const char * S9xSelectFilename (const char *def, const char *dir1, const char *ext1, const char *title)
{
static char s[PATH_MAX + 1];
char buffer[PATH_MAX + 1];
SetXRepeat(TRUE);
printf("\n%s (default: %s): ", title, def);
fflush(stdout);
SetXRepeat(FALSE);
if (fgets(buffer, PATH_MAX + 1, stdin))
{
char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1];
char *p = buffer;
while (isspace(*p))
p++;
if (!*p)
{
strncpy(buffer, def, PATH_MAX + 1);
buffer[PATH_MAX] = 0;
p = buffer;
}
char *q = strrchr(p, '\n');
if (q)
*q = 0;
_splitpath(p, drive, dir, fname, ext);
_makepath(s, drive, *dir ? dir : dir1, fname, *ext ? ext : ext1);
return (s);
}
return (NULL);
}
void S9xMessage (int type, int number, const char *message)
{
const int max = 36 * 3;
static char buffer[max + 1];
fprintf(stdout, "%s\n", message);
strncpy(buffer, message, max + 1);
buffer[max] = 0;
S9xSetInfoString(buffer);
}
const char * S9xStringInput (const char *message)
{
static char buffer[256];
printf("%s: ", message);
fflush(stdout);
if (fgets(buffer, sizeof(buffer) - 2, stdin))
return (buffer);
return (NULL);
}
void S9xSetTitle (const char *string)
{
XStoreName(GUI.display, GUI.window, string);
XFlush(GUI.display);
}
static void SetXRepeat (bool8 state)
{
if (state)
XAutoRepeatOn(GUI.display);
else
XAutoRepeatOff(GUI.display);
}
s9xcommand_t S9xGetDisplayCommandT (const char *n)
{
s9xcommand_t cmd;
cmd.type = S9xBadMapping;
cmd.multi_press = 0;
cmd.button_norpt = 0;
cmd.port[0] = 0xff;
cmd.port[1] = 0;
cmd.port[2] = 0;
cmd.port[3] = 0;
return (cmd);
}
char * S9xGetDisplayCommandName (s9xcommand_t cmd)
{
return (strdup("None"));
}
void S9xHandleDisplayCommand (s9xcommand_t cmd, int16 data1, int16 data2)
{
return;
}
bool8 S9xMapDisplayInput (const char *n, s9xcommand_t *cmd)
{
int i, d;
if (!isdigit(n[1]) || !isdigit(n[2]) || n[3] != ':')
goto unrecog;
d = ((n[1] - '0') * 10 + (n[2] - '0')) << 24;
switch (n[0])
{
case 'K':
{
KeyCode kc;
KeySym ks;
d |= 0x00000000;
for (i = 4; n[i] != '\0' && n[i] != '+'; i++) ;
if (n[i] == '\0' || i == 4)
i = 4;
else
{
for (i = 4; n[i] != '+'; i++)
{
switch (n[i])
{
case 'S': d |= ShiftMask << 8; break;
case 'C': d |= ControlMask << 8; break;
case 'A': d |= Mod1Mask << 8; break;
case 'M': d |= Mod4Mask << 8; break;
default: goto unrecog;
}
}
i++;
}
if ((ks = XStringToKeysym(n + i)) == NoSymbol)
goto unrecog;
if ((kc = XKeysymToKeycode(GUI.display, ks)) == 0)
goto unrecog;
d |= kc & 0xff;
return (S9xMapButton(d, *cmd, false));
}
case 'M':
{
char *c;
int j;
d |= 0x40000000;
if (!strncmp(n + 4, "Pointer", 7))
{
d |= 0x8000;
if (n[11] == '\0')
return (S9xMapPointer(d, *cmd, true));
i = 11;
}
else
if (n[4] == 'B')
i = 5;
else
goto unrecog;
d |= j = strtol(n + i, &c, 10);
if ((c != NULL && *c != '\0') || j > 0x7fff)
goto unrecog;
if (d & 0x8000)
return (S9xMapPointer(d, *cmd, true));
return (S9xMapButton(d, *cmd, false));
}
default:
break;
}
unrecog:
char *err = new char[strlen(n) + 34];
sprintf(err, "Unrecognized input device name '%s'", n);
perror(err);
delete [] err;
return (false);
}
bool S9xDisplayPollButton (uint32 id, bool *pressed)
{
return (false);
}
bool S9xDisplayPollAxis (uint32 id, int16 *value)
{
return (false);
}
bool S9xDisplayPollPointer (uint32 id, int16 *x, int16 *y)
{
if ((id & 0xc0008000) != 0x40008000)
return (false);
int d = (id >> 24) & 0x3f,
n = id & 0x7fff;
if (d != 0 || n != 0)
return (false);
*x = GUI.mouse_x;
*y = GUI.mouse_y;
return (true);
}