1
0
Fork 0
dosbox-staging/src/gui/sdlmain.cpp
Patryk Obara 8d5bc9537a Prevent a crash in Windows AMD drivers
Usually, when window is being created without SDL_WINDOW_OPENGL flag,
SDL2 internally re-creates it to support OpenGL during
SDL_CreateRenderer if needed.

This leads to crash in AMD OpenGL drivers (Windows only), which happens
for drivers: "opengl", "opengles", "opengles2".

When the window is created with correct flag from the get-go, the crash
does not happen.

On Linux, the code does not crash either way (at least not when using
Mesa and AMDGPU open source driver), so there's no point in propagating
the hack.

Also, remove a comment that is no longer relevant to the code below.
2020-03-27 00:54:11 +01:00

2887 lines
93 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"
extern "C" {
#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
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, height;
bool fixed;
bool display_res;
} full;
struct {
Bit16u width, height;
bool use_original_size = true;
} window;
Bit8u bpp;
bool fullscreen;
bool lazy_fullscreen;
bool lazy_fullscreen_req;
bool vsync;
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;
int ysensitivity;
Bitu sensitivity;
MouseControlType control_choice;
bool middle_will_release;
} 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)
#ifdef WORDS_BIGENDIAN
SDL_Surface* logos= SDL_CreateRGBSurfaceFrom((void*)logo,32,32,32,128,0xff000000,0x00ff0000,0x0000ff00,0);
#else
SDL_Surface* logos= SDL_CreateRGBSurfaceFrom((void*)logo,32,32,32,128,0x000000ff,0x0000ff00,0x00ff0000,0);
#endif
SDL_SetWindowIcon(sdl.window, logos);
#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(void) {
if (sdl.desktop.lazy_fullscreen) {
// sdl.desktop.lazy_fullscreen_req=true;
LOG_MSG("GFX LF: invalid screen change");
} else {
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.
* Suggestion: Use the desktop res if possible, with output=surface
* if one is not interested in scaling.
* On Android, desktop res is the only way.
*/
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) {
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();
}
void GFX_UpdateMouseAfterExposure(void) {
// 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 released 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/ has_run_once is false,
// then we're starting up the first time, so we:
// - 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)
return;
if (sdl.desktop.lazy_fullscreen) {
// sdl.desktop.lazy_fullscreen_req=true;
LOG_MSG("GFX LF: fullscreen switching not supported");
} else {
GFX_SwitchFullScreen();
}
}
void GFX_SwitchLazyFullscreen(bool lazy) {
sdl.desktop.lazy_fullscreen=lazy;
sdl.desktop.lazy_fullscreen_req=false;
}
void GFX_SwitchFullscreenNoReset(void) {
sdl.desktop.fullscreen=!sdl.desktop.fullscreen;
}
bool GFX_LazyFullscreenRequested(void) {
if (sdl.desktop.lazy_fullscreen) return sdl.desktop.lazy_fullscreen_req;
return false;
}
// 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_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;
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
}
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_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;
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
default:
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 = gimp_image.width;
sdl.draw.height = gimp_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 = gimp_image.width;
constexpr int src_h = gimp_image.height;
constexpr int src_bpp = gimp_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());
std::array<uint8_t, (src_w * src_h * src_bpp)> splash;
GIMP_IMAGE_RUN_LENGTH_DECODE(splash.data(), gimp_image.rle_pixel_data,
src_w * src_h, src_bpp);
assertm(out, "GFX_StartUpdate is supposed to give us buffer.");
uint32_t *pixels = reinterpret_cast<uint32_t *>(out);
size_t i = 0;
size_t j = 0;
static_assert(splash.size() % 3 == 0, "Reading 3 bytes at a time.");
while (i < splash.size()) {
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;
}
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.lazy_fullscreen=false;
sdl.desktop.lazy_fullscreen_req=false;
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;
sdl.desktop.window.width = 0;
sdl.desktop.window.height = 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"));
}
}
}
}
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();
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;
case SDL_WINDOWEVENT_EXPOSED:
if (sdl.draw.callback) sdl.draw.callback( GFX_CallBackRedraw );
GFX_UpdateMouseAfterExposure();
continue;
case SDL_WINDOWEVENT_FOCUS_GAINED:
SetPriority(sdl.priority.focus);
CPU_Disable_SkipAutoAdjust();
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();
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;
Pbool = sdl_sec->Add_bool("fullscreen",Property::Changeable::Always,false);
Pbool->Set_help("Start dosbox directly in fullscreen. (Press ALT-Enter to go back)");
Pbool = sdl_sec->Add_bool("vsync", Property::Changeable::Always, false);
Pbool->Set_help("Sync to Vblank IF supported by the output device and renderer.\n"
"It can reduce screen flickering, but it can also result in a slow DOSBox.");
Pstring = sdl_sec->Add_string("fullresolution", Property::Changeable::Always, "desktop");
Pstring->Set_help("What resolution to use for fullscreen: 'original', 'desktop' or\n"
"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;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
Bit32u rmask = 0xff000000;
Bit32u gmask = 0x00ff0000;
Bit32u bmask = 0x0000ff00;
#else
Bit32u rmask = 0x000000ff;
Bit32u gmask = 0x0000ff00;
Bit32u bmask = 0x00ff0000;
#endif
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;
}