1851 lines
45 KiB
C
1851 lines
45 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
Copyright (C) 2007 HermitWorks Entertainment Corporation
|
|
|
|
This file is part of the Space Trader source code.
|
|
|
|
The Space Trader 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.
|
|
|
|
The Space Trader 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 the Space Trader source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "../render-base/tr_local.h"
|
|
#include "win_glimp-local.h"
|
|
|
|
#include "../qcommon/qcommon.h"
|
|
|
|
#include "../sql/sql.h"
|
|
|
|
extern sqlInfo_t com_db;
|
|
|
|
|
|
static void GLW_InitExtensions( void );
|
|
|
|
glwstate_t glw_state;
|
|
|
|
cvar_t *vid_xpos; // X coordinate of window position
|
|
cvar_t *vid_ypos; // Y coordinate of window position
|
|
cvar_t *r_allowSoftwareGL; // don't abort out if the pixelformat claims software
|
|
|
|
bool glimp_suspendRender;
|
|
static uint suspendRenderCount;
|
|
|
|
static int GLW_PickPFD( PIXELFORMATDESCRIPTOR *pfds, int *samples, int numPFDs, PIXELFORMATDESCRIPTOR *pPFD )
|
|
{
|
|
int i;
|
|
int bestMatch = 0;
|
|
int iSamples = 0;
|
|
|
|
if( samples )
|
|
{
|
|
if( r_fsaa->integer > 0 )
|
|
iSamples = r_fsaa->integer;
|
|
}
|
|
|
|
// look for a best match
|
|
for( i = 1; i <= numPFDs; i++ )
|
|
{
|
|
//
|
|
// make sure this has hardware acceleration
|
|
//
|
|
if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 )
|
|
{
|
|
if ( !r_allowSoftwareGL->integer )
|
|
{
|
|
if ( r_verbose->integer )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...PFD %d rejected, software acceleration\n", i );
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// verify pixel type
|
|
if ( pfds[i].iPixelType != PFD_TYPE_RGBA )
|
|
{
|
|
if ( r_verbose->integer )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...PFD %d rejected, not RGBA\n", i );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// verify proper flags
|
|
if ( ( ( pfds[i].dwFlags & pPFD->dwFlags ) & pPFD->dwFlags ) != pPFD->dwFlags )
|
|
{
|
|
if ( r_verbose->integer )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...PFD %d rejected, improper flags (%x instead of %x)\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
|
|
// multisample samples
|
|
//
|
|
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;
|
|
}
|
|
}
|
|
|
|
// check multisample
|
|
if( samples && samples[bestMatch] != iSamples )
|
|
{
|
|
//prefer a perfect match
|
|
if ( samples[i] == iSamples )
|
|
{
|
|
bestMatch = i;
|
|
continue;
|
|
}
|
|
// otherwise if this PFD has more samples than our best, use it
|
|
else if( samples[bestMatch] < iSamples && samples[i] > samples[bestMatch] )
|
|
{
|
|
bestMatch = i;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bestMatch = i;
|
|
}
|
|
}
|
|
|
|
if ( !bestMatch )
|
|
return 0;
|
|
|
|
if ( ( pfds[bestMatch].dwFlags & PFD_GENERIC_FORMAT ) != 0 )
|
|
{
|
|
if ( !r_allowSoftwareGL->integer )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...no hardware acceleration found\n" );
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_ALL, "...using software emulation\n" );
|
|
}
|
|
}
|
|
else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...MCD acceleration found\n" );
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_ALL, "...hardware acceleration found\n" );
|
|
}
|
|
|
|
*pPFD = pfds[bestMatch];
|
|
|
|
if( samples && samples[bestMatch] )
|
|
{
|
|
glConfig.fsaaSamples = samples[bestMatch];
|
|
ri.Printf( PRINT_ALL, "...selected %i sample FSAA format\n", samples[bestMatch] );
|
|
}
|
|
else
|
|
glConfig.fsaaSamples = 1;
|
|
|
|
return bestMatch;
|
|
}
|
|
|
|
static int GLW_ChoosePFDEx( PIXELFORMATDESCRIPTOR *pPFD )
|
|
{
|
|
//create a temporary window to grab a GL context and
|
|
//enumerate from - SetPixelFormat restriction
|
|
|
|
int x, y, w, h;
|
|
int numFormats;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
HWND tmpWnd;
|
|
HDC tmpDC;
|
|
HGLRC tmpRC;
|
|
|
|
WNDCLASS tmpClass;
|
|
ATOM tmpClassAtom;
|
|
|
|
WinVars_t *winVars = (WinVars_t*)ri.PlatformGetVars();
|
|
|
|
tmpClass.cbClsExtra = 0;
|
|
tmpClass.cbWndExtra = 0;
|
|
tmpClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
|
|
tmpClass.hCursor = LoadCursor( NULL, IDC_ARROW );
|
|
tmpClass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
|
|
tmpClass.hInstance = winVars->hInstance;
|
|
tmpClass.lpfnWndProc = &DefWindowProc;
|
|
tmpClass.lpszClassName = "GL_ENUM_WND";
|
|
tmpClass.lpszMenuName = NULL;
|
|
tmpClass.style = CS_OWNDC;
|
|
|
|
tmpClassAtom = RegisterClass( &tmpClass );
|
|
|
|
if( winVars->hWnd )
|
|
{
|
|
RECT tmpRC;
|
|
|
|
GetWindowRect( winVars->hWnd, &tmpRC );
|
|
x = tmpRC.left;
|
|
y = tmpRC.top;
|
|
w = tmpRC.right - tmpRC.left;
|
|
h = tmpRC.bottom - tmpRC.top;
|
|
}
|
|
else
|
|
{
|
|
x = CW_USEDEFAULT;
|
|
y = CW_USEDEFAULT;
|
|
w = CW_USEDEFAULT;
|
|
h = CW_USEDEFAULT;
|
|
}
|
|
|
|
tmpWnd = CreateWindow( MAKEINTATOM( tmpClassAtom ), "GL_ENUM_WND", WS_OVERLAPPEDWINDOW,
|
|
x, y, w, h, NULL, NULL, winVars->hInstance, NULL );
|
|
tmpDC = GetDC( tmpWnd );
|
|
|
|
numFormats = DescribePixelFormat( tmpDC, 1, sizeof( PIXELFORMATDESCRIPTOR ), NULL );
|
|
|
|
for( i = 1; i <= numFormats; i++ )
|
|
{
|
|
PIXELFORMATDESCRIPTOR pfd;
|
|
pfd.nSize = sizeof( pfd );
|
|
DescribePixelFormat( tmpDC, i, sizeof( PIXELFORMATDESCRIPTOR ), &pfd );
|
|
|
|
if( pfd.dwFlags & PFD_SUPPORT_OPENGL )
|
|
{
|
|
if( SetPixelFormat( tmpDC, i, &pfd ) )
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( i <= numFormats )
|
|
{
|
|
tmpRC = wglCreateContext( tmpDC );
|
|
wglMakeCurrent( tmpDC, tmpRC );
|
|
|
|
if( glewInit() == GLEW_OK && WGLEW_ARB_pixel_format )
|
|
{
|
|
PIXELFORMATDESCRIPTOR *pfds = (PIXELFORMATDESCRIPTOR*)ri.Hunk_AllocateTempMemory( sizeof( PIXELFORMATDESCRIPTOR ) * (numFormats + 1) );
|
|
int *samples = GLEW_ARB_multisample ? (int*)ri.Hunk_AllocateTempMemory( sizeof( int ) * (numFormats + 1) ) : NULL;
|
|
|
|
for( i = 1; i <= numFormats; i++ )
|
|
{
|
|
pfds[i].nSize = sizeof( PIXELFORMATDESCRIPTOR );
|
|
DescribePixelFormat( tmpDC, i, sizeof( PIXELFORMATDESCRIPTOR ), pfds + i );
|
|
|
|
if( samples )
|
|
{
|
|
int qAttribs[2] = { WGL_SAMPLE_BUFFERS_ARB, WGL_SAMPLES_ARB };
|
|
int vAttribs[2];
|
|
|
|
wglGetPixelFormatAttribivARB( tmpDC, i, 0, 2, qAttribs, vAttribs );
|
|
samples[i] = vAttribs[0] ? vAttribs[1] : 0;
|
|
}
|
|
}
|
|
|
|
ret = GLW_PickPFD( pfds, samples, numFormats, pPFD );
|
|
|
|
if( samples )
|
|
ri.Hunk_FreeTempMemory( samples );
|
|
ri.Hunk_FreeTempMemory( pfds );
|
|
}
|
|
|
|
wglMakeCurrent( NULL, NULL );
|
|
wglDeleteContext( tmpRC );
|
|
}
|
|
|
|
ReleaseDC( tmpWnd, tmpDC );
|
|
DestroyWindow( tmpWnd );
|
|
|
|
UnregisterClass( MAKEINTATOM( tmpClassAtom ), winVars->hInstance );
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int GLW_ChoosePFD( HDC hDC, PIXELFORMATDESCRIPTOR *pPFD )
|
|
{
|
|
PIXELFORMATDESCRIPTOR *pfds;
|
|
int numFormats;
|
|
int i;
|
|
|
|
ri.Printf( PRINT_ALL, "...GLW_ChoosePFD( %d, %d, %d )\n", ( int ) pPFD->cColorBits, ( int ) pPFD->cDepthBits, ( int ) pPFD->cStencilBits );
|
|
|
|
// count number of PFDs
|
|
numFormats = DescribePixelFormat( hDC, 1, sizeof( PIXELFORMATDESCRIPTOR ), NULL );
|
|
|
|
ri.Printf( PRINT_ALL, "...%d PFDs found\n", numFormats );
|
|
|
|
pfds = (PIXELFORMATDESCRIPTOR*)ri.Hunk_AllocateTempMemory( sizeof( PIXELFORMATDESCRIPTOR ) * (numFormats + 1) );
|
|
// grab information
|
|
for( i = 1; i <= numFormats; i++ )
|
|
{
|
|
pfds[i].nSize = sizeof( PIXELFORMATDESCRIPTOR );
|
|
DescribePixelFormat( hDC, i, sizeof( PIXELFORMATDESCRIPTOR ), pfds + i );
|
|
}
|
|
|
|
i = GLW_PickPFD( pfds, NULL, numFormats, pPFD );
|
|
|
|
ri.Hunk_FreeTempMemory( pfds );
|
|
|
|
return i;
|
|
}
|
|
|
|
static const PIXELFORMATDESCRIPTOR GLW_DEFAULT_PFD =
|
|
{
|
|
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
|
|
};
|
|
|
|
static void GLW_CreatePFD( PIXELFORMATDESCRIPTOR *pPFD )
|
|
{
|
|
PIXELFORMATDESCRIPTOR src = GLW_DEFAULT_PFD;
|
|
|
|
src.cColorBits = (BYTE)r_colorbits->integer;
|
|
if( src.cColorBits == 0 )
|
|
src.cColorBits = 32;
|
|
else if( src.cColorBits < 16 )
|
|
src.cColorBits = 16;
|
|
else if( src.cColorBits < 24 )
|
|
src.cColorBits = 24;
|
|
|
|
src.cDepthBits = (BYTE)r_depthbits->integer;
|
|
if( src.cDepthBits == 0 )
|
|
src.cDepthBits = 24;
|
|
else if( src.cDepthBits < 16 )
|
|
src.cDepthBits = 16;
|
|
|
|
src.cStencilBits = (BYTE)r_stencilbits->integer;
|
|
|
|
if( r_stereo->integer )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...attempting to use stereo\n" );
|
|
src.dwFlags |= PFD_STEREO;
|
|
glConfig.stereoEnabled = qtrue;
|
|
}
|
|
else
|
|
glConfig.stereoEnabled = qfalse;
|
|
|
|
*pPFD = src;
|
|
}
|
|
|
|
#define MSG_ERR_NO_GL \
|
|
"Space Trader can not run on this machine since OpenGL (1.1 or later) failed to start."
|
|
|
|
#define MSG_ERR_OLD_VIDEO_DRIVER \
|
|
"Space Trader can not run on this machine since it is " \
|
|
"missing one or more required OpenGL extensions. Please " \
|
|
"update your video card drivers and try again."
|
|
|
|
static void GLW_Fail( const char *msg )
|
|
{
|
|
ri.Error( ERR_FATAL, va( "%s", msg ) );
|
|
}
|
|
|
|
void Glimp_PrintSystemInfo( void )
|
|
{
|
|
Win_PrintCpuInfo();
|
|
Win_PrintMemoryInfo();
|
|
Win_PrintOSVersion();
|
|
}
|
|
|
|
static void GLW_CheckExtensions( void )
|
|
{
|
|
bool good = true;
|
|
char missingExts[4096];
|
|
|
|
Q_strncpyz( missingExts, MSG_ERR_OLD_VIDEO_DRIVER "\nYour GL driver is missing support for:\n", sizeof( missingExts ) );
|
|
|
|
if( glewInit() != GLEW_OK )
|
|
GLW_Fail( MSG_ERR_NO_GL );
|
|
|
|
if( !GLEW_VERSION_1_1 )
|
|
{
|
|
good = false;
|
|
|
|
Q_strcat( missingExts, sizeof( missingExts ), "OpenGL 1.1" );
|
|
ri.Printf( PRINT_ERROR, "OpenGL 1.1 is missing.\n" );
|
|
}
|
|
|
|
if( !(GLEW_VERSION_1_2 || GLEW_EXT_texture_edge_clamp || GLEW_SGIS_texture_edge_clamp) )
|
|
{
|
|
good = false;
|
|
|
|
Q_strcat( missingExts, sizeof( missingExts ), "GL_{EXT|SGIS}_texture_edge_clamp\n" );
|
|
ri.Printf( PRINT_ERROR, "Missing GL_{EXT|SGIS}_texture_edge_clamp.\n" );
|
|
}
|
|
|
|
if( !(GLEW_VERSION_1_3 || GLEW_ARB_multitexture) )
|
|
{
|
|
good = false;
|
|
|
|
Q_strcat( missingExts, sizeof( missingExts ), "GL_ARB_multitexture\n" );
|
|
ri.Printf( PRINT_ERROR, "Missing GL_ARB_multitexture.\n" );
|
|
}
|
|
|
|
if( !(GLEW_VERSION_1_3 || GLEW_ARB_texture_env_combine || GLEW_EXT_texture_env_combine) )
|
|
{
|
|
good = false;
|
|
|
|
Q_strcat( missingExts, sizeof( missingExts ), "GL_{ARB|EXT}_texture_env_combine\n" );
|
|
ri.Printf( PRINT_ERROR, "Missing GL_{ARB|EXT}_texture_env_combine.\n" );
|
|
}
|
|
|
|
if( !good ) {
|
|
Glimp_PrintSystemInfo();
|
|
ri.Printf( PRINT_ALL, "%s\n%s\n%s\n%s\n",
|
|
(const char*)glGetString( GL_VENDOR ),
|
|
(const char*)glGetString( GL_RENDERER ),
|
|
(const char*)glGetString( GL_VERSION ),
|
|
(const char*)glGetString( GL_EXTENSIONS ) );
|
|
GLW_Fail( missingExts );
|
|
}
|
|
}
|
|
|
|
static bool GLW_ValidateFSMonitor( const char *mon )
|
|
{
|
|
bool goodMon;
|
|
|
|
sql_prepare( &com_db, "SELECT gl_level FROM monitors SEARCH dev_name ?1" );
|
|
sql_bindtext( &com_db, 1, mon );
|
|
|
|
if( sql_step( &com_db ) )
|
|
{
|
|
goodMon = sql_columnasint( &com_db, 0 ) > 0;
|
|
}
|
|
else
|
|
goodMon = false;
|
|
|
|
sql_done( &com_db );
|
|
|
|
return goodMon;
|
|
}
|
|
|
|
static bool GLW_ValidateFSMode( const char *mode, const char *mon )
|
|
{
|
|
int w, h;
|
|
bool goodMode;
|
|
|
|
w = atoi( COM_Parse( &mode ) );
|
|
h = atoi( COM_Parse( &mode ) );
|
|
|
|
sql_prepare( &com_db, "SELECT COUNT( * ) FROM fsmodes SEARCH dev_name ?1 WHERE w=?2 AND h=?3" );
|
|
sql_bindtext( &com_db, 1, mon );
|
|
sql_bindint( &com_db, 2, w );
|
|
sql_bindint( &com_db, 3, h );
|
|
sql_step( &com_db );
|
|
|
|
goodMode = sql_columnasint( &com_db, 0 ) >= 1;
|
|
|
|
sql_done( &com_db );
|
|
|
|
return goodMode;
|
|
}
|
|
|
|
static const char* GLW_GetDefaultFSMode( const char *mon )
|
|
{
|
|
const char *ret;
|
|
|
|
if( GLW_ValidateFSMode( GLW_DEFAULT_FS_RESOLUTION, mon ) )
|
|
return GLW_DEFAULT_FS_RESOLUTION;
|
|
|
|
sql_prepare( &com_db, "SELECT w:plain || ' ' || h:plain FROM fsmodes SEARCH dev_name ?1 LIMIT 1" );
|
|
sql_bindtext( &com_db, 1, mon );
|
|
|
|
if( sql_step( &com_db ) )
|
|
ret = va( "%s", sql_columnastext( &com_db, 0 ) );
|
|
else
|
|
ret = GLW_DEFAULT_FS_RESOLUTION; //return it anyway??
|
|
|
|
sql_done( &com_db );
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char* GLW_GetDefaultFSMonitor( void )
|
|
{
|
|
int max_level;
|
|
const char *ret = 0;
|
|
|
|
sql_prepare( &com_db, "SELECT MAX( gl_level ) FROM monitors" );
|
|
if( sql_step( &com_db ) )
|
|
max_level = sql_columnasint( &com_db, 0 );
|
|
else
|
|
max_level = -1;
|
|
sql_done( &com_db );
|
|
|
|
if( max_level >= 0 )
|
|
{
|
|
sql_prepare( &com_db, "SELECT dev_name FROM monitors SEARCH gl_level ?" );
|
|
sql_bindint( &com_db, 1, max_level );
|
|
}
|
|
else
|
|
sql_prepare( &com_db, "SELECT dev_name FROM monitors" );
|
|
|
|
//pick the first monitor that supports the default resolution
|
|
while( sql_step( &com_db ) )
|
|
{
|
|
const char *mon = sql_columnastext( &com_db, 0 );
|
|
|
|
if( GLW_ValidateFSMode( GLW_DEFAULT_FS_RESOLUTION, mon ) )
|
|
{
|
|
ret = va( "%s", mon );
|
|
break;
|
|
}
|
|
}
|
|
sql_done( &com_db );
|
|
|
|
//no good, try to pick the first monitor that supports the best GL support level
|
|
if( !ret )
|
|
{
|
|
if( max_level < 0 )
|
|
max_level = 0;
|
|
|
|
sql_prepare( &com_db, "SELECT dev_name FROM monitors SEARCH gl_level ? LIMIT 1" );
|
|
sql_bindint( &com_db, 1, max_level );
|
|
|
|
if( sql_step( &com_db ) )
|
|
ret = va( "%s", sql_columnastext( &com_db, 0 ) );
|
|
|
|
sql_done( &com_db );
|
|
}
|
|
|
|
//???
|
|
if( !ret )
|
|
ret = "";
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void GLW_ValidateFSModeCvar()
|
|
{
|
|
const char *mon, *val;
|
|
|
|
if( !r_fsmode )
|
|
return;
|
|
|
|
mon = r_fsmonitor->latchedString ? r_fsmonitor->latchedString : r_fsmonitor->string;
|
|
val = r_fsmode->latchedString ? r_fsmode->latchedString : r_fsmode->string;
|
|
|
|
if( !GLW_ValidateFSMode( val, mon ) )
|
|
{
|
|
//trying to set this to something invalid - no!
|
|
const char *def = GLW_GetDefaultFSMode( mon );
|
|
|
|
//check to make sure we're not setting it each frame
|
|
if( Q_stricmp( def, r_fsmode->string ) != 0 )
|
|
{
|
|
ri.Printf( PRINT_WARNING, "Invalid full-screen mode. Restoring default setting.\n" );
|
|
ri.Cvar_Set( r_fsmode->name, def );
|
|
}
|
|
}
|
|
}
|
|
|
|
static void GLW_ValidateFSMonitorCvar()
|
|
{
|
|
const char *val;
|
|
|
|
if( !r_fsmonitor )
|
|
return;
|
|
|
|
val = r_fsmonitor->latchedString ? r_fsmonitor->latchedString : r_fsmonitor->string;
|
|
|
|
if( !GLW_ValidateFSMonitor( val ) )
|
|
{
|
|
//trying to set this to something invalid - no!
|
|
const char *def = GLW_GetDefaultFSMonitor();
|
|
|
|
//check to make sure we're not setting it each frame
|
|
if( Q_stricmp( def, r_fsmonitor->string ) != 0 )
|
|
{
|
|
ri.Printf( PRINT_WARNING, "Invalid full-screen monitor. Restoring default setting.\n" );
|
|
ri.Cvar_Set( r_fsmonitor->name, def ); //set it back to what it was
|
|
GLW_ValidateFSModeCvar();
|
|
}
|
|
}
|
|
}
|
|
|
|
static int GLW_GetDisplayLevel( HDC dc )
|
|
{
|
|
int i;
|
|
PIXELFORMATDESCRIPTOR pfd;
|
|
|
|
pfd = GLW_DEFAULT_PFD;
|
|
i = GLW_ChoosePFD( dc, &pfd );
|
|
|
|
if( !i )
|
|
return 0;
|
|
|
|
pfd.nSize = sizeof( pfd );
|
|
DescribePixelFormat( dc, i, sizeof( PIXELFORMATDESCRIPTOR ), &pfd );
|
|
|
|
if( (pfd.dwFlags & PFD_SUPPORT_OPENGL) == 0 )
|
|
return 0;
|
|
|
|
if( pfd.dwFlags & PFD_GENERIC_FORMAT )
|
|
return 1; //software GL
|
|
|
|
return 2; //full GL
|
|
}
|
|
|
|
static BOOL CALLBACK GLW_GetMonitorDisplayModes( HMONITOR mon, HDC dc, LPRECT rc, LPARAM userData )
|
|
{
|
|
int i;
|
|
DEVMODE mode;
|
|
MONITORINFOEX info;
|
|
int gl_level;
|
|
|
|
REF_PARAM( rc );
|
|
REF_PARAM( userData );
|
|
|
|
info.cbSize = sizeof( info );
|
|
GetMonitorInfo( mon, (LPMONITORINFO)&info );
|
|
|
|
gl_level = 3;//GLW_GetDisplayLevel( dc );
|
|
|
|
sql_bindtext( &com_db, 1, info.szDevice );
|
|
sql_bindint( &com_db, 2, info.rcMonitor.left );
|
|
sql_bindint( &com_db, 3, info.rcMonitor.top );
|
|
sql_bindint( &com_db, 4, info.rcMonitor.right - info.rcMonitor.left );
|
|
sql_bindint( &com_db, 5, info.rcMonitor.bottom - info.rcMonitor.top );
|
|
sql_bindint( &com_db, 6, gl_level );
|
|
sql_step( &com_db );
|
|
|
|
mode.dmSize = sizeof( mode );
|
|
mode.dmDriverExtra = 0;
|
|
for( i = 0; EnumDisplaySettingsEx( info.szDevice, i, &mode, 0 ) != 0; i++ )
|
|
{
|
|
int id;
|
|
|
|
if( mode.dmBitsPerPel < 16 )
|
|
continue;
|
|
|
|
if( mode.dmPelsWidth < 640 || mode.dmPelsHeight < 480 )
|
|
continue;
|
|
|
|
sql_prepare( &com_db, "UPDATE OR INSERT fsmodes SET id=#,w=?1,h=?2,dev_name=?3 SEARCH dev_name ?3 WHERE w=?1 AND h=?2" );
|
|
sql_bindint( &com_db, 1, (int)mode.dmPelsWidth );
|
|
sql_bindint( &com_db, 2, (int)mode.dmPelsHeight );
|
|
sql_bindtext( &com_db, 3, info.szDevice );
|
|
sql_step( &com_db );
|
|
sql_done( &com_db );
|
|
|
|
//get the id of what we just added
|
|
|
|
sql_prepare( &com_db, "SELECT id FROM fsmodes SEARCH dev_name ?3 WHERE w=?1 AND h=?2" );
|
|
sql_bindint( &com_db, 1, (int)mode.dmPelsWidth );
|
|
sql_bindint( &com_db, 2, (int)mode.dmPelsHeight );
|
|
sql_bindtext( &com_db, 3, info.szDevice );
|
|
sql_step( &com_db );
|
|
id = sql_columnasint( &com_db, 0 );
|
|
sql_done( &com_db );
|
|
|
|
//and insert the other info into the other table
|
|
|
|
sql_prepare( &com_db, "UPDATE OR INSERT fsmodes_ext SET id=?1,hz=?2,bpp=?3 SEARCH id ?1 WHERE hz=?2 AND bpp=?3" );
|
|
sql_bindint( &com_db, 1, id );
|
|
sql_bindint( &com_db, 2, (int)mode.dmDisplayFrequency );
|
|
sql_bindint( &com_db, 3, (int)mode.dmBitsPerPel );
|
|
sql_step( &com_db );
|
|
sql_done( &com_db );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void GLW_GetDisplayModes( void )
|
|
{
|
|
HDC dc;
|
|
|
|
sql_exec( &com_db,
|
|
|
|
"CREATE TABLE monitors"
|
|
"("
|
|
"dev_name STRING, "
|
|
"x INTEGER, "
|
|
"y INTEGER, "
|
|
"w INTEGER, "
|
|
"h INTEGER, "
|
|
"gl_level INTEGER "
|
|
");"
|
|
|
|
"CREATE TABLE fsmodes"
|
|
"("
|
|
"id INTEGER, "
|
|
"w INTEGER, "
|
|
"h INTEGER, "
|
|
"dev_name STRING " //references monitors.dev_name
|
|
");"
|
|
|
|
//really should be merged onto the prior - except then
|
|
//the list gets too big and the GUI SELECT blows up and
|
|
//I hate
|
|
"CREATE TABLE fsmodes_ext"
|
|
"("
|
|
"id INTEGER, "
|
|
"hz INTEGER, "
|
|
"bpp INTEGER "
|
|
");"
|
|
);
|
|
|
|
sql_prepare( &com_db, "INSERT INTO monitors(dev_name,x,y,w,h,gl_level) VALUES(?,?,?,?,?,?);" );
|
|
|
|
dc = GetDC( NULL );
|
|
EnumDisplayMonitors( dc, NULL, GLW_GetMonitorDisplayModes, 0 );
|
|
ReleaseDC( NULL, dc );
|
|
|
|
sql_done( &com_db );
|
|
}
|
|
|
|
//try to set the r_fsmonitor field to
|
|
//whatever monitor the window is on
|
|
static void Q_EXTERNAL_CALL GLW_UseCurrentMonitor( void )
|
|
{
|
|
RECT rc;
|
|
|
|
int max_area = 0;
|
|
int max_level = 0;
|
|
char best_monitor[CCHDEVICENAME] = "";
|
|
|
|
WinVars_t *winVars = (WinVars_t*)ri.PlatformGetVars();
|
|
|
|
if( !winVars->hWnd )
|
|
//no window = no clue
|
|
return;
|
|
|
|
GetWindowRect( winVars->hWnd, &rc );
|
|
|
|
sql_prepare( &com_db, "SELECT x, y, w, h, gl_level, dev_name FROM monitors WHERE gl_level > 0" );
|
|
while( sql_step( &com_db ) )
|
|
{
|
|
int l = sql_columnasint( &com_db, 0 );
|
|
int t = sql_columnasint( &com_db, 1 );
|
|
int r = l + sql_columnasint( &com_db, 2 );
|
|
int b = t + sql_columnasint( &com_db, 3 );
|
|
|
|
int area;
|
|
|
|
int level = sql_columnasint( &com_db, 4 );
|
|
const char *name = sql_columnastext( &com_db, 5 );
|
|
|
|
if( l < rc.left )
|
|
l = rc.left;
|
|
|
|
if( r > rc.right )
|
|
r = rc.right;
|
|
|
|
if( t < rc.top )
|
|
t = rc.top;
|
|
|
|
if( b > rc.bottom )
|
|
b = rc.bottom;
|
|
|
|
area = (r - l) * (b - t);
|
|
|
|
if( !area )
|
|
//screen does not intersect window
|
|
continue;
|
|
|
|
if( level < max_level )
|
|
continue;
|
|
|
|
if( level == max_level && area < max_area )
|
|
//only accept a screen of equal GL level if it owns more window area
|
|
continue;
|
|
|
|
max_level = level;
|
|
max_area = area;
|
|
Q_strncpyz( best_monitor, name, sizeof( best_monitor ) );
|
|
}
|
|
sql_done( &com_db );
|
|
|
|
if( !best_monitor[0] )
|
|
//nothing to do
|
|
return;
|
|
|
|
//set the monitor cvar and make sure we
|
|
//didn't just invalidate the mode cvar
|
|
ri.Cvar_Set( r_fsmonitor->name, best_monitor );
|
|
GLW_ValidateFSModeCvar();
|
|
}
|
|
|
|
LRESULT CALLBACK GLW_SubclassWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
|
|
{
|
|
switch( uMsg )
|
|
{
|
|
|
|
case WM_ACTIVATE:
|
|
if( glw_state.cdsFullscreen )
|
|
{
|
|
if( LOWORD( wParam ) == WA_INACTIVE )
|
|
{
|
|
//ensure that we're *behind* the window that's being brought forward (this will clear the TOPMOST flag)
|
|
SetWindowPos( hwnd, GetForegroundWindow(), 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );
|
|
}
|
|
else
|
|
{
|
|
//get into the topmost list
|
|
SetWindowPos( hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
if( tr.frameCount && !glimp_suspendRender )
|
|
//as soon as we start rendering frames
|
|
//we can stop filling the background
|
|
return 1;
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
/*
|
|
We don't want any GDI painting, but neither do we want
|
|
Windows to keep sending paint events. Just let Windows
|
|
know that we've got it covered and everything will be ok.
|
|
*/
|
|
ValidateRect( hwnd, NULL );
|
|
return 0;
|
|
|
|
case WM_MOVE:
|
|
{
|
|
if( !r_fullscreen->integer )
|
|
{
|
|
POINT pt;
|
|
|
|
pt.x = 0;
|
|
pt.y = 0;
|
|
ClientToScreen( hwnd, &pt );
|
|
|
|
ri.Cvar_Set( "vid_xpos", va( "%i", pt.x ) );
|
|
ri.Cvar_Set( "vid_ypos", va( "%i", pt.y ) );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
ri.Printf( PRINT_DEVELOPER, "Main window got WM_DESTROY\n" );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return CallWindowProc( (WNDPROC)glw_state.wndproc, hwnd, uMsg & ~WM_PASS_MESSAGE_FLAG, wParam, lParam );
|
|
}
|
|
|
|
static void GLW_RegisterClass( void )
|
|
{
|
|
WNDCLASS wc;
|
|
static qboolean registered = qfalse;
|
|
|
|
WinVars_t *winVars = (WinVars_t*)ri.PlatformGetVars();
|
|
|
|
if( registered )
|
|
return;
|
|
|
|
wc.style = CS_OWNDC;
|
|
wc.lpfnWndProc = GLW_SubclassWndProc; //intended wndproc is in glw_state.wndproc, GLW proc will call it
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = SKIN_WNDEXTRA;
|
|
wc.hInstance = winVars->hInstance;
|
|
wc.hIcon = LoadIcon( winVars->hInstance, MAKEINTRESOURCE( IDI_ICON1 ) );
|
|
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
|
|
wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = WINDOW_CLASS_NAME;
|
|
|
|
UnregisterClass( WINDOW_CLASS_NAME, winVars->hInstance );
|
|
|
|
if( !RegisterClass( &wc ) )
|
|
ri.Error( ERR_FATAL, "GLW: could not register window class" );
|
|
|
|
registered = qtrue;
|
|
}
|
|
|
|
static void GLW_KillGLWnd( void )
|
|
{
|
|
WinVars_t *winVars = (WinVars_t*)ri.PlatformGetVars();
|
|
|
|
if( glw_state.hGLRC )
|
|
{
|
|
wglMakeCurrent( NULL, NULL );
|
|
wglDeleteContext( glw_state.hGLRC );
|
|
|
|
glw_state.hGLRC = NULL;
|
|
}
|
|
|
|
if( glw_state.hDC )
|
|
{
|
|
ReleaseDC( winVars->hWnd, glw_state.hDC );
|
|
|
|
glw_state.hDC = NULL;
|
|
}
|
|
|
|
if( winVars->hWnd )
|
|
{
|
|
DestroyWindow( winVars->hWnd );
|
|
winVars->hWnd = NULL;
|
|
}
|
|
|
|
if( glw_state.cdsFullscreen )
|
|
{
|
|
ChangeDisplaySettings( NULL, 0 );
|
|
glw_state.cdsFullscreen = qfalse;
|
|
}
|
|
}
|
|
|
|
static void GLW_CreateGLWnd( void )
|
|
{
|
|
int x, y, w, h;
|
|
HWND hParent;
|
|
float aspect;
|
|
DWORD s, es;
|
|
skinDef_t *skin;
|
|
|
|
int refresh = 0;
|
|
int colorDepth = 0;
|
|
|
|
WinVars_t *winVars = (WinVars_t*)ri.PlatformGetVars();
|
|
|
|
if( winVars->hWnd )
|
|
return;
|
|
|
|
x = 0;
|
|
y = 0;
|
|
|
|
if( r_fullscreen->integer )
|
|
{
|
|
int mode_id;
|
|
|
|
char * res = r_fsmode->string;
|
|
w = atoi( COM_Parse( &res ) );
|
|
h = atoi( COM_Parse( &res ) );
|
|
aspect = (float)w / (float)h;
|
|
|
|
sql_prepare( &com_db, "SELECT x, y FROM monitors SEARCH dev_name ?" );
|
|
sql_bindtext( &com_db, 1, r_fsmonitor->string );
|
|
if( sql_step( &com_db ) )
|
|
{
|
|
x = sql_columnasint( &com_db, 0 );
|
|
y = sql_columnasint( &com_db, 1 );
|
|
}
|
|
sql_done( &com_db );
|
|
|
|
//find the settings mode id
|
|
mode_id = -1;
|
|
sql_prepare( &com_db, "SELECT id FROM fsmodes SEARCH dev_name ?1 WHERE w=?2 AND h=?3" );
|
|
sql_bindtext( &com_db, 1, r_fsmonitor->string );
|
|
sql_bindint( &com_db, 2, w );
|
|
sql_bindint( &com_db, 3, h );
|
|
if( sql_step( &com_db ) )
|
|
mode_id = sql_columnasint( &com_db, 0 );
|
|
sql_done( &com_db );
|
|
|
|
//get a matching color mode
|
|
sql_prepare( &com_db, "SELECT bpp FROM fsmodes_ext SEARCH id ?1" );
|
|
sql_bindint( &com_db, 1, mode_id );
|
|
|
|
while( sql_step( &com_db ) )
|
|
{
|
|
int bpp = sql_columnasint( &com_db, 0 );
|
|
if( r_colorbits->integer )
|
|
{
|
|
if( bpp == r_colorbits->integer )
|
|
{
|
|
//take an exact match
|
|
colorDepth = bpp;
|
|
break;
|
|
}
|
|
|
|
if( bpp > r_colorbits->integer )
|
|
{
|
|
if( colorDepth < r_colorbits->integer || bpp < colorDepth )
|
|
//if we must go over, take the smallest value that goes over
|
|
colorDepth = bpp;
|
|
}
|
|
else if( bpp > colorDepth )
|
|
colorDepth = bpp;
|
|
}
|
|
else if( bpp > colorDepth )
|
|
colorDepth = bpp;
|
|
}
|
|
|
|
sql_done( &com_db );
|
|
|
|
//get a matching refresh rate
|
|
sql_prepare( &com_db, "SELECT hz FROM fsmodes_ext SEARCH id ?1 WHERE bpp=?2" );
|
|
sql_bindint( &com_db, 1, mode_id );
|
|
sql_bindint( &com_db, 2, colorDepth );
|
|
|
|
while( sql_step( &com_db ) )
|
|
{
|
|
int hz = sql_columnasint( &com_db, 0 );
|
|
if( r_displayRefresh->integer )
|
|
{
|
|
if( hz == r_displayRefresh->integer )
|
|
{
|
|
//take an exact match
|
|
refresh = hz;
|
|
break;
|
|
}
|
|
|
|
if( hz > r_displayRefresh->integer )
|
|
{
|
|
if( refresh < r_displayRefresh->integer || hz < refresh )
|
|
//if we must go over, take the smallest value that goes over
|
|
refresh = hz;
|
|
}
|
|
else if( hz > refresh )
|
|
refresh = hz;
|
|
}
|
|
else if( hz > refresh )
|
|
//take the highest refresh rate
|
|
refresh = hz;
|
|
}
|
|
|
|
sql_done( &com_db );
|
|
}
|
|
else
|
|
{
|
|
if( !R_GetModeInfo( &w, &h, &aspect, r_mode->integer ) )
|
|
{
|
|
//fall back to special modes
|
|
w = 0;
|
|
h = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Clean out old display mode changes.
|
|
|
|
Note that we *don't* skip this when we're going back into fullscreen
|
|
as it tends to produce some aweful bugs on some Windows installations.
|
|
*/
|
|
if( glw_state.cdsFullscreen )
|
|
ChangeDisplaySettings( NULL, 0 );
|
|
|
|
//window style bits
|
|
es = 0;
|
|
s = 0;
|
|
|
|
skin = NULL;
|
|
|
|
if( r_fullscreen->integer )
|
|
{
|
|
//go into full screen mode
|
|
|
|
RECT rc;
|
|
HMONITOR monitor;
|
|
MONITORINFOEX monitorInfo;
|
|
|
|
hParent = 0;
|
|
|
|
//make sure we're set up for multimon goodness
|
|
if( winVars->hWndHost )
|
|
{
|
|
GetWindowRect( winVars->hWndHost, &rc );
|
|
}
|
|
else
|
|
{
|
|
rc.left = x;
|
|
rc.top = y;
|
|
rc.right = x + w;
|
|
rc.bottom = y + h;
|
|
}
|
|
|
|
monitor = MonitorFromRect( &rc, MONITOR_DEFAULTTONEAREST );
|
|
|
|
monitorInfo.cbSize = sizeof( monitorInfo );
|
|
GetMonitorInfo( monitor, (LPMONITORINFO)&monitorInfo );
|
|
|
|
//if we got an invalid mode then use desktop resolution
|
|
if( w == 0 )
|
|
w = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
|
|
if( h == 0 )
|
|
h = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
|
|
|
|
//change that monitor's display size to <w, h>
|
|
//set the window rect to cover the display
|
|
//skip the festival of desktop flickering if not changing resolution
|
|
if( (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left) != w ||
|
|
(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top) != h )
|
|
{
|
|
DEVMODE dm;
|
|
memset( &dm, 0, sizeof( dm ) );
|
|
|
|
dm.dmSize = sizeof( dm );
|
|
|
|
dm.dmPelsWidth = w;
|
|
dm.dmPelsHeight = h;
|
|
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
|
|
|
|
if( refresh != 0 )
|
|
{
|
|
dm.dmDisplayFrequency = refresh;
|
|
dm.dmFields |= DM_DISPLAYFREQUENCY;
|
|
}
|
|
|
|
if( colorDepth != 0 )
|
|
{
|
|
dm.dmBitsPerPel = colorDepth;
|
|
dm.dmFields |= DM_BITSPERPEL;
|
|
}
|
|
|
|
if( ChangeDisplaySettingsEx( monitorInfo.szDevice, &dm, NULL, CDS_FULLSCREEN, NULL )
|
|
!= DISP_CHANGE_SUCCESSFUL )
|
|
{
|
|
//try again without color bits and frequency
|
|
dm.dmFields &= ~(DM_BITSPERPEL | DM_DISPLAYFREQUENCY);
|
|
dm.dmBitsPerPel = 0;
|
|
dm.dmDisplayFrequency = 0;
|
|
|
|
if( ChangeDisplaySettingsEx( monitorInfo.szDevice, &dm, NULL, CDS_FULLSCREEN, NULL )
|
|
!= DISP_CHANGE_SUCCESSFUL )
|
|
//failed...
|
|
ri.Printf( PRINT_WARNING, "Invalid fullscreen resolution, running at desktop resolution" );
|
|
}
|
|
}
|
|
|
|
//get the new monitor info
|
|
monitorInfo.cbSize = sizeof( monitorInfo );
|
|
GetMonitorInfo( monitor, (LPMONITORINFO)&monitorInfo );
|
|
|
|
x = monitorInfo.rcMonitor.left;
|
|
y = monitorInfo.rcMonitor.top;
|
|
w = monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left;
|
|
h = monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top;
|
|
|
|
s = WS_POPUP;
|
|
es = WS_EX_APPWINDOW | WS_EX_TOPMOST;
|
|
|
|
glw_state.cdsFullscreen = qtrue;
|
|
}
|
|
else if( winVars->hWndHost )
|
|
{
|
|
RECT rc;
|
|
|
|
hParent = winVars->hWndHost;
|
|
|
|
GetClientRect( winVars->hWndHost, &rc );
|
|
x = rc.left;
|
|
y = rc.top;
|
|
w = rc.right - rc.left;
|
|
h = rc.bottom - rc.top;
|
|
|
|
s = WS_CHILD;
|
|
es = WS_EX_NOPARENTNOTIFY;
|
|
}
|
|
else
|
|
{
|
|
RECT rc;
|
|
HMONITOR monitor;
|
|
MONITORINFO monitorInfo;
|
|
qboolean usedefault = qfalse;
|
|
|
|
if( w == 0 )
|
|
w = 640;
|
|
if( h == 0 )
|
|
h = 480;
|
|
|
|
vid_xpos = ri.Cvar_Get( "vid_xpos", va("%d",CW_USEDEFAULT), CVAR_ARCHIVE );
|
|
vid_ypos = ri.Cvar_Get( "vid_ypos", "0", CVAR_ARCHIVE );
|
|
|
|
x = vid_xpos->integer;
|
|
y = vid_ypos->integer;
|
|
|
|
if ( x == CW_USEDEFAULT ) {
|
|
x = 0;
|
|
usedefault = qtrue;
|
|
}
|
|
|
|
rc.left = x;
|
|
rc.top = y;
|
|
rc.right = x + w;
|
|
rc.bottom = y + h;
|
|
|
|
hParent = 0;
|
|
|
|
if( r_skin->string[0] )
|
|
skin = Skin_Load( r_skin->string );
|
|
|
|
//account for the border frame
|
|
if( skin )
|
|
{
|
|
s = WS_POPUP;
|
|
es = WS_EX_APPWINDOW;
|
|
|
|
Skin_AdjustWindowRect( &rc, skin );
|
|
AdjustWindowRectEx( &rc, s, FALSE, es );
|
|
}
|
|
else
|
|
{
|
|
s = WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION;
|
|
es = WS_EX_APPWINDOW;
|
|
|
|
AdjustWindowRectEx( &rc, s, FALSE, es );
|
|
}
|
|
|
|
x = rc.left;
|
|
y = rc.top;
|
|
w = rc.right - rc.left;
|
|
h = rc.bottom - rc.top;
|
|
|
|
//constrain to a monitor
|
|
//this is important as we can't get a
|
|
//pixel format if we're entirely off screen
|
|
monitor = MonitorFromRect( &rc, MONITOR_DEFAULTTONEAREST );
|
|
|
|
monitorInfo.cbSize = sizeof( monitorInfo );
|
|
GetMonitorInfo( monitor, &monitorInfo );
|
|
|
|
//if we're not actually intersecting the monitor
|
|
//(shoved off of the screen I guess) move back onto it
|
|
|
|
if( x > monitorInfo.rcWork.right )
|
|
//left window edge past right screen edge
|
|
x = monitorInfo.rcWork.right - w;
|
|
if( x + w < monitorInfo.rcWork.left )
|
|
//right window edge past left screen edge
|
|
x = monitorInfo.rcWork.left;
|
|
|
|
if( y > monitorInfo.rcWork.bottom )
|
|
//top window edge past bottom screen edge
|
|
y = monitorInfo.rcWork.bottom - h;
|
|
if( y + h < monitorInfo.rcWork.top )
|
|
//bottom window edge past top screen edge
|
|
y = monitorInfo.rcWork.top;
|
|
|
|
glw_state.cdsFullscreen = qfalse;
|
|
|
|
|
|
if ( usedefault ) {
|
|
x = monitorInfo.rcMonitor.left + ((monitorInfo.rcMonitor.right-monitorInfo.rcMonitor.left)-w)/2;
|
|
y = monitorInfo.rcMonitor.top + ((monitorInfo.rcMonitor.bottom-monitorInfo.rcMonitor.top)-h)/2;
|
|
}
|
|
}
|
|
|
|
winVars->hWnd = NULL;
|
|
|
|
if( skin )
|
|
{
|
|
Skin_RegisterClass();
|
|
winVars->hWnd = Skin_CreateWnd( skin, x, y, w, h );
|
|
}
|
|
|
|
if( !winVars->hWnd )
|
|
{
|
|
GLW_RegisterClass();
|
|
winVars->hWnd = CreateWindowEx( es, WINDOW_CLASS_NAME, WINDOW_CAPTION,
|
|
s, x, y, w, h, hParent, NULL, winVars->hInstance, NULL );
|
|
}
|
|
|
|
//the window now owns the skin and will free it
|
|
|
|
if( !winVars->hWnd )
|
|
ri.Error( ERR_FATAL, "GLW: could not create window" );
|
|
|
|
SetWindowPos( winVars->hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW );
|
|
|
|
ShowWindow( winVars->hWnd, SW_SHOWNORMAL );
|
|
SetFocus( winVars->hWnd );
|
|
|
|
//get the window to draw once (get rid of graphical clutter)
|
|
UpdateWindow( winVars->hWnd );
|
|
|
|
//fire up the GL
|
|
glw_state.hDC = GetDC( winVars->hWnd );
|
|
if( !glw_state.hDC )
|
|
ri.Error( ERR_FATAL, "GLW: could not get window DC" );
|
|
|
|
//set up the pixel format
|
|
{
|
|
int pixelFormat;
|
|
PIXELFORMATDESCRIPTOR pfd;
|
|
|
|
GLW_CreatePFD( &pfd );
|
|
pixelFormat = GLW_ChoosePFDEx( &pfd );
|
|
|
|
if( !pixelFormat )
|
|
{
|
|
pixelFormat = GLW_ChoosePFD( glw_state.hDC, &pfd );
|
|
glConfig.fsaaSamples = 1;
|
|
}
|
|
|
|
if( !pixelFormat )
|
|
ri.Error( ERR_FATAL, "GLW: no valid pixel format" );
|
|
|
|
pfd.nSize = sizeof( pfd );
|
|
DescribePixelFormat( glw_state.hDC, pixelFormat, sizeof( pfd ), &pfd );
|
|
if( !SetPixelFormat( glw_state.hDC, pixelFormat, &pfd ) )
|
|
ri.Error( ERR_FATAL, "GLW: could not set pixel format" );
|
|
|
|
glConfig.colorBits = pfd.cColorBits;
|
|
glConfig.depthBits = pfd.cDepthBits;
|
|
glConfig.stencilBits = pfd.cStencilBits;
|
|
glConfig.stereoEnabled = (pfd.dwFlags & PFD_STEREO) ? qtrue : qfalse;
|
|
|
|
ri.Printf( PRINT_ALL, "Using Pixel Format %i\n", pixelFormat );
|
|
}
|
|
|
|
glw_state.hGLRC = wglCreateContext( glw_state.hDC );
|
|
if( !glw_state.hGLRC || !wglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) )
|
|
ri.Error( ERR_FATAL, "GLW: could not initialize GL" );
|
|
|
|
GLW_CheckExtensions();
|
|
|
|
//R_PerfInit();
|
|
|
|
{
|
|
//get the actual sizes, in case Windows constrained our window
|
|
RECT rc;
|
|
GetClientRect( winVars->hWnd, &rc );
|
|
w = rc.right - rc.left;
|
|
h = rc.bottom - rc.top;
|
|
|
|
//fill in some glConfig stuff which *will* be referenced
|
|
//by various subsystem init functions (i.e. Cg, bloom)
|
|
glConfig.vidWidth = w;
|
|
glConfig.vidHeight = h;
|
|
glConfig.windowAspect = aspect;
|
|
}
|
|
|
|
glConfig.deviceSupportsGamma = qfalse;
|
|
glConfig.isFullscreen = glw_state.cdsFullscreen ? qtrue : qfalse;
|
|
|
|
glConfig.xscale = glConfig.vidWidth / 640.0f;
|
|
glConfig.yscale = glConfig.vidHeight / 480.0f;
|
|
|
|
if( glConfig.vidWidth * 480 > glConfig.vidHeight * 640 )
|
|
{
|
|
// wide screen
|
|
glConfig.xscale = glConfig.yscale;
|
|
glConfig.xbias = ((float)glConfig.vidWidth - (640.0F * glConfig.xscale)) * 0.5F;
|
|
}
|
|
else
|
|
{
|
|
// no wide screen
|
|
glConfig.xbias = 0.0f;
|
|
}
|
|
}
|
|
|
|
static void GLW_InitExtensions( void )
|
|
{
|
|
GLint glint;
|
|
|
|
ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" );
|
|
|
|
if( GLEW_EXT_texture_env_add )
|
|
{
|
|
glConfig.textureEnvAddAvailable = qtrue;
|
|
ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" );
|
|
}
|
|
else
|
|
{
|
|
glConfig.textureEnvAddAvailable = qfalse;
|
|
ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" );
|
|
}
|
|
|
|
if( WGLEW_EXT_swap_control )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...using WGL_EXT_swap_control\n" );
|
|
r_swapInterval->modified = qtrue; // force a set next frame
|
|
}
|
|
else
|
|
ri.Printf( PRINT_ALL, "...WGL_EXT_swap_control not found\n" );
|
|
|
|
if( GLEW_ARB_multitexture )
|
|
{
|
|
glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glint );
|
|
glConfig.maxActiveTextures = glint;
|
|
}
|
|
|
|
glGetIntegerv( GL_MAX_TEXTURE_SIZE, &glint );
|
|
glConfig.maxTextureSize = glint;
|
|
}
|
|
|
|
static void GLW_CheckForBustedDrivers()
|
|
{
|
|
const char *failCode = 0;
|
|
|
|
HDC dc = GetDC( NULL );
|
|
__try
|
|
{
|
|
PIXELFORMATDESCRIPTOR tmp;
|
|
memset( &tmp, 0, sizeof( tmp ) );
|
|
tmp.nSize = sizeof( tmp );
|
|
tmp.nVersion = 1;
|
|
|
|
DescribePixelFormat( dc, 1, sizeof( PIXELFORMATDESCRIPTOR ), &tmp );
|
|
}
|
|
__except( EXCEPTION_EXECUTE_HANDLER )
|
|
{
|
|
failCode = "Broken DescribePixelFormat API.";
|
|
}
|
|
ReleaseDC( NULL, dc );
|
|
|
|
if( failCode )
|
|
{
|
|
const char *msg = va(
|
|
"An error occurred while initializing OpenGL.\n"
|
|
"Please make sure that your drivers are up to date.\n\n"
|
|
"GL Error Details: '%s'", failCode );
|
|
GLW_Fail( msg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
** 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( void )
|
|
{
|
|
char buf[1024];
|
|
cvar_t *lastValidRenderer = ri.Cvar_Get( "r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE );
|
|
cvar_t *cv;
|
|
|
|
ri.Printf( PRINT_ALL, "Initializing OpenGL subsystem\n" );
|
|
|
|
GLW_CheckForBustedDrivers();
|
|
|
|
r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH );
|
|
|
|
// save off hInstance and wndproc
|
|
cv = ri.Cvar_Get( "win_hinstance", "", 0 );
|
|
sscanf( cv->string, "%i", (int*)&((WinVars_t*)ri.PlatformGetVars())->hInstance ); //FixMe: this is NOT 64-bit safe
|
|
|
|
cv = ri.Cvar_Get( "win_wndproc", "", 0 );
|
|
sscanf( cv->string, "%i", (int*)&glw_state.wndproc ); //FixMe: this is NOT 64-bit safe
|
|
|
|
GLW_GetDisplayModes();
|
|
|
|
r_mode = ri.Cvar_Get( "r_mode", "4", CVAR_ARCHIVE | CVAR_LATCH );
|
|
r_fullscreen = ri.Cvar_Get( "r_fullscreen", "0", CVAR_ARCHIVE | CVAR_LATCH );
|
|
|
|
r_fsmonitor = ri.Cvar_Get( "r_fsmonitor", GLW_GetDefaultFSMonitor(), CVAR_ARCHIVE | CVAR_LATCH );
|
|
GLW_ValidateFSMonitorCvar();
|
|
|
|
r_fsmode = ri.Cvar_Get( "r_fsmode", GLW_GetDefaultFSMode( r_fsmonitor->string ), CVAR_ARCHIVE | CVAR_LATCH );
|
|
GLW_ValidateFSModeCvar();
|
|
|
|
GLW_CreateGLWnd();
|
|
|
|
ri.Cmd_AddCommand( "vid_fsUseCurMon", GLW_UseCurrentMonitor );
|
|
|
|
// get our config strings
|
|
Q_strncpyz( glConfig.vendor_string, (const char*)glGetString( GL_VENDOR ), sizeof( glConfig.vendor_string ) );
|
|
Q_strncpyz( glConfig.renderer_string, (const char*)glGetString( GL_RENDERER ), sizeof( glConfig.renderer_string ) );
|
|
Q_strncpyz( glConfig.version_string, (const char*)glGetString( GL_VERSION ), sizeof( glConfig.version_string ) );
|
|
Q_strncpyz( glConfig.extensions_string, (const char*)glGetString( GL_EXTENSIONS ), sizeof( glConfig.extensions_string ) );
|
|
|
|
//
|
|
// chipset specific configuration
|
|
//
|
|
Q_strncpyz( buf, glConfig.renderer_string, sizeof(buf) );
|
|
Q_strlwr( buf );
|
|
|
|
//
|
|
// NOTE: if changing cvars, do it within this block. This allows them
|
|
// to be overridden when testing driver fixes, etc. but only sets
|
|
// them to their default state when the hardware is first installed/run.
|
|
//
|
|
if( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) )
|
|
ri.Cvar_Set( "r_textureMode", "LinearMipLinear" );
|
|
|
|
//
|
|
// this is where hardware specific workarounds that should be
|
|
// detected/initialized every startup should go.
|
|
//
|
|
|
|
/*
|
|
if( strstr( buf, "banshee" ) || strstr( buf, "voodoo3" ) )
|
|
;
|
|
else if( strstr( buf, "voodoo graphics/1 tmu/2 mb" ) )
|
|
;
|
|
else if( strstr( buf, "glzicd" ) )
|
|
;
|
|
else if( strstr( buf, "rage pro" ) || strstr( buf, "Rage Pro" ) || strstr( buf, "ragepro" ) )
|
|
;
|
|
else if( strstr( buf, "rage 128" ) )
|
|
;
|
|
else if( strstr( buf, "permedia2" ) )
|
|
;
|
|
else if( strstr( buf, "riva 128" ) )
|
|
;
|
|
else if( strstr( buf, "riva tnt " ) )
|
|
;
|
|
*/
|
|
|
|
ri.Cvar_Set( "r_lastValidRenderer", glConfig.renderer_string );
|
|
|
|
GLW_InitExtensions();
|
|
|
|
glimp_suspendRender = false;
|
|
}
|
|
|
|
/*
|
|
** GLimp_Shutdown
|
|
**
|
|
** This routine does all OS specific shutdown procedures for the OpenGL
|
|
** subsystem.
|
|
*/
|
|
void GLimp_Shutdown( void )
|
|
{
|
|
ri.Cmd_RemoveCommand( "vid_fsUseCurMon" );
|
|
|
|
GLW_KillGLWnd();
|
|
|
|
memset( &glConfig, 0, sizeof( glConfig ) );
|
|
}
|
|
|
|
static bool Glimp_WindowIsVisible( HWND hwnd )
|
|
{
|
|
RECT rc;
|
|
|
|
if( !IsWindowVisible( hwnd ) )
|
|
return false;
|
|
|
|
if( IsIconic( hwnd ) )
|
|
return false;
|
|
|
|
GetClientRect( hwnd, &rc );
|
|
if( rc.right == rc.left ||
|
|
rc.bottom == rc.top )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool Glimp_WindowAndParentsAreVisible( HWND wnd )
|
|
{
|
|
while( wnd )
|
|
{
|
|
if( !Glimp_WindowIsVisible( wnd ) )
|
|
return false;
|
|
|
|
wnd = GetParent( wnd );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GLimp_EndFrame( void )
|
|
{
|
|
BOOL bSwap;
|
|
|
|
WinVars_t *wv = (WinVars_t*)ri.PlatformGetVars();
|
|
|
|
if( !wv->hWnd )
|
|
return;
|
|
|
|
if( r_fsmonitor->modified )
|
|
{
|
|
r_fsmonitor->modified = qfalse;
|
|
|
|
GLW_ValidateFSMonitorCvar();
|
|
}
|
|
|
|
if( r_fsmode->modified )
|
|
{
|
|
//keep this valid at all times
|
|
r_fsmode->modified = qfalse;
|
|
|
|
GLW_ValidateFSModeCvar();
|
|
}
|
|
|
|
if( r_swapInterval->modified )
|
|
{
|
|
r_swapInterval->modified = qfalse;
|
|
|
|
if( !glConfig.stereoEnabled )
|
|
{ // why?
|
|
if( WGLEW_EXT_swap_control )
|
|
wglSwapIntervalEXT( r_swapInterval->integer );
|
|
}
|
|
}
|
|
|
|
{
|
|
#if 0
|
|
LARGE_INTEGER start, end, freq;
|
|
|
|
QueryPerformanceCounter( &start );
|
|
#endif
|
|
|
|
bSwap = SwapBuffers( glw_state.hDC );
|
|
|
|
#if 0
|
|
QueryPerformanceCounter( &end );
|
|
|
|
QueryPerformanceFrequency( &freq );
|
|
|
|
ri.Printf( PRINT_ALL, S_COLOR_CYAN"%10f\n", (float)((double)(end.QuadPart - start.QuadPart) / (double)freq.QuadPart) );
|
|
#endif
|
|
}
|
|
|
|
if( bSwap )
|
|
{
|
|
int i = suspendRenderCount;
|
|
if( i )
|
|
suspendRenderCount = i - 1;
|
|
}
|
|
else
|
|
{
|
|
DWORD err;
|
|
int i = suspendRenderCount;
|
|
|
|
if( i > 3 )
|
|
ri.Error( ERR_FATAL, "GLimp_EndFrame() - SwapBuffers() failed!\n" );
|
|
|
|
err = GetLastError();
|
|
|
|
if( Glimp_WindowAndParentsAreVisible( wv->hWnd ) )
|
|
{
|
|
suspendRenderCount = i + 1;
|
|
ri.Cmd_ExecuteText( EXEC_INSERT, "vid_restart;" );
|
|
}
|
|
else if( !suspendRenderCount )
|
|
suspendRenderCount = 1;
|
|
}
|
|
|
|
glimp_suspendRender = suspendRenderCount != 0;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void GLimp_LogComment( int level, char *comment, ... )
|
|
{
|
|
if( level && level > r_logLevel->integer )
|
|
return;
|
|
|
|
if( GLEW_GREMEDY_string_marker )
|
|
{
|
|
size_t len;
|
|
char msg[2048];
|
|
va_list vl;
|
|
|
|
va_start( vl, comment );
|
|
len = vsnprintf( msg, sizeof( msg ) - 1, comment, vl );
|
|
va_end( vl );
|
|
|
|
if( len == -1 )
|
|
len = sizeof( msg ) - 1;
|
|
|
|
glStringMarkerGREMEDY( len, msg );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
===========================================================
|
|
|
|
SMP acceleration
|
|
|
|
===========================================================
|
|
*/
|
|
|
|
HANDLE renderCommandsEvent;
|
|
HANDLE renderCompletedEvent;
|
|
HANDLE renderActiveEvent;
|
|
|
|
void (*glimpRenderThread)( void );
|
|
|
|
void GLimp_RenderThreadWrapper( void ) {
|
|
glimpRenderThread();
|
|
|
|
// unbind the context before we die
|
|
wglMakeCurrent( glw_state.hDC, NULL );
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
GLimp_SpawnRenderThread
|
|
=======================
|
|
*/
|
|
HANDLE renderThreadHandle;
|
|
int renderThreadId;
|
|
qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) {
|
|
|
|
renderCommandsEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
renderCompletedEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
renderActiveEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
|
|
glimpRenderThread = function;
|
|
|
|
renderThreadHandle = CreateThread(
|
|
NULL, // LPSECURITY_ATTRIBUTES lpsa,
|
|
0, // DWORD cbStack,
|
|
(LPTHREAD_START_ROUTINE)GLimp_RenderThreadWrapper, // LPTHREAD_START_ROUTINE lpStartAddr,
|
|
0, // LPVOID lpvThreadParm,
|
|
0, // DWORD fdwCreate,
|
|
&renderThreadId );
|
|
|
|
if ( !renderThreadHandle ) {
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
static void *smpData;
|
|
static int wglErrors;
|
|
|
|
void *GLimp_RendererSleep( void ) {
|
|
void *data;
|
|
|
|
if ( !wglMakeCurrent( glw_state.hDC, NULL ) ) {
|
|
wglErrors++;
|
|
}
|
|
|
|
ResetEvent( renderActiveEvent );
|
|
|
|
// after this, the front end can exit GLimp_FrontEndSleep
|
|
SetEvent( renderCompletedEvent );
|
|
|
|
WaitForSingleObject( renderCommandsEvent, INFINITE );
|
|
|
|
if ( !wglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) {
|
|
wglErrors++;
|
|
}
|
|
|
|
ResetEvent( renderCompletedEvent );
|
|
ResetEvent( renderCommandsEvent );
|
|
|
|
data = smpData;
|
|
|
|
// after this, the main thread can exit GLimp_WakeRenderer
|
|
SetEvent( renderActiveEvent );
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
void GLimp_FrontEndSleep( void ) {
|
|
WaitForSingleObject( renderCompletedEvent, INFINITE );
|
|
|
|
if ( !wglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) {
|
|
wglErrors++;
|
|
}
|
|
}
|
|
|
|
|
|
void GLimp_WakeRenderer( void *data ) {
|
|
smpData = data;
|
|
|
|
if ( !wglMakeCurrent( glw_state.hDC, NULL ) ) {
|
|
wglErrors++;
|
|
}
|
|
|
|
// after this, the renderer can continue through GLimp_RendererSleep
|
|
SetEvent( renderCommandsEvent );
|
|
|
|
WaitForSingleObject( renderActiveEvent, INFINITE );
|
|
}
|