snes9x/sdl/sdlvideo.cpp

572 lines
16 KiB
C++

/*
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
See CREDITS file to find the copyright owners of this file.
SDL Input/Audio/Video code (many lines of code come from snes9x & drnoksnes)
(c) Copyright 2011 Makoto Sugano (makoto.sugano@gmail.com)
Snes9x homepage: http://www.snes9x.com/
Permission to use, copy, modify and/or distribute Snes9x in both binary
and source form, for non-commercial purposes, is hereby granted without
fee, providing that this license information and copyright notice appear
with all copies and any derived work.
This software is provided 'as-is', without any express or implied
warranty. In no event shall the authors be held liable for any damages
arising from the use of this software or it's derivatives.
Snes9x is freeware for PERSONAL USE only. Commercial users should
seek permission of the copyright holders first. Commercial use includes,
but is not limited to, charging money for Snes9x or software derived from
Snes9x, including Snes9x or derivatives in commercial game bundles, and/or
using Snes9x as a promotion for your commercial product.
The copyright holders request that bug fixes and improvements to the code
should be forwarded to them so everyone can benefit from the modifications
in future versions.
Super NES and Super Nintendo Entertainment System are trademarks of
Nintendo Co., Limited and its subsidiary companies.
***********************************************************************************/
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.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"
#include "sdl_snes9x.h"
// grkenn - opengl backend
#ifdef USE_OPENGL
#include <SDL/SDL_opengl.h>
#endif
struct GUIData
{
SDL_Surface *sdl_screen;
uint8 *snes_buffer;
uint8 *blit_screen;
uint32 blit_screen_pitch;
int video_mode;
bool8 fullscreen;
bool8 double_buffer;
#ifdef USE_OPENGL
bool8 opengl;
GLint gl_filter;
GLuint texture;
GLuint displaylist;
#endif
};
static struct GUIData GUI;
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 void SetupImage (void);
static void TakedownImage (void);
static void Repaint (bool8);
void S9xExtraDisplayUsage (void)
{
S9xMessage(S9X_INFO, S9X_USAGE, "-fullscreen fullscreen mode");
S9xMessage(S9X_INFO, S9X_USAGE, "-double_buffer SDL double-buffering");
#ifdef USE_OPENGL
S9xMessage(S9X_INFO, S9X_USAGE, "-opengl opengl scaling (no filter)");
S9xMessage(S9X_INFO, S9X_USAGE, "-opengl_linear opengl scaling (bilinear)");
#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)
{
#ifdef USE_OPENGL
if (!strncasecmp(argv[i], "-opengl", 7))
{
GUI.opengl = TRUE;
printf ("Using OPENGL backend.\n");
GUI.gl_filter = GL_NEAREST;
if (!strncasecmp(argv[i], "-opengl_linear", 14))
{
printf ("\tGL_LINEAR filter selected.\n");
GUI.gl_filter = GL_LINEAR;
}
}
else
#endif
if (!strncasecmp(argv[i], "-double_buffer", 14))
{
printf ("Enable double buffer.\n");
GUI.double_buffer = TRUE;
}
else
if (!strncasecmp(argv[i], "-fullscreen", 11))
{
GUI.fullscreen = TRUE;
printf ("Entering fullscreen mode.\n");
}
else
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/SDL");
if (conf.Exists("Unix/SDL::VideoMode"))
{
GUI.video_mode = conf.GetUInt("Unix/SDL::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/SDL");
}
static void FatalError (const char *str)
{
fprintf(stderr, "%s\n", str);
S9xExit();
}
void S9xInitDisplay (int argc, char **argv)
{
if (SDL_Init(SDL_INIT_VIDEO) != 0)
{
printf("Unable to initialize SDL: %s\n", SDL_GetError());
}
atexit(SDL_Quit);
/*
* domaemon
*
* we just go along with RGB565 for now, nothing else..
*/
S9xSetRenderPixelFormat(RGB565);
S9xBlitFilterInit();
S9xBlit2xSaIFilterInit();
S9xBlitHQ2xFilterInit();
/*
* domaemon
*
* FIXME: The secreen size should be flexible
* FIXME: Check if the SDL screen is really in RGB565 mode. screen->fmt
*/
// grkenn - mode flags should be either OPENGL or (hwsurface with (double-buffer or none))
int MODE_FLAGS = SDL_HWSURFACE | (GUI.double_buffer == TRUE ? SDL_DOUBLEBUF : 0);
#ifdef USE_OPENGL
MODE_FLAGS = (GUI.opengl == TRUE ? SDL_OPENGL : MODE_FLAGS);
#endif
if (GUI.fullscreen == TRUE)
{
GUI.sdl_screen = SDL_SetVideoMode(0, 0, 16, SDL_FULLSCREEN | MODE_FLAGS);
} else {
GUI.sdl_screen = SDL_SetVideoMode(SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, 16, MODE_FLAGS);
}
if (GUI.sdl_screen == NULL)
{
printf("Unable to set video mode: %s\n", SDL_GetError());
exit(1);
} else {
printf("Allocated screen of size %dx%d\n",GUI.sdl_screen->w,GUI.sdl_screen->h);
}
// grkenn - set up opengl params
#ifdef USE_OPENGL
if (GUI.opengl == TRUE)
{
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 6 );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 0 );
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, (GUI.double_buffer == TRUE) );
}
#endif
/*
* domaemon
*
* buffer allocation, quite important
*/
SetupImage();
}
void S9xDeinitDisplay (void)
{
TakedownImage();
SDL_Quit();
S9xBlitFilterDeinit();
S9xBlit2xSaIFilterDeinit();
S9xBlitHQ2xFilterDeinit();
}
static void TakedownImage (void)
{
if (GUI.snes_buffer)
{
free(GUI.snes_buffer);
GUI.snes_buffer = NULL;
}
#ifdef USE_OPENGL
if (GUI.opengl == TRUE)
{
if (GUI.blit_screen) free(GUI.blit_screen);
if (GUI.texture) {
glDeleteTextures(1,&GUI.texture);
GUI.texture=0;
}
if (GUI.displaylist)
{
glDeleteLists(GUI.displaylist,1);
GUI.displaylist = 0;
}
}
#endif
S9xGraphicsDeinit();
}
static void SetupImage (void)
{
TakedownImage();
// FIXME: grkenn - SDL offers nice methods for creating (possibly hw-accel)
// drawing surfaces, we should use those instead of a raw buffer o' bytes
// domaemon: The whole unix code basically assumes output=(original * 2);
// This way the code can handle the SNES filters, which does the 2X.
int SNES_FULL_WIDTH = SNES_WIDTH * 2;
int SNES_FULL_HEIGHT = (SNES_HEIGHT_EXTENDED + 4) * 2;
GFX.Pitch = SNES_FULL_WIDTH * 2;
GUI.snes_buffer = (uint8 *) calloc(GFX.Pitch * SNES_FULL_HEIGHT, 1);
if (!GUI.snes_buffer)
FatalError("Failed to allocate GUI.snes_buffer.");
// domaemon: Add 2 lines before drawing.
GFX.Screen = (uint16 *) (GUI.snes_buffer + (GFX.Pitch * 2 * 2));
#ifdef USE_OPENGL
if (GUI.opengl == TRUE)
{
// enable GL texturing
glEnable(GL_TEXTURE_2D);
// create opengl texture
glGenTextures(1, &GUI.texture);
// attach to texture
glBindTexture(GL_TEXTURE_2D, GUI.texture);
// Compute next-highest power-of-2 for holding SNES buffer output
int tex_width = 1;
while(tex_width < SNES_FULL_WIDTH) tex_width *= 2;
int tex_height = 1;
while(tex_height < SNES_FULL_HEIGHT) tex_height *= 2;
printf("\tUsing PowerOf2 texture %dx%d\n",tex_width,tex_height);
// Fill the texture with empty data first
uint16 *empty = (uint16 *)calloc(tex_width*tex_height,sizeof(uint16));
//for (int q=0;q<tex_width*tex_height;q++) empty[q] = rand();
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex_width, tex_height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, empty);
free(empty);
// set texture parameters
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GUI.gl_filter); // Filter modes
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GUI.gl_filter);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
// Setup screen view based on display size.
// Set the clear color to all black (0, 0, 0)
glClearColor(0.0f,0.0f,0.0f,1.0f);
SDL_Surface *screen = SDL_GetVideoSurface();
glViewport(0,0,screen->w,screen->h);
glMatrixMode(GL_PROJECTION);
//glLoadIdentity();
// Compute the actual screen aspect ratio, and letter/pillarbox glOrtho to fit.
double screenAspect = (double)screen->w/screen->h;
double snesAspect = (double)SNES_FULL_WIDTH/SNES_FULL_HEIGHT;
double ratio = screenAspect / snesAspect;
GLuint x_off=0,y_off=0,win_w=SNES_FULL_WIDTH,win_h=SNES_FULL_HEIGHT;
printf("\tScreen (%dx%d) aspect %f vs SNES (%d,%d) aspect %f (ratio %f)\n",screen->w,screen->h,screenAspect,SNES_FULL_WIDTH,SNES_FULL_HEIGHT,snesAspect,ratio);
if (screenAspect > snesAspect)
{
// widescreen monitor, 4:3 snes
// match height, scale width
win_w *= ratio;
x_off=(win_w - SNES_FULL_WIDTH) / 2;
} else {
// narrow monitor, 4:3 snes
// match width, scale height
win_h /= ratio;
y_off = (win_h - SNES_FULL_HEIGHT) / 2;
}
if (win_w < SNES_FULL_WIDTH || win_h < SNES_FULL_HEIGHT) {
printf("oops");
exit(1);
}
glOrtho(0,win_w,win_h,0,-1.0f,1.0f);
printf("\tUsing size %dx%d with offset (%d,%d)\n",
win_w,win_h,x_off,y_off);
glMatrixMode(GL_MODELVIEW);
//glLoadIdentity();
// Create the display list to draw to the screen.
GUI.displaylist = glGenLists(1);
glNewList(GUI.displaylist,GL_COMPILE);
// clear screen
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glTexCoord2f(0,0);
glVertex2i(x_off,y_off);
glTexCoord2f((double)SNES_FULL_WIDTH / tex_width,0);
glVertex2i(x_off+SNES_FULL_WIDTH,y_off);
glTexCoord2f((double)SNES_FULL_WIDTH / tex_width,(double)SNES_FULL_HEIGHT / tex_height);
glVertex2i(x_off+SNES_FULL_WIDTH,y_off+SNES_FULL_HEIGHT);
glTexCoord2f(0,(double)SNES_FULL_HEIGHT / tex_height);
glVertex2i(x_off,y_off+SNES_FULL_HEIGHT);
glEnd();
glEndList();
// finally, a local texture to render to
GUI.blit_screen = (uint8 *) calloc(SNES_FULL_WIDTH*SNES_FULL_HEIGHT,sizeof(uint16));
GUI.blit_screen_pitch = SNES_FULL_WIDTH * 2; // window size =(*2); 2 byte pir pixel =(*2)
// All set!
}
else
#endif
if (GUI.fullscreen == TRUE)
{
int offset_height_pix;
int offset_width_pix;
int offset_byte;
offset_height_pix = (GUI.sdl_screen->h - (SNES_HEIGHT * 2)) / 2;
offset_width_pix = (GUI.sdl_screen->w - (SNES_WIDTH * 2)) / 2;
offset_byte = (GUI.sdl_screen->w * offset_height_pix + offset_width_pix) * 2;
GUI.blit_screen = (uint8 *) GUI.sdl_screen->pixels + offset_byte;
GUI.blit_screen_pitch = GUI.sdl_screen->w * 2;
}
else
{
GUI.blit_screen = (uint8 *) GUI.sdl_screen->pixels;
GUI.blit_screen_pitch = SNES_WIDTH * 2 * 2; // window size =(*2); 2 byte pir pixel =(*2)
}
S9xGraphicsInit();
}
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;
}
// domaemon: this is place where the rendering buffer size should be changed?
blitFn((uint8 *) GFX.Screen, GFX.Pitch, GUI.blit_screen, GUI.blit_screen_pitch, width, height);
// domaemon: does the height change on the fly?
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_OPENGL
// grkenn: TODO: no idea. But I did put opengl code here so that's something : )
if (GUI.opengl)
{
// update GL texture
//printf("Redrawing %d to %d\n",width,height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, SNES_WIDTH*2, SNES_HEIGHT_EXTENDED*2, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (uint16 *) GUI.blit_screen);
}
#endif
Repaint(TRUE);
prevWidth = width;
prevHeight = height;
}
static void Repaint (bool8 isFrameBoundry)
{
#ifdef USE_OPENGL
if (GUI.opengl) { glCallList(GUI.displaylist); SDL_GL_SwapBuffers(); }
else
#endif
SDL_Flip(GUI.sdl_screen);
}
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)
{
SDL_WM_SetCaption(string, string);
}
void S9xSetPalette (void)
{
return;
}