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.
2887 lines
93 KiB
C++
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;
|
|
}
|