1
0
Fork 0
dosbox-staging/src/gui/sdlmain.cpp
2020-04-04 04:20:28 +02:00

2913 lines
94 KiB
C++

/*
* Copyright (C) 2002-2020 The DOSBox Team
*
* 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.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "dosbox.h"
#include <array>
#include <cassert>
#include <cstdlib>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <math.h>
#ifdef WIN32
#include <signal.h>
#include <process.h>
#endif
#include <SDL.h>
#if C_OPENGL
#include <SDL_opengl.h>
#endif
#include "cross.h"
#include "video.h"
#include "mouse.h"
#include "pic.h"
#include "timer.h"
#include "setup.h"
#include "support.h"
#include "debug.h"
#include "mapper.h"
#include "vga.h"
#include "keyboard.h"
#include "cpu.h"
#include "control.h"
#include "render.h"
#include "../libs/ppscale/ppscale.h"
#define MAPPERFILE "mapper-sdl2-" VERSION ".map"
//#define DISABLE_JOYSTICK
#if C_OPENGL
//Define to disable the usage of the pixel buffer object
//#define DB_DISABLE_DBO
//Define to report opengl errors
//#define DB_OPENGL_ERROR
#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef APIENTRYP
#define APIENTRYP APIENTRY *
#endif
#ifndef GL_ARB_pixel_buffer_object
#define GL_ARB_pixel_buffer_object 1
#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB
#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC
#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED
#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF
#endif
#ifndef GL_ARB_vertex_buffer_object
#define GL_ARB_vertex_buffer_object 1
typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access);
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target);
#endif
PFNGLGENBUFFERSARBPROC glGenBuffersARB = NULL;
PFNGLBINDBUFFERARBPROC glBindBufferARB = NULL;
PFNGLDELETEBUFFERSARBPROC glDeleteBuffersARB = NULL;
PFNGLBUFFERDATAARBPROC glBufferDataARB = NULL;
PFNGLMAPBUFFERARBPROC glMapBufferARB = NULL;
PFNGLUNMAPBUFFERARBPROC glUnmapBufferARB = NULL;
/* Don't guard these with GL_VERSION_2_0 - Apple defines it but not these typedefs.
* If they're already defined they should match these definitions, so no conflicts.
*/
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index);
typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name);
typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);
typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);
//Change to NP, as Khronos changes include guard :(
typedef void (APIENTRYP PFNGLSHADERSOURCEPROC_NP) (GLuint shader, GLsizei count, const GLchar **string, const GLint *length);
typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1);
typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
/* Apple defines these functions in their GL header (as core functions)
* so we can't use their names as function pointers. We can't link
* directly as some platforms may not have them. So they get their own
* namespace here to keep the official names but avoid collisions.
*/
namespace gl2 {
PFNGLATTACHSHADERPROC glAttachShader = NULL;
PFNGLCOMPILESHADERPROC glCompileShader = NULL;
PFNGLCREATEPROGRAMPROC glCreateProgram = NULL;
PFNGLCREATESHADERPROC glCreateShader = NULL;
PFNGLDELETEPROGRAMPROC glDeleteProgram = NULL;
PFNGLDELETESHADERPROC glDeleteShader = NULL;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL;
PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation = NULL;
PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = NULL;
PFNGLGETSHADERIVPROC glGetShaderiv = NULL;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = NULL;
PFNGLLINKPROGRAMPROC glLinkProgram = NULL;
PFNGLSHADERSOURCEPROC_NP glShaderSource = NULL;
PFNGLUNIFORM2FPROC glUniform2f = NULL;
PFNGLUNIFORM1IPROC glUniform1i = NULL;
PFNGLUSEPROGRAMPROC glUseProgram = NULL;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL;
}
/* "using" is meant to hide identical names declared in outer scope
* but is unreliable, so just redefine instead.
*/
#define glAttachShader gl2::glAttachShader
#define glCompileShader gl2::glCompileShader
#define glCreateProgram gl2::glCreateProgram
#define glCreateShader gl2::glCreateShader
#define glDeleteProgram gl2::glDeleteProgram
#define glDeleteShader gl2::glDeleteShader
#define glEnableVertexAttribArray gl2::glEnableVertexAttribArray
#define glGetAttribLocation gl2::glGetAttribLocation
#define glGetProgramiv gl2::glGetProgramiv
#define glGetProgramInfoLog gl2::glGetProgramInfoLog
#define glGetShaderiv gl2::glGetShaderiv
#define glGetShaderInfoLog gl2::glGetShaderInfoLog
#define glGetUniformLocation gl2::glGetUniformLocation
#define glLinkProgram gl2::glLinkProgram
#define glShaderSource gl2::glShaderSource
#define glUniform2f gl2::glUniform2f
#define glUniform1i gl2::glUniform1i
#define glUseProgram gl2::glUseProgram
#define glVertexAttribPointer gl2::glVertexAttribPointer
#endif // C_OPENGL
#if !(ENVIRON_INCLUDED)
extern char** environ;
#endif
#ifdef WIN32
#include <winuser.h>
#define STDOUT_FILE "stdout.txt"
#define STDERR_FILE "stderr.txt"
#endif
#if C_SET_PRIORITY
#include <sys/resource.h>
#define PRIO_TOTAL (PRIO_MAX-PRIO_MIN)
#endif
SDL_bool mouse_is_captured = SDL_FALSE; // global for mapper
// Masks to be passed when creating SDL_Surface.
// Remove ifndef if they'll be needed for MacOS X builds.
#ifndef MACOSX
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
constexpr uint32_t RMASK = 0xff000000;
constexpr uint32_t GMASK = 0x00ff0000;
constexpr uint32_t BMASK = 0x0000ff00;
#else
constexpr uint32_t RMASK = 0x000000ff;
constexpr uint32_t GMASK = 0x0000ff00;
constexpr uint32_t BMASK = 0x00ff0000;
#endif
#endif // !MACOSX
enum MouseControlType {
CaptureOnClick = 1 << 0,
CaptureOnStart = 1 << 1,
Seamless = 1 << 2,
NoMouse = 1 << 3
};
enum SCREEN_TYPES {
SCREEN_SURFACE,
SCREEN_TEXTURE,
SCREEN_OPENGL
};
/* Modes of simple pixel scaling, currently encoded in the output type: */
enum ScalingMode {SmNone, SmNearest, SmPerfect};
enum PRIORITY_LEVELS {
PRIORITY_LEVEL_PAUSE,
PRIORITY_LEVEL_LOWEST,
PRIORITY_LEVEL_LOWER,
PRIORITY_LEVEL_NORMAL,
PRIORITY_LEVEL_HIGHER,
PRIORITY_LEVEL_HIGHEST
};
struct SDL_Block {
bool inited;
bool active; //If this isn't set don't draw
bool updating;
bool update_display_contents;
bool resizing_window;
ScalingMode scaling_mode;
struct {
Bit32u width;
Bit32u height;
double pixel_aspect;
Bitu flags;
double scalex, scaley;
GFX_CallBack_t callback;
} draw;
bool wait_on_error;
struct {
struct {
Bit16u width = 0;
Bit16u height = 0;
bool fixed = false;
bool display_res = false;
} full;
struct {
uint16_t width = 0; // TODO convert to int
uint16_t height = 0; // TODO convert to int
bool use_original_size = true;
} window;
Bit8u bpp = 0;
bool fullscreen = false;
bool vsync = false;
SCREEN_TYPES type;
SCREEN_TYPES want_type;
} desktop;
#if C_OPENGL
struct {
SDL_GLContext context;
int pitch = 0;
void *framebuf = nullptr;
GLuint buffer;
GLuint texture;
GLuint displaylist;
GLint max_texsize;
bool bilinear;
bool packed_pixel;
bool paletted_texture;
bool pixel_buffer_object;
bool use_shader;
GLuint program_object;
const char *shader_src;
struct {
GLint texture_size;
GLint input_size;
GLint output_size;
GLint frame_count;
} ruby;
GLuint actual_frame_count;
GLfloat vertex_data[2*3];
} opengl;
#endif // C_OPENGL
struct {
PRIORITY_LEVELS focus;
PRIORITY_LEVELS nofocus;
} priority;
SDL_Rect clip;
SDL_Surface *surface;
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
std::string render_driver = "";
int displayNumber;
struct {
SDL_Surface *input_surface = nullptr;
SDL_Texture *texture = nullptr;
SDL_PixelFormat *pixelFormat = nullptr;
} texture;
struct {
int xsensitivity = 0;
int ysensitivity = 0;
int sensitivity = 0;
MouseControlType control_choice = Seamless;
bool middle_will_release = true;
bool has_focus = false;
} mouse;
int ppscale_x, ppscale_y; /* x and y scales for pixel-perfect */
bool double_h, double_w; /* double-height and double-width flags */
SDL_Rect updateRects[1024];
Bitu num_joysticks;
#if defined (WIN32)
// Time when sdl regains focus (alt-tab) in windowed mode
Bit32u focus_ticks;
#endif
// state of alt-keys for certain special handlings
SDL_EventType laltstate;
SDL_EventType raltstate;
};
static SDL_Block sdl;
static void CleanupSDLResources();
#if C_OPENGL
static char const shader_src_default[] =
"varying vec2 v_texCoord;\n"
"#if defined(VERTEX)\n"
"uniform vec2 rubyTextureSize;\n"
"uniform vec2 rubyInputSize;\n"
"attribute vec4 a_position;\n"
"void main() {\n"
" gl_Position = a_position;\n"
" v_texCoord = vec2(a_position.x+1.0,1.0-a_position.y)/2.0*rubyInputSize/rubyTextureSize;\n"
"}\n"
"#elif defined(FRAGMENT)\n"
"uniform sampler2D rubyTexture;\n\n"
"void main() {\n"
" gl_FragColor = texture2D(rubyTexture, v_texCoord);\n"
"}\n"
"#endif\n";
#ifdef DB_OPENGL_ERROR
void OPENGL_ERROR(const char* message) {
GLenum r = glGetError();
if (r == GL_NO_ERROR) return;
LOG_MSG("errors from %s",message);
do {
LOG_MSG("%X",r);
} while ( (r=glGetError()) != GL_NO_ERROR);
}
#else
void OPENGL_ERROR(const char*) {
return;
}
#endif
#endif
static int SDL_Init_Wrapper(void)
{
// Don't init timers, GetTicks seems to work fine and they can use
// a fair amount of power (Macs again).
// Please report problems with audio and other things.
return SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO);
}
extern const char* RunningProgram;
extern bool CPU_CycleAutoAdjust;
//Globals for keyboard initialisation
bool startup_state_numlock=false;
bool startup_state_capslock=false;
void GFX_SetTitle(Bit32s cycles,int frameskip,bool paused){
char title[200] = { 0 };
static Bit32s internal_cycles = 0;
static int internal_frameskip = 0;
if (cycles != -1) internal_cycles = cycles;
if (frameskip != -1) internal_frameskip = frameskip;
if(CPU_CycleAutoAdjust) {
sprintf(title,"DOSBox %s, CPU speed: max %3d%% cycles, Frameskip %2d, Program: %8s",VERSION,internal_cycles,internal_frameskip,RunningProgram);
} else {
sprintf(title,"DOSBox %s, CPU speed: %8d cycles, Frameskip %2d, Program: %8s",VERSION,internal_cycles,internal_frameskip,RunningProgram);
}
if (paused) strcat(title," PAUSED");
SDL_SetWindowTitle(sdl.window,title);
}
static unsigned char logo[32*32*4]= {
#include "dosbox_logo.h"
};
static void SetIcon()
{
// Don't set it on macOS, as we use a nicer external icon there.
#if !defined(MACOSX)
SDL_Surface *s = SDL_CreateRGBSurfaceFrom((void*)logo, 32, 32, 32, 128,
RMASK, GMASK, BMASK, 0);
SDL_SetWindowIcon(sdl.window, s);
SDL_FreeSurface(s);
#endif // !defined(MACOSX)
}
static void KillSwitch(bool pressed) {
if (!pressed)
return;
throw 1;
}
static void PauseDOSBox(bool pressed) {
if (!pressed)
return;
GFX_SetTitle(-1,-1,true);
bool paused = true;
KEYBOARD_ClrBuffer();
SDL_Delay(500);
SDL_Event event;
while (SDL_PollEvent(&event)) {
// flush event queue.
}
/* NOTE: This is one of the few places where we use SDL key codes
with SDL 2.0, rather than scan codes. Is that the correct behavior? */
while (paused) {
SDL_WaitEvent(&event); // since we're not polling, cpu usage drops to 0.
switch (event.type) {
case SDL_QUIT: KillSwitch(true); break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_RESTORED) {
// We may need to re-create a texture and more
GFX_ResetScreen();
}
break;
case SDL_KEYDOWN: // Must use Pause/Break Key to resume.
case SDL_KEYUP:
if(event.key.keysym.sym == SDLK_PAUSE) {
paused = false;
GFX_SetTitle(-1,-1,false);
break;
}
#if defined (MACOSX)
if (event.key.keysym.sym == SDLK_q &&
(event.key.keysym.mod == KMOD_RGUI || event.key.keysym.mod == KMOD_LGUI)) {
/* On macs, all aps exit when pressing cmd-q */
KillSwitch(true);
break;
}
#endif
}
}
}
/* Reset the screen with current values in the sdl structure */
Bitu GFX_GetBestMode(Bitu flags)
{
if (sdl.scaling_mode == SmPerfect)
flags |= GFX_UNITY_SCALE;
switch (sdl.desktop.want_type) {
case SCREEN_SURFACE:
check_surface:
flags &= ~GFX_LOVE_8; //Disable love for 8bpp modes
switch (sdl.desktop.bpp) {
case 8:
if (flags & GFX_CAN_8) flags&=~(GFX_CAN_15|GFX_CAN_16|GFX_CAN_32);
break;
case 15:
if (flags & GFX_CAN_15) flags&=~(GFX_CAN_8|GFX_CAN_16|GFX_CAN_32);
break;
case 16:
if (flags & GFX_CAN_16) flags&=~(GFX_CAN_8|GFX_CAN_15|GFX_CAN_32);
break;
case 24:
case 32:
if (flags & GFX_CAN_32) flags&=~(GFX_CAN_8|GFX_CAN_15|GFX_CAN_16);
break;
}
flags |= GFX_CAN_RANDOM;
break;
#if C_OPENGL
case SCREEN_OPENGL:
#endif
case SCREEN_TEXTURE:
// We only accept 32bit output from the scalers here
if (!(flags&GFX_CAN_32)) goto check_surface;
flags|=GFX_SCALING;
flags&=~(GFX_CAN_8|GFX_CAN_15|GFX_CAN_16);
break;
default:
goto check_surface;
break;
}
return flags;
}
void GFX_ResetScreen(void) {
GFX_Stop();
if (sdl.draw.callback)
(sdl.draw.callback)( GFX_CallBackReset );
GFX_Start();
CPU_Reset_AutoAdjust();
}
void GFX_ForceFullscreenExit()
{
sdl.desktop.fullscreen = false;
GFX_ResetScreen();
}
static int int_log2 (int val) {
int log = 0;
while ((val >>= 1) != 0)
log++;
return log;
}
static SDL_Window * GFX_SetSDLWindowMode(Bit16u width, Bit16u height, bool fullscreen, SCREEN_TYPES screenType)
{
static SCREEN_TYPES lastType = SCREEN_SURFACE;
CleanupSDLResources();
int currWidth, currHeight;
if (sdl.window && sdl.resizing_window) {
SDL_GetWindowSize(sdl.window, &currWidth, &currHeight);
sdl.update_display_contents = ((width <= currWidth) && (height <= currHeight));
return sdl.window;
}
const bool first_window = !sdl.window;
/* If we change screen type, recreate the window. Furthermore, if
* it is our very first time then we simply create a new window.
*/
if (first_window || (lastType != screenType)) {
lastType = screenType;
if (sdl.window) {
SDL_DestroyWindow(sdl.window);
}
uint32_t flags = 0;
if (screenType == SCREEN_OPENGL)
flags |= SDL_WINDOW_OPENGL;
#if defined (WIN32)
// This is a hack for Windows 10 to prevent a crash in AMD OpenGL
// driver when window is being re-created by SDL2 internally to
// support OpenGL.
else if (screenType == SCREEN_TEXTURE && starts_with("opengl", sdl.render_driver))
flags |= SDL_WINDOW_OPENGL;
#endif
// Using undefined position will take care of placing and restoring the
// window by WM.
const int sdl_pos = SDL_WINDOWPOS_UNDEFINED_DISPLAY(sdl.displayNumber);
sdl.window = SDL_CreateWindow("", sdl_pos, sdl_pos, width, height, flags);
if (!sdl.window) {
return sdl.window;
}
#if defined (WIN32)
// Force window position when going straight to fullscreen.
// Otherwise, SDL will reset window position to 0,0 when
// switching to a window for the first time. This is happening
// on every OS, but only on Windows it's a really big problem
// (because window decorations are rendered off-screen).
//
// To work around this problem, center the window manually based on
// the original drawing size, and not window size.
//
// On Linux this workaround breaks window position on
// multi-monitor setups, so let's use it on Windows only.
if (first_window && fullscreen) {
const int x = (sdl.desktop.full.width - sdl.draw.width) / 2;
const int y = (sdl.desktop.full.height - sdl.draw.height) / 2;
SDL_SetWindowPosition(sdl.window, x, y);
}
#endif
GFX_SetTitle(-1, -1, false); // refresh title.
if (!fullscreen) {
goto finish;
}
}
/* Fullscreen mode switching has its limits, and is also problematic on
* some window managers. For now, the following may work up to some
* level. On X11, SDL_VIDEO_X11_LEGACY_FULLSCREEN=1 can also help,
* although it has its own issues.
*/
if (fullscreen) {
SDL_DisplayMode displayMode;
SDL_GetWindowDisplayMode(sdl.window, &displayMode);
displayMode.w = width;
displayMode.h = height;
SDL_SetWindowDisplayMode(sdl.window, &displayMode);
SDL_SetWindowFullscreen(sdl.window,
sdl.desktop.full.display_res ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN);
} else {
SDL_SetWindowSize(sdl.window, width, height);
SDL_SetWindowFullscreen(sdl.window, 0);
}
// Maybe some requested fullscreen resolution is unsupported?
finish:
SDL_GetWindowSize(sdl.window, &currWidth, &currHeight);
sdl.update_display_contents = ((width <= currWidth) && (height <= currHeight));
return sdl.window;
}
// Used for the mapper UI and more: Creates a fullscreen window with desktop res
// on Android, and a non-fullscreen window with the input dimensions otherwise.
SDL_Window * GFX_SetSDLSurfaceWindow(Bit16u width, Bit16u height)
{
return GFX_SetSDLWindowMode(width, height, false, SCREEN_SURFACE);
}
// Returns the rectangle in the current window to be used for scaling a
// sub-window with the given dimensions, like the mapper UI.
SDL_Rect GFX_GetSDLSurfaceSubwindowDims(Bit16u width, Bit16u height)
{
SDL_Rect rect;
rect.x = rect.y = 0;
rect.w = width;
rect.h = height;
return rect;
}
static SDL_Window * GFX_SetupWindowScaled(SCREEN_TYPES screenType)
{
Bit16u fixedWidth;
Bit16u fixedHeight;
if (sdl.desktop.fullscreen) {
fixedWidth = sdl.desktop.full.fixed ? sdl.desktop.full.width : 0;
fixedHeight = sdl.desktop.full.fixed ? sdl.desktop.full.height : 0;
} else {
fixedWidth = sdl.desktop.window.width;
fixedHeight = sdl.desktop.window.height;
}
if (fixedWidth && fixedHeight) {
double ratio_w=(double)fixedWidth/(sdl.draw.width*sdl.draw.scalex);
double ratio_h=(double)fixedHeight/(sdl.draw.height*sdl.draw.scaley);
if ( ratio_w < ratio_h) {
sdl.clip.w=fixedWidth;
sdl.clip.h=(Bit16u)(sdl.draw.height*sdl.draw.scaley*ratio_w + 0.1); //possible rounding issues
} else {
/*
* The 0.4 is there to correct for rounding issues.
* (partly caused by the rounding issues fix in RENDER_SetSize)
*/
sdl.clip.w=(Bit16u)(sdl.draw.width*sdl.draw.scalex*ratio_h + 0.4);
sdl.clip.h = fixedHeight;
}
if (sdl.desktop.fullscreen) {
sdl.window = GFX_SetSDLWindowMode(fixedWidth,
fixedHeight,
sdl.desktop.fullscreen,
screenType);
} else {
sdl.window = GFX_SetSDLWindowMode(sdl.clip.w,
sdl.clip.h,
sdl.desktop.fullscreen,
screenType);
}
if (sdl.window && SDL_GetWindowFlags(sdl.window) & SDL_WINDOW_FULLSCREEN) {
int windowWidth;
SDL_GetWindowSize(sdl.window, &windowWidth, NULL);
sdl.clip.x = (Sint16)((windowWidth - sdl.clip.w) / 2);
sdl.clip.y = (Sint16)((fixedHeight - sdl.clip.h) / 2);
} else {
sdl.clip.x = 0;
sdl.clip.y = 0;
}
return sdl.window;
} else {
sdl.clip.x=0;sdl.clip.y=0;
sdl.clip.w=(Bit16u)(sdl.draw.width*sdl.draw.scalex);
sdl.clip.h=(Bit16u)(sdl.draw.height*sdl.draw.scaley);
sdl.window = GFX_SetSDLWindowMode(sdl.clip.w, sdl.clip.h, sdl.desktop.fullscreen, screenType);
return sdl.window;
}
}
#if C_OPENGL
/* Create a GLSL shader object, load the shader source, and compile the shader. */
static GLuint BuildShader ( GLenum type, const char *shaderSrc ) {
GLuint shader;
GLint compiled;
const char* src_strings[2];
std::string top;
// look for "#version" because it has to occur first
const char *ver = strstr(shaderSrc, "#version ");
if (ver) {
const char *endline = strchr(ver+9, '\n');
if (endline) {
top.assign(shaderSrc, endline-shaderSrc+1);
shaderSrc = endline+1;
}
}
top += (type==GL_VERTEX_SHADER) ? "#define VERTEX 1\n":"#define FRAGMENT 1\n";
if (!sdl.opengl.bilinear)
top += "#define OPENGLNB 1\n";
src_strings[0] = top.c_str();
src_strings[1] = shaderSrc;
// Create the shader object
shader = glCreateShader(type);
if (shader == 0) return 0;
// Load the shader source
glShaderSource(shader, 2, src_strings, NULL);
// Compile the shader
glCompileShader(shader);
// Check the compile status
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint info_len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
if (info_len > 1) {
std::vector<GLchar> info_log(info_len);
glGetShaderInfoLog(shader, info_len, NULL, info_log.data());
LOG_MSG("Error compiling shader: %s", info_log.data());
}
glDeleteShader(shader);
return 0;
}
return shader;
}
static bool LoadGLShaders(const char *src, GLuint *vertex, GLuint *fragment) {
GLuint s = BuildShader(GL_VERTEX_SHADER, src);
if (s) {
*vertex = s;
s = BuildShader(GL_FRAGMENT_SHADER, src);
if (s) {
*fragment = s;
return true;
}
glDeleteShader(*vertex);
}
return false;
}
#endif
/* Get the maximum area available for drawing: */
/* TODO: The current implementation will not let the pixel-perfect mode to */
/* to apply even the simplest 1:2 and 2:1 corrections at the original */
/* window resolution, therefore: consider a special case for PP mode. */
static void GetAvailableArea(int &width, int &height)
{
bool fixed;
double par;
fixed = false;
if (sdl.desktop.fullscreen) {
if (sdl.desktop.full.fixed) {
width = sdl.desktop.full.width;
height = sdl.desktop.full.height;
fixed = true;
}
}
else {
if (sdl.desktop.window.width > 0) {
width = sdl.desktop.window.width;
height = sdl.desktop.window.height;
fixed = true;
}
}
if (!fixed) {
par = sdl.draw.pixel_aspect;
if (par > 1.0)
height = static_cast<int>(round(height * par));
if (par < 1.0)
width = static_cast<int>(round(width / par));
}
}
// ATT: aspect is the final aspect ratio of the image including its pixel dimensions and PAR
/*static void GetActualArea( Bit16u av_w, Bit16u av_h, Bit16u *w, Bit16u *h, double aspect )
{ double as_x, as_y;
if( aspect > 1.0 )
{ as_y = aspect; as_x = 1.0; } else
{ as_x = 1.0/aspect; as_y = 1.0; }
if( av_h / as_y < av_w / as_x )
{ *h = av_h; *w = round( (double)av_h / aspect ); } else
{ *w = av_w; *h = round( (double)av_w * aspect ); }
}*/
/* Initialise pixel-perfect mode: */
static bool InitPp(Bit16u avw, Bit16u avh)
{
bool ok;
double newpar;
/* TODO: consider reading apsect importance from the .ini-file */
ok = pp_getscale(
sdl.draw.width, sdl.draw.height,
sdl.draw.pixel_aspect,
avw, avh,
1.14, /* relative importance of aspect ratio */
&sdl.ppscale_x,
&sdl.ppscale_y
) == 0;
if (ok) {
newpar = ( double )sdl.ppscale_y / sdl.ppscale_x;
LOG_MSG( "Pixel-perfect scaling:\n"
"%ix%i (%#4.3g) --[%ix%i]--> %ix%i (%#4.3g)",
sdl.draw.width, sdl.draw.height, sdl.draw.pixel_aspect,
sdl.ppscale_x, sdl.ppscale_y,
sdl.ppscale_x * sdl.draw.width, sdl.ppscale_y * sdl.draw.height,
newpar);
}
return ok;
}
Bitu GFX_SetSize(Bitu width, Bitu height, Bitu flags,
double scalex, double scaley,
GFX_CallBack_t callback,
double pixel_aspect)
{
int avw, avh; /* available width and height */
Bitu retFlags = 0;
if (sdl.updating)
GFX_EndUpdate( 0 );
sdl.draw.pixel_aspect = pixel_aspect;
sdl.draw.width=width;
sdl.draw.height=height;
sdl.draw.callback=callback;
sdl.draw.scalex=scalex;
sdl.draw.scaley=scaley;
sdl.double_h = (flags & GFX_DBL_H) > 0;
sdl.double_w = (flags & GFX_DBL_W) > 0;
avw = width;
avh = height;
GetAvailableArea(avw, avh);
LOG_MSG("Input image: %ix%i DW: %i DH: %i PAR: %5.3g",
(int)width, (int)height, sdl.double_h, sdl.double_w, pixel_aspect );
LOG_MSG("Available area: %ix%i", avw, avh);
if (sdl.scaling_mode == SmPerfect) {
if (!InitPp(avw, avh)) {
LOG_MSG("Failed to initialise pixel-perfect mode, reverting to surface.");
goto dosurface;
}
}
switch (sdl.desktop.want_type) {
case SCREEN_SURFACE:
dosurface:
sdl.desktop.type=SCREEN_SURFACE;
sdl.clip.w=width;
sdl.clip.h=height;
if (sdl.desktop.fullscreen) {
if (sdl.desktop.full.fixed) {
sdl.clip.x = (Sint16)((sdl.desktop.full.width - width) / 2);
sdl.clip.y = (Sint16)((sdl.desktop.full.height - height) / 2);
sdl.window = GFX_SetSDLWindowMode(sdl.desktop.full.width,
sdl.desktop.full.height,
sdl.desktop.fullscreen,
sdl.desktop.type);
if (sdl.window == NULL)
E_Exit("Could not set fullscreen video mode %ix%i-%i: %s",
sdl.desktop.full.width,
sdl.desktop.full.height,
sdl.desktop.bpp,
SDL_GetError());
/* This may be required after an ALT-TAB leading to a window
minimize, which further effectively shrinks its size */
if ((sdl.clip.x < 0) || (sdl.clip.y < 0)) {
sdl.update_display_contents = false;
}
} else {
sdl.clip.x = 0;
sdl.clip.y = 0;
sdl.window = GFX_SetSDLWindowMode(width,
height,
sdl.desktop.fullscreen,
sdl.desktop.type);
if (sdl.window == NULL)
E_Exit("Could not set fullscreen video mode %ix%i-%i: %s",
(int)width,
(int)height,
sdl.desktop.bpp,
SDL_GetError());
}
} else {
sdl.clip.x = 0;
sdl.clip.y = 0;
sdl.window = GFX_SetSDLWindowMode(width,
height,
sdl.desktop.fullscreen,
sdl.desktop.type);
if (sdl.window == NULL)
E_Exit("Could not set windowed video mode %ix%i-%i: %s",
(int)width,
(int)height,
sdl.desktop.bpp,
SDL_GetError());
}
sdl.surface = SDL_GetWindowSurface(sdl.window);
if (sdl.surface == NULL)
E_Exit("Could not retrieve window surface: %s",SDL_GetError());
switch (sdl.surface->format->BitsPerPixel) {
case 8:
retFlags = GFX_CAN_8;
break;
case 15:
retFlags = GFX_CAN_15;
break;
case 16:
retFlags = GFX_CAN_16;
break;
case 32:
retFlags = GFX_CAN_32;
break;
}
/* Fix a glitch with aspect=true occuring when
changing between modes with different dimensions */
SDL_FillRect(sdl.surface, NULL, SDL_MapRGB(sdl.surface->format, 0, 0, 0));
SDL_UpdateWindowSurface(sdl.window);
break;
case SCREEN_TEXTURE: {
int imgw, imgh, wndw, wndh; /* image and window width and height */
/* TODO: set up all ScalingMode-related settings here. Currently, the */
/* interpolation hint is set at the reading of settings. */
if (sdl.scaling_mode != SmPerfect) {
if (!GFX_SetupWindowScaled(sdl.desktop.want_type)) {
LOG_MSG("SDL:Can't set video mode, falling back to surface");
goto dosurface;
}
} else {
imgw = sdl.ppscale_x * sdl.draw.width;
imgh = sdl.ppscale_y * sdl.draw.height;
if (sdl.desktop.fullscreen) {
wndh = avh; wndw = avw;
} else {
wndh = imgh; wndw = imgw;
}
sdl.clip.w = imgw;
sdl.clip.h = imgh;
sdl.clip.x = (wndw - imgw) / 2;
sdl.clip.y = (wndh - imgh) / 2;
sdl.window = GFX_SetSDLWindowMode(
wndw, wndh, sdl.desktop.fullscreen,
SCREEN_TEXTURE);
}
if (sdl.render_driver != "auto")
SDL_SetHint(SDL_HINT_RENDER_DRIVER, sdl.render_driver.c_str());
sdl.renderer = SDL_CreateRenderer(sdl.window, -1,
SDL_RENDERER_ACCELERATED |
(sdl.desktop.vsync ? SDL_RENDERER_PRESENTVSYNC : 0));
if (!sdl.renderer) {
LOG_MSG("%s\n", SDL_GetError());
LOG_MSG("SDL:Can't create renderer, falling back to surface");
goto dosurface;
}
/* SDL_PIXELFORMAT_ARGB8888 is possible with most
rendering drivers, "opengles" being a notable exception */
sdl.texture.texture = SDL_CreateTexture(sdl.renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, width, height);
/* SDL_PIXELFORMAT_ABGR8888 (not RGB) is the
only supported format for the "opengles" driver */
if (!sdl.texture.texture) {
if (flags & GFX_RGBONLY) goto dosurface;
sdl.texture.texture = SDL_CreateTexture(sdl.renderer, SDL_PIXELFORMAT_ABGR8888,
SDL_TEXTUREACCESS_STREAMING, width, height);
}
if (!sdl.texture.texture) {
SDL_DestroyRenderer(sdl.renderer);
sdl.renderer = NULL;
LOG_MSG("SDL:Can't create texture, falling back to surface");
goto dosurface;
}
sdl.texture.input_surface = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0);
if (!sdl.texture.input_surface) {
LOG_MSG("SDL: Error while preparing texture input");
goto dosurface;
}
SDL_SetRenderDrawColor(sdl.renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
sdl.desktop.type=SCREEN_TEXTURE;
Uint32 pixelFormat;
SDL_QueryTexture(sdl.texture.texture, &pixelFormat, NULL, NULL, NULL);
sdl.texture.pixelFormat = SDL_AllocFormat(pixelFormat);
switch (SDL_BITSPERPIXEL(pixelFormat)) {
case 8: retFlags = GFX_CAN_8; break;
case 15: retFlags = GFX_CAN_15; break;
case 16: retFlags = GFX_CAN_16; break;
case 24: /* SDL_BYTESPERPIXEL is probably 4, though. */
case 32: retFlags = GFX_CAN_32; break;
}
retFlags |= GFX_SCALING;
SDL_RendererInfo rendererInfo;
SDL_GetRendererInfo(sdl.renderer, &rendererInfo);
LOG_MSG("Using driver \"%s\" for texture renderer", rendererInfo.name);
if (rendererInfo.flags & SDL_RENDERER_ACCELERATED)
retFlags |= GFX_HARDWARE;
break;
}
#if C_OPENGL
case SCREEN_OPENGL:
{
if (sdl.opengl.pixel_buffer_object) {
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, 0);
if (sdl.opengl.buffer) glDeleteBuffersARB(1, &sdl.opengl.buffer);
} else {
free(sdl.opengl.framebuf);
}
sdl.opengl.framebuf=0;
if (!(flags & GFX_CAN_32))
goto dosurface;
int texsize = 2 << int_log2(width > height ? width : height);
if (texsize>sdl.opengl.max_texsize) {
LOG_MSG("SDL:OPENGL: No support for texturesize of %d, falling back to surface",texsize);
goto dosurface;
}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
GFX_SetupWindowScaled(sdl.desktop.want_type);
/* We may simply use SDL_BYTESPERPIXEL
here rather than SDL_BITSPERPIXEL */
if (!sdl.window || SDL_BYTESPERPIXEL(SDL_GetWindowPixelFormat(sdl.window))<2) {
LOG_MSG("SDL:OPENGL:Can't open drawing window, are you running in 16bpp(or higher) mode?");
goto dosurface;
}
sdl.opengl.context = SDL_GL_CreateContext(sdl.window);
if (sdl.opengl.context == NULL) {
LOG_MSG("SDL:OPENGL:Can't create OpenGL context, falling back to surface");
goto dosurface;
}
/* Sync to VBlank if desired */
SDL_GL_SetSwapInterval(sdl.desktop.vsync ? 1 : 0);
if (sdl.opengl.use_shader) {
GLuint prog=0;
// reset error
glGetError();
glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&prog);
// if there was an error this context doesn't support shaders
if (glGetError()==GL_NO_ERROR && (sdl.opengl.program_object==0 || prog!=sdl.opengl.program_object)) {
// check if existing program is valid
if (sdl.opengl.program_object) {
glUseProgram(sdl.opengl.program_object);
if (glGetError() != GL_NO_ERROR) {
// program is not usable (probably new context), purge it
glDeleteProgram(sdl.opengl.program_object);
sdl.opengl.program_object = 0;
}
}
// does program need to be rebuilt?
if (sdl.opengl.program_object == 0) {
GLuint vertexShader, fragmentShader;
const char *src = sdl.opengl.shader_src;
if (src && !LoadGLShaders(src, &vertexShader, &fragmentShader)) {
LOG_MSG("SDL:OPENGL:Failed to compile shader, falling back to default");
src = NULL;
}
if (src == NULL && !LoadGLShaders(shader_src_default, &vertexShader, &fragmentShader)) {
LOG_MSG("SDL:OPENGL:Failed to compile default shader!");
goto dosurface;
}
sdl.opengl.program_object = glCreateProgram();
if (!sdl.opengl.program_object) {
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
LOG_MSG("SDL:OPENGL:Can't create program object, falling back to surface");
goto dosurface;
}
glAttachShader(sdl.opengl.program_object, vertexShader);
glAttachShader(sdl.opengl.program_object, fragmentShader);
// Link the program
glLinkProgram(sdl.opengl.program_object);
// Even if we *are* successful, we may delete the shader objects
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// Check the link status
GLint isProgramLinked;
glGetProgramiv(sdl.opengl.program_object, GL_LINK_STATUS, &isProgramLinked);
if (!isProgramLinked) {
GLint info_len = 0;
glGetProgramiv(sdl.opengl.program_object, GL_INFO_LOG_LENGTH, &info_len);
if (info_len > 1) {
std::vector<GLchar> info_log(info_len);
glGetProgramInfoLog(sdl.opengl.program_object, info_len, NULL, info_log.data());
LOG_MSG("SDL:OPENGL:Error link program:\n %s", info_log.data());
}
glDeleteProgram(sdl.opengl.program_object);
sdl.opengl.program_object = 0;
goto dosurface;
}
glUseProgram(sdl.opengl.program_object);
GLint u = glGetAttribLocation(sdl.opengl.program_object, "a_position");
// upper left
sdl.opengl.vertex_data[0] = -1.0f;
sdl.opengl.vertex_data[1] = 1.0f;
// lower left
sdl.opengl.vertex_data[2] = -1.0f;
sdl.opengl.vertex_data[3] = -3.0f;
// upper right
sdl.opengl.vertex_data[4] = 3.0f;
sdl.opengl.vertex_data[5] = 1.0f;
// Load the vertex positions
glVertexAttribPointer(u, 2, GL_FLOAT, GL_FALSE, 0, sdl.opengl.vertex_data);
glEnableVertexAttribArray(u);
u = glGetUniformLocation(sdl.opengl.program_object, "rubyTexture");
glUniform1i(u, 0);
sdl.opengl.ruby.texture_size = glGetUniformLocation(sdl.opengl.program_object, "rubyTextureSize");
sdl.opengl.ruby.input_size = glGetUniformLocation(sdl.opengl.program_object, "rubyInputSize");
sdl.opengl.ruby.output_size = glGetUniformLocation(sdl.opengl.program_object, "rubyOutputSize");
sdl.opengl.ruby.frame_count = glGetUniformLocation(sdl.opengl.program_object, "rubyFrameCount");
// Don't force updating unless a shader depends on frame_count
RENDER_SetForceUpdate(sdl.opengl.ruby.frame_count != -1);
}
}
}
/* Create the texture and display list */
if (sdl.opengl.pixel_buffer_object) {
glGenBuffersARB(1, &sdl.opengl.buffer);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, sdl.opengl.buffer);
glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_EXT, width*height*4, NULL, GL_STREAM_DRAW_ARB);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, 0);
} else {
sdl.opengl.framebuf = malloc(width * height * 4); // 32 bit color
}
sdl.opengl.pitch=width*4;
int windowWidth, windowHeight;
SDL_GetWindowSize(sdl.window, &windowWidth, &windowHeight);
if (sdl.clip.x == 0 && sdl.clip.y == 0 &&
sdl.desktop.fullscreen &&
!sdl.desktop.full.fixed &&
(sdl.clip.w != windowWidth || sdl.clip.h != windowHeight)) {
// LOG_MSG("attempting to fix the centering to %d %d %d %d",(windowWidth-sdl.clip.w)/2,(windowHeight-sdl.clip.h)/2,sdl.clip.w,sdl.clip.h);
glViewport((windowWidth - sdl.clip.w) / 2,
(windowHeight - sdl.clip.h) / 2,
sdl.clip.w,
sdl.clip.h);
} else {
/* We don't just pass sdl.clip.y as-is, so we cover the case of non-vertical
* centering on Android (in order to leave room for the on-screen keyboard)
*/
glViewport(sdl.clip.x,
windowHeight - (sdl.clip.y + sdl.clip.h),
sdl.clip.w,
sdl.clip.h);
}
if (sdl.opengl.texture > 0) {
glDeleteTextures(1,&sdl.opengl.texture);
}
glGenTextures(1,&sdl.opengl.texture);
glBindTexture(GL_TEXTURE_2D,sdl.opengl.texture);
// No borders
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
if(
sdl.scaling_mode != SmNone || (
sdl.clip.h % height == 0 &&
sdl.clip.w % width == 0 )
) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
Bit8u* emptytex = new Bit8u[texsize * texsize * 4];
memset((void*) emptytex, 0, texsize * texsize * 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texsize, texsize, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, (const GLvoid*)emptytex);
delete [] emptytex;
glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
SDL_GL_SwapWindow(sdl.window);
glClear(GL_COLOR_BUFFER_BIT);
glDisable (GL_DEPTH_TEST);
glDisable (GL_LIGHTING);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
if (sdl.opengl.program_object) {
// Set shader variables
glUniform2f(sdl.opengl.ruby.texture_size, (float)texsize, (float)texsize);
glUniform2f(sdl.opengl.ruby.input_size, (float)width, (float)height);
glUniform2f(sdl.opengl.ruby.output_size, sdl.clip.w, sdl.clip.h);
// The following uniform is *not* set right now
sdl.opengl.actual_frame_count = 0;
} else {
GLfloat tex_width=((GLfloat)(width)/(GLfloat)texsize);
GLfloat tex_height=((GLfloat)(height)/(GLfloat)texsize);
glShadeModel(GL_FLAT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (glIsList(sdl.opengl.displaylist)) glDeleteLists(sdl.opengl.displaylist, 1);
sdl.opengl.displaylist = glGenLists(1);
glNewList(sdl.opengl.displaylist, GL_COMPILE);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower left
glTexCoord2f(0,tex_height*2); glVertex2f(-1.0f,-3.0f);
// upper right
glTexCoord2f(tex_width*2,0); glVertex2f(3.0f, 1.0f);
glEnd();
glEndList();
}
OPENGL_ERROR("End of setsize");
sdl.desktop.type=SCREEN_OPENGL;
retFlags = GFX_CAN_32 | GFX_SCALING;
if (sdl.opengl.pixel_buffer_object)
retFlags |= GFX_HARDWARE;
break;
}//OPENGL
#endif //C_OPENGL
default:
goto dosurface;
break;
}//CASE
if (retFlags)
GFX_Start();
return retFlags;
}
void GFX_SetShader(const char* src) {
#if C_OPENGL
if (!sdl.opengl.use_shader || src == sdl.opengl.shader_src)
return;
sdl.opengl.shader_src = src;
if (sdl.opengl.program_object) {
glDeleteProgram(sdl.opengl.program_object);
sdl.opengl.program_object = 0;
}
#endif
}
void GFX_ToggleMouseCapture(void) {
/*
* Only process mouse events when we have focus.
* This protects against out-of-order event issues such
* as acting on clicks before the window is drawn.
*/
if (!sdl.mouse.has_focus)
return;
assertm(sdl.mouse.control_choice != NoMouse,
"SDL: Mouse capture is invalid when NoMouse is configured [Logic Bug]");
mouse_is_captured = mouse_is_captured ? SDL_FALSE : SDL_TRUE;
if (SDL_SetRelativeMouseMode(mouse_is_captured) != 0) {
SDL_ShowCursor(SDL_ENABLE);
E_Exit("SDL: failed to %s relative-mode [SDL Bug]",
mouse_is_captured ? "put the mouse in" : "take the mouse out of");
}
LOG_MSG("SDL: %s the mouse", mouse_is_captured ? "captured" : "released");
}
static void ToggleMouseCapture(bool pressed) {
if (!pressed || sdl.desktop.fullscreen)
return;
GFX_ToggleMouseCapture();
}
/*
* Assesses the following:
* - current window size (full or not),
* - mouse capture state, (yes or no).
* - desired capture type (start, click, seamless), and
* - if we're starting up for the first time,
* to determine if the mouse-capture state should be toggled.
* Note that this also acts a filter: we don't want to repeatedly
* re-apply the same mouse capture state over and over again, so most
* of the time this function will (or should) decide to do nothing.
*/
void GFX_UpdateMouseState(void) {
// This function is only be run when the window is shown or has focus
sdl.mouse.has_focus = true;
// Used below
static bool has_run_once = false;
/*
* We've switched to or started in fullscreen, so capture the mouse
* This is valid for all modes except for nomouse.
*/
if (sdl.desktop.fullscreen
&& !mouse_is_captured
&& sdl.mouse.control_choice != NoMouse) {
GFX_ToggleMouseCapture();
/*
* If we've switched-back from fullscreen, then release the mouse
* if it's captured and in seamless-mode.
*/
} else if (!sdl.desktop.fullscreen
&& mouse_is_captured
&& sdl.mouse.control_choice == Seamless) {
GFX_ToggleMouseCapture();
SDL_ShowCursor(SDL_DISABLE);
/*
* If none of the above are true /and/ we're starting
* up the first time, then:
* - Capture the mouse if configured onstart is set.
* - Hide the mouse if seamless or nomouse are set.
*/
} else if (!has_run_once) {
if (sdl.mouse.control_choice == CaptureOnStart) {
SDL_RaiseWindow(sdl.window);
GFX_ToggleMouseCapture();
} else if (sdl.mouse.control_choice & (Seamless | NoMouse)) {
SDL_ShowCursor(SDL_DISABLE);
}
}
has_run_once = true;
}
#if defined (WIN32)
STICKYKEYS stick_keys = {sizeof(STICKYKEYS), 0};
void sticky_keys(bool restore){
static bool inited = false;
if (!inited){
inited = true;
SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS), &stick_keys, 0);
}
if (restore) {
SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &stick_keys, 0);
return;
}
//Get current sticky keys layout:
STICKYKEYS s = {sizeof(STICKYKEYS), 0};
SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS), &s, 0);
if ( !(s.dwFlags & SKF_STICKYKEYSON)) { //Not on already
s.dwFlags &= ~SKF_HOTKEYACTIVE;
SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &s, 0);
}
}
#endif
void GFX_SwitchFullScreen(void) {
#if defined (WIN32)
// We are about to switch to the opposite of our current mode
// (ie: opposite of whatever sdl.desktop.fullscreen holds).
// Sticky-keys should be set to the opposite of fullscreen,
// so we simply apply the bool of the mode we're switching out-of.
sticky_keys(sdl.desktop.fullscreen);
#endif
sdl.desktop.fullscreen = !sdl.desktop.fullscreen;
GFX_ResetScreen();
}
static void SwitchFullScreen(bool pressed)
{
if (pressed)
GFX_SwitchFullScreen();
}
// This function returns write'able buffer for user to draw upon. Successful
// return depends on properly initialized SDL_Block structure (which generally
// can be achieved via GFX_SetSize call), and specifically - properly initialized
// output-specific bits (sdl.surface, sdl.texture, sdl.opengl.framebuf, or
// sdl.openg.pixel_buffer_object fields).
//
// If everything is prepared correctly, this function returns true, assigns
// 'pixels' output parameter to to a buffer (with format specified via earlier
// GFX_SetSize call), and assigns 'pitch' to a number of bytes used for a single
// pixels row in 'pixels' buffer.
//
bool GFX_StartUpdate(uint8_t * &pixels, int &pitch)
{
if (!sdl.update_display_contents)
return false;
if (!sdl.active || sdl.updating)
return false;
switch (sdl.desktop.type) {
case SCREEN_TEXTURE:
assert(sdl.texture.input_surface);
pixels = static_cast<uint8_t *>(sdl.texture.input_surface->pixels);
pitch = sdl.texture.input_surface->pitch;
sdl.updating = true;
return true;
#if C_OPENGL
case SCREEN_OPENGL:
if (sdl.opengl.pixel_buffer_object) {
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, sdl.opengl.buffer);
pixels = static_cast<uint8_t *>(glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, GL_WRITE_ONLY));
} else {
pixels = static_cast<uint8_t *>(sdl.opengl.framebuf);
}
OPENGL_ERROR("end of start update");
if (pixels == nullptr)
return false;
static_assert(std::is_same<decltype(pitch), decltype((sdl.opengl.pitch))>::value,
"Our internal pitch types should be the same.");
pitch = sdl.opengl.pitch;
sdl.updating = true;
return true;
#endif
case SCREEN_SURFACE:
assert(sdl.surface);
pixels = static_cast<uint8_t *>(sdl.surface->pixels);
pixels += sdl.clip.y * sdl.surface->pitch;
pixels += sdl.clip.x * sdl.surface->format->BytesPerPixel;
static_assert(std::is_same<decltype(pitch), decltype((sdl.surface->pitch))>::value,
"SDL internal surface pitch type should match our type.");
pitch = sdl.surface->pitch;
sdl.updating = true;
return true;
}
return false;
}
void GFX_EndUpdate( const Bit16u *changedLines ) {
if (!sdl.update_display_contents)
return;
if (((sdl.desktop.type != SCREEN_OPENGL) || !RENDER_GetForceUpdate()) && !sdl.updating)
return;
bool actually_updating = sdl.updating;
sdl.updating=false;
switch (sdl.desktop.type) {
case SCREEN_TEXTURE:
assert(sdl.texture.input_surface);
SDL_UpdateTexture(sdl.texture.texture,
nullptr, // update entire texture
sdl.texture.input_surface->pixels,
sdl.texture.input_surface->pitch);
SDL_RenderClear(sdl.renderer);
SDL_RenderCopy(sdl.renderer, sdl.texture.texture, NULL, &sdl.clip);
SDL_RenderPresent(sdl.renderer);
break;
#if C_OPENGL
case SCREEN_OPENGL:
// Clear drawing area. Some drivers (on Linux) have more than 2 buffers and the screen might
// be dirty because of other programs.
if (!actually_updating) {
/* Don't really update; Just increase the frame counter.
* If we tried to update it may have not worked so well
* with VSync...
* (Think of 60Hz on the host with 70Hz on the client.)
*/
sdl.opengl.actual_frame_count++;
return;
}
glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (sdl.opengl.pixel_buffer_object) {
glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT);
glBindTexture(GL_TEXTURE_2D, sdl.opengl.texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
sdl.draw.width, sdl.draw.height, GL_BGRA_EXT,
GL_UNSIGNED_INT_8_8_8_8_REV, 0);
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_EXT, 0);
} else if (changedLines) {
Bitu y = 0, index = 0;
glBindTexture(GL_TEXTURE_2D, sdl.opengl.texture);
while (y < sdl.draw.height) {
if (!(index & 1)) {
y += changedLines[index];
} else {
Bit8u *pixels = (Bit8u *)sdl.opengl.framebuf + y * sdl.opengl.pitch;
Bitu height = changedLines[index];
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, y,
sdl.draw.width, height, GL_BGRA_EXT,
GL_UNSIGNED_INT_8_8_8_8_REV, pixels );
y += height;
}
index++;
}
} else {
return;
}
if (sdl.opengl.program_object) {
glUniform1i(sdl.opengl.ruby.frame_count, sdl.opengl.actual_frame_count++);
glDrawArrays(GL_TRIANGLES, 0, 3);
} else {
glCallList(sdl.opengl.displaylist);
}
SDL_GL_SwapWindow(sdl.window);
break;
#endif
case SCREEN_SURFACE:
if (changedLines) {
Bitu y = 0, index = 0, rectCount = 0;
while (y < sdl.draw.height) {
if (!(index & 1)) {
y += changedLines[index];
} else {
SDL_Rect *rect = &sdl.updateRects[rectCount++];
rect->x = sdl.clip.x;
rect->y = sdl.clip.y + y;
rect->w = (Bit16u)sdl.draw.width;
rect->h = changedLines[index];
y += changedLines[index];
}
index++;
}
if (rectCount)
SDL_UpdateWindowSurfaceRects(sdl.window, sdl.updateRects, rectCount);
}
break;
}
}
Bitu GFX_GetRGB(Bit8u red,Bit8u green,Bit8u blue) {
switch (sdl.desktop.type) {
case SCREEN_SURFACE:
return SDL_MapRGB(sdl.surface->format,red,green,blue);
case SCREEN_TEXTURE:
return SDL_MapRGB(sdl.texture.pixelFormat, red, green, blue);
case SCREEN_OPENGL:
return ((blue << 0) | (green << 8) | (red << 16)) | (255 << 24);
}
return 0;
}
void GFX_Stop() {
if (sdl.updating)
GFX_EndUpdate( 0 );
sdl.active=false;
}
void GFX_Start() {
sdl.active=true;
}
void GFX_ObtainDisplayDimensions() {
SDL_Rect displayDimensions;
SDL_GetDisplayBounds(sdl.displayNumber, &displayDimensions);
sdl.desktop.full.width = displayDimensions.w;
sdl.desktop.full.height = displayDimensions.h;
}
/* Manually update display dimensions in case of a window resize,
* IF there is the need for that ("yes" on Android, "no" otherwise).
* Used for the mapper UI on Android.
* Reason is the usage of GFX_GetSDLSurfaceSubwindowDims, as well as a
* mere notification of the fact that the window's dimensions are modified.
*/
void GFX_UpdateDisplayDimensions(int width, int height)
{
if (sdl.desktop.full.display_res && sdl.desktop.fullscreen) {
/* Note: We should not use GFX_ObtainDisplayDimensions
(SDL_GetDisplayBounds) on Android after a screen rotation:
The older values from application startup are returned. */
sdl.desktop.full.width = width;
sdl.desktop.full.height = height;
}
}
static void CleanupSDLResources()
{
if (sdl.texture.pixelFormat) {
SDL_FreeFormat(sdl.texture.pixelFormat);
sdl.texture.pixelFormat = nullptr;
}
if (sdl.texture.texture) {
SDL_DestroyTexture(sdl.texture.texture);
sdl.texture.texture = nullptr;
}
if (sdl.texture.input_surface) {
SDL_FreeSurface(sdl.texture.input_surface);
sdl.texture.input_surface = nullptr;
}
if (sdl.renderer) {
SDL_DestroyRenderer(sdl.renderer);
sdl.renderer = nullptr;
}
#if C_OPENGL
if (sdl.opengl.context) {
SDL_GL_DeleteContext(sdl.opengl.context);
sdl.opengl.context = 0;
}
#endif
}
static void GUI_ShutDown(Section *)
{
GFX_Stop();
if (sdl.draw.callback)
(sdl.draw.callback)( GFX_CallBackStop );
if (sdl.desktop.fullscreen)
GFX_SwitchFullScreen();
if (mouse_is_captured)
GFX_ToggleMouseCapture();
CleanupSDLResources();
}
static void SetPriority(PRIORITY_LEVELS level) {
#if C_SET_PRIORITY
// Do nothing if priorties are not the same and not root, else the highest
// priority can not be set as users can only lower priority (not restore it)
if((sdl.priority.focus != sdl.priority.nofocus ) &&
(getuid()!=0) ) return;
#endif
switch (level) {
#ifdef WIN32
case PRIORITY_LEVEL_PAUSE: // if DOSBox is paused, assume idle priority
case PRIORITY_LEVEL_LOWEST:
SetPriorityClass(GetCurrentProcess(),IDLE_PRIORITY_CLASS);
break;
case PRIORITY_LEVEL_LOWER:
SetPriorityClass(GetCurrentProcess(),BELOW_NORMAL_PRIORITY_CLASS);
break;
case PRIORITY_LEVEL_NORMAL:
SetPriorityClass(GetCurrentProcess(),NORMAL_PRIORITY_CLASS);
break;
case PRIORITY_LEVEL_HIGHER:
SetPriorityClass(GetCurrentProcess(),ABOVE_NORMAL_PRIORITY_CLASS);
break;
case PRIORITY_LEVEL_HIGHEST:
SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS);
break;
#elif C_SET_PRIORITY
/* Linux use group as dosbox has mulitple threads under linux */
case PRIORITY_LEVEL_PAUSE: // if DOSBox is paused, assume idle priority
case PRIORITY_LEVEL_LOWEST:
setpriority (PRIO_PGRP, 0,PRIO_MAX);
break;
case PRIORITY_LEVEL_LOWER:
setpriority (PRIO_PGRP, 0,PRIO_MAX-(PRIO_TOTAL/3));
break;
case PRIORITY_LEVEL_NORMAL:
setpriority (PRIO_PGRP, 0,PRIO_MAX-(PRIO_TOTAL/2));
break;
case PRIORITY_LEVEL_HIGHER:
setpriority (PRIO_PGRP, 0,PRIO_MAX-((3*PRIO_TOTAL)/5) );
break;
case PRIORITY_LEVEL_HIGHEST:
setpriority (PRIO_PGRP, 0,PRIO_MAX-((3*PRIO_TOTAL)/4) );
break;
#endif
default:
break;
}
}
#ifdef WIN32
extern Bit8u int10_font_14[256 * 14];
static void OutputString(Bitu x,Bitu y,const char * text,Bit32u color,Bit32u color2,SDL_Surface * output_surface) {
Bit32u * draw=(Bit32u*)(((Bit8u *)output_surface->pixels)+((y)*output_surface->pitch))+x;
while (*text) {
Bit8u * font=&int10_font_14[(*text)*14];
Bitu i,j;
Bit32u * draw_line=draw;
for (i=0;i<14;i++) {
Bit8u map=*font++;
for (j=0;j<8;j++) {
*(draw_line + j) = map & 0x80 ? color : color2;
map<<=1;
}
draw_line+=output_surface->pitch/4;
}
text++;
draw+=8;
}
}
#endif
#include "dosbox_staging_splash.c"
static SDL_Window * SetDefaultWindowMode()
{
if (sdl.window)
return sdl.window;
sdl.draw.width = splash_image.width;
sdl.draw.height = splash_image.height;
if (sdl.desktop.fullscreen) {
return GFX_SetSDLWindowMode(sdl.desktop.full.width,
sdl.desktop.full.height,
sdl.desktop.fullscreen,
sdl.desktop.want_type);
}
if (sdl.desktop.window.use_original_size) {
return GFX_SetSDLWindowMode(sdl.draw.width,
sdl.draw.height,
false,
sdl.desktop.want_type);
}
return GFX_SetSDLWindowMode(sdl.desktop.window.width,
sdl.desktop.window.height,
false,
sdl.desktop.want_type);
}
/*
* Please leave the Splash screen stuff in working order.
* We spend a lot of time making DOSBox.
*/
static void DisplaySplash(uint32_t time_ms)
{
assert(sdl.window);
constexpr int src_w = splash_image.width;
constexpr int src_h = splash_image.height;
constexpr int src_bpp = splash_image.bytes_per_pixel;
static_assert(src_bpp == 3, "Source image expected in RGB format.");
const auto flags = GFX_SetSize(src_w, src_h, GFX_CAN_32, 1.0, 1.0, nullptr, 1.0);
if (!(flags & GFX_CAN_32)) {
LOG_MSG("Can't show 32bpp splash.");
return;
}
uint8_t *out = nullptr;
int pitch = 0;
if (!GFX_StartUpdate(out, pitch))
E_Exit("%s", SDL_GetError());
uint32_t *pixels = reinterpret_cast<uint32_t *>(out);
assertm(pixels, "GFX_StartUpdate is supposed to give us buffer.");
const int buf_width = pitch / 4;
assertm(buf_width >= src_w, "Row length needs to be big enough.");
std::array<uint8_t, (src_w * src_h * src_bpp)> splash;
GIMP_IMAGE_RUN_LENGTH_DECODE(splash.data(), splash_image.rle_pixel_data,
src_w * src_h, src_bpp);
size_t i = 0;
size_t j = 0;
static_assert(splash.size() % 3 == 0, "Reading 3 bytes at a time.");
for (int y = 0; y < src_h; y++) {
// copy a row of pixels to output buffer
for (int x = 0; x < src_w; x++) {
const uint32_t r = splash[i++];
const uint32_t g = splash[i++];
const uint32_t b = splash[i++];
pixels[j++] = (r << 16) | (g << 8) | b;
}
// pad with black until the end of row
// only output=surface actually needs this
for (int x = src_w; x < buf_width; x++)
pixels[j++] = 0;
}
const uint16_t lines[2] = {0, src_h}; // output=surface won't work otherwise
GFX_EndUpdate(lines);
SDL_Delay(time_ms);
}
//extern void UI_Run(bool);
void Restart(bool pressed);
static void GUI_StartUp(Section * sec) {
sec->AddDestroyFunction(&GUI_ShutDown);
Section_prop * section=static_cast<Section_prop *>(sec);
sdl.active=false;
sdl.updating=false;
sdl.resizing_window = false;
sdl.update_display_contents = true;
sdl.desktop.fullscreen=section->Get_bool("fullscreen");
sdl.wait_on_error=section->Get_bool("waitonerror");
Prop_multival* p=section->Get_multival("priority");
std::string focus = p->GetSection()->Get_string("active");
std::string notfocus = p->GetSection()->Get_string("inactive");
if (focus == "lowest") { sdl.priority.focus = PRIORITY_LEVEL_LOWEST; }
else if (focus == "lower") { sdl.priority.focus = PRIORITY_LEVEL_LOWER; }
else if (focus == "normal") { sdl.priority.focus = PRIORITY_LEVEL_NORMAL; }
else if (focus == "higher") { sdl.priority.focus = PRIORITY_LEVEL_HIGHER; }
else if (focus == "highest") { sdl.priority.focus = PRIORITY_LEVEL_HIGHEST; }
if (notfocus == "lowest") { sdl.priority.nofocus=PRIORITY_LEVEL_LOWEST; }
else if (notfocus == "lower") { sdl.priority.nofocus=PRIORITY_LEVEL_LOWER; }
else if (notfocus == "normal") { sdl.priority.nofocus=PRIORITY_LEVEL_NORMAL; }
else if (notfocus == "higher") { sdl.priority.nofocus=PRIORITY_LEVEL_HIGHER; }
else if (notfocus == "highest") { sdl.priority.nofocus=PRIORITY_LEVEL_HIGHEST; }
else if (notfocus == "pause") {
/* we only check for pause here, because it makes no sense
* for DOSBox to be paused while it has focus
*/
sdl.priority.nofocus=PRIORITY_LEVEL_PAUSE;
}
SetPriority(sdl.priority.focus); //Assume focus on startup
sdl.desktop.full.fixed=false;
const char* fullresolution=section->Get_string("fullresolution");
sdl.desktop.full.width = 0;
sdl.desktop.full.height = 0;
if(fullresolution && *fullresolution) {
char res[100];
safe_strncpy( res, fullresolution, sizeof( res ));
fullresolution = lowcase (res);//so x and X are allowed
if (strcmp(fullresolution,"original")) {
sdl.desktop.full.fixed = true;
if (strcmp(fullresolution,"desktop")) { //desktop = 0x0
char* height = const_cast<char*>(strchr(fullresolution,'x'));
if (height && * height) {
*height = 0;
sdl.desktop.full.height = (Bit16u)atoi(height+1);
sdl.desktop.full.width = (Bit16u)atoi(res);
}
}
}
}
Bit16u windowspercentage = 0;
const char* windowresolution=section->Get_string("windowresolution");
if(windowresolution && *windowresolution) {
char res[100];
safe_strncpy( res,windowresolution, sizeof( res ));
windowresolution = lowcase (res);//so x and X are allowed
if(strcmp(windowresolution,"original")) {
sdl.desktop.window.use_original_size = false;
char* height = const_cast<char*>(strchr(windowresolution,'x'));
if(height && *height) {
*height = 0;
sdl.desktop.window.height = (Bit16u)atoi(height+1);
sdl.desktop.window.width = (Bit16u)atoi(res);
} else {
char* percentage = const_cast<char*>(strchr(windowresolution,'%'));
if(percentage && *percentage) {
*percentage = 0;
windowspercentage = (Bit16u) atoi(res);
if (windowspercentage) putenv(const_cast<char*>("SDL_VIDEO_CENTERED=1"));
}
}
}
}
// TODO vsync option is disabled for the time being, as it does not work
// correctly and is causing serious bugs.
// sdl.desktop.vsync = section->Get_bool("vsync");
sdl.displayNumber = section->Get_int("display");
if ((sdl.displayNumber < 0) || (sdl.displayNumber >= SDL_GetNumVideoDisplays())) {
sdl.displayNumber = 0;
LOG_MSG("SDL:Display number out of bounds, switching back to 0");
}
sdl.desktop.full.display_res = sdl.desktop.full.fixed && (!sdl.desktop.full.width || !sdl.desktop.full.height);
if (sdl.desktop.full.display_res) {
GFX_ObtainDisplayDimensions();
}
std::string output=section->Get_string("output");
if (output == "surface") {
sdl.desktop.want_type=SCREEN_SURFACE;
} else if (output == "texture") {
sdl.desktop.want_type=SCREEN_TEXTURE;
sdl.scaling_mode = SmNone;
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
} else if (output == "texturenb") {
sdl.desktop.want_type=SCREEN_TEXTURE;
sdl.scaling_mode = SmNearest;
// Currently the default, but... oh well
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
} else if (output == "texturepp") {
sdl.desktop.want_type=SCREEN_TEXTURE;
sdl.scaling_mode = SmPerfect;
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
#if C_OPENGL
} else if (output == "opengl") {
sdl.desktop.want_type=SCREEN_OPENGL;
sdl.scaling_mode = SmNone;
sdl.opengl.bilinear = true;
} else if (output == "openglnb") {
sdl.desktop.want_type=SCREEN_OPENGL;
sdl.scaling_mode = SmNearest;
sdl.opengl.bilinear = false;
#endif
} else {
LOG_MSG("SDL: Unsupported output device %s, switching back to surface",output.c_str());
sdl.desktop.want_type=SCREEN_SURFACE;//SHOULDN'T BE POSSIBLE anymore
}
sdl.texture.texture = 0;
sdl.texture.pixelFormat = 0;
sdl.render_driver = section->Get_string("texture_renderer");
lowcase(sdl.render_driver);
#if C_OPENGL
if (sdl.desktop.want_type == SCREEN_OPENGL) { /* OPENGL is requested */
if (!SetDefaultWindowMode()) {
LOG_MSG("Could not create OpenGL window, switching back to surface");
sdl.desktop.want_type = SCREEN_SURFACE;
} else {
sdl.opengl.context = SDL_GL_CreateContext(sdl.window);
if (sdl.opengl.context == 0) {
LOG_MSG("Could not create OpenGL context, switching back to surface");
sdl.desktop.want_type = SCREEN_SURFACE;
}
}
if (sdl.desktop.want_type == SCREEN_OPENGL) {
sdl.opengl.program_object = 0;
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray");
glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)SDL_GL_GetProcAddress("glGetAttribLocation");
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
glShaderSource = (PFNGLSHADERSOURCEPROC_NP)SDL_GL_GetProcAddress("glShaderSource");
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
glUniform1i = (PFNGLUNIFORM1IPROC)SDL_GL_GetProcAddress("glUniform1i");
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer");
sdl.opengl.use_shader = (glAttachShader && glCompileShader && glCreateProgram && glDeleteProgram && glDeleteShader && \
glEnableVertexAttribArray && glGetAttribLocation && glGetProgramiv && glGetProgramInfoLog && \
glGetShaderiv && glGetShaderInfoLog && glGetUniformLocation && glLinkProgram && glShaderSource && \
glUniform2f && glUniform1i && glUseProgram && glVertexAttribPointer);
sdl.opengl.buffer=0;
sdl.opengl.framebuf=0;
sdl.opengl.texture=0;
sdl.opengl.displaylist=0;
glGetIntegerv (GL_MAX_TEXTURE_SIZE, &sdl.opengl.max_texsize);
glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)SDL_GL_GetProcAddress("glGenBuffersARB");
glBindBufferARB = (PFNGLBINDBUFFERARBPROC)SDL_GL_GetProcAddress("glBindBufferARB");
glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)SDL_GL_GetProcAddress("glDeleteBuffersARB");
glBufferDataARB = (PFNGLBUFFERDATAARBPROC)SDL_GL_GetProcAddress("glBufferDataARB");
glMapBufferARB = (PFNGLMAPBUFFERARBPROC)SDL_GL_GetProcAddress("glMapBufferARB");
glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)SDL_GL_GetProcAddress("glUnmapBufferARB");
const char * gl_ext = (const char *)glGetString (GL_EXTENSIONS);
if(gl_ext && *gl_ext){
sdl.opengl.packed_pixel=(strstr(gl_ext,"EXT_packed_pixels") != NULL);
sdl.opengl.paletted_texture=(strstr(gl_ext,"EXT_paletted_texture") != NULL);
sdl.opengl.pixel_buffer_object=(strstr(gl_ext,"GL_ARB_pixel_buffer_object") != NULL ) &&
glGenBuffersARB && glBindBufferARB && glDeleteBuffersARB && glBufferDataARB &&
glMapBufferARB && glUnmapBufferARB;
} else {
sdl.opengl.packed_pixel = false;
sdl.opengl.paletted_texture = false;
sdl.opengl.pixel_buffer_object = false;
}
#ifdef DB_DISABLE_DBO
sdl.opengl.pixel_buffer_object = false;
#endif
LOG_MSG("OpenGL extension: pixel_bufer_object %d",sdl.opengl.pixel_buffer_object);
}
} /* OPENGL is requested end */
#endif //OPENGL
if (!SetDefaultWindowMode())
E_Exit("Could not initialize video: %s", SDL_GetError());
// FIXME the code updated sdl.desktop.bpp in here (has effect in setting up scalers)
SDL_SetWindowTitle(sdl.window, "DOSBox");
SetIcon();
const bool tiny_fullresolution = splash_image.width > sdl.desktop.full.width ||
splash_image.height > sdl.desktop.full.height;
if (!(sdl.desktop.fullscreen && tiny_fullresolution)) {
GFX_Start();
DisplaySplash(1000);
GFX_Stop();
}
// Apply the user's mouse settings
Section_prop* s = section->Get_multival("capture_mouse")->GetSection();
const std::string control_choice = s->Get_string("mouse_control");
std::string mouse_control_msg;
if (control_choice == "onclick") {
sdl.mouse.control_choice = CaptureOnClick;
mouse_control_msg = "will be captured after clicking";
} else if (control_choice == "onstart") {
sdl.mouse.control_choice = CaptureOnStart;
mouse_control_msg = "will be captured immediately on start";
} else if (control_choice == "seamless") {
sdl.mouse.control_choice = Seamless;
mouse_control_msg = "will move seamlessly without being captured";
} else if (control_choice == "nomouse") {
sdl.mouse.control_choice = NoMouse;
mouse_control_msg = "will not be active";
} else {
assert(sdl.mouse.control_choice == CaptureOnClick);
}
std:: string middle_control_msg;
if (std::string(s->Get_string("middle_control")) == "middlerelease") {
sdl.mouse.middle_will_release = true;
if (sdl.mouse.control_choice & (CaptureOnClick | CaptureOnStart))
middle_control_msg = " and middle-click will uncapture the mouse";
} else {
if (sdl.mouse.control_choice & (CaptureOnClick | CaptureOnStart))
middle_control_msg = " and middle-clicks will be sent to the game";
}
LOG_MSG("SDL: Mouse %s%s.", mouse_control_msg.c_str(), middle_control_msg.c_str());
// Only setup the Ctrl+F10 handler if the mouse is capturable
if (sdl.mouse.control_choice & (CaptureOnStart | CaptureOnClick)) {
MAPPER_AddHandler(ToggleMouseCapture,MK_f10,MMOD1,"capmouse","Cap Mouse");
}
// Apply the user's mouse sensitivity settings
Prop_multival* p3 = section->Get_multival("sensitivity");
sdl.mouse.xsensitivity = p3->GetSection()->Get_int("xsens");
sdl.mouse.ysensitivity = p3->GetSection()->Get_int("ysens");
/* Get some Event handlers */
MAPPER_AddHandler(KillSwitch,MK_f9,MMOD1,"shutdown","ShutDown");
MAPPER_AddHandler(SwitchFullScreen,MK_return,MMOD2,"fullscr","Fullscreen");
MAPPER_AddHandler(Restart,MK_home,MMOD1|MMOD2,"restart","Restart");
#if C_DEBUG
/* Pause binds with activate-debugger */
#else
MAPPER_AddHandler(&PauseDOSBox, MK_pause, MMOD2, "pause", "Pause DBox");
#endif
/* Get Keyboard state of numlock and capslock */
SDL_Keymod keystate = SDL_GetModState();
if(keystate&KMOD_NUM) startup_state_numlock = true;
if(keystate&KMOD_CAPS) startup_state_capslock = true;
}
static void HandleMouseMotion(SDL_MouseMotionEvent * motion) {
if (mouse_is_captured || sdl.mouse.control_choice == Seamless)
Mouse_CursorMoved((float)motion->xrel*sdl.mouse.xsensitivity/100.0f,
(float)motion->yrel*sdl.mouse.ysensitivity/100.0f,
(float)(motion->x-sdl.clip.x)/(sdl.clip.w-1)*sdl.mouse.xsensitivity/100.0f,
(float)(motion->y-sdl.clip.y)/(sdl.clip.h-1)*sdl.mouse.ysensitivity/100.0f,
mouse_is_captured);
}
static void HandleMouseButton(SDL_MouseButtonEvent * button) {
switch (button->state) {
case SDL_PRESSED:
if (!sdl.desktop.fullscreen
&& sdl.mouse.control_choice & (CaptureOnStart | CaptureOnClick)
&& ((sdl.mouse.middle_will_release && button->button == SDL_BUTTON_MIDDLE)
|| !mouse_is_captured)) {
GFX_ToggleMouseCapture();
break; // Don't pass click to mouse handler
}
switch (button->button) {
case SDL_BUTTON_LEFT:
Mouse_ButtonPressed(0);
break;
case SDL_BUTTON_RIGHT:
Mouse_ButtonPressed(1);
break;
case SDL_BUTTON_MIDDLE:
Mouse_ButtonPressed(2);
break;
}
break;
case SDL_RELEASED:
switch (button->button) {
case SDL_BUTTON_LEFT:
Mouse_ButtonReleased(0);
break;
case SDL_BUTTON_RIGHT:
Mouse_ButtonReleased(1);
break;
case SDL_BUTTON_MIDDLE:
Mouse_ButtonReleased(2);
break;
}
break;
}
}
void GFX_LosingFocus(void) {
sdl.laltstate=SDL_KEYUP;
sdl.raltstate=SDL_KEYUP;
MAPPER_LosingFocus();
}
bool GFX_IsFullscreen(void) {
return sdl.desktop.fullscreen;
}
#if defined(MACOSX)
#define DB_POLLSKIP 3
#else
//Not used yet, see comment below
#define DB_POLLSKIP 1
#endif
void GFX_HandleVideoResize(int width, int height)
{
/* Maybe a screen rotation has just occurred, so we simply resize.
There may be a different cause for a forced resized, though. */
if (sdl.desktop.full.display_res && sdl.desktop.fullscreen) {
/* Note: We should not use GFX_ObtainDisplayDimensions
(SDL_GetDisplayBounds) on Android after a screen rotation:
The older values from application startup are returned. */
sdl.desktop.full.width = width;
sdl.desktop.full.height = height;
}
/* Even if the new window's dimensions are actually the desired ones
* we may still need to re-obtain a new window surface or do
* a different thing. So we basically reset the screen, but without
* touching the window itself (or else we may end in an infinite loop).
*
* Furthermore, if the new dimensions are *not* the desired ones, we
* don't fight it. Rather than attempting to resize it back, we simply
* keep the window as-is and disable screen updates. This is done
* in SDL_SetSDLWindowSurface by setting sdl.update_display_contents
* to false.
*/
sdl.resizing_window = true;
GFX_ResetScreen();
sdl.resizing_window = false;
}
void GFX_Events() {
//Don't poll too often. This can be heavy on the OS, especially Macs.
//In idle mode 3000-4000 polls are done per second without this check.
//Macs, with this code, max 250 polls per second. (non-macs unused default max 500)
//Currently not implemented for all platforms, given the ALT-TAB stuff for WIN32.
#if defined (MACOSX)
static int last_check = 0;
int current_check = GetTicks();
if (current_check - last_check <= DB_POLLSKIP) return;
last_check = current_check;
#endif
SDL_Event event;
#if defined (REDUCE_JOYSTICK_POLLING)
static int poll_delay = 0;
int time = GetTicks();
if (time - poll_delay > 20) {
poll_delay = time;
if (sdl.num_joysticks > 0) SDL_JoystickUpdate();
MAPPER_UpdateJoysticks();
}
#endif
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_RESTORED:
/* We may need to re-create a texture
* and more on Android. Another case:
* Update surface while using X11.
*/
GFX_ResetScreen();
continue;
case SDL_WINDOWEVENT_RESIZED:
GFX_HandleVideoResize(event.window.data1, event.window.data2);
continue;
/*
* EXPOSED indicates that the window needs to be redrawn.
* Note that on Windows/Linux-X11/Wayland/macOS, the EXPOSED event
* is fired after toggling between full vs windowed modes. However this is
* never fired on the Raspberry Pi (when rendering to the Framebuffer);
* therefore we rely on the FOCUS_GAINED event to catch window startup
* and size toggles.
*/
case SDL_WINDOWEVENT_EXPOSED:
if (sdl.draw.callback)
sdl.draw.callback(GFX_CallBackRedraw);
GFX_UpdateMouseState();
continue;
case SDL_WINDOWEVENT_FOCUS_GAINED:
SetPriority(sdl.priority.focus);
CPU_Disable_SkipAutoAdjust();
GFX_UpdateMouseState();
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
#ifdef WIN32
if (sdl.desktop.fullscreen) {
VGA_KillDrawing();
GFX_ForceFullscreenExit();
}
#endif
SetPriority(sdl.priority.nofocus);
GFX_LosingFocus();
CPU_Enable_SkipAutoAdjust();
sdl.mouse.has_focus = false;
break;
default: ;
}
/* Non-focus priority is set to pause; check to see if we've lost window or input focus
* i.e. has the window been minimised or made inactive?
*/
if (sdl.priority.nofocus == PRIORITY_LEVEL_PAUSE) {
if ((event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) || (event.window.event == SDL_WINDOWEVENT_MINIMIZED)) {
/* Window has lost focus, pause the emulator.
* This is similar to what PauseDOSBox() does, but the exit criteria is different.
* Instead of waiting for the user to hit Alt-Break, we wait for the window to
* regain window or input focus.
*/
bool paused = true;
SDL_Event ev;
GFX_SetTitle(-1,-1,true);
KEYBOARD_ClrBuffer();
// SDL_Delay(500);
// while (SDL_PollEvent(&ev)) {
// flush event queue.
// }
while (paused) {
// WaitEvent waits for an event rather than polling, so CPU usage drops to zero
SDL_WaitEvent(&ev);
switch (ev.type) {
case SDL_QUIT: throw(0); break; // a bit redundant at linux at least as the active events gets before the quit event.
case SDL_WINDOWEVENT: // wait until we get window focus back
if ((ev.window.event == SDL_WINDOWEVENT_FOCUS_LOST) || (ev.window.event == SDL_WINDOWEVENT_MINIMIZED) || (ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) || (ev.window.event == SDL_WINDOWEVENT_RESTORED) || (ev.window.event == SDL_WINDOWEVENT_EXPOSED)) {
// We've got focus back, so unpause and break out of the loop
if ((ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) || (ev.window.event == SDL_WINDOWEVENT_RESTORED) || (ev.window.event == SDL_WINDOWEVENT_EXPOSED)) {
paused = false;
GFX_SetTitle(-1,-1,false);
SetPriority(sdl.priority.focus);
CPU_Disable_SkipAutoAdjust();
}
/* Now poke a "release ALT" command into the keyboard buffer
* we have to do this, otherwise ALT will 'stick' and cause
* problems with the app running in the DOSBox.
*/
KEYBOARD_AddKey(KBD_leftalt, false);
KEYBOARD_AddKey(KBD_rightalt, false);
if (ev.window.event == SDL_WINDOWEVENT_RESTORED) {
// We may need to re-create a texture and more
GFX_ResetScreen();
}
}
break;
}
}
}
}
break;
case SDL_MOUSEMOTION:
HandleMouseMotion(&event.motion);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (sdl.mouse.control_choice != NoMouse)
HandleMouseButton(&event.button);
break;
case SDL_QUIT:
throw(0);
break;
#ifdef WIN32
case SDL_KEYDOWN:
case SDL_KEYUP:
// ignore event alt+tab
if (event.key.keysym.sym == SDLK_LALT)
sdl.laltstate = (SDL_EventType)event.key.type;
if (event.key.keysym.sym == SDLK_RALT)
sdl.raltstate = (SDL_EventType)event.key.type;
if (((event.key.keysym.sym==SDLK_TAB)) && ((sdl.laltstate==SDL_KEYDOWN) || (sdl.raltstate==SDL_KEYDOWN)))
break;
// This can happen as well.
if (((event.key.keysym.sym == SDLK_TAB )) && (event.key.keysym.mod & KMOD_ALT)) break;
// ignore tab events that arrive just after regaining focus. (likely the result of alt-tab)
if ((event.key.keysym.sym == SDLK_TAB) && (GetTicks() - sdl.focus_ticks < 2)) break;
#endif
#if defined (MACOSX)
case SDL_KEYDOWN:
case SDL_KEYUP:
/* On macs CMD-Q is the default key to close an application */
if (event.key.keysym.sym == SDLK_q &&
(event.key.keysym.mod == KMOD_RGUI || event.key.keysym.mod == KMOD_LGUI)) {
KillSwitch(true);
break;
}
#endif
default:
void MAPPER_CheckEvent(SDL_Event * event);
MAPPER_CheckEvent(&event);
}
}
}
#if defined (WIN32)
static BOOL WINAPI ConsoleEventHandler(DWORD event) {
switch (event) {
case CTRL_SHUTDOWN_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_BREAK_EVENT:
raise(SIGTERM);
return TRUE;
case CTRL_C_EVENT:
default: //pass to the next handler
return FALSE;
}
}
#endif
/* static variable to show wether there is not a valid stdout.
* Fixes some bugs when -noconsole is used in a read only directory */
static bool no_stdout = false;
void GFX_ShowMsg(char const* format,...) {
char buf[512];
va_list msg;
va_start(msg,format);
vsnprintf(buf,sizeof(buf),format,msg);
va_end(msg);
buf[sizeof(buf) - 1] = '\0';
if (!no_stdout) puts(buf); //Else buf is parsed again. (puts adds end of line)
}
static std::vector<std::string> Get_SDL_TextureRenderers()
{
const int n = SDL_GetNumRenderDrivers();
std::vector<std::string> drivers;
drivers.reserve(n + 1);
drivers.push_back("auto");
SDL_RendererInfo info;
for (int i = 0; i < n; i++) {
if (SDL_GetRenderDriverInfo(i, &info))
continue;
if (info.flags & SDL_RENDERER_TARGETTEXTURE)
drivers.push_back(info.name);
}
return drivers;
}
void Config_Add_SDL() {
Section_prop * sdl_sec=control->AddSection_prop("sdl",&GUI_StartUp);
sdl_sec->AddInitFunction(&MAPPER_StartUp);
Prop_bool* Pbool;
Prop_string* Pstring;
Prop_int* Pint;
Prop_multival* Pmulti;
Section_prop* Psection;
constexpr auto always = Property::Changeable::Always;
constexpr auto deprecated = Property::Changeable::Deprecated;
Pbool = sdl_sec->Add_bool("fullscreen", always, false);
Pbool->Set_help("Start DOSBox directly in fullscreen.\n"
"Press Alt-Enter to switch back to window.");
Pbool = sdl_sec->Add_bool("vsync", deprecated, false);
Pbool->Set_help("Vertical sync setting not implemented (setting ignored)");
Pstring = sdl_sec->Add_string("fullresolution", always, "desktop");
Pstring->Set_help("What resolution to use for fullscreen: 'original', 'desktop'\n"
"or a fixed size (e.g. 1024x768).");
Pstring = sdl_sec->Add_string("windowresolution", always, "original");
Pstring->Set_help("Scale the window to this size. Value 'original' will resize\n"
"window to the resolution picked by the emulated program.\n"
"Not supported when 'output' is set to 'surface'.");
const char *outputs[] = {
"surface",
"texture",
"texturenb",
"texturepp",
#if C_OPENGL
"opengl",
"openglnb",
#endif
0
};
#if C_OPENGL
Pstring = sdl_sec->Add_string("output", Property::Changeable::Always, "opengl");
#else
Pstring = sdl_sec->Add_string("output", Property::Changeable::Always, "texture");
#endif
Pstring->Set_help("What video system to use for output.");
Pstring->Set_values(outputs);
Pstring = sdl_sec->Add_string("texture_renderer", always, "auto");
Pstring->Set_help("Choose a renderer driver if output=texture or texturenb.\n"
"Use output=auto for an automatic choice.");
Pstring->Set_values(Get_SDL_TextureRenderers());
// Define mouse control settings
Pmulti = sdl_sec->Add_multi("capture_mouse", always, " ");
const char *mouse_controls[] = {
"seamless", // default
"onclick",
"onstart",
"nomouse",
0
};
const char *middle_controls[] = {
"middlerelease", // default
"middlegame",
0
};
// Generate and set the mouse control defaults from above arrays
std::string mouse_control_defaults(mouse_controls[0]);
mouse_control_defaults += " ";
mouse_control_defaults += middle_controls[0];
Pmulti->SetValue(mouse_control_defaults);
// Add the mouse and middle control as sub-sections
Psection = Pmulti->GetSection();
Psection->Add_string("mouse_control", always, mouse_controls[0])->Set_values(mouse_controls);
Psection->Add_string("middle_control", always, middle_controls[0])->Set_values(middle_controls);
// Construct and set the help block using defaults set above
std::string mouse_control_help(
"Choose a mouse control method:\n"
" onclick: The mouse will be captured after the first\n"
" click inside the window.\n"
" onstart: The mouse is captured immediately on start\n"
" (similar to real DOS).\n"
" seamless: The mouse can move seamlessly in and out of DOSBox\n"
" window and cannot be captured.\n"
" nomouse: The mouse is disabled and hidden without any\n"
" input sent to the game.\n"
"Choose how middle-clicks are handled (second parameter):\n"
" middlegame: Middle-clicks are sent to the game\n"
" (Ctrl-F10 uncaptures the mouse).\n"
" middlerelease: Middle-clicks are used to uncapture the mouse\n"
" (not sent to the game). However, middle-clicks\n"
" will be sent to the game in fullscreen or when\n"
" seamless control is set.\n"
" Ctrl-F10 will also uncapture the mouse.\n"
"Defaults (if not present or incorrect): ");
mouse_control_help += mouse_control_defaults;
Pmulti->Set_help(mouse_control_help);
Pmulti = sdl_sec->Add_multi("sensitivity",Property::Changeable::Always, ",");
Pmulti->Set_help("Mouse sensitivity. The optional second parameter specifies vertical sensitivity (e.g. 100,-50).");
Pmulti->SetValue("100");
Pint = Pmulti->GetSection()->Add_int("xsens",Property::Changeable::Always,100);
Pint->SetMinMax(-1000,1000);
Pint = Pmulti->GetSection()->Add_int("ysens",Property::Changeable::Always,100);
Pint->SetMinMax(-1000,1000);
Pbool = sdl_sec->Add_bool("waitonerror",Property::Changeable::Always, true);
Pbool->Set_help("Wait before closing the console if dosbox has an error.");
Pmulti = sdl_sec->Add_multi("priority", Property::Changeable::Always, ",");
Pmulti->SetValue("higher,normal");
Pmulti->Set_help("Priority levels for dosbox. Second entry behind the comma is for when dosbox is not focused/minimized.\n"
"pause is only valid for the second entry.");
const char* actt[] = { "lowest", "lower", "normal", "higher", "highest", "pause", 0};
Pstring = Pmulti->GetSection()->Add_string("active",Property::Changeable::Always,"higher");
Pstring->Set_values(actt);
const char* inactt[] = { "lowest", "lower", "normal", "higher", "highest", "pause", 0};
Pstring = Pmulti->GetSection()->Add_string("inactive",Property::Changeable::Always,"normal");
Pstring->Set_values(inactt);
Pstring = sdl_sec->Add_path("mapperfile",Property::Changeable::Always,MAPPERFILE);
Pstring->Set_help("File used to load/save the key/event mappings from. Resetmapper only works with the default value.");
}
static void show_warning(char const * const message) {
#ifndef WIN32
fprintf(stderr, "%s", message);
return;
#else
if (!sdl.inited && SDL_Init(SDL_INIT_VIDEO|SDL_INIT_NOPARACHUTE) < 0) {
sdl.inited = true;
printf("%s",message);
return;
}
if (!sdl.window && !GFX_SetSDLSurfaceWindow(640, 400))
return;
sdl.surface = SDL_GetWindowSurface(sdl.window);
if (!sdl.surface)
return;
SDL_Surface *splash_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 640, 400, 32,
RMASK, GMASK, BMASK, 0);
if (!splash_surf) return;
int x = 120,y = 20;
std::string m(message),m2;
std::string::size_type a,b,c,d;
while(m.size()) { //Max 50 characters. break on space before or on a newline
c = m.find('\n');
d = m.rfind(' ',50);
if(c>d) a=b=d; else a=b=c;
if( a != std::string::npos) b++;
m2 = m.substr(0,a); m.erase(0,b);
OutputString(x,y,m2.c_str(),0xffffffff,0,splash_surf);
y += 20;
}
SDL_BlitSurface(splash_surf, NULL, sdl.surface, NULL);
SDL_UpdateWindowSurface(sdl.window);
SDL_Delay(12000);
#endif // WIN32
}
static void launcheditor() {
std::string path,file;
Cross::CreatePlatformConfigDir(path);
Cross::GetPlatformConfigName(file);
path += file;
FILE* f = fopen(path.c_str(),"r");
if(!f && !control->PrintConfig(path.c_str())) {
printf("tried creating %s. but failed.\n",path.c_str());
exit(1);
}
if(f) fclose(f);
/* if(edit.empty()) {
printf("no editor specified.\n");
exit(1);
}*/
std::string edit;
while(control->cmdline->FindString("-editconf",edit,true)) //Loop until one succeeds
execlp(edit.c_str(),edit.c_str(),path.c_str(),(char*) 0);
//if you get here the launching failed!
printf("can't find editor(s) specified at the command line.\n");
exit(1);
}
#if C_DEBUG
extern void DEBUG_ShutDown(Section * /*sec*/);
#endif
void MIXER_CloseAudioDevice(void);
void restart_program(std::vector<std::string> & parameters) {
char** newargs = new char* [parameters.size() + 1];
// parameter 0 is the executable path
// contents of the vector follow
// last one is NULL
for(Bitu i = 0; i < parameters.size(); i++) newargs[i] = (char*)parameters[i].c_str();
newargs[parameters.size()] = NULL;
MIXER_CloseAudioDevice();
SDL_Delay(50);
SDL_Quit();
#if C_DEBUG
// shutdown curses
DEBUG_ShutDown(NULL);
#endif
if(execvp(newargs[0], newargs) == -1) {
#ifdef WIN32
if(newargs[0][0] == '\"') {
//everything specifies quotes around it if it contains a space, however my system disagrees
std::string edit = parameters[0];
edit.erase(0,1);edit.erase(edit.length() - 1,1);
//However keep the first argument of the passed argv (newargs) with quotes, as else repeated restarts go wrong.
if(execvp(edit.c_str(), newargs) == -1) E_Exit("Restarting failed");
}
#endif
E_Exit("Restarting failed");
}
delete [] newargs;
}
void Restart(bool pressed) { // mapper handler
(void) pressed; // deliberately unused but required for API compliance
restart_program(control->startup_params);
}
static void launchcaptures(std::string const& edit) {
std::string path,file;
Section* t = control->GetSection("dosbox");
if(t) file = t->GetPropValue("captures");
if(!t || file == NO_SUCH_PROPERTY) {
printf("Config system messed up.\n");
exit(1);
}
Cross::CreatePlatformConfigDir(path);
path += file;
Cross::CreateDir(path);
struct stat cstat;
if(stat(path.c_str(),&cstat) || (cstat.st_mode & S_IFDIR) == 0) {
printf("%s doesn't exists or isn't a directory.\n",path.c_str());
exit(1);
}
/* if(edit.empty()) {
printf("no editor specified.\n");
exit(1);
}*/
execlp(edit.c_str(),edit.c_str(),path.c_str(),(char*) 0);
//if you get here the launching failed!
printf("can't find filemanager %s\n",edit.c_str());
exit(1);
}
static void printconfiglocation() {
std::string path,file;
Cross::CreatePlatformConfigDir(path);
Cross::GetPlatformConfigName(file);
path += file;
FILE* f = fopen(path.c_str(),"r");
if(!f && !control->PrintConfig(path.c_str())) {
printf("tried creating %s. but failed",path.c_str());
exit(1);
}
if(f) fclose(f);
printf("%s\n",path.c_str());
exit(0);
}
static void eraseconfigfile() {
FILE* f = fopen("dosbox.conf","r");
if(f) {
fclose(f);
show_warning("Warning: dosbox.conf exists in current working directory.\nThis will override the configuration file at runtime.\n");
}
std::string path,file;
Cross::GetPlatformConfigDir(path);
Cross::GetPlatformConfigName(file);
path += file;
f = fopen(path.c_str(),"r");
if(!f) exit(0);
fclose(f);
unlink(path.c_str());
exit(0);
}
static void erasemapperfile() {
FILE* g = fopen("dosbox.conf","r");
if(g) {
fclose(g);
show_warning("Warning: dosbox.conf exists in current working directory.\nKeymapping might not be properly reset.\n"
"Please reset configuration as well and delete the dosbox.conf.\n");
}
std::string path,file=MAPPERFILE;
Cross::GetPlatformConfigDir(path);
path += file;
FILE* f = fopen(path.c_str(),"r");
if(!f) exit(0);
fclose(f);
unlink(path.c_str());
exit(0);
}
void Disable_OS_Scaling() {
#if defined (WIN32)
typedef BOOL (*function_set_dpi_pointer)();
function_set_dpi_pointer function_set_dpi;
function_set_dpi = (function_set_dpi_pointer) GetProcAddress(LoadLibrary("user32.dll"), "SetProcessDPIAware");
if (function_set_dpi) {
function_set_dpi();
}
#endif
}
//extern void UI_Init(void);
int main(int argc, char* argv[]) {
int rcode = 0; // assume good until proven otherwise
try {
Disable_OS_Scaling(); //Do this early on, maybe override it through some parameter.
CommandLine com_line(argc,argv);
Config myconf(&com_line);
control=&myconf;
/* Init the configuration system and add default values */
Config_Add_SDL();
DOSBOX_Init();
std::string editor;
if(control->cmdline->FindString("-editconf",editor,false)) launcheditor();
if(control->cmdline->FindString("-opencaptures",editor,true)) launchcaptures(editor);
if(control->cmdline->FindExist("-eraseconf")) eraseconfigfile();
if(control->cmdline->FindExist("-resetconf")) eraseconfigfile();
if(control->cmdline->FindExist("-erasemapper")) erasemapperfile();
if(control->cmdline->FindExist("-resetmapper")) erasemapperfile();
/* Can't disable the console with debugger enabled */
#if defined(WIN32) && !(C_DEBUG)
if (control->cmdline->FindExist("-noconsole")) {
FreeConsole();
/* Redirect standard input and standard output */
if(freopen(STDOUT_FILE, "w", stdout) == NULL)
no_stdout = true; // No stdout so don't write messages
freopen(STDERR_FILE, "w", stderr);
setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* Line buffered */
setbuf(stderr, NULL); /* No buffering */
} else {
if (AllocConsole()) {
fclose(stdin);
fclose(stdout);
fclose(stderr);
freopen("CONIN$","r",stdin);
freopen("CONOUT$","w",stdout);
freopen("CONOUT$","w",stderr);
}
SetConsoleTitle("DOSBox Status Window");
}
#endif //defined(WIN32) && !(C_DEBUG)
if (control->cmdline->FindExist("-version") ||
control->cmdline->FindExist("--version") ) {
printf("\nDOSBox version %s, copyright 2002-2020 DOSBox Team.\n\n",VERSION);
printf("DOSBox is written by the DOSBox Team (See AUTHORS file))\n");
printf("DOSBox comes with ABSOLUTELY NO WARRANTY. This is free software,\n");
printf("and you are welcome to redistribute it under certain conditions;\n");
printf("please read the COPYING file thoroughly before doing so.\n\n");
return 0;
}
if(control->cmdline->FindExist("-printconf")) printconfiglocation();
#if C_DEBUG
DEBUG_SetupConsole();
#endif
#if defined(WIN32)
SetConsoleCtrlHandler((PHANDLER_ROUTINE) ConsoleEventHandler,TRUE);
#endif
/* Display Welcometext in the console */
LOG_MSG("DOSBox version %s",VERSION);
LOG_MSG("Copyright 2002-2020 DOSBox Team, published under GNU GPL.");
LOG_MSG("---");
/* Init SDL */
/* Or debian/ubuntu with older libsdl version as they have done this themselves, but then differently.
* with this variable they will work correctly. I've only tested the 1.2.14 behaviour against the windows version
* of libsdl
*/
putenv(const_cast<char*>("SDL_DISABLE_LOCK_KEYS=1"));
if (SDL_Init_Wrapper() < 0)
E_Exit("Can't init SDL %s", SDL_GetError());
sdl.inited = true;
// Once initialized, ensure we clean up SDL for all exit conditions
atexit(SDL_Quit);
#ifndef DISABLE_JOYSTICK
//Initialise Joystick separately. This way we can warn when it fails instead
//of exiting the application
if( SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0 )
LOG_MSG("Failed to init joystick support");
#endif
sdl.laltstate = SDL_KEYUP;
sdl.raltstate = SDL_KEYUP;
sdl.num_joysticks=SDL_NumJoysticks();
/* Parse configuration files */
std::string config_file, config_path, config_combined;
Cross::GetPlatformConfigDir(config_path);
//First parse -userconf
if(control->cmdline->FindExist("-userconf",true)){
config_file.clear();
Cross::GetPlatformConfigDir(config_path);
Cross::GetPlatformConfigName(config_file);
config_combined = config_path + config_file;
control->ParseConfigFile(config_combined.c_str());
if(!control->configfiles.size()) {
//Try to create the userlevel configfile.
config_file.clear();
Cross::CreatePlatformConfigDir(config_path);
Cross::GetPlatformConfigName(config_file);
config_combined = config_path + config_file;
if(control->PrintConfig(config_combined.c_str())) {
LOG_MSG("CONFIG: Generating default configuration.\nWriting it to %s",config_combined.c_str());
//Load them as well. Makes relative paths much easier
control->ParseConfigFile(config_combined.c_str());
}
}
}
//Second parse -conf switches
while(control->cmdline->FindString("-conf",config_file,true)) {
if (!control->ParseConfigFile(config_file.c_str())) {
// try to load it from the user directory
if (!control->ParseConfigFile((config_path + config_file).c_str())) {
LOG_MSG("CONFIG: Can't open specified config file: %s",config_file.c_str());
}
}
}
// if none found => parse localdir conf
if(!control->configfiles.size()) control->ParseConfigFile("dosbox.conf");
// if none found => parse userlevel conf
if(!control->configfiles.size()) {
config_file.clear();
Cross::GetPlatformConfigName(config_file);
control->ParseConfigFile((config_path + config_file).c_str());
}
if(!control->configfiles.size()) {
//Try to create the userlevel configfile.
config_file.clear();
Cross::CreatePlatformConfigDir(config_path);
Cross::GetPlatformConfigName(config_file);
config_combined = config_path + config_file;
if(control->PrintConfig(config_combined.c_str())) {
LOG_MSG("CONFIG: Generating default configuration.\nWriting it to %s",config_combined.c_str());
//Load them as well. Makes relative paths much easier
control->ParseConfigFile(config_combined.c_str());
} else {
LOG_MSG("CONFIG: Using default settings. Create a configfile to change them");
}
}
#if (ENVIRON_LINKED)
control->ParseEnv(environ);
#endif
// UI_Init();
// if (control->cmdline->FindExist("-startui")) UI_Run(false);
/* Init all the sections */
control->Init();
/* Some extra SDL Functions */
Section_prop * sdl_sec=static_cast<Section_prop *>(control->GetSection("sdl"));
if (control->cmdline->FindExist("-fullscreen") || sdl_sec->Get_bool("fullscreen")) {
if(!sdl.desktop.fullscreen) { //only switch if not already in fullscreen
GFX_SwitchFullScreen();
}
}
// Apply key bindings only after all subsystems have added them
MAPPER_BindKeys();
// With the default key binds in place, render the mapper UI if requested
if (control->cmdline->FindExist("-startmapper"))
MAPPER_DisplayUI();
/* Start up main machine */
control->StartUp();
/* Shutdown everything */
} catch (char * error) {
rcode = 1;
GFX_ShowMsg("Exit to error: %s",error);
fflush(NULL);
if(sdl.wait_on_error) {
//TODO Maybe look for some way to show message in linux?
#if (C_DEBUG)
GFX_ShowMsg("Press enter to continue");
fflush(NULL);
fgetc(stdin);
#elif defined(WIN32)
Sleep(5000);
#endif
}
}
catch (...) {
// just exit
rcode = 1;
}
#if defined (WIN32)
sticky_keys(true); //Might not be needed if the shutdown function switches to windowed mode, but it doesn't hurt
#endif
return rcode;
}
void GFX_GetSize(int &width, int &height, bool &fullscreen) {
width = sdl.draw.width;
height = sdl.draw.height;
fullscreen = sdl.desktop.fullscreen;
}