mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
583 lines
14 KiB
C++
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();
|
|
}
|