Add #6173: New SDL 2 based video and sound drivers (#7086)

pull/104/head
nikolas 5 years ago committed by Niels Martin Hansen
parent b18a58d5e1
commit 2d27e8e685

@ -751,7 +751,7 @@ check_params() {
log 1 "checking GDI video driver... not Windows, skipping"
fi
if [ -z "$allegro_config" ] && [ -z "$sdl_config" ] && [ "$with_cocoa" = 0 ] && [ "$os" != "MINGW" ] && [ "$os" != "CYGWIN" ]; then
if [ -z "$allegro_config" ] && [ -z "$sdl2_config" ] && [ -z "$sdl_config" ] && [ "$with_cocoa" = 0 ] && [ "$os" != "MINGW" ] && [ "$os" != "CYGWIN" ]; then
log 1 "configure: error: no video driver development files found"
log 1 " If you want a dedicated server use --enable-dedicated as parameter"
exit 1
@ -1629,7 +1629,16 @@ make_cflags_and_ldflags() {
fi
fi
if [ -n "$sdl_config" ]; then
if [ -n "$sdl2_config" ]; then
CFLAGS="$CFLAGS -DWITH_SDL2"
# SDL must not add _GNU_SOURCE as it breaks many platforms
CFLAGS="$CFLAGS `$sdl2_config --cflags | sed 's@-D_GNU_SOURCE[^ ]*@@'`"
if [ "$enable_static" != "0" ]; then
LIBS="$LIBS `$sdl2_config --static --libs`"
else
LIBS="$LIBS `$sdl2_config --libs`"
fi
elif [ -n "$sdl_config" ]; then
CFLAGS="$CFLAGS -DWITH_SDL"
# SDL must not add _GNU_SOURCE as it breaks many platforms
CFLAGS="$CFLAGS `$sdl_config --cflags | sed 's@-D_GNU_SOURCE[^ ]*@@'`"
@ -2426,7 +2435,13 @@ detect_sdl() {
sleep 5
fi
detect_pkg_config "$with_sdl" "sdl" "sdl_config" "1.2"
if [ $with_sdl = "sdl1" ]; then
detect_pkg_config "2" "sdl" "sdl_config" "1.2"
elif [ $with_sdl = "sdl2" ] || [ -x `which sdl2-config` ]; then
detect_pkg_config "2" "sdl2" "sdl2_config" "2.0"
else
detect_pkg_config "$with_sdl" "sdl" "sdl_config" "1.2"
fi
}
detect_osx_sdk() {
@ -3496,7 +3511,7 @@ showhelp() {
echo " --with-allegro[=\"pkg-config allegro\"]"
echo " enables Allegro video driver support"
echo " --with-cocoa enables COCOA video driver (OSX ONLY)"
echo " --with-sdl[=\"pkg-config sdl\"] enables SDL video driver support"
echo " --with-sdl[=\"sdl1|sdl2\"] enables SDL video driver support"
echo " --with-zlib[=\"pkg-config zlib\"]"
echo " enables zlib support"
echo " --with-liblzma[=\"pkg-config liblzma\"]"

1
configure vendored

@ -110,6 +110,7 @@ AWKCOMMAND='
if ($0 == "ALLEGRO" && "'$allegro_config'" == "") { next; }
if ($0 == "SDL" && "'$sdl_config'" == "") { next; }
if ($0 == "SDL2" && "'$sdl2_config'" == "") { next; }
if ($0 == "PNG" && "'$png_config'" == "") { next; }
if ($0 == "OSX" && "'$os'" != "OSX") { next; }
if ($0 == "OS2" && "'$os'" != "OS2") { next; }

@ -3,7 +3,7 @@ Section: games
Priority: optional
Maintainer: Matthijs Kooijman <matthijs@stdin.nl>
Uploaders: Jordi Mallach <jordi@debian.org>
Build-Depends: debhelper (>= 7.0.50), libsdl-dev, zlib1g-dev, libpng-dev, libfreetype6-dev, libfontconfig-dev, libicu-dev, liblzma-dev, liblzo2-dev
Build-Depends: debhelper (>= 7.0.50), libsdl2-dev, zlib1g-dev, libpng-dev, libfreetype6-dev, libfontconfig-dev, libicu-dev, liblzma-dev, liblzo2-dev
Standards-Version: 3.8.4
Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/openttd.git
Vcs-Git: git://anonscm.debian.org/collab-maint/openttd.git

@ -91,7 +91,7 @@ Group: Amusements/Games/Strategy/Other
Requires: %{name}
Conflicts: %{name}-dedicated
BuildRequires: SDL-devel
BuildRequires: SDL2-devel
BuildRequires: fontconfig-devel
%if 0%{?rhel_version} != 600

@ -314,6 +314,7 @@ safeguards.h
screenshot.h
sound/sdl_s.h
video/sdl_v.h
video/sdl2_v.h
settings_func.h
settings_gui.h
settings_internal.h
@ -1097,6 +1098,9 @@ video/null_v.cpp
#if SDL
video/sdl_v.cpp
#end
#if SDL2
video/sdl2_v.cpp
#end
#if WIN32
video/win32_v.cpp
#end
@ -1139,6 +1143,9 @@ sound/null_s.cpp
#if SDL
sound/sdl_s.cpp
#end
#if SDL2
sound/sdl2_s.cpp
#end
#if WIN32
sound/win32_s.cpp
#if USE_XAUDIO2

@ -61,9 +61,9 @@
#ifdef WITH_LZO
#include <lzo/lzo1x.h>
#endif
#ifdef WITH_SDL
#if defined(WITH_SDL) || defined(WITH_SDL2)
# include <SDL.h>
#endif /* WITH_SDL */
#endif /* WITH_SDL || WITH_SDL2 */
#ifdef WITH_ZLIB
# include <zlib.h>
#endif
@ -267,9 +267,13 @@ char *CrashLog::LogLibraries(char *buffer, const char *last) const
#endif /* WITH_PNG */
#ifdef WITH_SDL
const SDL_version *v = SDL_Linked_Version();
buffer += seprintf(buffer, last, " SDL: %d.%d.%d\n", v->major, v->minor, v->patch);
#endif /* WITH_SDL */
const SDL_version *sdl_v = SDL_Linked_Version();
buffer += seprintf(buffer, last, " SDL1: %d.%d.%d\n", sdl_v->major, sdl_v->minor, sdl_v->patch);
#elif defined(WITH_SDL2)
SDL_version sdl2_v;
SDL_GetVersion(&sdl2_v);
buffer += seprintf(buffer, last, " SDL2: %d.%d.%d\n", sdl2_v.major, sdl2_v.minor, sdl2_v.patch);
#endif
#ifdef WITH_ZLIB
buffer += seprintf(buffer, last, " Zlib: %s\n", zlibVersion());

@ -0,0 +1,70 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD 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, version 2.
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file sdl2_s.cpp Playing sound via SDL2. */
#ifdef WITH_SDL2
#include "../stdafx.h"
#include "../mixer.h"
#include "sdl_s.h"
#include <SDL.h>
#include "../safeguards.h"
/** Factory for the SDL sound driver. */
static FSoundDriver_SDL iFSoundDriver_SDL;
/**
* Callback that fills the sound buffer.
* @param userdata Ignored.
* @param stream The stream to put data into.
* @param len The length of the stream in bytes.
*/
static void CDECL fill_sound_buffer(void *userdata, Uint8 *stream, int len)
{
MxMixSamples(stream, len / 4);
}
const char *SoundDriver_SDL::Start(const char * const *parm)
{
SDL_AudioSpec spec;
SDL_AudioSpec spec_actual;
/* Only initialise SDL if the video driver hasn't done it already */
int ret_code = 0;
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
ret_code = SDL_Init(SDL_INIT_AUDIO);
} else if (SDL_WasInit(SDL_INIT_AUDIO) == 0) {
ret_code = SDL_InitSubSystem(SDL_INIT_AUDIO);
}
if (ret_code == -1) return SDL_GetError();
spec.freq = GetDriverParamInt(parm, "hz", 44100);
spec.format = AUDIO_S16SYS;
spec.channels = 2;
spec.samples = GetDriverParamInt(parm, "samples", 1024);
spec.callback = fill_sound_buffer;
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &spec_actual, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
MxInitialize(spec_actual.freq);
SDL_PauseAudioDevice(dev, 0);
return nullptr;
}
void SoundDriver_SDL::Stop()
{
SDL_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
SDL_Quit(); // If there's nothing left, quit SDL
}
}
#endif /* WITH_SDL2 */

@ -0,0 +1,830 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD 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, version 2.
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file sdl2_v.cpp Implementation of the SDL2 video driver. */
#ifdef WITH_SDL2
#include "../stdafx.h"
#include "../openttd.h"
#include "../gfx_func.h"
#include "../rev.h"
#include "../blitter/factory.hpp"
#include "../network/network.h"
#include "../thread.h"
#include "../progress.h"
#include "../core/random_func.hpp"
#include "../core/math_func.hpp"
#include "../fileio_func.h"
#include "../framerate_type.h"
#include "sdl2_v.h"
#include <SDL.h>
#include <mutex>
#include <condition_variable>
#include <algorithm>
#include "../safeguards.h"
static FVideoDriver_SDL iFVideoDriver_SDL;
static SDL_Window *_sdl_window;
static SDL_Surface *_sdl_surface;
static SDL_Surface *_sdl_realscreen;
/** Whether the drawing is/may be done in a separate thread. */
static bool _draw_threaded;
/** Mutex to keep the access to the shared memory controlled. */
static std::recursive_mutex *_draw_mutex = nullptr;
/** Signal to draw the next frame. */
static std::condition_variable_any *_draw_signal = nullptr;
/** Should we keep continue drawing? */
static volatile bool _draw_continue;
static Palette _local_palette;
static SDL_Palette *_sdl_palette;
#define MAX_DIRTY_RECTS 100
static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS];
static int _num_dirty_rects;
/* Size of window */
static int _window_size_w;
static int _window_size_h;
void VideoDriver_SDL::MakeDirty(int left, int top, int width, int height)
{
if (_num_dirty_rects < MAX_DIRTY_RECTS) {
_dirty_rects[_num_dirty_rects].x = left;
_dirty_rects[_num_dirty_rects].y = top;
_dirty_rects[_num_dirty_rects].w = width;
_dirty_rects[_num_dirty_rects].h = height;
}
_num_dirty_rects++;
}
static void UpdatePalette(bool init = false)
{
SDL_Color pal[256];
for (int i = 0; i != _local_palette.count_dirty; i++) {
pal[i].r = _local_palette.palette[_local_palette.first_dirty + i].r;
pal[i].g = _local_palette.palette[_local_palette.first_dirty + i].g;
pal[i].b = _local_palette.palette[_local_palette.first_dirty + i].b;
pal[i].a = 0;
}
SDL_SetPaletteColors(_sdl_palette, pal, _local_palette.first_dirty, _local_palette.count_dirty);
SDL_SetSurfacePalette(_sdl_surface, _sdl_palette);
if (_sdl_surface != _sdl_realscreen && init) {
/* When using a shadow surface, also set our palette on the real screen. This lets SDL
* allocate as many colors (or approximations) as
* possible, instead of using only the default SDL
* palette. This allows us to get more colors exactly
* right and might allow using better approximations for
* other colors.
*
* Note that colors allocations are tried in-order, so
* this favors colors further up into the palette. Also
* note that if two colors from the same animation
* sequence are approximated using the same color, that
* animation will stop working.
*
* Since changing the system palette causes the colours
* to change right away, and allocations might
* drastically change, we can't use this for animation,
* since that could cause weird coloring between the
* palette change and the blitting below, so we only set
* the real palette during initialisation.
*/
SDL_SetSurfacePalette(_sdl_realscreen, _sdl_palette);
}
if (_sdl_surface != _sdl_realscreen && !init) {
/* We're not using real hardware palette, but are letting SDL
* approximate the palette during shadow -> screen copy. To
* change the palette, we need to recopy the entire screen.
*
* Note that this operation can slow down the rendering
* considerably, especially since changing the shadow
* palette will need the next blit to re-detect the
* best mapping of shadow palette colors to real palette
* colors from scratch.
*/
SDL_BlitSurface(_sdl_surface, nullptr, _sdl_realscreen, nullptr);
SDL_UpdateWindowSurface(_sdl_window);
}
}
static void InitPalette()
{
_local_palette = _cur_palette;
_local_palette.first_dirty = 0;
_local_palette.count_dirty = 256;
UpdatePalette(true);
}
static void CheckPaletteAnim()
{
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
switch (blitter->UsePaletteAnimation()) {
case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
UpdatePalette();
break;
case Blitter::PALETTE_ANIMATION_BLITTER:
blitter->PaletteAnimate(_local_palette);
break;
case Blitter::PALETTE_ANIMATION_NONE:
break;
default:
NOT_REACHED();
}
_cur_palette.count_dirty = 0;
}
}
static void DrawSurfaceToScreen()
{
PerformanceMeasurer framerate(PFE_VIDEO);
int n = _num_dirty_rects;
if (n == 0) return;
_num_dirty_rects = 0;
if (n > MAX_DIRTY_RECTS) {
if (_sdl_surface != _sdl_realscreen) {
SDL_BlitSurface(_sdl_surface, nullptr, _sdl_realscreen, nullptr);
}
SDL_UpdateWindowSurface(_sdl_window);
} else {
if (_sdl_surface != _sdl_realscreen) {
for (int i = 0; i < n; i++) {
SDL_BlitSurface(
_sdl_surface, &_dirty_rects[i],
_sdl_realscreen, &_dirty_rects[i]);
}
}
SDL_UpdateWindowSurfaceRects(_sdl_window, _dirty_rects, n);
}
}
static void DrawSurfaceToScreenThread()
{
/* First tell the main thread we're started */
std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
_draw_signal->notify_one();
/* Now wait for the first thing to draw! */
_draw_signal->wait(*_draw_mutex);
while (_draw_continue) {
CheckPaletteAnim();
/* Then just draw and wait till we stop */
DrawSurfaceToScreen();
_draw_signal->wait(lock);
}
}
static void GetVideoModes()
{
int modes = SDL_GetNumDisplayModes(0);
if (modes == 0) usererror("sdl: no modes available");
_resolutions.clear();
SDL_DisplayMode mode;
for (int i = 0; i < modes; i++) {
SDL_GetDisplayMode(0, i, &mode);
uint w = mode.w;
uint h = mode.h;
if (w < 640 || h < 480) continue; // reject too small resolutions
if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(w, h)) != _resolutions.end()) continue;
_resolutions.emplace_back(w, h);
}
if (_resolutions.empty()) usererror("No usable screen resolutions found!\n");
SortResolutions();
}
static void GetAvailableVideoMode(uint *w, uint *h)
{
/* All modes available? */
if (!_fullscreen || _resolutions.empty()) return;
/* Is the wanted mode among the available modes? */
if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(*w, *h)) != _resolutions.end()) return;
/* Use the closest possible resolution */
uint best = 0;
uint delta = Delta(_resolutions[0].width, *w) * Delta(_resolutions[0].height, *h);
for (uint i = 1; i != _resolutions.size(); ++i) {
uint newdelta = Delta(_resolutions[i].width, *w) * Delta(_resolutions[i].height, *h);
if (newdelta < delta) {
best = i;
delta = newdelta;
}
}
*w = _resolutions[best].width;
*h = _resolutions[best].height;
}
bool VideoDriver_SDL::CreateMainSurface(uint w, uint h, bool resize)
{
SDL_Surface *newscreen;
char caption[50];
int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
GetAvailableVideoMode(&w, &h);
DEBUG(driver, 1, "SDL2: using mode %ux%ux%d", w, h, bpp);
if (bpp == 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
/* Free any previously allocated shadow surface */
if (_sdl_surface != nullptr && _sdl_surface != _sdl_realscreen) SDL_FreeSurface(_sdl_surface);
seprintf(caption, lastof(caption), "OpenTTD %s", _openttd_revision);
if (_sdl_window == nullptr) {
Uint32 flags = SDL_WINDOW_SHOWN;
if (_fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN;
} else {
flags |= SDL_WINDOW_RESIZABLE;
}
_sdl_window = SDL_CreateWindow(
caption,
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
w, h,
flags);
if (_sdl_window == nullptr) {
DEBUG(driver, 0, "SDL2: Couldn't allocate a window to draw on");
return false;
}
char icon_path[MAX_PATH];
if (FioFindFullPath(icon_path, lastof(icon_path), BASESET_DIR, "openttd.32.bmp") != nullptr) {
/* Give the application an icon */
SDL_Surface *icon = SDL_LoadBMP(icon_path);
if (icon != nullptr) {
/* Get the colourkey, which will be magenta */
uint32 rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
SDL_SetColorKey(icon, SDL_TRUE, rgbmap);
SDL_SetWindowIcon(_sdl_window, icon);
SDL_FreeSurface(icon);
}
}
}
if (resize) SDL_SetWindowSize(_sdl_window, w, h);
newscreen = SDL_GetWindowSurface(_sdl_window);
if (newscreen == NULL) {
DEBUG(driver, 0, "SDL2: Couldn't get window surface: %s", SDL_GetError());
return false;
}
_sdl_realscreen = newscreen;
if (bpp == 8) {
newscreen = SDL_CreateRGBSurfaceWithFormat(0, w, h, 8, SDL_PIXELFORMAT_INDEX8);
if (newscreen == nullptr) {
DEBUG(driver, 0, "SDL2: Couldn't allocate shadow surface: %s", SDL_GetError());
return false;
}
}
if (_sdl_palette == nullptr) {
_sdl_palette = SDL_AllocPalette(256);
}
if (_sdl_palette == nullptr) {
DEBUG(driver, 0, "SDL_AllocPalette() failed: %s", SDL_GetError());
return false;
}
/* Delay drawing for this cycle; the next cycle will redraw the whole screen */
_num_dirty_rects = 0;
_screen.width = newscreen->w;
_screen.height = newscreen->h;
_screen.pitch = newscreen->pitch / (bpp / 8);
_screen.dst_ptr = newscreen->pixels;
_sdl_surface = newscreen;
/* When in full screen, we will always have the mouse cursor
* within the window, even though SDL does not give us the
* appropriate event to know this. */
if (_fullscreen) _cursor.in_window = true;
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
blitter->PostResize();
InitPalette();
GameSizeChanged();
return true;
}
bool VideoDriver_SDL::ClaimMousePointer()
{
SDL_ShowCursor(0);
return true;
}
struct VkMapping {
SDL_Keycode vk_from;
byte vk_count;
byte map_to;
};
#define AS(x, z) {x, 0, z}
#define AM(x, y, z, w) {x, (byte)(y - x), z}
static const VkMapping _vk_mapping[] = {
/* Pageup stuff + up/down */
AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN),
AS(SDLK_UP, WKC_UP),
AS(SDLK_DOWN, WKC_DOWN),
AS(SDLK_LEFT, WKC_LEFT),
AS(SDLK_RIGHT, WKC_RIGHT),
AS(SDLK_HOME, WKC_HOME),
AS(SDLK_END, WKC_END),
AS(SDLK_INSERT, WKC_INSERT),
AS(SDLK_DELETE, WKC_DELETE),
/* Map letters & digits */
AM(SDLK_a, SDLK_z, 'A', 'Z'),
AM(SDLK_0, SDLK_9, '0', '9'),
AS(SDLK_ESCAPE, WKC_ESC),
AS(SDLK_PAUSE, WKC_PAUSE),
AS(SDLK_BACKSPACE, WKC_BACKSPACE),
AS(SDLK_SPACE, WKC_SPACE),
AS(SDLK_RETURN, WKC_RETURN),
AS(SDLK_TAB, WKC_TAB),
/* Function keys */
AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12),
/* Numeric part. */
AM(SDLK_KP_0, SDLK_KP_9, '0', '9'),
AS(SDLK_KP_DIVIDE, WKC_NUM_DIV),
AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL),
AS(SDLK_KP_MINUS, WKC_NUM_MINUS),
AS(SDLK_KP_PLUS, WKC_NUM_PLUS),
AS(SDLK_KP_ENTER, WKC_NUM_ENTER),
AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL),
/* Other non-letter keys */
AS(SDLK_SLASH, WKC_SLASH),
AS(SDLK_SEMICOLON, WKC_SEMICOLON),
AS(SDLK_EQUALS, WKC_EQUALS),
AS(SDLK_LEFTBRACKET, WKC_L_BRACKET),
AS(SDLK_BACKSLASH, WKC_BACKSLASH),
AS(SDLK_RIGHTBRACKET, WKC_R_BRACKET),
AS(SDLK_QUOTE, WKC_SINGLEQUOTE),
AS(SDLK_COMMA, WKC_COMMA),
AS(SDLK_MINUS, WKC_MINUS),
AS(SDLK_PERIOD, WKC_PERIOD)
};
static uint ConvertSdlKeyIntoMy(SDL_Keysym *sym, WChar *character)
{
const VkMapping *map;
uint key = 0;
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
if ((uint)(sym->sym - map->vk_from) <= map->vk_count) {
key = sym->sym - map->vk_from + map->map_to;
break;
}
}
/* check scancode for BACKQUOTE key, because we want the key left of "1", not anything else (on non-US keyboards) */
#if defined(_WIN32) || defined(__OS2__)
if (sym->scancode == 41) key = WKC_BACKQUOTE;
#elif defined(__APPLE__)
if (sym->scancode == 10) key = WKC_BACKQUOTE;
#elif defined(__SVR4) && defined(__sun)
if (sym->scancode == 60) key = WKC_BACKQUOTE;
if (sym->scancode == 49) key = WKC_BACKSPACE;
#elif defined(__sgi__)
if (sym->scancode == 22) key = WKC_BACKQUOTE;
#else
if (sym->scancode == 49) key = WKC_BACKQUOTE;
#endif
/* META are the command keys on mac */
if (sym->mod & KMOD_GUI) key |= WKC_META;
if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT;
if (sym->mod & KMOD_CTRL) key |= WKC_CTRL;
if (sym->mod & KMOD_ALT) key |= WKC_ALT;
/* The mod keys have no character. Prevent '?' */
if (sym->mod & KMOD_GUI ||
sym->mod & KMOD_SHIFT ||
sym->mod & KMOD_CTRL ||
sym->mod & KMOD_ALT) {
*character = WKC_NONE;
} else {
*character = sym->sym;
}
return key;
}
/**
* Like ConvertSdlKeyIntoMy(), but takes an SDL_Keycode as input
* instead of an SDL_Keysym.
*/
static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
{
const VkMapping *map;
uint key = 0;
for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
if ((uint)(kc - map->vk_from) <= map->vk_count) {
key = kc - map->vk_from + map->map_to;
break;
}
}
/* check scancode for BACKQUOTE key, because we want the key left
of "1", not anything else (on non-US keyboards) */
SDL_Scancode sc = SDL_GetScancodeFromKey(kc);
if (sc == SDL_SCANCODE_GRAVE) key = WKC_BACKQUOTE;
return key;
}
int VideoDriver_SDL::PollEvent()
{
SDL_Event ev;
if (!SDL_PollEvent(&ev)) return -2;
switch (ev.type) {
case SDL_MOUSEMOTION:
if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) {
SDL_WarpMouseInWindow(_sdl_window, _cursor.pos.x, _cursor.pos.y);
}
HandleMouseEvents();
break;
case SDL_MOUSEWHEEL:
if (ev.wheel.y > 0) {
_cursor.wheel--;
} else if (ev.wheel.y < 0) {
_cursor.wheel++;
}
break;
case SDL_MOUSEBUTTONDOWN:
if (_rightclick_emulate && SDL_GetModState() & KMOD_CTRL) {
ev.button.button = SDL_BUTTON_RIGHT;
}
switch (ev.button.button) {
case SDL_BUTTON_LEFT:
_left_button_down = true;
break;
case SDL_BUTTON_RIGHT:
_right_button_down = true;
_right_button_clicked = true;
break;
default: break;
}
HandleMouseEvents();
break;
case SDL_MOUSEBUTTONUP:
if (_rightclick_emulate) {
_right_button_down = false;
_left_button_down = false;
_left_button_clicked = false;
} else if (ev.button.button == SDL_BUTTON_LEFT) {
_left_button_down = false;
_left_button_clicked = false;
} else if (ev.button.button == SDL_BUTTON_RIGHT) {
_right_button_down = false;
}
HandleMouseEvents();
break;
case SDL_QUIT:
HandleExitGameRequest();
break;
case SDL_KEYDOWN: // Toggle full-screen on ALT + ENTER/F
if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_GUI)) &&
(ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) {
if (ev.key.repeat == 0) ToggleFullScreen(!_fullscreen);
} else {
WChar character;
uint keycode = ConvertSdlKeyIntoMy(&ev.key.keysym, &character);
// Only handle non-text keys here. Text is handled in
// SDL_TEXTINPUT below.
if (keycode == WKC_DELETE ||
keycode == WKC_NUM_ENTER ||
keycode == WKC_LEFT ||
keycode == WKC_RIGHT ||
keycode & WKC_META ||
keycode & WKC_SHIFT ||
keycode & WKC_CTRL ||
keycode & WKC_ALT ||
(keycode >= WKC_F1 && keycode <= WKC_F12) ||
!IsValidChar(character, CS_ALPHANUMERAL)) {
HandleKeypress(keycode, character);
}
}
break;
case SDL_TEXTINPUT: {
WChar character;
SDL_Keycode kc = SDL_GetKeyFromName(ev.text.text);
uint keycode = ConvertSdlKeycodeIntoMy(kc);
Utf8Decode(&character, ev.text.text);
HandleKeypress(keycode, character);
break;
}
case SDL_WINDOWEVENT: {
if (ev.window.event == SDL_WINDOWEVENT_EXPOSED) {
// Force a redraw of the entire screen.
_num_dirty_rects = MAX_DIRTY_RECTS + 1;
} else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
int w = max(ev.window.data1, 64);
int h = max(ev.window.data2, 64);
CreateMainSurface(w, h, w != ev.window.data1 || h != ev.window.data2);
} else if (ev.window.event == SDL_WINDOWEVENT_ENTER) {
// mouse entered the window, enable cursor
_cursor.in_window = true;
} else if (ev.window.event == SDL_WINDOWEVENT_LEAVE) {
// mouse left the window, undraw cursor
UndrawMouseCursor();
_cursor.in_window = false;
}
break;
}
}
return -1;
}
const char *VideoDriver_SDL::Start(const char * const *parm)
{
/* Explicitly disable hardware acceleration. Enabling this causes
* UpdateWindowSurface() to update the window's texture instead of
* its surface. */
SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION , "0");
/* Just on the offchance the audio subsystem started before the video system,
* check whether any part of SDL has been initialised before getting here.
* Slightly duplicated with sound/sdl_s.cpp */
int ret_code = 0;
if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
ret_code = SDL_InitSubSystem(SDL_INIT_VIDEO);
}
if (ret_code < 0) return SDL_GetError();
GetVideoModes();
if (!CreateMainSurface(_cur_resolution.width, _cur_resolution.height, false)) {
return SDL_GetError();
}
const char *dname = SDL_GetVideoDriver(0);
DEBUG(driver, 1, "SDL2: using driver '%s'", dname);
MarkWholeScreenDirty();
_draw_threaded = GetDriverParam(parm, "no_threads") == nullptr && GetDriverParam(parm, "no_thread") == nullptr;
return nullptr;
}
void VideoDriver_SDL::Stop()
{
SDL_QuitSubSystem(SDL_INIT_VIDEO);
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
SDL_Quit(); // If there's nothing left, quit SDL
}
}
void VideoDriver_SDL::MainLoop()
{
uint32 cur_ticks = SDL_GetTicks();
uint32 last_cur_ticks = cur_ticks;
uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
uint32 mod;
int numkeys;
const Uint8 *keys;
CheckPaletteAnim();
std::thread draw_thread;
std::unique_lock<std::recursive_mutex> draw_lock;
if (_draw_threaded) {
/* Initialise the mutex first, because that's the thing we *need*
* directly in the newly created thread. */
_draw_mutex = new std::recursive_mutex();
if (_draw_mutex == nullptr) {
_draw_threaded = false;
} else {
draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
_draw_signal = new std::condition_variable_any();
_draw_continue = true;
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &DrawSurfaceToScreenThread);
/* Free the mutex if we won't be able to use it. */
if (!_draw_threaded) {
draw_lock.unlock();
draw_lock.release();
delete _draw_mutex;
delete _draw_signal;
_draw_mutex = nullptr;
_draw_signal = nullptr;
} else {
/* Wait till the draw mutex has started itself. */
_draw_signal->wait(*_draw_mutex);
}
}
}
DEBUG(driver, 1, "SDL2: using %sthreads", _draw_threaded ? "" : "no ");
for (;;) {
uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
InteractiveRandom(); // randomness
while (PollEvent() == -1) {}
if (_exit_game) break;
mod = SDL_GetModState();
keys = SDL_GetKeyboardState(&numkeys);
#if defined(_DEBUG)
if (_shift_pressed)
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application */
if (keys[SDL_SCANCODE_TAB] && (mod & KMOD_ALT) == 0)
#endif /* defined(_DEBUG) */
{
if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
} else if (_fast_forward & 2) {
_fast_forward = 0;
}
cur_ticks = SDL_GetTicks();
if (SDL_TICKS_PASSED(cur_ticks, next_tick) || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
_realtime_tick += cur_ticks - last_cur_ticks;
last_cur_ticks = cur_ticks;
next_tick = cur_ticks + MILLISECONDS_PER_TICK;
bool old_ctrl_pressed = _ctrl_pressed;
_ctrl_pressed = !!(mod & KMOD_CTRL);
_shift_pressed = !!(mod & KMOD_SHIFT);
/* determine which directional keys are down */
_dirkeys =
(keys[SDL_SCANCODE_LEFT] ? 1 : 0) |
(keys[SDL_SCANCODE_UP] ? 2 : 0) |
(keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
/* The gameloop is the part that can run asynchronously. The rest
* except sleeping can't. */
if (_draw_mutex != nullptr) draw_lock.unlock();
GameLoop();
if (_draw_mutex != nullptr) draw_lock.lock();
UpdateWindows();
_local_palette = _cur_palette;
} else {
/* Release the thread while sleeping */
if (_draw_mutex != nullptr) draw_lock.unlock();
CSleep(1);
if (_draw_mutex != nullptr) draw_lock.lock();
NetworkDrawChatMessage();
DrawMouseCursor();
}
/* End of the critical part. */
if (_draw_mutex != nullptr && !HasModalProgress()) {
_draw_signal->notify_one();
} else {
/* Oh, we didn't have threads, then just draw unthreaded */
CheckPaletteAnim();
DrawSurfaceToScreen();
}
}
if (_draw_mutex != nullptr) {
_draw_continue = false;
/* Sending signal if there is no thread blocked
* is very valid and results in noop */
_draw_signal->notify_one();
if (draw_lock.owns_lock()) draw_lock.unlock();
draw_lock.release();
draw_thread.join();
delete _draw_mutex;
delete _draw_signal;
_draw_mutex = nullptr;
_draw_signal = nullptr;
}
}
bool VideoDriver_SDL::ChangeResolution(int w, int h)
{
std::unique_lock<std::recursive_mutex> lock;
if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
return CreateMainSurface(w, h, true);
}
bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen)
{
std::unique_lock<std::recursive_mutex> lock;
if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
/* Remember current window size */
if (fullscreen) {
SDL_GetWindowSize(_sdl_window, &_window_size_w, &_window_size_h);
/* Find fullscreen window size */
SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) < 0) {
DEBUG(driver, 0, "SDL_GetCurrentDisplayMode() failed: %s", SDL_GetError());
} else {
SDL_SetWindowSize(_sdl_window, dm.w, dm.h);
}
}
DEBUG(driver, 1, "SDL2: Setting %s", fullscreen ? "fullscreen" : "windowed");
int ret = SDL_SetWindowFullscreen(_sdl_window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
if (ret == 0) {
/* Switching resolution succeeded, set fullscreen value of window. */
_fullscreen = fullscreen;
if (!fullscreen) SDL_SetWindowSize(_sdl_window, _window_size_w, _window_size_h);
} else {
DEBUG(driver, 0, "SDL_SetWindowFullscreen() failed: %s", SDL_GetError());
}
return ret == 0;
}
bool VideoDriver_SDL::AfterBlitterChange()
{
int w, h;
SDL_GetWindowSize(_sdl_window, &w, &h);
return CreateMainSurface(w, h, false);
}
void VideoDriver_SDL::AcquireBlitterLock()
{
if (_draw_mutex != nullptr) _draw_mutex->lock();
}
void VideoDriver_SDL::ReleaseBlitterLock()
{
if (_draw_mutex != nullptr) _draw_mutex->unlock();
}
#endif /* WITH_SDL2 */

