rallyunlimited-engine/code/win32/win_glimp.c
2024-02-02 19:46:17 +03:00

1568 lines
37 KiB
C

/*
===========================================================================
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
===========================================================================
*/
/*
** WIN_GLIMP.C
**
** This file contains ALL Win32 specific stuff having to do with the
** OpenGL refresh. When a port is being made the following functions
** must be implemented by the port:
**
** GLimp_EndFrame
** GLimp_Init
** GLimp_LogComment
** GLimp_Shutdown
**
** Note that the GLW_xxx functions are Windows specific GL-subsystem
** related functions that are relevant ONLY to win_glimp.c
*/
#include "../client/client.h"
#include "resource.h"
#include "win_local.h"
#include "glw_win.h"
#ifdef USE_OPENGL_API
#include "../renderer/qgl.h"
// Enable High Performance Graphics while using Integrated Graphics.
Q_EXPORT DWORD NvOptimusEnablement = 0x00000001; // Nvidia
Q_EXPORT int AmdPowerXpressRequestHighPerformance = 1; // AMD
#endif
typedef enum {
RSERR_OK,
RSERR_INVALID_FULLSCREEN,
RSERR_INVALID_MODE,
RSERR_UNKNOWN
} rserr_t;
#define TRY_PFD_SUCCESS 0
#define TRY_PFD_FAIL_SOFT 1
#define TRY_PFD_FAIL_HARD 2
#ifndef PFD_SUPPORT_COMPOSITION
#define PFD_SUPPORT_COMPOSITION 0x00008000
#endif
static DEVMODE dm_desktop;
static DEVMODE dm_current;
static rserr_t GLW_SetMode( int mode, const char *modeFS, int colorbits,
qboolean cdsFullscreen, qboolean vulkan );
//
// function declaration
//
#ifdef USE_OPENGL_API
qboolean QGL_Init( const char *dllname );
void QGL_Shutdown( qboolean unloadDLL );
#endif
#ifdef USE_VULKAN_API
qboolean QVK_Init( void );
void QVK_Shutdown( qboolean unloadDLL );
#endif
//
// variable declarations
//
glwstate_t glw_state;
// GLimp-specific cvars
#ifdef USE_OPENGL_API
static cvar_t *r_maskMinidriver; // allow a different dll name to be treated as if it were opengl32.dll
static cvar_t *r_stereoEnabled;
static cvar_t *r_verbose; // used for verbose debug spew
#endif
/*
** GLW_StartDriverAndSetMode
*/
static rserr_t GLW_StartDriverAndSetMode( int mode, const char *modeFS, int colorbits,
qboolean cdsFullscreen, qboolean vulkan )
{
rserr_t err;
err = GLW_SetMode( mode, modeFS, colorbits, cdsFullscreen, 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;
}
#ifdef USE_OPENGL_API
/*
** GLW_ChoosePFD
**
** Helper function that replaces ChoosePixelFormat.
*/
static int GLW_ChoosePFD( HDC hDC, PIXELFORMATDESCRIPTOR *pPFD )
{
PIXELFORMATDESCRIPTOR *pfds;
int maxPFD, bestMatch;
int i;
Com_Printf( "...GLW_ChoosePFD( %d, %d, %d )\n", ( int ) pPFD->cColorBits, ( int ) pPFD->cDepthBits, ( int ) pPFD->cStencilBits );
// count number of PFDs
#ifdef _MSC_VER
__try {
maxPFD = DescribePixelFormat( hDC, 1, sizeof( PIXELFORMATDESCRIPTOR ), NULL );
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {
Com_Error( ERR_FATAL, "DescribePixelFormat() crashed" );
return 0;
}
#else
maxPFD = DescribePixelFormat( hDC, 1, sizeof( PIXELFORMATDESCRIPTOR ), NULL );
#endif
pfds = Z_Malloc( ( maxPFD + 1 ) * sizeof( PIXELFORMATDESCRIPTOR ) );
Com_Printf( "...%d PFDs found\n", maxPFD - 1 );
// grab information
for ( i = 1; i <= maxPFD; i++ )
{
DescribePixelFormat( hDC, i, sizeof( PIXELFORMATDESCRIPTOR ), &pfds[i] );
}
__rescan:
bestMatch = 0;
// look for a best match
for ( i = 1; i <= maxPFD; i++ )
{
//
// make sure this has hardware acceleration
//
if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 )
{
if ( !r_allowSoftwareGL->integer )
{
if ( r_verbose->integer )
{
Com_Printf( "...PFD %d rejected, software acceleration\n", i );
}
continue;
}
}
// verify pixel type
if ( pfds[i].iPixelType != PFD_TYPE_RGBA )
{
if ( r_verbose->integer )
{
Com_Printf( "...PFD %d rejected, not RGBA\n", i );
}
continue;
}
// verify proper flags
if ( ( pfds[i].dwFlags & pPFD->dwFlags ) != pPFD->dwFlags )
{
if ( r_verbose->integer )
{
Com_Printf( "...PFD %d rejected, improper flags (%lx instead of %lx)\n", i, pfds[i].dwFlags, pPFD->dwFlags );
}
continue;
}
// verify enough bits
if ( pfds[i].cDepthBits < 15 )
{
continue;
}
if ( ( pfds[i].cStencilBits < 4 ) && ( pPFD->cStencilBits > 0 ) )
{
continue;
}
//
// selection criteria (in order of priority):
//
// PFD_STEREO
// colorBits
// depthBits
// stencilBits
//
if ( bestMatch )
{
// check stereo
if ( ( pfds[i].dwFlags & PFD_STEREO ) && ( !( pfds[bestMatch].dwFlags & PFD_STEREO ) ) && ( pPFD->dwFlags & PFD_STEREO ) )
{
bestMatch = i;
continue;
}
if ( !( pfds[i].dwFlags & PFD_STEREO ) && ( pfds[bestMatch].dwFlags & PFD_STEREO ) && ( pPFD->dwFlags & PFD_STEREO ) )
{
bestMatch = i;
continue;
}
// check color
if ( pfds[bestMatch].cColorBits != pPFD->cColorBits )
{
// prefer perfect match
if ( pfds[i].cColorBits == pPFD->cColorBits )
{
bestMatch = i;
continue;
}
// otherwise if this PFD has more bits than our best, use it
else if ( pfds[i].cColorBits > pfds[bestMatch].cColorBits )
{
bestMatch = i;
continue;
}
}
// check depth
if ( pfds[bestMatch].cDepthBits != pPFD->cDepthBits )
{
// prefer perfect match
if ( pfds[i].cDepthBits == pPFD->cDepthBits )
{
bestMatch = i;
continue;
}
// otherwise if this PFD has more bits than our best, use it
else if ( pfds[i].cDepthBits > pfds[bestMatch].cDepthBits )
{
bestMatch = i;
continue;
}
}
// check stencil
if ( pfds[bestMatch].cStencilBits != pPFD->cStencilBits )
{
// prefer perfect match
if ( pfds[i].cStencilBits == pPFD->cStencilBits )
{
bestMatch = i;
continue;
}
// otherwise if this PFD has more bits than our best, use it
else if ( ( pfds[i].cStencilBits > pfds[bestMatch].cStencilBits ) &&
( pPFD->cStencilBits > 0 ) )
{
bestMatch = i;
continue;
}
}
}
else
{
bestMatch = i;
}
}
if ( !bestMatch )
{
if ( pPFD->dwFlags & PFD_SUPPORT_COMPOSITION )
{
// this can be a problem if we are working via RDP for example
pPFD->dwFlags &= ~PFD_SUPPORT_COMPOSITION;
goto __rescan;
}
Z_Free( pfds );
return 0;
}
if ( ( pfds[bestMatch].dwFlags & PFD_GENERIC_FORMAT ) != 0 )
{
if ( !r_allowSoftwareGL->integer )
{
Com_Printf( "...no hardware acceleration found\n" );
Z_Free( pfds );
return 0;
}
else
{
Com_Printf( "...using software emulation\n" );
}
}
else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED )
{
Com_Printf( "...MCD acceleration found\n" );
}
else
{
Com_Printf( "...hardware acceleration found\n" );
}
*pPFD = pfds[bestMatch];
Z_Free( pfds );
return bestMatch;
}
/*
** void GLW_CreatePFD
**
** Helper function zeros out then fills in a PFD
*/
static void GLW_CreatePFD( PIXELFORMATDESCRIPTOR *pPFD, int colorbits, int depthbits, int stencilbits, qboolean stereo )
{
PIXELFORMATDESCRIPTOR src =
{
sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd
1, // version number
PFD_DRAW_TO_WINDOW | // support window
PFD_SUPPORT_OPENGL | // support OpenGL
PFD_DOUBLEBUFFER, // double buffered
PFD_TYPE_RGBA, // RGBA type
24, // 24-bit color depth
0, 0, 0, 0, 0, 0, // color bits ignored
0, // no alpha buffer
0, // shift bit ignored
0, // no accumulation buffer
0, 0, 0, 0, // accum bits ignored
24, // 24-bit z-buffer
8, // 8-bit stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main layer
0, // reserved
0, 0, 0 // layer masks ignored
};
src.cColorBits = colorbits;
src.cDepthBits = depthbits;
src.cStencilBits = stencilbits;
if ( !glw_state.cdsFullscreen )
{
src.dwFlags |= PFD_SUPPORT_COMPOSITION;
}
if ( stereo )
{
Com_Printf( "...attempting to use stereo\n" );
src.dwFlags |= PFD_STEREO;
glw_state.config->stereoEnabled = qtrue;
}
else
{
glw_state.config->stereoEnabled = qfalse;
}
*pPFD = src;
}
/*
** GLW_MakeContext
*/
static int GLW_MakeContext( PIXELFORMATDESCRIPTOR *pPFD )
{
int pixelformat;
//
// don't putz around with pixelformat if it's already set (e.g. this is a soft
// reset of the graphics system)
//
if ( !glw_state.pixelFormatSet )
{
//
// choose, set, and describe our desired pixel format. If we're
// using a minidriver then we need to bypass the GDI functions,
// otherwise use the GDI functions.
//
if ( ( pixelformat = GLW_ChoosePFD( glw_state.hDC, pPFD ) ) == 0 )
{
Com_Printf( "...GLW_ChoosePFD failed\n" );
return TRY_PFD_FAIL_SOFT;
}
Com_Printf( "...PIXELFORMAT %d selected\n", pixelformat );
DescribePixelFormat( glw_state.hDC, pixelformat, sizeof( *pPFD ), pPFD );
if ( SetPixelFormat( glw_state.hDC, pixelformat, pPFD ) == FALSE )
{
Com_Printf( "...SetPixelFormat failed\n" );
return TRY_PFD_FAIL_SOFT;
}
glw_state.pixelFormatSet = qtrue;
}
//
// startup the OpenGL subsystem by creating a context and making it current
//
if ( !glw_state.hGLRC )
{
Com_Printf( "...creating GL context: " );
if ( ( glw_state.hGLRC = qwglCreateContext( glw_state.hDC ) ) == 0 )
{
Com_Printf( "failed\n" );
return TRY_PFD_FAIL_HARD;
}
Com_Printf( "succeeded\n" );
Com_Printf( "...making context current: " );
if ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) )
{
qwglDeleteContext( glw_state.hGLRC );
glw_state.hGLRC = NULL;
Com_Printf( "failed\n" );
return TRY_PFD_FAIL_HARD;
}
Com_Printf( "succeeded\n" );
}
return TRY_PFD_SUCCESS;
}
/*
** GLW_InitOpenGLDriver
**
** - get a DC if one doesn't exist
** - create an HGLRC if one doesn't exist
*/
static qboolean GLW_InitOpenGLDriver( int colorbits )
{
int tpfd;
int depthbits, stencilbits;
static PIXELFORMATDESCRIPTOR pfd; // save between frames since 'tr' gets cleared
Com_Printf( "Initializing OpenGL driver\n" );
//
// get a DC for our window if we don't already have one allocated
//
if ( glw_state.hDC == NULL )
{
Com_Printf( "...getting DC: " );
if ( ( glw_state.hDC = GetDC( g_wv.hWnd ) ) == NULL )
{
Com_Printf( "failed\n" );
return qfalse;
}
Com_Printf( "succeeded\n" );
}
//
// implicitly assume Z-buffer depth == desktop color depth
//
if ( cl_depthbits->integer == 0 ) {
if ( colorbits > 16 ) {
depthbits = 24;
} else {
depthbits = 16;
}
} else {
depthbits = cl_depthbits->integer;
}
//
// do not allow stencil if Z-buffer depth likely won't contain it
//
stencilbits = cl_stencilbits->integer;
if ( depthbits < 24 )
{
stencilbits = 0;
}
//
// make two attempts to set the PIXELFORMAT
//
//
// first attempt: r_colorbits, depthbits, and r_stencilbits
//
if ( !glw_state.pixelFormatSet )
{
GLW_CreatePFD( &pfd, colorbits, depthbits, stencilbits, r_stereoEnabled->integer != 0 );
if ( ( tpfd = GLW_MakeContext( &pfd ) ) != TRY_PFD_SUCCESS )
{
if ( tpfd == TRY_PFD_FAIL_HARD )
{
Com_Printf( S_COLOR_YELLOW "...failed hard\n" );
return qfalse;
}
//
// punt if we've already tried the desktop bit depth and no stencil bits
//
if ( ( r_colorbits->integer == glw_state.desktopBitsPixel ) &&
( stencilbits == 0 ) )
{
ReleaseDC( g_wv.hWnd, glw_state.hDC );
glw_state.hDC = NULL;
Com_Printf( "...failed to find an appropriate PIXELFORMAT\n" );
return qfalse;
}
//
// second attempt: desktop's color bits and no stencil
//
if ( colorbits > glw_state.desktopBitsPixel )
{
colorbits = glw_state.desktopBitsPixel;
}
GLW_CreatePFD( &pfd, colorbits, depthbits, 0, r_stereoEnabled->integer != 0 );
if ( GLW_MakeContext( &pfd ) != TRY_PFD_SUCCESS )
{
if ( glw_state.hDC )
{
ReleaseDC( g_wv.hWnd, glw_state.hDC );
glw_state.hDC = NULL;
}
Com_Printf( "...failed to find an appropriate PIXELFORMAT\n" );
return qfalse;
}
}
/*
** report if stereo is desired but unavailable
*/
if ( !( pfd.dwFlags & PFD_STEREO ) && ( r_stereoEnabled->integer != 0 ) )
{
Com_Printf( "...failed to select stereo pixel format\n" );
glw_state.config->stereoEnabled = qfalse;
}
}
/*
** store PFD specifics
*/
glw_state.config->colorBits = ( int ) pfd.cColorBits;
glw_state.config->depthBits = ( int ) pfd.cDepthBits;
glw_state.config->stencilBits = ( int ) pfd.cStencilBits;
return qtrue;
}
#endif // USE_OPENGL_API
/*
** GLW_InitVulkanDriver
*/
#ifdef USE_VULKAN_API
static qboolean GLW_InitVulkanDriver( int colorbits )
{
int depthbits;
int stencilbits;
// implicitly assume Z-buffer depth == desktop color depth
if ( cl_depthbits->integer == 0 ) {
if ( colorbits > 16 ) {
depthbits = 24;
} else {
depthbits = 16;
}
} else {
depthbits = cl_depthbits->integer;
}
// do not allow stencil if Z-buffer depth likely won't contain it
stencilbits = cl_stencilbits->integer;
if ( depthbits < 24 ) {
stencilbits = 0;
}
glw_state.config->colorBits = colorbits;
glw_state.config->depthBits = depthbits;
glw_state.config->stencilBits = stencilbits;
return qtrue;
}
#endif
/*
** GLW_CreateWindow
**
** Responsible for creating the Win32 window and initializing the OpenGL/Vulkan drivers.
*/
static qboolean GLW_CreateWindow( int width, int height, int colorbits, qboolean cdsFullscreen, qboolean vulkan )
{
static qboolean s_classRegistered = qfalse;
RECT r;
int stylebits;
int x, y, w, h;
int exstyle;
qboolean oldFullscreen;
qboolean res = qfalse;
//
// register the window class if necessary
//
if ( !s_classRegistered )
{
WNDCLASS wc;
memset( &wc, 0, sizeof( wc ) );
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_wv.hInstance;
wc.hIcon = LoadIcon( g_wv.hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH)(LRESULT)COLOR_GRAYTEXT;
wc.lpszMenuName = 0;
wc.lpszClassName = T(CLIENT_WINDOW_TITLE);
if ( !RegisterClass( &wc ) )
{
Com_Error( ERR_FATAL, "%s: could not register window class", __func__ );
return qfalse;
}
s_classRegistered = qtrue;
// Com_Printf( "...registered window class\n" );
}
r.left = vid_xpos->integer;
r.top = vid_ypos->integer;
r.right = r.left + width;
r.bottom = r.top + height;
UpdateMonitorInfo( &r );
//
// create the HWND if one does not already exist
//
if ( !g_wv.hWnd )
{
//
// compute width and height
//
//r.left = 0;
//r.top = 0;
//r.right = width;
//r.bottom = height;
g_wv.borderless = 0;
if ( cdsFullscreen )
{
exstyle = WINDOW_ESTYLE_FULLSCREEN;
stylebits = WINDOW_STYLE_FULLSCREEN;
}
else
{
exstyle = WINDOW_ESTYLE_NORMAL;
if ( r_noborder->integer ) {
stylebits = WINDOW_STYLE_NORMAL_NB;
g_wv.borderless = r_noborder->integer;
} else {
stylebits = WINDOW_STYLE_NORMAL;
}
AdjustWindowRect( &r, stylebits, FALSE );
}
w = r.right - r.left;
h = r.bottom - r.top;
// select monitor from window rect
r.left = vid_xpos->integer;
r.top = vid_ypos->integer;
r.right = r.left + w;
r.bottom = r.top + h;
UpdateMonitorInfo( &r );
if ( cdsFullscreen )
{
x = glw_state.desktopX;
y = glw_state.desktopY;
}
else
{
x = vid_xpos->integer;
y = vid_ypos->integer;
// adjust window coordinates if necessary
// so that the window is completely on screen
if ( w < glw_state.desktopWidth && (x + w) > glw_state.desktopWidth + glw_state.desktopX )
x = ( glw_state.desktopWidth + glw_state.desktopX - w );
if ( h < glw_state.desktopHeight && (y + h) > glw_state.desktopHeight + glw_state.desktopY )
y = ( glw_state.desktopHeight + glw_state.desktopY - h );
if ( x < glw_state.desktopX )
x = glw_state.desktopX;
if ( y < glw_state.desktopY )
y = glw_state.desktopY;
}
stylebits &= ~WS_VISIBLE; // show window only after successive OpenGL/Vulkan initialization
oldFullscreen = glw_state.cdsFullscreen;
glw_state.cdsFullscreen = cdsFullscreen;
g_wv.hWnd = CreateWindowEx( exstyle, TEXT(CLIENT_WINDOW_TITLE), AtoW(cl_title),
stylebits, x, y, w, h, NULL, NULL, g_wv.hInstance, NULL );
if ( !g_wv.hWnd )
{
glw_state.cdsFullscreen = oldFullscreen;
Com_Error( ERR_FATAL, "GLW_CreateWindow() - Couldn't create window" );
return qfalse;
}
// we must reflect actual drawable dimensions in glconfig
GetClientRect( g_wv.hWnd, &r );
glw_state.config->vidWidth = r.right - r.left;
glw_state.config->vidHeight = r.bottom - r.top;
Com_Printf( "...created window@%d,%d (%dx%d)\n", x, y, w, h );
}
else
{
Com_Printf( "...window already present, CreateWindowEx skipped\n" );
}
if ( colorbits == 0 )
colorbits = dm_desktop.dmBitsPerPel;
#ifdef USE_VULKAN_API
if ( vulkan )
res = GLW_InitVulkanDriver( colorbits );
#endif
#ifdef USE_OPENGL_API
if ( !vulkan )
res = GLW_InitOpenGLDriver( colorbits );
#endif
if ( !res )
{
//ShowWindow( g_wv.hWnd, SW_HIDE );
DestroyWindow( g_wv.hWnd );
g_wv.hWnd = NULL;
return qfalse;
}
//SetForegroundWindow( g_wv.hWnd );
//SetFocus( g_wv.hWnd );
//ShowWindow( g_wv.hWnd, SW_SHOW );
//UpdateWindow( g_wv.hWnd );
return qtrue;
}
static void PrintCDSError( int value )
{
switch ( value )
{
case DISP_CHANGE_RESTART:
Com_Printf( "restart required\n" );
break;
case DISP_CHANGE_BADPARAM:
Com_Printf( "bad param\n" );
break;
case DISP_CHANGE_BADFLAGS:
Com_Printf( "bad flags\n" );
break;
case DISP_CHANGE_FAILED:
Com_Printf( "DISP_CHANGE_FAILED\n" );
break;
case DISP_CHANGE_BADMODE:
Com_Printf( "bad mode\n" );
break;
case DISP_CHANGE_NOTUPDATED:
Com_Printf( "not updated\n" );
break;
default:
Com_Printf( "unknown error %d\n", value );
break;
}
}
static void ResetDisplaySettings( qboolean verbose )
{
if ( verbose )
Com_Printf( "...restoring display settings\n" );
if ( glw_state.displayName[0] )
ChangeDisplaySettingsEx( glw_state.displayName, NULL, NULL, 0, NULL );
else
ChangeDisplaySettings( NULL, 0 );
}
static LONG ApplyDisplaySettings( DEVMODE *dm )
{
DEVMODE curr;
LONG lResult;
BOOL bResult;
Com_Memset( &curr, 0, sizeof( curr ) );
curr.dmSize = sizeof( DEVMODE );
// Get current display mode on current monitor
if ( glw_state.displayName[0] )
bResult = EnumDisplaySettings( glw_state.displayName, ENUM_CURRENT_SETTINGS, &curr );
else
bResult = EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &curr );
if ( !bResult )
return DISP_CHANGE_FAILED;
#ifdef FAST_MODE_SWITCH
// Check if current resolution is the same as we want to set
if ( curr.dmDisplayFrequency &&
curr.dmPelsWidth == dm->dmPelsWidth &&
curr.dmPelsHeight == dm->dmPelsHeight &&
(curr.dmBitsPerPel == dm->dmBitsPerPel || dm->dmBitsPerPel == 0 ) &&
(curr.dmDisplayFrequency == dm->dmDisplayFrequency || dm->dmDisplayFrequency ==0))
{
memcpy( &dm_current, &curr, sizeof( dm_current ) );
return DISP_CHANGE_SUCCESSFUL; // simulate success
}
#endif
// Uninitialized?
if ( dm->dmDisplayFrequency == 0 && dm->dmPelsWidth == 0 &&
dm->dmPelsHeight == 0 && dm->dmBitsPerPel == 0 ) {
if ( dm_desktop.dmPelsWidth && dm_desktop.dmPelsHeight ) {
return ApplyDisplaySettings( &dm_desktop );
}
}
// Apply requested mode
if ( glw_state.displayName[0] )
lResult = ChangeDisplaySettingsEx( glw_state.displayName, dm, NULL, CDS_FULLSCREEN, NULL );
else
lResult = ChangeDisplaySettings( dm, 0 );
if ( lResult == DISP_CHANGE_SUCCESSFUL )
memcpy( &dm_current, dm, sizeof( dm_current ) );
return lResult;
}
void SetGameDisplaySettings( void )
{
ApplyDisplaySettings( &dm_current );
}
void SetDesktopDisplaySettings( void )
{
ResetDisplaySettings( qfalse );
memset( &dm_desktop, 0, sizeof( dm_desktop ) );
dm_desktop.dmSize = sizeof( DEVMODE );
if ( glw_state.displayName[0] )
EnumDisplaySettings( glw_state.displayName, ENUM_CURRENT_SETTINGS, &dm_desktop );
else
EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &dm_desktop );
}
void UpdateMonitorInfo( const RECT *target )
{
MONITORINFOEX mInfo;
DEVMODE devMode;
HMONITOR hMon;
const RECT *Rect;
int w, h, x ,y;
glw_state.monitorCount = GetSystemMetrics( SM_CMONITORS );
if ( target )
Rect = target;
else if ( g_wv.winRectValid )
Rect = &g_wv.winRect;
else
Rect = &g_wv.conRect;
// try to get more correct data
hMon = MonitorFromRect( Rect, MONITOR_DEFAULTTONEAREST );
memset( &mInfo, 0, sizeof( mInfo ) );
mInfo.cbSize = sizeof( MONITORINFOEX );
memset( &devMode, 0, sizeof( devMode ) );
devMode.dmSize = sizeof( DEVMODE );
if ( GetMonitorInfo( hMon, (LPMONITORINFO)&mInfo ) && EnumDisplaySettings( mInfo.szDevice, ENUM_CURRENT_SETTINGS, &devMode ) ) {
w = mInfo.rcMonitor.right - mInfo.rcMonitor.left;
h = mInfo.rcMonitor.bottom - mInfo.rcMonitor.top;
x = mInfo.rcMonitor.left;
y = mInfo.rcMonitor.top;
// try to detect DPI scale
// we can't properly handle it but at least detect monitor resolution
// and inform user in console
if ( devMode.dmPelsWidth > w || devMode.dmPelsHeight > h ) {
int scaleX, scaleY;
scaleX = (devMode.dmPelsWidth * 100) / w;
scaleY = (devMode.dmPelsHeight * 100) / h;
if ( scaleX == scaleY ) {
Com_Printf( S_COLOR_YELLOW "...detected DPI scale: %i%%\n", scaleX );
w = devMode.dmPelsWidth;
h = devMode.dmPelsHeight;
}
}
if ( glw_state.desktopWidth != w || glw_state.desktopHeight != h ||
glw_state.desktopX != x || glw_state.desktopY != y ||
glw_state.hMonitor != hMon ) {
// track monitor and gamma change
qboolean gammaSet = glw_state.gammaSet;
GLW_RestoreGamma();
glw_state.desktopWidth = w;
glw_state.desktopHeight = h;
glw_state.desktopX = x;
glw_state.desktopY = y;
glw_state.hMonitor = hMon;
memcpy( glw_state.displayName, mInfo.szDevice, sizeof( glw_state.displayName ) );
glw_state.desktopBitsPixel = devMode.dmBitsPerPel;
Com_Printf( "...current monitor: %ix%i@%i,%i %s\n",
w, h, x, y, WtoA( mInfo.szDevice ) );
if ( gammaSet && re.SetColorMappings ) {
re.SetColorMappings();
}
}
glw_state.workArea = mInfo.rcWork;
} else {
// no information about current monitor, get desktop settings
HDC hDC = GetDC( GetDesktopWindow() );
glw_state.desktopX = 0;
glw_state.desktopY = 0;
glw_state.desktopWidth = GetDeviceCaps( hDC, HORZRES );
glw_state.desktopHeight = GetDeviceCaps( hDC, VERTRES );
ReleaseDC( GetDesktopWindow(), hDC );
glw_state.displayName[0] = '\0';
SystemParametersInfo( SPI_GETWORKAREA, 0, &glw_state.workArea, 0 );
}
}
/*
** GLW_SetMode
*/
static rserr_t GLW_SetMode( int mode, const char *modeFS, int colorbits, qboolean cdsFullscreen, qboolean vulkan )
{
//HDC hDC;
RECT r;
const char *win_fs[] = { "W", "FS" };
glconfig_t *config = glw_state.config;
int cdsRet;
DEVMODE dm;
r.left = vid_xpos->integer;
r.top = vid_ypos->integer;
r.right = r.left + 320;
r.bottom = r.top + 240;
UpdateMonitorInfo( &r );
if ( dm_desktop.dmSize == 0 )
{
SetDesktopDisplaySettings();
}
//
// print out informational messages
//
Com_Printf( "...setting mode %d:", mode );
if ( !CL_GetModeInfo( &config->vidWidth, &config->vidHeight, &config->windowAspect,
mode, modeFS, glw_state.desktopWidth, glw_state.desktopHeight, cdsFullscreen ) )
{
Com_Printf( " invalid mode\n" );
return RSERR_INVALID_MODE;
}
Com_Printf( " %d %d %s\n", config->vidWidth, config->vidHeight, win_fs[ cdsFullscreen ] );
//
// verify desktop bit depth
//
if ( glw_state.desktopBitsPixel < 15 || glw_state.desktopBitsPixel == 24 )
{
if ( colorbits == 0 || ( !cdsFullscreen && colorbits >= 15 ) )
{
if ( MessageBox( NULL,
T("It is highly unlikely that a correct\n") \
T("windowed display can be initialized with\n") \
T("the current desktop display depth. Select\n") \
T("'OK' to try anyway. Press 'Cancel' if you\n") \
T("have a 3Dfx Voodoo, Voodoo-2, or Voodoo Rush\n") \
T("3D accelerator installed, or if you otherwise\n") \
T("wish to quit."), T("Low Desktop Color Depth"),
MB_OKCANCEL | MB_ICONEXCLAMATION ) != IDOK )
{
return RSERR_INVALID_MODE;
}
}
}
// do a CDS if needed
if ( cdsFullscreen )
{
memset( &dm, 0, sizeof( dm ) );
dm.dmSize = sizeof( dm );
dm.dmPelsWidth = config->vidWidth;
dm.dmPelsHeight = config->vidHeight;
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
if ( Cvar_VariableIntegerValue( "r_displayRefresh" ) )
{
dm.dmDisplayFrequency = Cvar_VariableIntegerValue( "r_displayRefresh" );
dm.dmFields |= DM_DISPLAYFREQUENCY;
}
else // try to set at least desktop refresh rate?
if ( (dm_desktop.dmDisplayFrequency
&& dm.dmPelsWidth <= dm_desktop.dmPelsWidth
&& dm.dmPelsHeight <= dm_desktop.dmPelsWidth)
|| (dm_current.dmDisplayFrequency
&& dm.dmPelsWidth <= dm_current.dmPelsWidth
&& dm.dmPelsHeight <= dm_current.dmPelsWidth)) {
//dm.dmDisplayFrequency = dm_desktop.dmDisplayFrequency;
//dm.dmFields |= DM_DISPLAYFREQUENCY;
//Com_Printf("...using display refresh rate: %iHz\n",
// dm_desktop.dmDisplayFrequency );
}
// try to change color depth if possible
if ( colorbits != 0 )
{
dm.dmBitsPerPel = colorbits;
dm.dmFields |= DM_BITSPERPEL;
Com_Printf( "...using colorsbits of %d\n", colorbits );
}
else
{
Com_Printf( "...using desktop display depth of %d\n", glw_state.desktopBitsPixel );
}
//
// if we're already in fullscreen then just create the window
//
if ( glw_state.cdsFullscreen )
{
Com_Printf( "...already fullscreen, avoiding redundant CDS\n" );
if ( !GLW_CreateWindow( config->vidWidth, config->vidHeight, colorbits, qtrue, vulkan ) )
{
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
return RSERR_INVALID_MODE;
}
}
//
// need to call CDS
//
else
{
Com_Printf( "...calling CDS: " );
// try setting the exact mode requested, because some drivers don't report
// the low res modes in EnumDisplaySettings, but still work
if ( ( cdsRet = ApplyDisplaySettings( &dm ) ) == DISP_CHANGE_SUCCESSFUL )
{
Com_Printf( "ok\n" );
if ( !GLW_CreateWindow( config->vidWidth, config->vidHeight, colorbits, qtrue, vulkan ) )
{
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
return RSERR_INVALID_MODE;
}
}
else
{
//
// the exact mode failed, so scan EnumDisplaySettings for the next largest mode
//
DEVMODE devmode;
int modeNum;
Com_Printf( "failed, " );
PrintCDSError( cdsRet );
Com_Printf( "...trying next higher resolution:" );
// we could do a better matching job here...
for ( modeNum = 0 ; ; modeNum++ ) {
BOOL bResult;
Com_Memset( &devmode, 0, sizeof( devmode ) );
devmode.dmSize = sizeof( DEVMODE );
if ( glw_state.displayName[0] )
bResult = EnumDisplaySettings( glw_state.displayName, modeNum, &devmode );
else
bResult = EnumDisplaySettings( NULL, modeNum, &devmode );
if ( !bResult ) {
modeNum = -1;
break;
}
if ( devmode.dmPelsWidth >= config->vidWidth
&& devmode.dmPelsHeight >= config->vidHeight
&& devmode.dmBitsPerPel >= 15 ) {
break;
}
}
if ( modeNum != -1 && ( cdsRet = ApplyDisplaySettings( &devmode ) ) == DISP_CHANGE_SUCCESSFUL )
{
Com_Printf( " ok\n" );
if ( !GLW_CreateWindow( config->vidWidth, config->vidHeight, colorbits, qtrue, vulkan) )
{
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
return RSERR_INVALID_MODE;
}
}
else
{
Com_Printf( " failed, " );
PrintCDSError( cdsRet );
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
glw_state.config->isFullscreen = qfalse;
if ( !GLW_CreateWindow( config->vidWidth, config->vidHeight, colorbits, qfalse, vulkan ) )
{
return RSERR_INVALID_MODE;
}
return RSERR_INVALID_FULLSCREEN;
}
}
}
}
else // !cdsFullscreen
{
if ( glw_state.cdsFullscreen )
{
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
}
if ( !GLW_CreateWindow( config->vidWidth, config->vidHeight, colorbits, qfalse, vulkan ) )
{
return RSERR_INVALID_MODE;
}
}
//
// success, now check display frequency, although this won't be valid on Voodoo(2)
//
memset( &dm, 0, sizeof( dm ) );
dm.dmSize = sizeof( dm );
if ( EnumDisplaySettings( glw_state.displayName, ENUM_CURRENT_SETTINGS, &dm ) )
{
glw_state.config->displayFrequency = dm.dmDisplayFrequency;
}
// NOTE: this is overridden later on standalone 3Dfx drivers
glw_state.config->isFullscreen = cdsFullscreen;
glw_state.config->colorBits = dm.dmBitsPerPel;
return RSERR_OK;
}
#ifdef USE_OPENGL_API
/*
** GLW_LoadOpenGL
**
** GLimp_win.c internal function that attempts to load and use
** a specific OpenGL DLL.
*/
static qboolean GLW_LoadOpenGL( const char *drivername )
{
char buffer[ 256 ];
qboolean cdsFullscreen;
glconfig_t *config = glw_state.config;
Q_strncpyz( buffer, drivername, sizeof( buffer ) );
Q_strlwr( buffer );
if ( Q_stricmp( buffer, OPENGL_DRIVER_NAME ) == 0 || r_maskMinidriver->integer )
{
config->driverType = GLDRV_ICD;
}
else
{
config->driverType = GLDRV_STANDALONE;
Com_Printf( "...assuming '%s' is a standalone driver\n", drivername );
}
//
// load the driver and bind our function pointers to it
//
if ( QGL_Init( buffer ) )
{
cdsFullscreen = (r_fullscreen->integer != 0);
// create the window and set up the context
if ( GLW_StartDriverAndSetMode( r_mode->integer, r_modeFullscreen->string, r_colorbits->integer, cdsFullscreen, qfalse ) != RSERR_OK )
{
// if we're on a 24/32-bit desktop try it again but with a 16-bit desktop
if ( r_colorbits->integer != 16 || cdsFullscreen != qtrue || r_mode->integer != 3 )
{
if ( GLW_StartDriverAndSetMode( 3, "", 16, qtrue, qfalse ) != RSERR_OK )
{
goto fail;
}
}
}
return qtrue;
}
fail:
QGL_Shutdown( qtrue );
return qfalse;
}
static void GLimp_SwapBuffers( void )
{
if ( !SwapBuffers( glw_state.hDC ) )
{
Com_Error( ERR_FATAL, "GLimp_EndFrame() - SwapBuffers() failed!\n" );
}
}
/*
** GLimp_EndFrame
*/
void GLimp_EndFrame( void )
{
//
// swapinterval stuff
//
if ( r_swapInterval->modified ) {
r_swapInterval->modified = qfalse;
//if ( !glConfig.stereoEnabled ) { // why?
if ( qwglSwapIntervalEXT ) {
qwglSwapIntervalEXT( r_swapInterval->integer );
}
//}
}
// don't flip if drawing to front buffer
if ( Q_stricmp( cl_drawBuffer->string, "GL_FRONT" ) != 0 ) {
GLimp_SwapBuffers();
}
}
static qboolean GLW_StartOpenGL( void )
{
//
// load and initialize the specific OpenGL driver
//
if ( !GLW_LoadOpenGL( r_glDriver->string ) )
{
if ( Q_stricmp( r_glDriver->string, OPENGL_DRIVER_NAME ) != 0 )
{
// try default driver
if ( GLW_LoadOpenGL( OPENGL_DRIVER_NAME ) )
{
Cvar_Set( "r_glDriver", OPENGL_DRIVER_NAME );
r_glDriver->modified = qfalse;
return qtrue;
}
}
Com_Error( ERR_FATAL, "GLW_StartOpenGL() - could not load OpenGL subsystem\n" );
return qfalse;
}
return qtrue;
}
/*
** GLimp_Init
**
** This is the platform specific OpenGL initialization function. It
** is responsible for loading OpenGL, initializing it, setting
** extensions, creating a window of the appropriate size, doing
** fullscreen manipulations, etc. Its overall responsibility is
** to make sure that a functional OpenGL subsystem is operating
** when it returns to the ref.
*/
void GLimp_Init( glconfig_t *config )
{
Com_Printf( "Initializing OpenGL subsystem\n" );
// glimp-specific
r_maskMinidriver = Cvar_Get( "r_maskMinidriver", "0", CVAR_LATCH );
Cvar_SetDescription( r_maskMinidriver, "If set to 1, then a mini driver will be treated as a normal ICD." );
r_stereoEnabled = Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE_ND | CVAR_LATCH );
Cvar_SetDescription( r_stereoEnabled, "Enable stereo rendering for techniques like shutter glasses." );
r_verbose = Cvar_Get( "r_verbose", "0", 0 );
Cvar_SetDescription( r_verbose, "Turns on additional startup information when renderer is starting up." );
// feedback to renderer configuration
glw_state.config = config;
// load appropriate DLL and initialize subsystem
if ( !GLW_StartOpenGL() )
return;
//glConfig.driverType = GLDRV_ICD;
config->hardwareType = GLHW_GENERIC;
// optional
#define GLE( ret, name, ... ) q##name = GL_GetProcAddress( XSTRING( name ) )
QGL_Swp_PROCS;
#undef GLE
if ( qwglSwapIntervalEXT ) {
Com_Printf( "...using WGL_EXT_swap_control\n" );
r_swapInterval->modified = qtrue; // force a set next frame
} else {
Com_Printf( "...WGL_EXT_swap_control not found\n" );
}
// show main window after all initializations
ShowWindow( g_wv.hWnd, SW_SHOW );
IN_Init();
HandleEvents();
Key_ClearStates();
}
/*
** GLimp_Shutdown
**
** This routine does all OS specific shutdown procedures for the OpenGL
** subsystem.
*/
void GLimp_Shutdown( qboolean unloadDLL )
{
const char *success[] = { "failed", "success" };
int retVal;
// FIXME: Brian, we need better fallbacks from partially initialized failures
if ( !qwglMakeCurrent ) {
return;
}
IN_Shutdown();
Com_Printf( "Shutting down OpenGL subsystem\n" );
// restore gamma. We do this first because 3Dfx's extension needs a valid OGL subsystem
GLW_RestoreGamma();
// set current context to NULL
if ( qwglMakeCurrent )
{
retVal = qwglMakeCurrent( NULL, NULL ) != 0;
Com_Printf( "...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] );
}
// delete HGLRC
if ( glw_state.hGLRC )
{
retVal = qwglDeleteContext( glw_state.hGLRC ) != 0;
Com_Printf( "...deleting GL context: %s\n", success[retVal] );
glw_state.hGLRC = NULL;
}
// release DC
if ( glw_state.hDC )
{
retVal = ReleaseDC( g_wv.hWnd, glw_state.hDC ) != 0;
Com_Printf( "...releasing DC: %s\n", success[retVal] );
glw_state.hDC = NULL;
}
// destroy window
if ( g_wv.hWnd )
{
Com_Printf( "...destroying window\n" );
//ShowWindow( g_wv.hWnd, SW_HIDE );
DestroyWindow( g_wv.hWnd );
g_wv.hWnd = NULL;
glw_state.pixelFormatSet = qfalse;
}
// reset display settings
if ( glw_state.cdsFullscreen )
{
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
}
// shutdown QGL subsystem
QGL_Shutdown( unloadDLL );
}
#endif // USE_OPENGL_API
#ifdef USE_VULKAN_API
static qboolean GLW_LoadVulkan( void )
{
//
// load the driver and bind our function pointers to it
//
if ( QVK_Init() )
{
qboolean cdsFullscreen = (r_fullscreen->integer != 0);
// create the window and set up the context
if ( GLW_StartDriverAndSetMode( r_mode->integer, r_modeFullscreen->string, r_colorbits->integer, cdsFullscreen, qtrue ) == RSERR_OK )
return qtrue;
}
QVK_Shutdown( qtrue );
return qfalse;
}
static qboolean GLW_StartVulkan( void )
{
//
// load and initialize Vulkan driver
//
if ( !GLW_LoadVulkan() ) {
Com_Error( ERR_FATAL, "GLW_StartVulkan() - could not load Vulkan subsystem\n" );
return qfalse;
}
return qtrue;
}
/*
** VKimp_Init
**
** This is the platform specific Vulkan initialization function. It
** is responsible for loading Vulkan, initializing it, setting
** extensions, creating a window of the appropriate size, doing
** fullscreen manipulations, etc. Its overall responsibility is
** to make sure that a functional Vulkan subsystem is operating
** when it returns to the ref.
*/
void VKimp_Init( glconfig_t *config )
{
Com_Printf( "Initializing Vulkan subsystem\n" );
// feedback to renderer configuration
glw_state.config = config;
// load appropriate DLL and initialize subsystem
if ( !GLW_StartVulkan() )
return;
config->driverType = GLDRV_ICD;
config->hardwareType = GLHW_GENERIC;
// show main window after all initializations
ShowWindow( g_wv.hWnd, SW_SHOW );
IN_Init();
HandleEvents();
Key_ClearStates();
}
/*
** VKimp_Shutdown
**
** This routine does all OS specific shutdown procedures for the Vulkan
** subsystem.
*/
void VKimp_Shutdown( qboolean unloadDLL )
{
IN_Shutdown();
Com_Printf( "Shutting down Vulkan subsystem\n" );
// restore gamma
GLW_RestoreGamma();
// destroy window
if ( g_wv.hWnd )
{
Com_Printf( "...destroying window\n" );
//ShowWindow( g_wv.hWnd, SW_HIDE );
DestroyWindow( g_wv.hWnd );
g_wv.hWnd = NULL;
}
// reset display settings
if ( glw_state.cdsFullscreen )
{
ResetDisplaySettings( qtrue );
glw_state.cdsFullscreen = qfalse;
}
// shutdown QVK subsystem
QVK_Shutdown( unloadDLL );
}
#endif // USE_VULKAN_API