2023-04-30 00:03:50 +02:00
/*
2023-05-08 12:22:01 +02:00
* ScreenHandler . cpp , part of VCMI engine
2023-04-30 00:03:50 +02:00
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
2023-05-08 12:22:01 +02:00
# include "ScreenHandler.h"
2023-04-30 00:03:50 +02:00
# include "../../lib/CConfigHandler.h"
2024-02-11 23:09:01 +02:00
# include "../../lib/constants/StringConstants.h"
2023-04-30 00:03:50 +02:00
# include "../gui/CGuiHandler.h"
2023-05-18 19:32:29 +02:00
# include "../eventsSDL/NotificationHandler.h"
2023-05-16 14:10:26 +02:00
# include "../gui/WindowHandler.h"
2023-04-30 00:03:50 +02:00
# include "CMT.h"
# include "SDL_Extensions.h"
2023-04-30 00:38:28 +02:00
# ifdef VCMI_ANDROID
# include "../lib/CAndroidVMHelper.h"
# endif
2023-07-05 17:14:37 +02:00
# ifdef VCMI_IOS
# include "ios / utils.h"
# endif
2023-04-30 00:03:50 +02:00
# include <SDL.h>
2023-05-08 12:22:01 +02:00
// TODO: should be made into a private members of ScreenHandler
2023-07-05 16:17:43 +02:00
static SDL_Window * mainWindow = nullptr ;
2023-04-30 00:38:28 +02:00
SDL_Renderer * mainRenderer = nullptr ;
SDL_Texture * screenTexture = nullptr ;
SDL_Surface * screen = nullptr ; //main screen surface
SDL_Surface * screen2 = nullptr ; //and hlp surface (used to store not-active interfaces layer)
SDL_Surface * screenBuf = screen ; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
2024-04-25 17:53:12 +02:00
static const std : : string NAME = GameConstants : : VCMI_VERSION ; //application name
2024-08-03 22:14:51 +02:00
static constexpr Point heroes3Resolution = Point ( 800 , 600 ) ;
2023-04-30 00:03:50 +02:00
2023-05-08 12:22:01 +02:00
std : : tuple < int , int > ScreenHandler : : getSupportedScalingRange ( ) const
2023-04-30 00:03:50 +02:00
{
2023-04-30 18:20:40 +02:00
// H3 resolution, any resolution smaller than that is not correctly supported
2024-08-03 22:14:51 +02:00
static constexpr Point minResolution = heroes3Resolution ;
2023-04-30 18:20:40 +02:00
// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
2024-08-03 22:14:51 +02:00
static constexpr double minimalScaling = 50 ;
2023-04-30 18:20:40 +02:00
2023-07-13 19:11:08 +02:00
Point renderResolution = getRenderResolution ( ) ;
2023-07-05 16:17:43 +02:00
double reservedAreaWidth = settings [ " video " ] [ " reservedWidth " ] . Float ( ) ;
Point availableResolution = Point ( renderResolution . x * ( 1 - reservedAreaWidth ) , renderResolution . y ) ;
double maximalScalingWidth = 100.0 * availableResolution . x / minResolution . x ;
double maximalScalingHeight = 100.0 * availableResolution . y / minResolution . y ;
2023-04-30 18:20:40 +02:00
double maximalScaling = std : : min ( maximalScalingWidth , maximalScalingHeight ) ;
2023-05-04 22:50:33 +02:00
return { minimalScaling , maximalScaling } ;
}
2023-07-05 16:17:01 +02:00
Rect ScreenHandler : : convertLogicalPointsToWindow ( const Rect & input ) const
{
Rect result ;
// FIXME: use SDL_RenderLogicalToWindow instead? Needs to be tested on ios
float scaleX , scaleY ;
SDL_Rect viewport ;
SDL_RenderGetScale ( mainRenderer , & scaleX , & scaleY ) ;
SDL_RenderGetViewport ( mainRenderer , & viewport ) ;
# ifdef VCMI_IOS
// TODO ios: looks like SDL bug actually, try fixing there
const auto nativeScale = iOS_utils : : screenScale ( ) ;
scaleX / = nativeScale ;
scaleY / = nativeScale ;
# endif
result . x = ( viewport . x + input . x ) * scaleX ;
result . y = ( viewport . y + input . y ) * scaleY ;
result . w = input . w * scaleX ;
result . h = input . h * scaleY ;
return result ;
}
2024-10-09 19:37:47 +02:00
int ScreenHandler : : getInterfaceScalingPercentage ( ) const
2023-05-04 22:50:33 +02:00
{
auto [ minimalScaling , maximalScaling ] = getSupportedScalingRange ( ) ;
2023-05-08 12:22:01 +02:00
int userScaling = settings [ " video " ] [ " resolution " ] [ " scaling " ] . Integer ( ) ;
2024-10-01 20:50:41 +02:00
if ( userScaling = = 0 ) // autodetection
{
# ifdef VCMI_MOBILE
// for mobiles - stay at maximum scaling unless we have large screen
// might be better to check screen DPI / physical dimensions, but way more complex, and may result in different edge cases, e.g. chromebooks / tv's
int preferredMinimalScaling = 200 ;
# else
// for PC - avoid downscaling if possible
int preferredMinimalScaling = 100 ;
# endif
// prefer a little below maximum - to give space for extended UI
int preferredMaximalScaling = maximalScaling * 10 / 12 ;
userScaling = std : : max ( std : : min ( maximalScaling , preferredMinimalScaling ) , preferredMaximalScaling ) ;
}
2023-05-08 12:22:01 +02:00
int scaling = std : : clamp ( userScaling , minimalScaling , maximalScaling ) ;
2024-10-09 19:37:47 +02:00
return scaling ;
}
2023-04-30 18:20:40 +02:00
2024-10-09 19:37:47 +02:00
Point ScreenHandler : : getPreferredLogicalResolution ( ) const
{
Point renderResolution = getRenderResolution ( ) ;
double reservedAreaWidth = settings [ " video " ] [ " reservedWidth " ] . Float ( ) ;
2023-04-30 18:20:40 +02:00
2024-10-09 19:37:47 +02:00
int scaling = getInterfaceScalingPercentage ( ) ;
Point availableResolution = Point ( renderResolution . x * ( 1 - reservedAreaWidth ) , renderResolution . y ) ;
Point logicalResolution = availableResolution * 100.0 / scaling ;
2023-04-30 18:20:40 +02:00
return logicalResolution ;
2023-04-30 00:03:50 +02:00
}
2024-07-21 23:11:02 +02:00
int ScreenHandler : : getScalingFactor ( ) const
{
2024-08-03 22:14:51 +02:00
switch ( upscalingFilter )
{
case EUpscalingFilter : : NONE : return 1 ;
case EUpscalingFilter : : XBRZ_2 : return 2 ;
case EUpscalingFilter : : XBRZ_3 : return 3 ;
case EUpscalingFilter : : XBRZ_4 : return 4 ;
}
throw std : : runtime_error ( " invalid upscaling filter " ) ;
2024-07-21 23:11:02 +02:00
}
Point ScreenHandler : : getLogicalResolution ( ) const
{
return Point ( screen - > w , screen - > h ) / getScalingFactor ( ) ;
}
2023-07-13 19:11:08 +02:00
Point ScreenHandler : : getRenderResolution ( ) const
2023-07-05 16:17:43 +02:00
{
assert ( mainRenderer ! = nullptr ) ;
Point result ;
SDL_GetRendererOutputSize ( mainRenderer , & result . x , & result . y ) ;
return result ;
}
Point ScreenHandler : : getPreferredWindowResolution ( ) const
2023-04-30 00:03:50 +02:00
{
2023-05-08 12:22:01 +02:00
if ( getPreferredWindowMode ( ) = = EWindowMode : : FULLSCREEN_BORDERLESS_WINDOWED )
2023-04-30 00:03:50 +02:00
{
SDL_Rect bounds ;
2023-05-08 18:29:56 +02:00
if ( SDL_GetDisplayBounds ( getPreferredDisplayIndex ( ) , & bounds ) = = 0 )
return Point ( bounds . w , bounds . h ) ;
2023-04-30 00:03:50 +02:00
}
2023-05-08 18:29:56 +02:00
const JsonNode & video = settings [ " video " ] ;
int width = video [ " resolution " ] [ " width " ] . Integer ( ) ;
int height = video [ " resolution " ] [ " height " ] . Integer ( ) ;
return Point ( width , height ) ;
2023-04-30 00:03:50 +02:00
}
2023-05-08 12:22:01 +02:00
int ScreenHandler : : getPreferredDisplayIndex ( ) const
2023-04-30 00:03:50 +02:00
{
# ifdef VCMI_MOBILE
// Assuming no multiple screens on Android / ios?
return 0 ;
# else
if ( mainWindow ! = nullptr )
2023-05-08 18:29:56 +02:00
{
int result = SDL_GetWindowDisplayIndex ( mainWindow ) ;
if ( result > = 0 )
return result ;
}
return settings [ " video " ] [ " displayIndex " ] . Integer ( ) ;
2023-04-30 00:03:50 +02:00
# endif
}
2023-05-08 12:22:01 +02:00
EWindowMode ScreenHandler : : getPreferredWindowMode ( ) const
2023-04-30 00:03:50 +02:00
{
# ifdef VCMI_MOBILE
// On Android / ios game will always render to screen size
2023-05-08 12:22:01 +02:00
return EWindowMode : : FULLSCREEN_BORDERLESS_WINDOWED ;
2023-04-30 00:03:50 +02:00
# else
const JsonNode & video = settings [ " video " ] ;
bool fullscreen = video [ " fullscreen " ] . Bool ( ) ;
bool realFullscreen = settings [ " video " ] [ " realFullscreen " ] . Bool ( ) ;
if ( ! fullscreen )
return EWindowMode : : WINDOWED ;
if ( realFullscreen )
2023-05-08 12:22:01 +02:00
return EWindowMode : : FULLSCREEN_EXCLUSIVE ;
2023-04-30 00:03:50 +02:00
else
2023-05-08 12:22:01 +02:00
return EWindowMode : : FULLSCREEN_BORDERLESS_WINDOWED ;
2023-04-30 00:03:50 +02:00
# endif
}
2023-05-08 12:22:01 +02:00
ScreenHandler : : ScreenHandler ( )
2023-04-30 00:03:50 +02:00
{
2023-05-07 22:24:49 +02:00
# ifdef VCMI_WINDOWS
// set VCMI as "per-monitor DPI awareness". This completely disables any DPI-scaling by system.
// Might not be the best solution since VCMI can't automatically adjust to DPI changes (including moving to monitors with different DPI scaling)
// However this fixed unintuitive bug where player selects specific resolution for windowed mode, but ends up with completely different one due to scaling
// NOTE: requires SDL 2.24.
SDL_SetHint ( SDL_HINT_WINDOWS_DPI_AWARENESS , " permonitor " ) ;
# endif
2024-04-03 14:34:22 +02:00
if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER ) )
2023-04-30 00:03:50 +02:00
{
logGlobal - > error ( " Something was wrong: %s " , SDL_GetError ( ) ) ;
exit ( - 1 ) ;
}
const auto & logCallback = [ ] ( void * userdata , int category , SDL_LogPriority priority , const char * message )
{
logGlobal - > debug ( " SDL(category %d; priority %d) %s " , category , priority , message ) ;
} ;
SDL_LogSetOutputFunction ( logCallback , nullptr ) ;
# ifdef VCMI_ANDROID
// manually setting egl pixel format, as a possible solution for sdl2<->android problem
// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
SDL_GL_SetAttribute ( SDL_GL_RED_SIZE , 5 ) ;
SDL_GL_SetAttribute ( SDL_GL_GREEN_SIZE , 6 ) ;
SDL_GL_SetAttribute ( SDL_GL_BLUE_SIZE , 5 ) ;
SDL_GL_SetAttribute ( SDL_GL_DEPTH_SIZE , 0 ) ;
# endif // VCMI_ANDROID
validateSettings ( ) ;
2023-05-08 12:22:01 +02:00
recreateWindowAndScreenBuffers ( ) ;
2023-04-30 00:03:50 +02:00
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : recreateWindowAndScreenBuffers ( )
2023-04-30 00:03:50 +02:00
{
2023-05-08 12:22:01 +02:00
destroyScreenBuffers ( ) ;
2023-04-30 00:03:50 +02:00
if ( mainWindow = = nullptr )
2023-05-08 12:22:01 +02:00
initializeWindow ( ) ;
2023-04-30 00:03:50 +02:00
else
2023-05-08 12:22:01 +02:00
updateWindowState ( ) ;
2023-04-30 00:03:50 +02:00
2023-05-08 12:22:01 +02:00
initializeScreenBuffers ( ) ;
2023-04-30 00:03:50 +02:00
if ( ! settings [ " session " ] [ " headless " ] . Bool ( ) & & settings [ " general " ] [ " notifications " ] . Bool ( ) )
{
NotificationHandler : : init ( mainWindow ) ;
}
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : updateWindowState ( )
2023-04-30 00:03:50 +02:00
{
2023-05-08 18:29:56 +02:00
# ifndef VCMI_MOBILE
2023-04-30 00:03:50 +02:00
int displayIndex = getPreferredDisplayIndex ( ) ;
switch ( getPreferredWindowMode ( ) )
{
2023-05-08 12:22:01 +02:00
case EWindowMode : : FULLSCREEN_EXCLUSIVE :
2023-04-30 00:03:50 +02:00
{
2023-07-05 17:12:41 +02:00
// for some reason, VCMI fails to switch from FULLSCREEN_BORDERLESS_WINDOWED to FULLSCREEN_EXCLUSIVE directly
// Switch to windowed mode first to avoid this bug
SDL_SetWindowFullscreen ( mainWindow , 0 ) ;
2023-04-30 00:03:50 +02:00
SDL_SetWindowFullscreen ( mainWindow , SDL_WINDOW_FULLSCREEN ) ;
SDL_DisplayMode mode ;
SDL_GetDesktopDisplayMode ( displayIndex , & mode ) ;
2023-07-05 16:17:43 +02:00
Point resolution = getPreferredWindowResolution ( ) ;
2023-04-30 00:03:50 +02:00
mode . w = resolution . x ;
mode . h = resolution . y ;
SDL_SetWindowDisplayMode ( mainWindow , & mode ) ;
SDL_SetWindowPosition ( mainWindow , SDL_WINDOWPOS_UNDEFINED_DISPLAY ( displayIndex ) , SDL_WINDOWPOS_UNDEFINED_DISPLAY ( displayIndex ) ) ;
return ;
}
2023-05-08 12:22:01 +02:00
case EWindowMode : : FULLSCREEN_BORDERLESS_WINDOWED :
2023-04-30 00:03:50 +02:00
{
SDL_SetWindowFullscreen ( mainWindow , SDL_WINDOW_FULLSCREEN_DESKTOP ) ;
SDL_SetWindowPosition ( mainWindow , SDL_WINDOWPOS_UNDEFINED_DISPLAY ( displayIndex ) , SDL_WINDOWPOS_UNDEFINED_DISPLAY ( displayIndex ) ) ;
return ;
}
case EWindowMode : : WINDOWED :
{
2023-07-05 16:17:43 +02:00
Point resolution = getPreferredWindowResolution ( ) ;
2023-04-30 00:03:50 +02:00
SDL_SetWindowFullscreen ( mainWindow , 0 ) ;
SDL_SetWindowSize ( mainWindow , resolution . x , resolution . y ) ;
SDL_SetWindowPosition ( mainWindow , SDL_WINDOWPOS_CENTERED_DISPLAY ( displayIndex ) , SDL_WINDOWPOS_CENTERED_DISPLAY ( displayIndex ) ) ;
return ;
}
}
# endif
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : initializeWindow ( )
2023-04-30 00:03:50 +02:00
{
mainWindow = createWindow ( ) ;
if ( mainWindow = = nullptr )
2023-08-06 12:31:10 +02:00
{
const char * error = SDL_GetError ( ) ;
Point dimensions = getPreferredWindowResolution ( ) ;
std : : string messagePattern = " Failed to create SDL Window of size %d x %d. Reason: %s " ;
std : : string message = boost : : str ( boost : : format ( messagePattern ) % dimensions . x % dimensions . y % error ) ;
2023-08-06 17:12:36 +02:00
handleFatalError ( message , true ) ;
2023-08-06 12:31:10 +02:00
}
2023-04-30 00:03:50 +02:00
2023-09-26 16:06:01 +02:00
// create first available renderer if no preferred one is set
// use no SDL_RENDERER_SOFTWARE or SDL_RENDERER_ACCELERATED flag, so HW accelerated will be preferred but SW renderer will also be possible
uint32_t rendererFlags = 0 ;
if ( settings [ " video " ] [ " vsync " ] . Bool ( ) )
{
rendererFlags | = SDL_RENDERER_PRESENTVSYNC ;
}
mainRenderer = SDL_CreateRenderer ( mainWindow , getPreferredRenderingDriver ( ) , rendererFlags ) ;
2023-04-30 00:03:50 +02:00
if ( mainRenderer = = nullptr )
2024-01-05 21:46:44 +02:00
{
const char * error = SDL_GetError ( ) ;
std : : string messagePattern = " Failed to create SDL renderer. Reason: %s " ;
std : : string message = boost : : str ( boost : : format ( messagePattern ) % error ) ;
handleFatalError ( message , true ) ;
}
2023-04-30 00:03:50 +02:00
2024-08-03 22:14:51 +02:00
selectUpscalingFilter ( ) ;
selectDownscalingFilter ( ) ;
2023-04-30 00:03:50 +02:00
SDL_RendererInfo info ;
SDL_GetRendererInfo ( mainRenderer , & info ) ;
logGlobal - > info ( " Created renderer %s " , info . name ) ;
}
2024-08-03 22:14:51 +02:00
EUpscalingFilter ScreenHandler : : loadUpscalingFilter ( ) const
{
static const std : : map < std : : string , EUpscalingFilter > upscalingFilterTypes =
{
{ " auto " , EUpscalingFilter : : AUTO } ,
{ " none " , EUpscalingFilter : : NONE } ,
{ " xbrz2 " , EUpscalingFilter : : XBRZ_2 } ,
{ " xbrz3 " , EUpscalingFilter : : XBRZ_3 } ,
{ " xbrz4 " , EUpscalingFilter : : XBRZ_4 }
} ;
auto filterName = settings [ " video " ] [ " upscalingFilter " ] . String ( ) ;
auto filter = upscalingFilterTypes . at ( filterName ) ;
if ( filter ! = EUpscalingFilter : : AUTO )
return filter ;
2024-08-17 21:11:30 +02:00
// else - autoselect
2024-10-01 20:50:23 +02:00
Point outputResolution = getRenderResolution ( ) ;
Point logicalResolution = getPreferredLogicalResolution ( ) ;
float scaleX = static_cast < float > ( outputResolution . x ) / logicalResolution . x ;
float scaleY = static_cast < float > ( outputResolution . x ) / logicalResolution . x ;
float scaling = std : : min ( scaleX , scaleY ) ;
if ( scaling < = 1.001f )
return EUpscalingFilter : : NONE ; // running at original resolution or even lower than that - no need for xbrz
if ( scaling < = 2.001f )
return EUpscalingFilter : : XBRZ_2 ; // resolutions below 1200p (including 1080p / FullHD)
if ( scaling < = 3.001f )
return EUpscalingFilter : : XBRZ_3 ; // resolutions below 2400p (including 1440p and 2160p / 4K)
return EUpscalingFilter : : XBRZ_4 ; // Only for massive displays, e.g. 8K
2024-08-03 22:14:51 +02:00
}
void ScreenHandler : : selectUpscalingFilter ( )
{
upscalingFilter = loadUpscalingFilter ( ) ;
logGlobal - > debug ( " Selected upscaling filter %d " , static_cast < int > ( upscalingFilter ) ) ;
}
void ScreenHandler : : selectDownscalingFilter ( )
{
SDL_SetHint ( SDL_HINT_RENDER_SCALE_QUALITY , settings [ " video " ] [ " downscalingFilter " ] . String ( ) . c_str ( ) ) ;
logGlobal - > debug ( " Selected downscaling filter %s " , settings [ " video " ] [ " downscalingFilter " ] . String ( ) ) ;
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : initializeScreenBuffers ( )
2023-04-30 00:03:50 +02:00
{
# ifdef VCMI_ENDIAN_BIG
int bmask = 0xff000000 ;
int gmask = 0x00ff0000 ;
int rmask = 0x0000ff00 ;
int amask = 0x000000ff ;
# else
int bmask = 0x000000ff ;
int gmask = 0x0000ff00 ;
int rmask = 0x00ff0000 ;
int amask = 0xFF000000 ;
# endif
2024-08-03 22:14:51 +02:00
auto logicalSize = getPreferredLogicalResolution ( ) * getScalingFactor ( ) ;
2023-04-30 00:03:50 +02:00
SDL_RenderSetLogicalSize ( mainRenderer , logicalSize . x , logicalSize . y ) ;
screen = SDL_CreateRGBSurface ( 0 , logicalSize . x , logicalSize . y , 32 , rmask , gmask , bmask , amask ) ;
if ( nullptr = = screen )
{
logGlobal - > error ( " Unable to create surface %dx%d with %d bpp: %s " , logicalSize . x , logicalSize . y , 32 , SDL_GetError ( ) ) ;
throw std : : runtime_error ( " Unable to create surface " ) ;
}
//No blending for screen itself. Required for proper cursor rendering.
SDL_SetSurfaceBlendMode ( screen , SDL_BLENDMODE_NONE ) ;
screenTexture = SDL_CreateTexture ( mainRenderer , SDL_PIXELFORMAT_ARGB8888 , SDL_TEXTUREACCESS_STREAMING , logicalSize . x , logicalSize . y ) ;
if ( nullptr = = screenTexture )
{
logGlobal - > error ( " Unable to create screen texture " ) ;
logGlobal - > error ( SDL_GetError ( ) ) ;
throw std : : runtime_error ( " Unable to create screen texture " ) ;
}
screen2 = CSDL_Ext : : copySurface ( screen ) ;
if ( nullptr = = screen2 )
{
throw std : : runtime_error ( " Unable to copy surface \n " ) ;
}
2023-05-16 15:07:03 +02:00
if ( GH . windows ( ) . count ( ) > 1 )
2023-05-07 23:45:47 +02:00
screenBuf = screen2 ;
else
screenBuf = screen ;
2023-04-30 00:38:28 +02:00
clearScreen ( ) ;
2023-04-30 00:03:50 +02:00
}
2023-05-08 12:22:01 +02:00
SDL_Window * ScreenHandler : : createWindowImpl ( Point dimensions , int flags , bool center )
2023-04-30 00:03:50 +02:00
{
2023-04-30 00:38:28 +02:00
int displayIndex = getPreferredDisplayIndex ( ) ;
2023-04-30 00:03:50 +02:00
int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY ( displayIndex ) : SDL_WINDOWPOS_UNDEFINED_DISPLAY ( displayIndex ) ;
return SDL_CreateWindow ( NAME . c_str ( ) , positionFlags , positionFlags , dimensions . x , dimensions . y , flags ) ;
}
2023-05-08 12:22:01 +02:00
SDL_Window * ScreenHandler : : createWindow ( )
2023-04-30 00:03:50 +02:00
{
# ifndef VCMI_MOBILE
2023-07-05 16:17:43 +02:00
Point dimensions = getPreferredWindowResolution ( ) ;
2023-04-30 00:03:50 +02:00
2023-04-30 00:38:28 +02:00
switch ( getPreferredWindowMode ( ) )
{
2023-05-08 12:22:01 +02:00
case EWindowMode : : FULLSCREEN_EXCLUSIVE :
2023-04-30 00:38:28 +02:00
return createWindowImpl ( dimensions , SDL_WINDOW_FULLSCREEN , false ) ;
2023-05-08 12:22:01 +02:00
case EWindowMode : : FULLSCREEN_BORDERLESS_WINDOWED :
2023-04-30 00:38:28 +02:00
return createWindowImpl ( Point ( ) , SDL_WINDOW_FULLSCREEN_DESKTOP , false ) ;
case EWindowMode : : WINDOWED :
2023-05-08 00:07:45 +02:00
return createWindowImpl ( dimensions , SDL_WINDOW_RESIZABLE , true ) ;
2023-04-30 00:03:50 +02:00
2023-04-30 00:38:28 +02:00
default :
return nullptr ;
} ;
2023-04-30 00:03:50 +02:00
# endif
# ifdef VCMI_IOS
SDL_SetHint ( SDL_HINT_IOS_HIDE_HOME_INDICATOR , " 1 " ) ;
SDL_SetHint ( SDL_HINT_RETURN_KEY_HIDES_IME , " 1 " ) ;
uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI ;
2023-04-30 00:38:28 +02:00
SDL_Window * result = createWindowImpl ( Point ( ) , windowFlags | SDL_WINDOW_METAL , false ) ;
2023-04-30 00:03:50 +02:00
if ( result ! = nullptr )
return result ;
logGlobal - > warn ( " Metal unavailable, using OpenGLES " ) ;
2023-04-30 00:38:28 +02:00
return createWindowImpl ( Point ( ) , windowFlags , false ) ;
2023-04-30 00:03:50 +02:00
# endif
# ifdef VCMI_ANDROID
2023-04-30 00:38:28 +02:00
return createWindowImpl ( Point ( ) , SDL_WINDOW_FULLSCREEN , false ) ;
2023-04-30 00:03:50 +02:00
# endif
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : onScreenResize ( )
2023-04-30 00:03:50 +02:00
{
2023-05-08 12:22:01 +02:00
recreateWindowAndScreenBuffers ( ) ;
2023-04-30 00:03:50 +02:00
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : validateSettings ( )
2023-04-30 00:03:50 +02:00
{
2023-05-08 18:29:56 +02:00
# ifndef VCMI_MOBILE
2023-04-30 00:03:50 +02:00
{
int displayIndex = settings [ " video " ] [ " displayIndex " ] . Integer ( ) ;
int displaysCount = SDL_GetNumVideoDisplays ( ) ;
if ( displayIndex > = displaysCount )
{
Settings writer = settings . write [ " video " ] [ " displayIndex " ] ;
writer - > Float ( ) = 0 ;
}
}
if ( getPreferredWindowMode ( ) = = EWindowMode : : WINDOWED )
{
//we only check that our desired window size fits on screen
int displayIndex = getPreferredDisplayIndex ( ) ;
2023-07-05 16:17:43 +02:00
Point resolution = getPreferredWindowResolution ( ) ;
2023-04-30 00:03:50 +02:00
SDL_DisplayMode mode ;
if ( SDL_GetDesktopDisplayMode ( displayIndex , & mode ) = = 0 )
{
if ( resolution . x > mode . w | | resolution . y > mode . h )
{
2023-04-30 18:20:40 +02:00
Settings writer = settings . write [ " video " ] [ " resolution " ] ;
2023-04-30 00:03:50 +02:00
writer [ " width " ] . Float ( ) = mode . w ;
writer [ " height " ] . Float ( ) = mode . h ;
}
}
}
2023-05-08 12:22:01 +02:00
if ( getPreferredWindowMode ( ) = = EWindowMode : : FULLSCREEN_EXCLUSIVE )
2023-04-30 00:03:50 +02:00
{
2023-05-08 12:22:01 +02:00
auto legalOptions = getSupportedResolutions ( ) ;
2023-07-05 16:17:43 +02:00
Point selectedResolution = getPreferredWindowResolution ( ) ;
2023-05-08 12:22:01 +02:00
if ( ! vstd : : contains ( legalOptions , selectedResolution ) )
{
// resolution selected for fullscreen mode is not supported by display
// try to find current display resolution and use it instead as "reasonable default"
SDL_DisplayMode mode ;
if ( SDL_GetDesktopDisplayMode ( getPreferredDisplayIndex ( ) , & mode ) = = 0 )
{
Settings writer = settings . write [ " video " ] [ " resolution " ] ;
writer [ " width " ] . Float ( ) = mode . w ;
writer [ " height " ] . Float ( ) = mode . h ;
}
}
2023-04-30 00:03:50 +02:00
}
# endif
}
2023-05-08 12:22:01 +02:00
int ScreenHandler : : getPreferredRenderingDriver ( ) const
2023-04-30 00:03:50 +02:00
{
int result = - 1 ;
const JsonNode & video = settings [ " video " ] ;
int driversCount = SDL_GetNumRenderDrivers ( ) ;
std : : string preferredDriverName = video [ " driver " ] . String ( ) ;
logGlobal - > info ( " Found %d render drivers " , driversCount ) ;
for ( int it = 0 ; it < driversCount ; it + + )
{
SDL_RendererInfo info ;
2023-05-08 18:29:56 +02:00
if ( SDL_GetRenderDriverInfo ( it , & info ) = = 0 )
2023-04-30 00:03:50 +02:00
{
2023-05-08 18:29:56 +02:00
std : : string driverName ( info . name ) ;
if ( ! preferredDriverName . empty ( ) & & driverName = = preferredDriverName )
{
result = it ;
logGlobal - > info ( " \t %s (active) " , driverName ) ;
}
else
logGlobal - > info ( " \t %s " , driverName ) ;
2023-04-30 00:03:50 +02:00
}
else
2023-05-08 18:29:56 +02:00
logGlobal - > info ( " \t (error) " ) ;
2023-04-30 00:03:50 +02:00
}
return result ;
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : destroyScreenBuffers ( )
2023-04-30 00:03:50 +02:00
{
2023-05-08 12:22:01 +02:00
// screenBuf is not a separate surface, but points to either screen or screen2 - just set to null
screenBuf = nullptr ;
2023-04-30 00:03:50 +02:00
if ( nullptr ! = screen2 )
{
SDL_FreeSurface ( screen2 ) ;
screen2 = nullptr ;
}
if ( nullptr ! = screen )
{
SDL_FreeSurface ( screen ) ;
screen = nullptr ;
}
if ( nullptr ! = screenTexture )
{
SDL_DestroyTexture ( screenTexture ) ;
screenTexture = nullptr ;
}
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : destroyWindow ( )
2023-04-30 00:03:50 +02:00
{
if ( nullptr ! = mainRenderer )
{
SDL_DestroyRenderer ( mainRenderer ) ;
mainRenderer = nullptr ;
}
if ( nullptr ! = mainWindow )
{
SDL_DestroyWindow ( mainWindow ) ;
mainWindow = nullptr ;
}
}
2023-05-08 12:22:01 +02:00
void ScreenHandler : : close ( )
2023-04-30 00:03:50 +02:00
{
if ( settings [ " general " ] [ " notifications " ] . Bool ( ) )
NotificationHandler : : destroy ( ) ;
2023-05-08 12:22:01 +02:00
destroyScreenBuffers ( ) ;
2023-04-30 00:03:50 +02:00
destroyWindow ( ) ;
SDL_Quit ( ) ;
}
2023-04-30 00:38:28 +02:00
2023-05-08 12:22:01 +02:00
void ScreenHandler : : clearScreen ( )
2023-04-30 00:38:28 +02:00
{
SDL_SetRenderDrawColor ( mainRenderer , 0 , 0 , 0 , 255 ) ;
SDL_RenderClear ( mainRenderer ) ;
SDL_RenderPresent ( mainRenderer ) ;
}
2023-04-30 17:47:52 +02:00
2023-05-08 12:22:01 +02:00
std : : vector < Point > ScreenHandler : : getSupportedResolutions ( ) const
2023-04-30 17:47:52 +02:00
{
2023-07-11 17:59:22 +02:00
int displayID = getPreferredDisplayIndex ( ) ;
2023-04-30 17:47:52 +02:00
return getSupportedResolutions ( displayID ) ;
}
2023-05-08 12:22:01 +02:00
std : : vector < Point > ScreenHandler : : getSupportedResolutions ( int displayIndex ) const
2023-04-30 17:47:52 +02:00
{
2023-05-08 12:22:01 +02:00
//NOTE: this method is never called on Android/iOS, only on desktop systems
2023-04-30 17:47:52 +02:00
std : : vector < Point > result ;
int modesCount = SDL_GetNumDisplayModes ( displayIndex ) ;
for ( int i = 0 ; i < modesCount ; + + i )
{
SDL_DisplayMode mode ;
2023-05-08 18:29:56 +02:00
if ( SDL_GetDisplayMode ( displayIndex , i , & mode ) = = 0 )
{
Point resolution ( mode . w , mode . h ) ;
result . push_back ( resolution ) ;
}
2023-04-30 17:47:52 +02:00
}
2023-05-07 18:45:03 +02:00
boost : : range : : sort ( result , [ ] ( const auto & left , const auto & right )
{
return left . x * left . y < right . x * right . y ;
} ) ;
2023-05-08 12:22:01 +02:00
// erase potential duplicates, e.g. resolutions with different framerate / bits per pixel
2023-05-07 18:45:03 +02:00
result . erase ( boost : : unique ( result ) . end ( ) , result . end ( ) ) ;
2023-04-30 17:47:52 +02:00
return result ;
}
2023-09-19 11:20:16 +02:00
bool ScreenHandler : : hasFocus ( )
{
ui32 flags = SDL_GetWindowFlags ( mainWindow ) ;
return flags & SDL_WINDOW_INPUT_FOCUS ;
2023-09-23 23:55:29 +02:00
}