@ -0,0 +1,53 @@
/* $Id$ */
/*
* This file is part of OpenTTD.
* OpenTTD 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, version 2.
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file sdl2_v.h Base of the SDL2 video driver. */
#ifndef VIDEO_SDL_H
#define VIDEO_SDL_H
#include "video_driver.hpp"
/** The SDL video driver. */
class VideoDriver_SDL : public VideoDriver {
public:
const char *Start(const char * const *param) override;
void Stop() override;
void MakeDirty(int left, int top, int width, int height) override;
void MainLoop() override;
bool ChangeResolution(int w, int h) override;
bool ToggleFullscreen(bool fullscreen) override;
bool AfterBlitterChange() override;
void AcquireBlitterLock() override;
void ReleaseBlitterLock() override;
bool ClaimMousePointer() override;
const char *GetName() const override { return "sdl"; }
private:
int PollEvent();
bool CreateMainSurface(uint w, uint h, bool resize);
};
/** Factory for the SDL video driver. */
class FVideoDriver_SDL : public DriverFactoryBase {
public:
FVideoDriver_SDL() : DriverFactoryBase(Driver::DT_VIDEO, 5, "sdl", "SDL Video Driver") {}
Driver *CreateInstance() const override { return new VideoDriver_SDL(); }
};
#endif /* VIDEO_SDL_H */
Loading…
Cancel
Save