2023-06-20 04:33:09 +00:00
# include "StarMainApplication.hpp"
# include "StarLogging.hpp"
# include "StarSignalHandler.hpp"
# include "StarTickRateMonitor.hpp"
# include "StarRenderer_opengl20.hpp"
2023-06-23 08:13:26 +00:00
# include "StarTtlCache.hpp"
# include "StarImage.hpp"
# include "StarImageProcessing.hpp"
2023-06-20 04:33:09 +00:00
# include "SDL.h"
# include "StarPlatformServices_pc.hpp"
namespace Star {
Maybe < Key > keyFromSdlKeyCode ( SDL_Keycode sym ) {
static HashMap < int , Key > KeyCodeMap {
{ SDLK_BACKSPACE , Key : : Backspace } ,
{ SDLK_TAB , Key : : Tab } ,
{ SDLK_CLEAR , Key : : Clear } ,
{ SDLK_RETURN , Key : : Return } ,
{ SDLK_PAUSE , Key : : Pause } ,
{ SDLK_ESCAPE , Key : : Escape } ,
{ SDLK_SPACE , Key : : Space } ,
{ SDLK_EXCLAIM , Key : : Exclaim } ,
{ SDLK_QUOTEDBL , Key : : QuotedBL } ,
{ SDLK_HASH , Key : : Hash } ,
{ SDLK_DOLLAR , Key : : Dollar } ,
{ SDLK_AMPERSAND , Key : : Ampersand } ,
{ SDLK_QUOTE , Key : : Quote } ,
{ SDLK_LEFTPAREN , Key : : LeftParen } ,
{ SDLK_RIGHTPAREN , Key : : RightParen } ,
{ SDLK_ASTERISK , Key : : Asterisk } ,
{ SDLK_PLUS , Key : : Plus } ,
{ SDLK_COMMA , Key : : Comma } ,
{ SDLK_MINUS , Key : : Minus } ,
{ SDLK_PERIOD , Key : : Period } ,
{ SDLK_SLASH , Key : : Slash } ,
{ SDLK_0 , Key : : Zero } ,
{ SDLK_1 , Key : : One } ,
{ SDLK_2 , Key : : Two } ,
{ SDLK_3 , Key : : Three } ,
{ SDLK_4 , Key : : Four } ,
{ SDLK_5 , Key : : Five } ,
{ SDLK_6 , Key : : Six } ,
{ SDLK_7 , Key : : Seven } ,
{ SDLK_8 , Key : : Eight } ,
{ SDLK_9 , Key : : Nine } ,
{ SDLK_COLON , Key : : Colon } ,
{ SDLK_SEMICOLON , Key : : Semicolon } ,
{ SDLK_LESS , Key : : Less } ,
{ SDLK_EQUALS , Key : : Equals } ,
{ SDLK_GREATER , Key : : Greater } ,
{ SDLK_QUESTION , Key : : Question } ,
{ SDLK_AT , Key : : At } ,
{ SDLK_LEFTBRACKET , Key : : LeftBracket } ,
{ SDLK_BACKSLASH , Key : : Backslash } ,
{ SDLK_RIGHTBRACKET , Key : : RightBracket } ,
{ SDLK_CARET , Key : : Caret } ,
{ SDLK_UNDERSCORE , Key : : Underscore } ,
{ SDLK_BACKQUOTE , Key : : Backquote } ,
{ SDLK_a , Key : : A } ,
{ SDLK_b , Key : : B } ,
{ SDLK_c , Key : : C } ,
{ SDLK_d , Key : : D } ,
{ SDLK_e , Key : : E } ,
{ SDLK_f , Key : : F } ,
{ SDLK_g , Key : : G } ,
{ SDLK_h , Key : : H } ,
{ SDLK_i , Key : : I } ,
{ SDLK_j , Key : : J } ,
{ SDLK_k , Key : : K } ,
{ SDLK_l , Key : : L } ,
{ SDLK_m , Key : : M } ,
{ SDLK_n , Key : : N } ,
{ SDLK_o , Key : : O } ,
{ SDLK_p , Key : : P } ,
{ SDLK_q , Key : : Q } ,
{ SDLK_r , Key : : R } ,
{ SDLK_s , Key : : S } ,
{ SDLK_t , Key : : T } ,
{ SDLK_u , Key : : U } ,
{ SDLK_v , Key : : V } ,
{ SDLK_w , Key : : W } ,
{ SDLK_x , Key : : X } ,
{ SDLK_y , Key : : Y } ,
{ SDLK_z , Key : : Z } ,
{ SDLK_DELETE , Key : : Delete } ,
{ SDLK_KP_0 , Key : : Kp0 } ,
{ SDLK_KP_1 , Key : : Kp1 } ,
{ SDLK_KP_2 , Key : : Kp2 } ,
{ SDLK_KP_3 , Key : : Kp3 } ,
{ SDLK_KP_4 , Key : : Kp4 } ,
{ SDLK_KP_5 , Key : : Kp5 } ,
{ SDLK_KP_6 , Key : : Kp6 } ,
{ SDLK_KP_7 , Key : : Kp7 } ,
{ SDLK_KP_8 , Key : : Kp8 } ,
{ SDLK_KP_9 , Key : : Kp9 } ,
{ SDLK_KP_PERIOD , Key : : Kp_period } ,
{ SDLK_KP_DIVIDE , Key : : Kp_divide } ,
{ SDLK_KP_MULTIPLY , Key : : Kp_multiply } ,
{ SDLK_KP_MINUS , Key : : Kp_minus } ,
{ SDLK_KP_PLUS , Key : : Kp_plus } ,
{ SDLK_KP_ENTER , Key : : Kp_enter } ,
{ SDLK_KP_EQUALS , Key : : Kp_equals } ,
{ SDLK_UP , Key : : Up } ,
{ SDLK_DOWN , Key : : Down } ,
{ SDLK_RIGHT , Key : : Right } ,
{ SDLK_LEFT , Key : : Left } ,
{ SDLK_INSERT , Key : : Insert } ,
{ SDLK_HOME , Key : : Home } ,
{ SDLK_END , Key : : End } ,
{ SDLK_PAGEUP , Key : : PageUp } ,
{ SDLK_PAGEDOWN , Key : : PageDown } ,
{ SDLK_F1 , Key : : F1 } ,
{ SDLK_F2 , Key : : F2 } ,
{ SDLK_F3 , Key : : F3 } ,
{ SDLK_F4 , Key : : F4 } ,
{ SDLK_F5 , Key : : F5 } ,
{ SDLK_F6 , Key : : F6 } ,
{ SDLK_F7 , Key : : F7 } ,
{ SDLK_F8 , Key : : F8 } ,
{ SDLK_F9 , Key : : F9 } ,
{ SDLK_F10 , Key : : F10 } ,
{ SDLK_F11 , Key : : F11 } ,
{ SDLK_F12 , Key : : F12 } ,
{ SDLK_F13 , Key : : F13 } ,
{ SDLK_F14 , Key : : F14 } ,
{ SDLK_F15 , Key : : F15 } ,
{ SDLK_NUMLOCKCLEAR , Key : : NumLock } ,
{ SDLK_CAPSLOCK , Key : : CapsLock } ,
{ SDLK_SCROLLLOCK , Key : : ScrollLock } ,
{ SDLK_RSHIFT , Key : : RShift } ,
{ SDLK_LSHIFT , Key : : LShift } ,
{ SDLK_RCTRL , Key : : RCtrl } ,
{ SDLK_LCTRL , Key : : LCtrl } ,
{ SDLK_RALT , Key : : RAlt } ,
{ SDLK_LALT , Key : : LAlt } ,
{ SDLK_RGUI , Key : : RGui } ,
{ SDLK_LGUI , Key : : LGui } ,
{ SDLK_MODE , Key : : AltGr } ,
{ SDLK_APPLICATION , Key : : Compose } ,
{ SDLK_HELP , Key : : Help } ,
{ SDLK_PRINTSCREEN , Key : : PrintScreen } ,
{ SDLK_SYSREQ , Key : : SysReq } ,
{ SDLK_PAUSE , Key : : Pause } ,
{ SDLK_MENU , Key : : Menu } ,
{ SDLK_POWER , Key : : Power }
} ;
return KeyCodeMap . maybe ( sym ) ;
}
KeyMod keyModsFromSdlKeyMods ( uint16_t mod ) {
2023-06-28 12:52:09 +00:00
return static_cast < KeyMod > ( mod ) ;
2023-06-20 04:33:09 +00:00
}
MouseButton mouseButtonFromSdlMouseButton ( uint8_t button ) {
2023-06-28 12:52:09 +00:00
switch ( button ) {
case SDL_BUTTON_LEFT : return MouseButton : : Left ;
case SDL_BUTTON_MIDDLE : return MouseButton : : Middle ;
case SDL_BUTTON_RIGHT : return MouseButton : : Right ;
case SDL_BUTTON_X1 : return MouseButton : : FourthButton ;
default : return MouseButton : : FifthButton ;
}
}
ControllerAxis controllerAxisFromSdlControllerAxis ( uint8_t axis ) {
switch ( axis ) {
case SDL_CONTROLLER_AXIS_LEFTX : return ControllerAxis : : LeftX ;
case SDL_CONTROLLER_AXIS_LEFTY : return ControllerAxis : : LeftY ;
case SDL_CONTROLLER_AXIS_RIGHTX : return ControllerAxis : : RightX ;
case SDL_CONTROLLER_AXIS_RIGHTY : return ControllerAxis : : RightY ;
case SDL_CONTROLLER_AXIS_TRIGGERLEFT : return ControllerAxis : : TriggerLeft ;
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT : return ControllerAxis : : TriggerRight ;
default : return ControllerAxis : : Invalid ;
}
}
ControllerButton controllerButtonFromSdlControllerButton ( uint8_t button ) {
switch ( button ) {
case SDL_CONTROLLER_BUTTON_A : return ControllerButton : : A ;
case SDL_CONTROLLER_BUTTON_B : return ControllerButton : : B ;
case SDL_CONTROLLER_BUTTON_X : return ControllerButton : : X ;
case SDL_CONTROLLER_BUTTON_Y : return ControllerButton : : Y ;
case SDL_CONTROLLER_BUTTON_BACK : return ControllerButton : : Back ;
case SDL_CONTROLLER_BUTTON_GUIDE : return ControllerButton : : Guide ;
case SDL_CONTROLLER_BUTTON_START : return ControllerButton : : Start ;
case SDL_CONTROLLER_BUTTON_LEFTSTICK : return ControllerButton : : LeftStick ;
case SDL_CONTROLLER_BUTTON_RIGHTSTICK : return ControllerButton : : RightStick ;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER : return ControllerButton : : LeftShoulder ;
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER : return ControllerButton : : RightShoulder ;
case SDL_CONTROLLER_BUTTON_DPAD_UP : return ControllerButton : : DPadUp ;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN : return ControllerButton : : DPadDown ;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT : return ControllerButton : : DPadLeft ;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT : return ControllerButton : : DPadRight ;
case SDL_CONTROLLER_BUTTON_MISC1 : return ControllerButton : : Misc1 ;
case SDL_CONTROLLER_BUTTON_PADDLE1 : return ControllerButton : : Paddle1 ;
case SDL_CONTROLLER_BUTTON_PADDLE2 : return ControllerButton : : Paddle2 ;
case SDL_CONTROLLER_BUTTON_PADDLE3 : return ControllerButton : : Paddle3 ;
case SDL_CONTROLLER_BUTTON_PADDLE4 : return ControllerButton : : Paddle4 ;
case SDL_CONTROLLER_BUTTON_TOUCHPAD : return ControllerButton : : Touchpad ;
default : return ControllerButton : : Invalid ;
}
2023-06-20 04:33:09 +00:00
}
class SdlPlatform {
public :
SdlPlatform ( ApplicationUPtr application , StringList cmdLineArgs ) {
m_application = move ( application ) ;
// extract application path from command line args
String applicationPath = cmdLineArgs . first ( ) ;
cmdLineArgs = cmdLineArgs . slice ( 1 ) ;
StringList platformArguments ;
eraseWhere ( cmdLineArgs , [ & platformArguments ] ( String & argument ) {
if ( argument . beginsWith ( " +platform " ) ) {
platformArguments . append ( move ( argument ) ) ;
return true ;
}
return false ;
} ) ;
Logger : : info ( " Application: Initializing SDL " ) ;
if ( SDL_Init ( 0 ) )
2023-06-27 10:23:44 +00:00
throw ApplicationException ( strf ( " Couldn't initialize SDL: {} " , SDL_GetError ( ) ) ) ;
2023-06-20 04:33:09 +00:00
if ( char * basePath = SDL_GetBasePath ( ) ) {
File : : changeDirectory ( basePath ) ;
SDL_free ( basePath ) ;
}
m_signalHandler . setHandleInterrupt ( true ) ;
m_signalHandler . setHandleFatal ( true ) ;
try {
Logger : : info ( " Application: startup... " ) ;
m_application - > startup ( cmdLineArgs ) ;
} catch ( std : : exception const & e ) {
throw ApplicationException ( " Application threw exception during startup " , e ) ;
}
Logger : : info ( " Application: Initializing SDL Video " ) ;
if ( SDL_InitSubSystem ( SDL_INIT_VIDEO ) )
2023-06-27 10:23:44 +00:00
throw ApplicationException ( strf ( " Couldn't initialize SDL Video: {} " , SDL_GetError ( ) ) ) ;
2023-06-20 04:33:09 +00:00
2023-06-28 12:52:09 +00:00
Logger : : info ( " Application: Initializing SDL Controller " ) ;
if ( SDL_InitSubSystem ( SDL_INIT_GAMECONTROLLER ) )
throw ApplicationException ( strf ( " Couldn't initialize SDL Controller: {} " , SDL_GetError ( ) ) ) ;
2023-06-20 04:33:09 +00:00
2023-07-13 10:47:53 +00:00
# ifdef STAR_SYSTEM_WINDOWS // Newer SDL is defaulting to xaudio2, which does not support audio capture
SDL_setenv ( " SDL_AUDIODRIVER " , " directsound " , 1 ) ;
# endif
Logger : : info ( " Application: Initializing SDL Audio " ) ;
2023-06-20 04:33:09 +00:00
if ( SDL_InitSubSystem ( SDL_INIT_AUDIO ) )
2023-07-13 10:47:53 +00:00
throw ApplicationException ( strf ( " Couldn't initialize SDL Audio: {} " , SDL_GetError ( ) ) ) ;
Logger : : info ( " Application: using Audio Driver '{}' " , SDL_GetCurrentAudioDriver ( ) ) ;
2023-06-20 04:33:09 +00:00
SDL_JoystickEventState ( SDL_ENABLE ) ;
m_platformServices = PcPlatformServices : : create ( applicationPath , platformArguments ) ;
if ( ! m_platformServices )
Logger : : info ( " Application: No platform services available " ) ;
Logger : : info ( " Application: Creating SDL Window " ) ;
m_sdlWindow = SDL_CreateWindow ( m_windowTitle . utf8Ptr ( ) , SDL_WINDOWPOS_UNDEFINED , SDL_WINDOWPOS_UNDEFINED ,
m_windowSize [ 0 ] , m_windowSize [ 1 ] , SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE ) ;
if ( ! m_sdlWindow )
2023-06-27 10:23:44 +00:00
throw ApplicationException : : format ( " Application: Could not create SDL window: {} " , SDL_GetError ( ) ) ;
2023-06-20 04:33:09 +00:00
SDL_ShowWindow ( m_sdlWindow ) ;
SDL_RaiseWindow ( m_sdlWindow ) ;
int width ;
int height ;
SDL_GetWindowSize ( m_sdlWindow , & width , & height ) ;
m_windowSize = Vec2U ( width , height ) ;
m_sdlGlContext = SDL_GL_CreateContext ( m_sdlWindow ) ;
if ( ! m_sdlGlContext )
2023-06-27 10:23:44 +00:00
throw ApplicationException : : format ( " Application: Could not create OpenGL context: {} " , SDL_GetError ( ) ) ;
2023-06-20 04:33:09 +00:00
2023-06-21 05:25:10 +00:00
SDL_GL_SwapWindow ( m_sdlWindow ) ;
2023-06-20 04:33:09 +00:00
setVSyncEnabled ( m_windowVSync ) ;
SDL_StopTextInput ( ) ;
SDL_AudioSpec desired = { } ;
desired . freq = 44100 ;
desired . format = AUDIO_S16SYS ;
2023-06-28 15:29:29 +00:00
desired . samples = 1024 ;
2023-06-20 04:33:09 +00:00
desired . channels = 2 ;
desired . userdata = this ;
desired . callback = [ ] ( void * userdata , Uint8 * stream , int len ) {
( ( SdlPlatform * ) ( userdata ) ) - > getAudioData ( stream , len ) ;
} ;
SDL_AudioSpec obtained = { } ;
2023-07-13 09:12:55 +00:00
m_sdlAudioOutputDevice = SDL_OpenAudioDevice ( NULL , 0 , & desired , & obtained , 0 ) ;
if ( ! m_sdlAudioOutputDevice ) {
2023-06-20 04:33:09 +00:00
Logger : : error ( " Application: Could not open audio device, no sound available! " ) ;
} else if ( obtained . freq ! = desired . freq | | obtained . channels ! = desired . channels | | obtained . format ! = desired . format ) {
2023-07-13 09:12:55 +00:00
SDL_CloseAudioDevice ( m_sdlAudioOutputDevice ) ;
2023-06-20 04:33:09 +00:00
Logger : : error ( " Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available! " ) ;
} else {
2023-06-27 10:23:44 +00:00
Logger : : info ( " Application: Opened default audio device with 44.1khz / 16 bit stereo audio, {} sample size buffer " , obtained . samples ) ;
2023-07-13 09:12:55 +00:00
SDL_PauseAudioDevice ( m_sdlAudioOutputDevice , 0 ) ;
2023-06-20 04:33:09 +00:00
}
m_renderer = make_shared < OpenGl20Renderer > ( ) ;
m_renderer - > setScreenSize ( m_windowSize ) ;
2023-06-23 08:13:26 +00:00
m_cursorCache . setTimeToLive ( 30000 ) ;
2023-06-20 04:33:09 +00:00
}
~ SdlPlatform ( ) {
2023-06-28 13:34:59 +00:00
2023-07-13 09:12:55 +00:00
if ( m_sdlAudioOutputDevice )
SDL_CloseAudioDevice ( m_sdlAudioOutputDevice ) ;
closeAudioInputDevice ( ) ;
2023-06-20 04:33:09 +00:00
m_renderer . reset ( ) ;
Logger : : info ( " Application: Destroying SDL Window " ) ;
SDL_DestroyWindow ( m_sdlWindow ) ;
SDL_Quit ( ) ;
}
2023-07-13 09:12:55 +00:00
bool openAudioInputDevice ( const char * name , int freq , int channels , void * userdata , SDL_AudioCallback callback ) {
SDL_AudioSpec desired = { } ;
desired . freq = freq ;
desired . format = AUDIO_S16SYS ;
desired . samples = 1024 ;
desired . channels = channels ;
desired . userdata = userdata ;
desired . callback = callback ;
closeAudioInputDevice ( ) ;
SDL_AudioSpec obtained = { } ;
2023-07-13 10:47:53 +00:00
m_sdlAudioInputDevice = SDL_OpenAudioDevice ( name , 1 , & desired , & obtained , 0 ) ;
2023-07-14 03:13:19 +00:00
if ( m_sdlAudioInputDevice ) {
2023-07-16 13:04:09 +00:00
if ( name )
Logger : : info ( " Opened audio input device '{}' " , name ) ;
else
Logger : : info ( " Opened default audio input device " ) ;
2023-07-14 03:13:19 +00:00
SDL_PauseAudioDevice ( m_sdlAudioInputDevice , 0 ) ;
}
2023-07-13 10:47:53 +00:00
else
Logger : : info ( " Failed to open audio input device: {} " , SDL_GetError ( ) ) ;
return m_sdlAudioInputDevice ! = 0 ;
2023-07-13 09:12:55 +00:00
}
bool closeAudioInputDevice ( ) {
if ( m_sdlAudioInputDevice ) {
2023-07-16 13:04:09 +00:00
Logger : : info ( " Closing audio input device " ) ;
2023-07-13 09:12:55 +00:00
SDL_CloseAudioDevice ( m_sdlAudioInputDevice ) ;
m_sdlAudioInputDevice = 0 ;
return true ;
}
return false ;
}
2023-06-23 08:13:26 +00:00
void cleanup ( ) {
m_cursorCache . ptr ( m_currentCursor ) ;
m_cursorCache . cleanup ( ) ;
}
2023-06-20 04:33:09 +00:00
void run ( ) {
try {
Logger : : info ( " Application: initialization... " ) ;
m_application - > applicationInit ( make_shared < Controller > ( this ) ) ;
Logger : : info ( " Application: renderer initialization... " ) ;
m_application - > renderInit ( m_renderer ) ;
Logger : : info ( " Application: main update loop... " ) ;
m_updateTicker . reset ( ) ;
m_renderTicker . reset ( ) ;
bool quit = false ;
while ( true ) {
2023-06-27 14:51:02 +00:00
cleanup ( ) ;
2023-06-20 04:33:09 +00:00
for ( auto const & event : processEvents ( ) )
m_application - > processInput ( event ) ;
if ( m_platformServices )
m_platformServices - > update ( ) ;
if ( m_platformServices - > overlayActive ( ) )
SDL_ShowCursor ( 1 ) ;
else
SDL_ShowCursor ( m_cursorVisible ? 1 : 0 ) ;
int updatesBehind = max < int > ( round ( m_updateTicker . ticksBehind ( ) ) , 1 ) ;
updatesBehind = min < int > ( updatesBehind , m_maxFrameSkip + 1 ) ;
for ( int i = 0 ; i < updatesBehind ; + + i ) {
m_application - > update ( ) ;
m_updateRate = m_updateTicker . tick ( ) ;
}
m_renderer - > startFrame ( ) ;
m_application - > render ( ) ;
m_renderer - > finishFrame ( ) ;
SDL_GL_SwapWindow ( m_sdlWindow ) ;
m_renderRate = m_renderTicker . tick ( ) ;
if ( m_quitRequested ) {
Logger : : info ( " Application: quit requested " ) ;
quit = true ;
}
if ( m_signalHandler . interruptCaught ( ) ) {
Logger : : info ( " Application: Interrupt caught " ) ;
quit = true ;
}
if ( quit ) {
Logger : : info ( " Application: quitting... " ) ;
break ;
}
int64_t spareMilliseconds = round ( m_updateTicker . spareTime ( ) * 1000 ) ;
if ( spareMilliseconds > 0 )
Thread : : sleepPrecise ( spareMilliseconds ) ;
}
} catch ( std : : exception const & e ) {
2023-06-27 10:23:44 +00:00
Logger : : error ( " Application: exception thrown, shutting down: {} " , outputException ( e , true ) ) ;
2023-06-20 04:33:09 +00:00
}
try {
Logger : : info ( " Application: shutdown... " ) ;
m_application - > shutdown ( ) ;
} catch ( std : : exception const & e ) {
2023-06-27 10:23:44 +00:00
Logger : : error ( " Application: threw exception during shutdown: {} " , outputException ( e , true ) ) ;
2023-06-20 04:33:09 +00:00
}
2023-07-13 09:12:55 +00:00
SDL_CloseAudioDevice ( m_sdlAudioOutputDevice ) ;
2023-06-28 13:34:59 +00:00
m_SdlControllers . clear ( ) ;
SDL_SetCursor ( NULL ) ;
m_cursorCache . clear ( ) ;
2023-06-20 04:33:09 +00:00
m_application . reset ( ) ;
}
private :
struct Controller : public ApplicationController {
Controller ( SdlPlatform * parent )
: parent ( parent ) { }
Maybe < String > getClipboard ( ) override {
2023-11-29 02:45:13 +00:00
Maybe < String > string ;
if ( SDL_HasClipboardText ( ) ) {
auto text = SDL_GetClipboardText ( ) ;
if ( text & & * text ! = NULL ) {
string . emplace ( text ) ;
SDL_free ( text ) ;
}
}
return string ;
2023-06-20 04:33:09 +00:00
}
void setClipboard ( String text ) override {
SDL_SetClipboardText ( text . utf8Ptr ( ) ) ;
}
void setTargetUpdateRate ( float targetUpdateRate ) override {
parent - > m_updateTicker . setTargetTickRate ( targetUpdateRate ) ;
}
void setUpdateTrackWindow ( float updateTrackWindow ) override {
parent - > m_updateTicker . setWindow ( updateTrackWindow ) ;
}
void setApplicationTitle ( String title ) override {
parent - > m_windowTitle = move ( title ) ;
if ( parent - > m_sdlWindow )
SDL_SetWindowTitle ( parent - > m_sdlWindow , parent - > m_windowTitle . utf8Ptr ( ) ) ;
}
void setFullscreenWindow ( Vec2U fullScreenResolution ) override {
if ( parent - > m_windowMode ! = WindowMode : : Fullscreen | | parent - > m_windowSize ! = fullScreenResolution ) {
SDL_DisplayMode requestedDisplayMode = { SDL_PIXELFORMAT_RGB888 , ( int ) fullScreenResolution [ 0 ] , ( int ) fullScreenResolution [ 1 ] , 0 , 0 } ;
int currentDisplayIndex = SDL_GetWindowDisplayIndex ( parent - > m_sdlWindow ) ;
SDL_DisplayMode targetDisplayMode ;
if ( SDL_GetClosestDisplayMode ( currentDisplayIndex , & requestedDisplayMode , & targetDisplayMode ) ! = NULL ) {
if ( SDL_SetWindowDisplayMode ( parent - > m_sdlWindow , & requestedDisplayMode ) = = 0 ) {
if ( parent - > m_windowMode = = WindowMode : : Fullscreen )
SDL_SetWindowFullscreen ( parent - > m_sdlWindow , 0 ) ;
2023-06-28 15:12:52 +00:00
else if ( parent - > m_windowMode = = WindowMode : : Borderless )
SDL_SetWindowBordered ( parent - > m_sdlWindow , SDL_TRUE ) ;
else if ( parent - > m_windowMode = = WindowMode : : Maximized )
SDL_RestoreWindow ( parent - > m_sdlWindow ) ;
parent - > m_windowMode = WindowMode : : Fullscreen ;
2023-06-20 04:33:09 +00:00
SDL_SetWindowFullscreen ( parent - > m_sdlWindow , SDL_WINDOW_FULLSCREEN ) ;
} else {
2023-06-27 10:23:44 +00:00
Logger : : warn ( " Failed to set resolution {}, {} " , ( unsigned ) requestedDisplayMode . w , ( unsigned ) requestedDisplayMode . h ) ;
2023-06-20 04:33:09 +00:00
}
} else {
2023-06-27 10:23:44 +00:00
Logger : : warn ( " Unable to set requested display resolution {}, {} " , ( int ) fullScreenResolution [ 0 ] , ( int ) fullScreenResolution [ 1 ] ) ;
2023-06-20 04:33:09 +00:00
}
SDL_DisplayMode actualDisplayMode ;
if ( SDL_GetWindowDisplayMode ( parent - > m_sdlWindow , & actualDisplayMode ) = = 0 ) {
parent - > m_windowSize = { ( unsigned ) actualDisplayMode . w , ( unsigned ) actualDisplayMode . h } ;
// call these manually since no SDL_WindowEvent is triggered when changing between fullscreen resolutions for some reason
parent - > m_renderer - > setScreenSize ( parent - > m_windowSize ) ;
parent - > m_application - > windowChanged ( parent - > m_windowMode , parent - > m_windowSize ) ;
} else {
Logger : : error ( " Couldn't get window display mode! " ) ;
}
}
}
void setNormalWindow ( Vec2U windowSize ) override {
2023-06-28 15:12:52 +00:00
auto window = parent - > m_sdlWindow ;
2023-06-20 04:33:09 +00:00
if ( parent - > m_windowMode ! = WindowMode : : Normal | | parent - > m_windowSize ! = windowSize ) {
2023-06-28 15:12:52 +00:00
if ( parent - > m_windowMode = = WindowMode : : Fullscreen )
SDL_SetWindowFullscreen ( window , 0 ) ;
else if ( parent - > m_windowMode = = WindowMode : : Borderless )
SDL_SetWindowBordered ( window , SDL_TRUE ) ;
2023-06-20 04:33:09 +00:00
else if ( parent - > m_windowMode = = WindowMode : : Maximized )
2023-06-28 15:12:52 +00:00
SDL_RestoreWindow ( window ) ;
2023-06-20 04:33:09 +00:00
2023-06-28 15:12:52 +00:00
SDL_SetWindowBordered ( window , SDL_TRUE ) ;
SDL_SetWindowSize ( window , windowSize [ 0 ] , windowSize [ 1 ] ) ;
SDL_SetWindowPosition ( window , SDL_WINDOWPOS_CENTERED , SDL_WINDOWPOS_CENTERED ) ;
2023-06-20 04:33:09 +00:00
parent - > m_windowMode = WindowMode : : Normal ;
parent - > m_windowSize = windowSize ;
}
}
void setMaximizedWindow ( ) override {
if ( parent - > m_windowMode ! = WindowMode : : Maximized ) {
2023-06-28 15:12:52 +00:00
if ( parent - > m_windowMode = = WindowMode : : Fullscreen )
2023-06-20 04:33:09 +00:00
SDL_SetWindowFullscreen ( parent - > m_sdlWindow , 0 ) ;
2023-06-28 15:12:52 +00:00
else if ( parent - > m_windowMode = = WindowMode : : Borderless )
2023-06-21 05:25:10 +00:00
SDL_SetWindowBordered ( parent - > m_sdlWindow , SDL_TRUE ) ;
2023-06-20 04:33:09 +00:00
2023-06-28 15:12:52 +00:00
SDL_RestoreWindow ( parent - > m_sdlWindow ) ;
2023-06-20 04:33:09 +00:00
SDL_MaximizeWindow ( parent - > m_sdlWindow ) ;
parent - > m_windowMode = WindowMode : : Maximized ;
}
}
void setBorderlessWindow ( ) override {
if ( parent - > m_windowMode ! = WindowMode : : Borderless ) {
2023-06-28 15:12:52 +00:00
if ( parent - > m_windowMode = = WindowMode : : Fullscreen )
SDL_SetWindowFullscreen ( parent - > m_sdlWindow , 0 ) ;
else if ( parent - > m_windowMode = = WindowMode : : Maximized )
SDL_RestoreWindow ( parent - > m_sdlWindow ) ;
2023-06-21 05:25:10 +00:00
SDL_SetWindowBordered ( parent - > m_sdlWindow , SDL_FALSE ) ;
2023-06-20 04:33:09 +00:00
parent - > m_windowMode = WindowMode : : Borderless ;
SDL_DisplayMode actualDisplayMode ;
2023-06-28 15:12:52 +00:00
if ( SDL_GetDesktopDisplayMode ( SDL_GetWindowDisplayIndex ( parent - > m_sdlWindow ) , & actualDisplayMode ) = = 0 ) {
2023-06-20 04:33:09 +00:00
parent - > m_windowSize = { ( unsigned ) actualDisplayMode . w , ( unsigned ) actualDisplayMode . h } ;
2023-06-28 15:12:52 +00:00
SDL_SetWindowPosition ( parent - > m_sdlWindow , 0 , 0 ) ;
SDL_SetWindowSize ( parent - > m_sdlWindow , parent - > m_windowSize [ 0 ] , parent - > m_windowSize [ 1 ] ) ;
2023-06-20 04:33:09 +00:00
parent - > m_renderer - > setScreenSize ( parent - > m_windowSize ) ;
parent - > m_application - > windowChanged ( parent - > m_windowMode , parent - > m_windowSize ) ;
} else {
2023-06-28 15:12:52 +00:00
Logger : : error ( " Couldn't get desktop display mode! " ) ;
2023-06-20 04:33:09 +00:00
}
}
}
void setVSyncEnabled ( bool vSync ) override {
if ( parent - > m_windowVSync ! = vSync ) {
parent - > setVSyncEnabled ( vSync ) ;
parent - > m_windowVSync = vSync ;
}
}
void setMaxFrameSkip ( unsigned maxFrameSkip ) override {
parent - > m_maxFrameSkip = maxFrameSkip ;
}
void setCursorVisible ( bool cursorVisible ) override {
parent - > m_cursorVisible = cursorVisible ;
}
2023-11-01 21:12:21 +00:00
void setCursorPosition ( Vec2I cursorPosition ) override {
SDL_WarpMouseInWindow ( parent - > m_sdlWindow , cursorPosition [ 0 ] , cursorPosition [ 1 ] ) ;
}
2023-06-23 08:13:26 +00:00
bool setCursorImage ( const String & id , const ImageConstPtr & image , unsigned scale , const Vec2I & offset ) override {
return parent - > setCursorImage ( id , image , scale , offset ) ;
}
2023-06-20 04:33:09 +00:00
void setAcceptingTextInput ( bool acceptingTextInput ) override {
if ( acceptingTextInput ! = parent - > m_acceptingTextInput ) {
if ( acceptingTextInput )
SDL_StartTextInput ( ) ;
else
SDL_StopTextInput ( ) ;
parent - > m_acceptingTextInput = acceptingTextInput ;
}
}
AudioFormat enableAudio ( ) override {
parent - > m_audioEnabled = true ;
SDL_PauseAudio ( false ) ;
return AudioFormat { 44100 , 2 } ;
}
void disableAudio ( ) override {
parent - > m_audioEnabled = false ;
SDL_PauseAudio ( true ) ;
}
2023-07-13 09:12:55 +00:00
bool openAudioInputDevice ( const char * name , int freq , int channels , void * userdata , AudioCallback callback ) override {
return parent - > openAudioInputDevice ( name , freq , channels , userdata , callback ) ;
} ;
bool closeAudioInputDevice ( ) override {
return parent - > closeAudioInputDevice ( ) ;
} ;
2023-06-20 04:33:09 +00:00
float updateRate ( ) const override {
return parent - > m_updateRate ;
}
float renderFps ( ) const override {
return parent - > m_renderRate ;
}
StatisticsServicePtr statisticsService ( ) const override {
if ( parent - > m_platformServices )
return parent - > m_platformServices - > statisticsService ( ) ;
return { } ;
}
P2PNetworkingServicePtr p2pNetworkingService ( ) const override {
if ( parent - > m_platformServices )
return parent - > m_platformServices - > p2pNetworkingService ( ) ;
return { } ;
}
UserGeneratedContentServicePtr userGeneratedContentService ( ) const override {
if ( parent - > m_platformServices )
return parent - > m_platformServices - > userGeneratedContentService ( ) ;
return { } ;
}
DesktopServicePtr desktopService ( ) const override {
if ( parent - > m_platformServices )
return parent - > m_platformServices - > desktopService ( ) ;
return { } ;
}
void quit ( ) override {
parent - > m_quitRequested = true ;
}
SdlPlatform * parent ;
} ;
List < InputEvent > processEvents ( ) {
List < InputEvent > inputEvents ;
SDL_Event event ;
while ( SDL_PollEvent ( & event ) ) {
Maybe < InputEvent > starEvent ;
2023-06-28 12:52:09 +00:00
switch ( event . type ) {
case SDL_WINDOWEVENT :
2023-06-20 04:33:09 +00:00
if ( event . window . event = = SDL_WINDOWEVENT_MAXIMIZED | | event . window . event = = SDL_WINDOWEVENT_RESTORED ) {
auto windowFlags = SDL_GetWindowFlags ( m_sdlWindow ) ;
2023-06-28 15:12:52 +00:00
if ( windowFlags & SDL_WINDOW_MAXIMIZED )
2023-06-20 04:33:09 +00:00
m_windowMode = WindowMode : : Maximized ;
2023-06-28 15:12:52 +00:00
else if ( windowFlags & SDL_WINDOW_FULLSCREEN )
m_windowMode = WindowMode : : Fullscreen ;
else if ( windowFlags & SDL_WINDOW_BORDERLESS )
m_windowMode = WindowMode : : Borderless ;
else
2023-06-20 04:33:09 +00:00
m_windowMode = WindowMode : : Normal ;
m_application - > windowChanged ( m_windowMode , m_windowSize ) ;
} else if ( event . window . event = = SDL_WINDOWEVENT_RESIZED | | event . window . event = = SDL_WINDOWEVENT_SIZE_CHANGED ) {
m_windowSize = Vec2U ( event . window . data1 , event . window . data2 ) ;
m_renderer - > setScreenSize ( m_windowSize ) ;
m_application - > windowChanged ( m_windowMode , m_windowSize ) ;
}
2023-06-28 12:52:09 +00:00
break ;
case SDL_KEYDOWN :
2023-06-20 04:33:09 +00:00
if ( ! event . key . repeat ) {
if ( auto key = keyFromSdlKeyCode ( event . key . keysym . sym ) )
starEvent . set ( KeyDownEvent { * key , keyModsFromSdlKeyMods ( event . key . keysym . mod ) } ) ;
}
2023-06-28 12:52:09 +00:00
break ;
case SDL_KEYUP :
2023-06-20 04:33:09 +00:00
if ( auto key = keyFromSdlKeyCode ( event . key . keysym . sym ) )
starEvent . set ( KeyUpEvent { * key } ) ;
2023-06-28 12:52:09 +00:00
break ;
case SDL_TEXTINPUT :
2023-06-20 04:33:09 +00:00
starEvent . set ( TextInputEvent { String ( event . text . text ) } ) ;
2023-06-28 12:52:09 +00:00
break ;
case SDL_MOUSEMOTION :
2023-06-20 04:33:09 +00:00
starEvent . set ( MouseMoveEvent {
{ event . motion . xrel , - event . motion . yrel } , { event . motion . x , ( int ) m_windowSize [ 1 ] - event . motion . y } } ) ;
2023-06-28 12:52:09 +00:00
break ;
case SDL_MOUSEBUTTONDOWN :
2023-06-20 04:33:09 +00:00
starEvent . set ( MouseButtonDownEvent { mouseButtonFromSdlMouseButton ( event . button . button ) ,
{ event . button . x , ( int ) m_windowSize [ 1 ] - event . button . y } } ) ;
2023-06-28 12:52:09 +00:00
break ;
case SDL_MOUSEBUTTONUP :
2023-06-20 04:33:09 +00:00
starEvent . set ( MouseButtonUpEvent { mouseButtonFromSdlMouseButton ( event . button . button ) ,
{ event . button . x , ( int ) m_windowSize [ 1 ] - event . button . y } } ) ;
2023-06-28 12:52:09 +00:00
break ;
case SDL_MOUSEWHEEL :
2023-06-20 04:33:09 +00:00
int x , y ;
SDL_GetMouseState ( & x , & y ) ;
starEvent . set ( MouseWheelEvent { event . wheel . y < 0 ? MouseWheel : : Down : MouseWheel : : Up , { x , ( int ) m_windowSize [ 1 ] - y } } ) ;
2023-06-28 12:52:09 +00:00
break ;
case SDL_CONTROLLERAXISMOTION :
starEvent . set ( ControllerAxisEvent {
( ControllerId ) event . caxis . which ,
controllerAxisFromSdlControllerAxis ( event . caxis . axis ) ,
( float ) event . caxis . value / 32768.0f
} ) ;
break ;
case SDL_CONTROLLERBUTTONDOWN :
starEvent . set ( ControllerButtonDownEvent { ( ControllerId ) event . cbutton . which , controllerButtonFromSdlControllerButton ( event . cbutton . button ) } ) ;
break ;
case SDL_CONTROLLERBUTTONUP :
starEvent . set ( ControllerButtonUpEvent { ( ControllerId ) event . cbutton . which , controllerButtonFromSdlControllerButton ( event . cbutton . button ) } ) ;
break ;
case SDL_CONTROLLERDEVICEADDED :
{
auto insertion = m_SdlControllers . insert_or_assign ( event . cdevice . which , SDLGameControllerUPtr ( SDL_GameControllerOpen ( event . cdevice . which ) , SDL_GameControllerClose ) ) ;
if ( SDL_GameController * controller = insertion . first - > second . get ( ) )
Logger : : info ( " Controller device '{}' added " , SDL_GameControllerName ( controller ) ) ;
}
break ;
case SDL_CONTROLLERDEVICEREMOVED :
{
auto find = m_SdlControllers . find ( event . cdevice . which ) ;
if ( find ! = m_SdlControllers . end ( ) ) {
if ( SDL_GameController * controller = find - > second . get ( ) )
Logger : : info ( " Controller device '{}' removed " , SDL_GameControllerName ( controller ) ) ;
m_SdlControllers . erase ( event . cdevice . which ) ;
}
}
break ;
case SDL_QUIT :
2023-06-20 04:33:09 +00:00
m_quitRequested = true ;
starEvent . reset ( ) ;
2023-06-28 12:52:09 +00:00
break ;
2023-06-20 04:33:09 +00:00
}
if ( starEvent )
inputEvents . append ( starEvent . take ( ) ) ;
}
return inputEvents ;
}
void getAudioData ( Uint8 * stream , int len ) {
if ( m_audioEnabled ) {
m_application - > getAudioData ( ( int16_t * ) stream , len / 4 ) ;
} else {
for ( int i = 0 ; i < len ; + + i )
stream [ i ] = 0 ;
}
}
void setVSyncEnabled ( bool vsyncEnabled ) {
if ( vsyncEnabled ) {
// If VSync is requested, try for late swap tearing first, then fall back
// to regular VSync
Logger : : info ( " Application: Enabling VSync with late swap tearing " ) ;
if ( SDL_GL_SetSwapInterval ( - 1 ) < 0 ) {
Logger : : info ( " Application: Enabling VSync late swap tearing failed, falling back to full VSync " ) ;
SDL_GL_SetSwapInterval ( 1 ) ;
}
} else {
Logger : : info ( " Application: Disabling VSync " ) ;
SDL_GL_SetSwapInterval ( 0 ) ;
}
}
2023-06-23 08:13:26 +00:00
static const size_t MaximumCursorDimensions = 128 ;
static const size_t MaximumCursorPixelCount = MaximumCursorDimensions * MaximumCursorDimensions ;
bool setCursorImage ( const String & id , const ImageConstPtr & image , unsigned scale , const Vec2I & offset ) {
auto imageSize = image - > size ( ) . piecewiseMultiply ( Vec2U : : filled ( scale ) ) ;
if ( ! scale | | imageSize . max ( ) > MaximumCursorDimensions | | ( size_t ) ( imageSize [ 0 ] * imageSize [ 1 ] ) > MaximumCursorPixelCount )
return m_cursorVisible = false ;
auto & entry = m_cursorCache . get ( m_currentCursor = { scale , offset , id } , [ & ] ( auto const & ) {
auto entry = std : : make_shared < CursorEntry > ( ) ;
2023-06-26 08:05:00 +00:00
List < ImageOperation > operations ;
if ( scale ! = 1 )
operations = {
FlipImageOperation { FlipImageOperation : : Mode : : FlipY } , // SDL wants an Australian cursor.
BorderImageOperation { 1 , Vec4B ( ) , Vec4B ( ) , false , false } , // Nearest scaling fucks up and clips half off the edges, work around this with border+crop for now.
ScaleImageOperation { ScaleImageOperation : : Mode : : Nearest , Vec2F : : filled ( scale ) } ,
CropImageOperation { RectI : : withSize ( Vec2I : : filled ( ceilf ( ( float ) scale / 2 ) ) , Vec2I ( imageSize ) ) }
2023-06-23 08:13:26 +00:00
} ;
else
2023-06-26 08:05:00 +00:00
operations = { FlipImageOperation { FlipImageOperation : : Mode : : FlipY } } ;
auto newImage = std : : make_shared < Image > ( move ( processImageOperations ( operations , * image ) ) ) ;
// Fix fully transparent pixels inverting the underlying display pixel on Windows (allowing this could be made configurable per cursor later!)
newImage - > forEachPixel ( [ ] ( unsigned x , unsigned y , Vec4B & pixel ) { if ( ! pixel [ 3 ] ) pixel [ 0 ] = pixel [ 1 ] = pixel [ 2 ] = 0 ; } ) ;
entry - > image = move ( newImage ) ;
2023-06-23 08:13:26 +00:00
auto size = entry - > image - > size ( ) ;
uint32_t pixelFormat ;
switch ( entry - > image - > pixelFormat ( ) ) {
case PixelFormat : : RGB24 : // I know this conversion looks wrong, but it's correct. I'm confused too.
pixelFormat = SDL_PIXELFORMAT_BGR888 ;
break ;
case PixelFormat : : RGBA32 :
pixelFormat = SDL_PIXELFORMAT_ABGR8888 ;
break ;
case PixelFormat : : BGR24 :
pixelFormat = SDL_PIXELFORMAT_RGB888 ;
break ;
case PixelFormat : : BGRA32 :
pixelFormat = SDL_PIXELFORMAT_ARGB8888 ;
break ;
default :
pixelFormat = SDL_PIXELFORMAT_UNKNOWN ;
}
entry - > sdlSurface . reset ( SDL_CreateRGBSurfaceWithFormatFrom (
( void * ) entry - > image - > data ( ) ,
size [ 0 ] , size [ 1 ] ,
entry - > image - > bitsPerPixel ( ) ,
entry - > image - > bytesPerPixel ( ) * size [ 0 ] ,
pixelFormat )
) ;
entry - > sdlCursor . reset ( SDL_CreateColorCursor ( entry - > sdlSurface . get ( ) , offset [ 0 ] * scale , offset [ 1 ] * scale ) ) ;
return entry ;
} ) ;
SDL_SetCursor ( entry - > sdlCursor . get ( ) ) ;
return m_cursorVisible = true ;
}
2023-06-20 04:33:09 +00:00
SignalHandler m_signalHandler ;
TickRateApproacher m_updateTicker = TickRateApproacher ( 60.0f , 1.0f ) ;
float m_updateRate = 0.0f ;
TickRateMonitor m_renderTicker = TickRateMonitor ( 1.0f ) ;
float m_renderRate = 0.0f ;
SDL_Window * m_sdlWindow = nullptr ;
SDL_GLContext m_sdlGlContext = nullptr ;
2023-07-13 09:12:55 +00:00
SDL_AudioDeviceID m_sdlAudioOutputDevice = 0 ;
SDL_AudioDeviceID m_sdlAudioInputDevice = 0 ;
2023-06-23 08:13:26 +00:00
2023-06-28 12:52:09 +00:00
typedef std : : unique_ptr < SDL_GameController , decltype ( & SDL_GameControllerClose ) > SDLGameControllerUPtr ;
StableHashMap < int , SDLGameControllerUPtr > m_SdlControllers ;
2023-06-23 08:13:26 +00:00
typedef std : : unique_ptr < SDL_Surface , decltype ( & SDL_FreeSurface ) > SDLSurfaceUPtr ;
typedef std : : unique_ptr < SDL_Cursor , decltype ( & SDL_FreeCursor ) > SDLCursorUPtr ;
struct CursorEntry {
ImageConstPtr image = nullptr ;
SDLSurfaceUPtr sdlSurface ;
SDLCursorUPtr sdlCursor ;
CursorEntry ( ) : image ( nullptr ) , sdlSurface ( nullptr , SDL_FreeSurface ) , sdlCursor ( nullptr , SDL_FreeCursor ) { } ;
} ;
typedef tuple < unsigned , Vec2I , String > CursorDescriptor ;
HashTtlCache < CursorDescriptor , std : : shared_ptr < CursorEntry > > m_cursorCache ;
CursorDescriptor m_currentCursor ;
2023-06-20 04:33:09 +00:00
Vec2U m_windowSize = { 800 , 600 } ;
WindowMode m_windowMode = WindowMode : : Normal ;
2023-06-23 05:45:03 +00:00
String m_windowTitle = " Starbound " ;
2023-06-20 04:33:09 +00:00
bool m_windowVSync = true ;
unsigned m_maxFrameSkip = 5 ;
bool m_cursorVisible = true ;
bool m_acceptingTextInput = false ;
bool m_audioEnabled = false ;
bool m_quitRequested = false ;
OpenGl20RendererPtr m_renderer ;
ApplicationUPtr m_application ;
PcPlatformServicesUPtr m_platformServices ;
} ;
int runMainApplication ( ApplicationUPtr application , StringList cmdLineArgs ) {
try {
{
SdlPlatform platform ( move ( application ) , move ( cmdLineArgs ) ) ;
platform . run ( ) ;
}
Logger : : info ( " Application: stopped gracefully " ) ;
return 0 ;
} catch ( std : : exception const & e ) {
fatalException ( e , true ) ;
} catch ( . . . ) {
fatalError ( " Unknown Exception " , true ) ;
}
return 1 ;
}
}