nestopia/source/unix/video.cpp
2014-09-19 00:02:20 -04:00

583 lines
14 KiB
C++

/*
* Nestopia UE
*
* Copyright (C) 2007-2008 R. Belmont
* Copyright (C) 2012-2014 R. Danbrook
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "core/api/NstApiEmulator.hpp"
#include "core/api/NstApiInput.hpp"
#include "core/api/NstApiVideo.hpp"
#include "main.h"
#include "audio.h"
#include "video.h"
#include "config.h"
#include "cursor.h"
#ifdef _GTK
#include "gtkui/gtkui.h"
extern GtkWidget *drawingarea;
#endif
using namespace Nes::Api;
GLuint screenTexID = 0;
void *videobuf;
SDL_Window *sdlwindow;
SDL_Window *embedwindow;
SDL_GLContext glcontext;
SDL_DisplayMode displaymode;
Video::RenderState::Filter filter;
Video::RenderState renderstate;
dimensions_t basesize, rendersize;
extern bool playing;
extern settings_t conf;
extern nstpaths_t nstpaths;
extern Emulator emulator;
void opengl_init_structures() {
// init OpenGL and set up for blitting
int scalefactor = conf.video_scale_factor;
// Fix the fencepost issue when masking overscan
float fencepost = scalefactor / 2.0;
glEnable( GL_TEXTURE_2D );
glGenTextures( 1, &screenTexID ) ;
glBindTexture( GL_TEXTURE_2D, screenTexID ) ;
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, conf.video_linear_filter ? GL_LINEAR : GL_NEAREST) ;
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ) ;
conf.video_fullscreen ?
glViewport( displaymode.w / 2.0f - rendersize.w / 2.0f, 0, rendersize.w, rendersize.h) :
glViewport( 0, 0, rendersize.w, rendersize.h);
glDisable( GL_DEPTH_TEST );
glDisable( GL_ALPHA_TEST );
glDisable( GL_BLEND );
glDisable( GL_LIGHTING );
glDisable( GL_TEXTURE_3D_EXT );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if (conf.video_unmask_overscan) {
glOrtho(
conf.video_linear_filter ? 2.0 : 0.0,
(GLdouble)rendersize.w,
(GLdouble)rendersize.h,
0.0,
-1.0, 1.0
);
}
else {
glOrtho(
conf.video_linear_filter ? 2.0 : 0.0,
conf.video_linear_filter ? (GLdouble)rendersize.w - 2.0 : (GLdouble)rendersize.w,
(GLdouble)rendersize.h - (OVERSCAN_BOTTOM * scalefactor) + fencepost,
(GLdouble)(OVERSCAN_TOP * scalefactor) - fencepost,
-1.0, 1.0
);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void opengl_cleanup() {
// tears down OpenGL when it's no longer needed
if (screenTexID) { glDeleteTextures( 1, &screenTexID ); }
if (videobuf) {
free(videobuf);
videobuf = NULL;
}
}
void opengl_blit() {
// blit the image using OpenGL
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
basesize.w, basesize.h,
0,
GL_BGRA,
GL_UNSIGNED_BYTE,
videobuf);
glBegin( GL_QUADS ) ;
glTexCoord2f(1.0f, 1.0f);
glVertex2f(rendersize.w, rendersize.h);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(rendersize.w, 0);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(0, 0);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(0, rendersize.h);
glEnd();
#ifdef _GTK
if (conf.misc_disable_gui) { SDL_GL_SwapWindow(sdlwindow); }
else { conf.video_fullscreen ? SDL_GL_SwapWindow(sdlwindow) : SDL_GL_SwapWindow(embedwindow); }
#else
SDL_GL_SwapWindow(sdlwindow);
#endif
}
void video_init() {
// Initialize video
opengl_cleanup();
video_set_dimensions();
video_set_filter();
// Allocate the video buffer
videobuf = malloc(renderstate.bits.count * renderstate.width * renderstate.height);
opengl_init_structures();
video_set_cursor();
}
void video_toggle_fullscreen() {
// Toggle between fullscreen and window mode
if (!playing) { return; }
Uint32 flags;
conf.video_fullscreen ^= 1;
if (conf.video_fullscreen) {
flags = SDL_WINDOW_FULLSCREEN_DESKTOP;
}
else { flags = 0; }
#ifdef _GTK
if (conf.video_fullscreen) {
SDL_DestroyWindow(sdlwindow);
video_create_standalone();
SDL_GL_MakeCurrent(sdlwindow, glcontext);
}
else {
if (!conf.misc_disable_gui) {
SDL_GL_MakeCurrent(embedwindow, glcontext);
SDL_DestroyWindow(sdlwindow);
gtkui_resize();
}
else {
video_set_dimensions();
SDL_SetWindowFullscreen(sdlwindow, flags);
SDL_SetWindowSize(sdlwindow, rendersize.w, rendersize.h);
}
}
#else
SDL_SetWindowFullscreen(sdlwindow, flags);
SDL_SetWindowSize(sdlwindow, rendersize.w, rendersize.h);
#endif
video_set_cursor();
video_init();
}
void video_toggle_filter() {
conf.video_filter++;
// Intentionally leaving out scalex
if (conf.video_filter > 4) { conf.video_filter = 0; }
// The scalex filter only allows 3x scale and crashes otherwise
if (conf.video_filter == 5 && conf.video_scale_factor == 4) {
conf.video_scale_factor = 3;
}
video_init();
}
void video_toggle_filterupdate() {
// Clear the filter update flag
Video video(emulator);
video.ClearFilterUpdateFlag();
}
void video_toggle_scalefactor() {
// Toggle video scale factor
conf.video_scale_factor++;
if (conf.video_scale_factor > 4) { conf.video_scale_factor = 1; }
// The scalex filter only allows 3x scale and crashes otherwise
if (conf.video_filter == 5 && conf.video_scale_factor == 4) {
conf.video_scale_factor = 1;
}
video_init();
}
void video_create_standalone() {
// Create a standalone SDL window
int displayindex;
Uint32 windowflags = SDL_WINDOW_SHOWN|SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE;
if (conf.video_fullscreen) {
SDL_ShowCursor(0);
windowflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
sdlwindow = SDL_CreateWindow(
nstpaths.gamename, // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
rendersize.w, // width, in pixels
rendersize.h, // height, in pixels
windowflags);
if(sdlwindow == NULL) {
fprintf(stderr, "Could not create window: %s\n", SDL_GetError());
}
displayindex = SDL_GetWindowDisplayIndex(sdlwindow);
SDL_GetDesktopDisplayMode(displayindex, &displaymode);
//printf("w: %d\th: %d\n", displaymode.w, displaymode.h);
//printf("Window Flags: %x\n", SDL_GetWindowFlags(sdlwindow));
SDL_GL_MakeCurrent(sdlwindow, glcontext);
SDL_GL_SetSwapInterval(conf.timing_vsync);
}
void video_create_embedded() {
// Create an embedded SDL window
#ifdef _GTK
GdkWindow *gdkwindow;
gdkwindow = gtk_widget_get_window(drawingarea);
embedwindow = SDL_CreateWindowFrom((void *)GDK_WINDOW_XID(gtk_widget_get_window(drawingarea)));
embedwindow->flags |= SDL_WINDOW_OPENGL;
SDL_GL_LoadLibrary(NULL);
#endif
}
void video_create() {
// Create the necessary window(s)
#ifdef _GTK
if (conf.misc_disable_gui) {
video_create_standalone();
glcontext = SDL_GL_CreateContext(sdlwindow);
}
else {
video_create_embedded();
glcontext = SDL_GL_CreateContext(embedwindow);
if (conf.video_fullscreen) {
video_create_standalone();
glcontext = SDL_GL_CreateContext(sdlwindow);
}
}
#else
video_create_standalone();
glcontext = SDL_GL_CreateContext(sdlwindow);
#endif
if(glcontext == NULL) {
fprintf(stderr, "Could not create glcontext: %s\n", SDL_GetError());
}
}
void video_destroy() {
// Destroy the video window
SDL_DestroyWindow(sdlwindow);
}
void video_set_filter() {
// Set the filter
Video video(emulator);
int scalefactor = conf.video_scale_factor;
switch(conf.video_filter) {
case 0: // None
filter = Video::RenderState::FILTER_NONE;
break;
case 1: // NTSC
filter = Video::RenderState::FILTER_NTSC;
break;
case 2: // xBR
switch (scalefactor) {
case 2:
filter = Video::RenderState::FILTER_2XBR;
break;
case 3:
filter = Video::RenderState::FILTER_3XBR;
break;
case 4:
filter = Video::RenderState::FILTER_4XBR;
break;
default:
filter = Video::RenderState::FILTER_NONE;
break;
}
break;
case 3: // scale HQx
switch (scalefactor) {
case 2:
filter = Video::RenderState::FILTER_HQ2X;
break;
case 3:
filter = Video::RenderState::FILTER_HQ3X;
break;
case 4:
filter = Video::RenderState::FILTER_HQ4X;
break;
default:
filter = Video::RenderState::FILTER_NONE;
break;
}
break;
case 4: // 2xSaI
filter = Video::RenderState::FILTER_2XSAI;
break;
case 5: // scale x
switch (scalefactor) {
case 2:
filter = Video::RenderState::FILTER_SCALE2X;
break;
case 3:
filter = Video::RenderState::FILTER_SCALE3X;
break;
default:
filter = Video::RenderState::FILTER_NONE;
break;
}
break;
break;
}
// Set the sprite limit: false = enable sprite limit, true = disable sprite limit
video.EnableUnlimSprites(conf.video_unlimited_sprites ? true : false);
// Set Palette options
switch (conf.video_palette_mode) {
case 0: // YUV
video.GetPalette().SetMode(Video::Palette::MODE_YUV);
break;
case 1: // RGB
video.GetPalette().SetMode(Video::Palette::MODE_RGB);
}
// Set YUV Decoder/Picture options
if (video.GetPalette().GetMode() != Video::Palette::MODE_RGB) {
switch (conf.video_decoder) {
case 0: // Consumer
video.SetDecoder(Video::DECODER_CONSUMER);
break;
case 1: // Canonical
video.SetDecoder(Video::DECODER_CANONICAL);
break;
case 2: // Alternative (Canonical with yellow boost)
video.SetDecoder(Video::DECODER_ALTERNATIVE);
break;
default: break;
}
video.SetBrightness(conf.video_brightness);
video.SetSaturation(conf.video_saturation);
video.SetContrast(conf.video_contrast);
video.SetHue(conf.video_hue);
}
// Set NTSC options
switch (conf.video_ntsc_mode) {
case 0: // Composite
video.SetSharpness(Video::DEFAULT_SHARPNESS_COMP);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_COMP);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_COMP);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_COMP);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_COMP);
break;
case 1: // S-Video
video.SetSharpness(Video::DEFAULT_SHARPNESS_SVIDEO);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_SVIDEO);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_SVIDEO);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_SVIDEO);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_SVIDEO);
break;
case 2: // RGB
video.SetSharpness(Video::DEFAULT_SHARPNESS_RGB);
video.SetColorResolution(Video::DEFAULT_COLOR_RESOLUTION_RGB);
video.SetColorBleed(Video::DEFAULT_COLOR_BLEED_RGB);
video.SetColorArtifacts(Video::DEFAULT_COLOR_ARTIFACTS_RGB);
video.SetColorFringing(Video::DEFAULT_COLOR_FRINGING_RGB);
break;
default: break;
}
// Set xBR options
if (conf.video_filter == 2) {
video.SetCornerRounding(conf.video_xbr_corner_rounding);
video.SetBlend(conf.video_xbr_pixel_blending);
}
// Set up the render state parameters
renderstate.filter = filter;
renderstate.width = basesize.w;
renderstate.height = basesize.h;
renderstate.bits.count = 32;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
renderstate.bits.mask.r = 0x000000ff;
renderstate.bits.mask.g = 0xff000000;
renderstate.bits.mask.b = 0x00ff0000;
#else
renderstate.bits.mask.r = 0x00ff0000;
renderstate.bits.mask.g = 0x0000ff00;
renderstate.bits.mask.b = 0x000000ff;
#endif
if (NES_FAILED(video.SetRenderState(renderstate))) {
fprintf(stderr, "Nestopia core rejected render state\n");
exit(1);
}
}
void video_set_dimensions() {
// Set up the video dimensions
int scalefactor = conf.video_scale_factor;
switch(conf.video_filter) {
case 0: // None
basesize.w = Video::Output::WIDTH;
basesize.h = Video::Output::HEIGHT;
conf.video_tv_aspect == true ? rendersize.w = TV_WIDTH * scalefactor : rendersize.w = basesize.w * scalefactor;
rendersize.h = basesize.h * scalefactor;
break;
case 1: // NTSC
basesize.w = Video::Output::NTSC_WIDTH;
rendersize.w = (basesize.w / 2) * scalefactor;
basesize.h = Video::Output::HEIGHT;
rendersize.h = basesize.h * scalefactor;
break;
case 2: // xBR
case 3: // HqX
case 5: // ScaleX
if (conf.video_filter == 5 && scalefactor == 4) {
fprintf(stderr, "error: ScaleX filter cannot scale to 4x\n");
conf.video_scale_factor = scalefactor = 3;
}
basesize.w = Video::Output::WIDTH * scalefactor;
basesize.h = Video::Output::HEIGHT * scalefactor;
conf.video_tv_aspect == true ? rendersize.w = TV_WIDTH * scalefactor : rendersize.w = basesize.w;
rendersize.h = basesize.h;
break;
case 4: // 2xSaI
basesize.w = Video::Output::WIDTH * 2;
basesize.h = Video::Output::HEIGHT * 2;
conf.video_tv_aspect == true ? rendersize.w = TV_WIDTH * scalefactor : rendersize.w = Video::Output::WIDTH * scalefactor;
rendersize.h = Video::Output::HEIGHT * scalefactor;
break;
}
if (!conf.video_unmask_overscan) {
rendersize.h -= (OVERSCAN_TOP + OVERSCAN_BOTTOM) * scalefactor;
}
// Calculate the aspect from the height because it's smaller
float aspect = (float)displaymode.h / (float)rendersize.h;
if (!conf.video_stretch_aspect && conf.video_fullscreen && sdlwindow) {
rendersize.h *= aspect;
rendersize.w *= aspect;
}
else if (conf.video_fullscreen && sdlwindow) {
rendersize.h = displaymode.h;
rendersize.w = displaymode.w;
}
#ifdef _GTK
if (!conf.misc_disable_gui) {
SDL_SetWindowSize(embedwindow, rendersize.w, rendersize.h);
gtkui_resize();
}
#endif
SDL_SetWindowSize(sdlwindow, rendersize.w, rendersize.h);
}
void video_set_cursor() {
// Set the cursor to what it needs to be
int cursor;
bool zapper;
if (Input(emulator).GetConnectedController(0) == 5 ||
Input(emulator).GetConnectedController(1) == 5) {
zapper = true;
cursor_set_crosshair();
}
else {
zapper = false;
cursor_set_default();
}
if (conf.video_fullscreen) { cursor = zapper; }
else { cursor = true; }
SDL_ShowCursor(cursor);
}
void video_set_title(const char *title) {
// Set the window title
SDL_SetWindowTitle(sdlwindow, title);
}
long video_lock_screen(void*& ptr) {
ptr = videobuf;
return basesize.w * 4;
}
void video_unlock_screen(void*) {
opengl_blit();
}