rallyunlimited-engine/code/sdl/sdl_glimp.c

852 lines
19 KiB
C
Raw Permalink Normal View History

2024-02-02 16:46:17 +00:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#ifdef USE_LOCAL_HEADERS
# include "SDL.h"
#ifdef USE_VULKAN_API
# include "SDL_vulkan.h"
#endif
#else
# include <SDL.h>
#ifdef USE_VULKAN_API
# include <SDL_vulkan.h>
#endif
#endif
#include "../client/client.h"
#include "../renderercommon/tr_public.h"
#include "sdl_glw.h"
#include "sdl_icon.h"
typedef enum {
RSERR_OK,
RSERR_INVALID_FULLSCREEN,
RSERR_INVALID_MODE,
RSERR_FATAL_ERROR,
RSERR_UNKNOWN
} rserr_t;
glwstate_t glw_state;
SDL_Window *SDL_window = NULL;
static SDL_GLContext SDL_glContext = NULL;
#ifdef USE_VULKAN_API
static PFN_vkGetInstanceProcAddr qvkGetInstanceProcAddr;
#endif
cvar_t *r_stereoEnabled;
cvar_t *in_nograb;
/*
===============
GLimp_Shutdown
===============
*/
void GLimp_Shutdown( qboolean unloadDLL )
{
IN_Shutdown();
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
if ( glw_state.isFullscreen )
SDL_WarpMouseGlobal( glw_state.desktop_width / 2, glw_state.desktop_height / 2 );
if ( unloadDLL )
SDL_QuitSubSystem( SDL_INIT_VIDEO );
}
/*
===============
GLimp_Minimize
Minimize the game so that user is back at the desktop
===============
*/
void GLimp_Minimize( void )
{
SDL_MinimizeWindow( SDL_window );
}
/*
===============
GLimp_LogComment
===============
*/
void GLimp_LogComment( const char *comment )
{
}
static int FindNearestDisplay( int *x, int *y, int w, int h )
{
const int cx = *x + w / 2;
const int cy = *y + h / 2;
int i, index, numDisplays;
SDL_Rect *list, *m;
index = -1; // selected display index
numDisplays = SDL_GetNumVideoDisplays();
if ( numDisplays <= 0 )
return -1;
glw_state.monitorCount = numDisplays;
list = Z_Malloc( numDisplays * sizeof( list[0] ) );
for ( i = 0; i < numDisplays; i++ )
{
SDL_GetDisplayBounds( i, list + i );
//Com_Printf( "[%i]: x=%i, y=%i, w=%i, h=%i\n", i, list[i].x, list[i].y, list[i].w, list[i].h );
}
// select display by window center intersection
for ( i = 0; i < numDisplays; i++ )
{
m = list + i;
if ( cx >= m->x && cx < (m->x + m->w) && cy >= m->y && cy < (m->y + m->h) )
{
index = i;
break;
}
}
// select display by nearest distance between window center and display center
if ( index == -1 )
{
unsigned long nearest, dist;
int dx, dy;
nearest = ~0UL;
for ( i = 0; i < numDisplays; i++ )
{
m = list + i;
dx = (m->x + m->w/2) - cx;
dy = (m->y + m->h/2) - cy;
dist = ( dx * dx ) + ( dy * dy );
if ( dist < nearest )
{
nearest = dist;
index = i;
}
}
}
// adjust x and y coordinates if needed
if ( index >= 0 )
{
m = list + index;
if ( *x < m->x )
*x = m->x;
if ( *y < m->y )
*y = m->y;
}
Z_Free( list );
return index;
}
static SDL_HitTestResult SDL_HitTestFunc( SDL_Window *win, const SDL_Point *area, void *data )
{
if ( Key_GetCatcher() & KEYCATCH_CONSOLE && keys[ K_ALT ].down )
return SDL_HITTEST_DRAGGABLE;
return SDL_HITTEST_NORMAL;
}
/*
===============
GLimp_SetMode
===============
*/
static int GLW_SetMode( int mode, const char *modeFS, qboolean fullscreen, qboolean vulkan )
{
glconfig_t *config = glw_state.config;
int perChannelColorBits;
int colorBits, depthBits, stencilBits;
int i;
SDL_DisplayMode desktopMode;
int display;
int x;
int y;
Uint32 flags = SDL_WINDOW_SHOWN;
#ifdef USE_VULKAN_API
if ( vulkan ) {
flags |= SDL_WINDOW_VULKAN;
Com_Printf( "Initializing Vulkan display\n");
} else
#endif
{
flags |= SDL_WINDOW_OPENGL;
Com_Printf( "Initializing OpenGL display\n");
}
// If a window exists, note its display index
if ( SDL_window != NULL )
{
display = SDL_GetWindowDisplayIndex( SDL_window );
if ( display < 0 )
{
Com_DPrintf( "SDL_GetWindowDisplayIndex() failed: %s\n", SDL_GetError() );
}
}
else
{
x = vid_xpos->integer;
y = vid_ypos->integer;
// find out to which display our window belongs to
// according to previously stored \vid_xpos and \vid_ypos coordinates
display = FindNearestDisplay( &x, &y, 640, 480 );
//Com_Printf("Selected display: %i\n", display );
}
if ( display >= 0 && SDL_GetDesktopDisplayMode( display, &desktopMode ) == 0 )
{
glw_state.desktop_width = desktopMode.w;
glw_state.desktop_height = desktopMode.h;
}
else
{
glw_state.desktop_width = 640;
glw_state.desktop_height = 480;
}
config->isFullscreen = fullscreen;
glw_state.isFullscreen = fullscreen;
Com_Printf( "...setting mode %d:", mode );
if ( !CL_GetModeInfo( &config->vidWidth, &config->vidHeight, &config->windowAspect, mode, modeFS, glw_state.desktop_width, glw_state.desktop_height, fullscreen ) )
{
Com_Printf( " invalid mode\n" );
return RSERR_INVALID_MODE;
}
Com_Printf( " %d %d\n", config->vidWidth, config->vidHeight );
// Destroy existing state if it exists
if ( SDL_glContext != NULL )
{
SDL_GL_DeleteContext( SDL_glContext );
SDL_glContext = NULL;
}
if ( SDL_window != NULL )
{
SDL_GetWindowPosition( SDL_window, &x, &y );
Com_DPrintf( "Existing window at %dx%d before being destroyed\n", x, y );
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
}
gw_active = qfalse;
gw_minimized = qtrue;
if ( fullscreen )
{
flags |= SDL_WINDOW_FULLSCREEN;
}
else if ( r_noborder->integer )
{
flags |= SDL_WINDOW_BORDERLESS;
}
//flags |= SDL_WINDOW_ALLOW_HIGHDPI;
colorBits = r_colorbits->value;
if ( colorBits == 0 || colorBits > 24 )
colorBits = 24;
if ( cl_depthbits->integer == 0 )
{
// implicitly assume Z-buffer depth == desktop color depth
if ( colorBits > 16 )
depthBits = 24;
else
depthBits = 16;
}
else
depthBits = cl_depthbits->integer;
stencilBits = cl_stencilbits->integer;
// do not allow stencil if Z-buffer depth likely won't contain it
if ( depthBits < 24 )
stencilBits = 0;
for ( i = 0; i < 16; i++ )
{
int testColorBits, testDepthBits, testStencilBits;
int realColorBits[3];
// 0 - default
// 1 - minus colorBits
// 2 - minus depthBits
// 3 - minus stencil
if ((i % 4) == 0 && i)
{
// one pass, reduce
switch (i / 4)
{
case 2 :
if (colorBits == 24)
colorBits = 16;
break;
case 1 :
if (depthBits == 24)
depthBits = 16;
else if (depthBits == 16)
depthBits = 8;
case 3 :
if (stencilBits == 24)
stencilBits = 16;
else if (stencilBits == 16)
stencilBits = 8;
}
}
testColorBits = colorBits;
testDepthBits = depthBits;
testStencilBits = stencilBits;
if ((i % 4) == 3)
{ // reduce colorBits
if (testColorBits == 24)
testColorBits = 16;
}
if ((i % 4) == 2)
{ // reduce depthBits
if (testDepthBits == 24)
testDepthBits = 16;
else if (testDepthBits == 16)
testDepthBits = 8;
}
if ((i % 4) == 1)
{ // reduce stencilBits
if (testStencilBits == 24)
testStencilBits = 16;
else if (testStencilBits == 16)
testStencilBits = 8;
else
testStencilBits = 0;
}
if ( testColorBits == 24 )
perChannelColorBits = 8;
else
perChannelColorBits = 4;
#ifdef USE_VULKAN_API
if ( !vulkan )
#endif
{
#ifdef __sgi /* Fix for SGIs grabbing too many bits of color */
if (perChannelColorBits == 4)
perChannelColorBits = 0; /* Use minimum size for 16-bit color */
/* Need alpha or else SGIs choose 36+ bit RGB mode */
SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 1 );
#endif
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, perChannelColorBits );
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, perChannelColorBits );
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, perChannelColorBits );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, testDepthBits );
SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, testStencilBits );
SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 0 );
SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, 0 );
if ( r_stereoEnabled->integer )
{
config->stereoEnabled = qtrue;
SDL_GL_SetAttribute( SDL_GL_STEREO, 1 );
}
else
{
config->stereoEnabled = qfalse;
SDL_GL_SetAttribute( SDL_GL_STEREO, 0 );
}
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
if ( !r_allowSoftwareGL->integer )
SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 );
}
if ( ( SDL_window = SDL_CreateWindow( cl_title, x, y, config->vidWidth, config->vidHeight, flags ) ) == NULL )
{
Com_DPrintf( "SDL_CreateWindow failed: %s\n", SDL_GetError() );
continue;
}
if ( fullscreen )
{
SDL_DisplayMode mode;
switch ( testColorBits )
{
case 16: mode.format = SDL_PIXELFORMAT_RGB565; break;
case 24: mode.format = SDL_PIXELFORMAT_RGB24; break;
default: Com_DPrintf( "testColorBits is %d, can't fullscreen\n", testColorBits ); continue;
}
mode.w = config->vidWidth;
mode.h = config->vidHeight;
mode.refresh_rate = /* config->displayFrequency = */ Cvar_VariableIntegerValue( "r_displayRefresh" );
mode.driverdata = NULL;
if ( SDL_SetWindowDisplayMode( SDL_window, &mode ) < 0 )
{
Com_DPrintf( "SDL_SetWindowDisplayMode failed: %s\n", SDL_GetError( ) );
continue;
}
if ( SDL_GetWindowDisplayMode( SDL_window, &mode ) >= 0 )
{
config->displayFrequency = mode.refresh_rate;
config->vidWidth = mode.w;
config->vidHeight = mode.h;
}
}
#ifdef USE_VULKAN_API
if ( vulkan )
{
config->colorBits = testColorBits;
config->depthBits = testDepthBits;
config->stencilBits = testStencilBits;
}
else
#endif
{
if ( !SDL_glContext )
{
if ( ( SDL_glContext = SDL_GL_CreateContext( SDL_window ) ) == NULL )
{
Com_DPrintf( "SDL_GL_CreateContext failed: %s\n", SDL_GetError( ) );
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
continue;
}
}
if ( SDL_GL_SetSwapInterval( r_swapInterval->integer ) == -1 )
{
Com_DPrintf( "SDL_GL_SetSwapInterval failed: %s\n", SDL_GetError( ) );
}
SDL_GL_GetAttribute( SDL_GL_RED_SIZE, &realColorBits[0] );
SDL_GL_GetAttribute( SDL_GL_GREEN_SIZE, &realColorBits[1] );
SDL_GL_GetAttribute( SDL_GL_BLUE_SIZE, &realColorBits[2] );
SDL_GL_GetAttribute( SDL_GL_DEPTH_SIZE, &config->depthBits );
SDL_GL_GetAttribute( SDL_GL_STENCIL_SIZE, &config->stencilBits );
config->colorBits = realColorBits[0] + realColorBits[1] + realColorBits[2];
} // if ( !vulkan )
Com_Printf( "Using %d color bits, %d depth, %d stencil display.\n", config->colorBits, config->depthBits, config->stencilBits );
break;
}
if ( SDL_window )
{
#ifndef USE_ICON
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(
(void *)CLIENT_WINDOW_ICON.pixel_data,
CLIENT_WINDOW_ICON.width,
CLIENT_WINDOW_ICON.height,
CLIENT_WINDOW_ICON.bytes_per_pixel * 8,
CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width,
#ifdef Q3_LITTLE_ENDIAN
0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000
#else
0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
#endif
);
if ( icon )
{
SDL_SetWindowIcon( SDL_window, icon );
SDL_FreeSurface( icon );
}
#endif
}
else
{
Com_Printf( "Couldn't get a visual\n" );
return RSERR_INVALID_MODE;
}
if ( !fullscreen && r_noborder->integer )
SDL_SetWindowHitTest( SDL_window, SDL_HitTestFunc, NULL );
#ifdef USE_VULKAN_API
if ( vulkan )
SDL_Vulkan_GetDrawableSize( SDL_window, &config->vidWidth, &config->vidHeight );
else
#endif
SDL_GL_GetDrawableSize( SDL_window, &config->vidWidth, &config->vidHeight );
// save render dimensions as renderer may change it in advance
glw_state.window_width = config->vidWidth;
glw_state.window_height = config->vidHeight;
SDL_WarpMouseInWindow( SDL_window, glw_state.window_width / 2, glw_state.window_height / 2 );
return RSERR_OK;
}
/*
===============
GLimp_StartDriverAndSetMode
===============
*/
static rserr_t GLimp_StartDriverAndSetMode( int mode, const char *modeFS, qboolean fullscreen, qboolean vulkan )
{
rserr_t err;
if ( fullscreen && in_nograb->integer )
{
Com_Printf( "Fullscreen not allowed with \\in_nograb 1\n");
Cvar_Set( "r_fullscreen", "0" );
r_fullscreen->modified = qfalse;
fullscreen = qfalse;
}
if ( !SDL_WasInit( SDL_INIT_VIDEO ) )
{
const char *driverName;
if ( SDL_Init( SDL_INIT_VIDEO ) != 0 )
{
Com_Printf( "SDL_Init( SDL_INIT_VIDEO ) FAILED (%s)\n", SDL_GetError() );
return RSERR_FATAL_ERROR;
}
driverName = SDL_GetCurrentVideoDriver();
Com_Printf( "SDL using driver \"%s\"\n", driverName );
}
err = GLW_SetMode( mode, modeFS, fullscreen, vulkan );
switch ( err )
{
case RSERR_INVALID_FULLSCREEN:
Com_Printf( "...WARNING: fullscreen unavailable in this mode\n" );
return err;
case RSERR_INVALID_MODE:
Com_Printf( "...WARNING: could not set the given mode (%d)\n", mode );
return err;
default:
break;
}
return RSERR_OK;
}
/*
===============
GLimp_Init
This routine is responsible for initializing the OS specific portions
of OpenGL
===============
*/
void GLimp_Init( glconfig_t *config )
{
rserr_t err;
#ifndef _WIN32
InitSig();
#endif
Com_DPrintf( "GLimp_Init()\n" );
glw_state.config = config; // feedback renderer configuration
in_nograb = Cvar_Get( "in_nograb", "0", 0 );
Cvar_SetDescription( in_nograb, "Do not capture mouse in game, may be useful during online streaming." );
r_allowSoftwareGL = Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH );
r_swapInterval = Cvar_Get( "r_swapInterval", "0", CVAR_ARCHIVE | CVAR_LATCH );
r_stereoEnabled = Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH );
Cvar_SetDescription( r_stereoEnabled, "Enable stereo rendering for techniques like shutter glasses." );
// Create the window and set up the context
err = GLimp_StartDriverAndSetMode( r_mode->integer, r_modeFullscreen->string, r_fullscreen->integer, qfalse );
if ( err != RSERR_OK )
{
if ( err == RSERR_FATAL_ERROR )
{
Com_Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem" );
return;
}
if ( r_mode->integer != 3 || ( r_fullscreen->integer && atoi( r_modeFullscreen->string ) != 3 ) )
{
Com_Printf( "Setting \\r_mode %d failed, falling back on \\r_mode %d\n", r_mode->integer, 3 );
if ( GLimp_StartDriverAndSetMode( 3, "", r_fullscreen->integer, qfalse ) != RSERR_OK )
{
// Nothing worked, give up
Com_Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem" );
return;
}
}
}
// These values force the UI to disable driver selection
config->driverType = GLDRV_ICD;
config->hardwareType = GLHW_GENERIC;
// This depends on SDL_INIT_VIDEO, hence having it here
IN_Init();
HandleEvents();
Key_ClearStates();
}
/*
===============
GLimp_EndFrame
Responsible for doing a swapbuffers
===============
*/
void GLimp_EndFrame( void )
{
// don't flip if drawing to front buffer
if ( Q_stricmp( cl_drawBuffer->string, "GL_FRONT" ) != 0 )
{
SDL_GL_SwapWindow( SDL_window );
}
}
/*
===============
GL_GetProcAddress
Used by opengl renderers to resolve all qgl* function pointers
===============
*/
void *GL_GetProcAddress( const char *symbol )
{
return SDL_GL_GetProcAddress( symbol );
}
#ifdef USE_VULKAN_API
/*
===============
VKimp_Init
This routine is responsible for initializing the OS specific portions
of Vulkan
===============
*/
void VKimp_Init( glconfig_t *config )
{
rserr_t err;
#ifndef _WIN32
InitSig();
#endif
Com_DPrintf( "VKimp_Init()\n" );
in_nograb = Cvar_Get( "in_nograb", "0", CVAR_ARCHIVE );
Cvar_SetDescription( in_nograb, "Do not capture mouse in game, may be useful during online streaming." );
r_swapInterval = Cvar_Get( "r_swapInterval", "0", CVAR_ARCHIVE | CVAR_LATCH );
r_stereoEnabled = Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH );
Cvar_SetDescription( r_stereoEnabled, "Enable stereo rendering for techniques like shutter glasses." );
// feedback to renderer configuration
glw_state.config = config;
// Create the window and set up the context
err = GLimp_StartDriverAndSetMode( r_mode->integer, r_modeFullscreen->string, r_fullscreen->integer, qtrue /* Vulkan */ );
if ( err != RSERR_OK )
{
if ( err == RSERR_FATAL_ERROR )
{
Com_Error( ERR_FATAL, "VKimp_Init() - could not load Vulkan subsystem" );
return;
}
Com_Printf( "Setting r_mode %d failed, falling back on r_mode %d\n", r_mode->integer, 3 );
err = GLimp_StartDriverAndSetMode( 3, "", r_fullscreen->integer, qtrue /* Vulkan */ );
if( err != RSERR_OK )
{
// Nothing worked, give up
Com_Error( ERR_FATAL, "VKimp_Init() - could not load Vulkan subsystem" );
return;
}
}
qvkGetInstanceProcAddr = SDL_Vulkan_GetVkGetInstanceProcAddr();
if ( qvkGetInstanceProcAddr == NULL )
{
SDL_QuitSubSystem( SDL_INIT_VIDEO );
Com_Error( ERR_FATAL, "VKimp_Init: qvkGetInstanceProcAddr is NULL" );
}
// These values force the UI to disable driver selection
config->driverType = GLDRV_ICD;
config->hardwareType = GLHW_GENERIC;
// This depends on SDL_INIT_VIDEO, hence having it here
IN_Init();
HandleEvents();
Key_ClearStates();
}
/*
===============
VK_GetInstanceProcAddr
===============
*/
void *VK_GetInstanceProcAddr( VkInstance instance, const char *name )
{
return qvkGetInstanceProcAddr( instance, name );
}
/*
===============
VK_CreateSurface
===============
*/
qboolean VK_CreateSurface( VkInstance instance, VkSurfaceKHR *surface )
{
if ( SDL_Vulkan_CreateSurface( SDL_window, instance, surface ) == SDL_TRUE )
return qtrue;
else
return qfalse;
}
/*
===============
VKimp_Shutdown
===============
*/
void VKimp_Shutdown( qboolean unloadDLL )
{
IN_Shutdown();
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
if ( glw_state.isFullscreen )
SDL_WarpMouseGlobal( glw_state.desktop_width / 2, glw_state.desktop_height / 2 );
if ( unloadDLL )
SDL_QuitSubSystem( SDL_INIT_VIDEO );
}
#endif // USE_VULKAN_API
/*
===============
Sys_GetClipboardData
===============
*/
char *Sys_GetClipboardData( void )
{
#ifdef DEDICATED
return NULL;
#else
char *data = NULL;
char *cliptext;
if ( ( cliptext = SDL_GetClipboardText() ) != NULL ) {
if ( cliptext[0] != '\0' ) {
size_t bufsize = strlen( cliptext ) + 1;
data = Z_Malloc( bufsize );
Q_strncpyz( data, cliptext, bufsize );
// find first listed char and set to '\0'
strtok( data, "\n\r\b" );
}
SDL_free( cliptext );
}
return data;
#endif
}
/*
===============
Sys_SetClipboardBitmap
===============
*/
void Sys_SetClipboardBitmap( const byte *bitmap, int length )
{
#ifdef _WIN32
HGLOBAL hMem;
byte *ptr;
if ( !OpenClipboard( NULL ) )
return;
EmptyClipboard();
hMem = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, length );
if ( hMem != NULL ) {
ptr = ( byte* )GlobalLock( hMem );
if ( ptr != NULL ) {
memcpy( ptr, bitmap, length );
}
GlobalUnlock( hMem );
SetClipboardData( CF_DIB, hMem );
}
CloseClipboard();
#endif
}