mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-12-01 00:12:39 +00:00
e0d55dc63c
only apply hw gamma ramps where and when needed fixed the start-up console window staying visible when starting in full-screen black window background to avoid the white flashes when hw gamma is enabled
1158 lines
30 KiB
C++
1158 lines
30 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_Shutdown
|
|
**
|
|
** Note that the GLW_xxx functions are Windows specific GL-subsystem
|
|
** related functions that are relevant ONLY to win_glimp.c
|
|
*/
|
|
|
|
#if _MSC_VER
|
|
#pragma warning (disable: 4996) // deprecated ZOMGOVERRUN! nannywhine
|
|
#endif
|
|
|
|
#include "../renderer/tr_local.h"
|
|
#include "resource.h"
|
|
#include "glw_win.h"
|
|
#include "wglext.h"
|
|
#include "win_local.h"
|
|
|
|
|
|
#define TRY_PFD_SUCCESS 0
|
|
#define TRY_PFD_FAIL_SOFT 1
|
|
#define TRY_PFD_FAIL_HARD 2
|
|
|
|
glwstate_t glw_state;
|
|
|
|
|
|
/*
|
|
** ChoosePFD
|
|
**
|
|
** Helper function that replaces ChoosePixelFormat.
|
|
*/
|
|
#define MAX_PFDS 256
|
|
|
|
static int GLW_ChoosePFD( HDC hDC, PIXELFORMATDESCRIPTOR *pPFD )
|
|
{
|
|
PIXELFORMATDESCRIPTOR pfds[MAX_PFDS+1];
|
|
int maxPFD = 0;
|
|
int i;
|
|
int bestMatch = 0;
|
|
|
|
ri.Printf( PRINT_DEVELOPER, "...GLW_ChoosePFD( %d, %d, %d )\n", ( int ) pPFD->cColorBits, ( int ) pPFD->cDepthBits, ( int ) pPFD->cStencilBits );
|
|
|
|
maxPFD = DescribePixelFormat( hDC, 1, sizeof( PIXELFORMATDESCRIPTOR ), &pfds[0] );
|
|
if ( maxPFD > MAX_PFDS )
|
|
{
|
|
ri.Printf( PRINT_WARNING, "...numPFDs > MAX_PFDS (%d > %d)\n", maxPFD, MAX_PFDS );
|
|
maxPFD = MAX_PFDS;
|
|
}
|
|
|
|
ri.Printf( PRINT_DEVELOPER, "...%d PFDs found\n", maxPFD - 1 );
|
|
|
|
// grab information
|
|
for ( i = 1; i <= maxPFD; i++ )
|
|
{
|
|
DescribePixelFormat( hDC, i, sizeof( PIXELFORMATDESCRIPTOR ), &pfds[i] );
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// verify pixel type
|
|
if ( pfds[i].iPixelType != PFD_TYPE_RGBA )
|
|
{
|
|
if ( r_verbose->integer )
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...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_DEVELOPER, "...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
|
|
//
|
|
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 )
|
|
return 0;
|
|
|
|
if ( pfds[bestMatch].dwFlags & (PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED) ) {
|
|
ri.Printf( PRINT_ALL, "...no OpenGL ICD found\n" );
|
|
return 0;
|
|
}
|
|
|
|
*pPFD = pfds[bestMatch];
|
|
|
|
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, qbool 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 = (BYTE)colorbits;
|
|
src.cDepthBits = (BYTE)depthbits;
|
|
src.cStencilBits = (BYTE)stencilbits;
|
|
|
|
if ( stereo )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...attempting to use stereo\n" );
|
|
src.dwFlags |= PFD_STEREO;
|
|
glConfig.stereoEnabled = qtrue;
|
|
}
|
|
else
|
|
{
|
|
glConfig.stereoEnabled = qfalse;
|
|
}
|
|
|
|
*pPFD = src;
|
|
}
|
|
|
|
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 (glw_state.nPendingPF)
|
|
pixelformat = glw_state.nPendingPF;
|
|
else
|
|
if ( ( pixelformat = GLW_ChoosePFD( glw_state.hDC, pPFD ) ) == 0 )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...GLW_ChoosePFD failed\n");
|
|
return TRY_PFD_FAIL_SOFT;
|
|
}
|
|
ri.Printf( PRINT_DEVELOPER, "...PIXELFORMAT %d selected\n", pixelformat );
|
|
|
|
DescribePixelFormat( glw_state.hDC, pixelformat, sizeof( *pPFD ), pPFD );
|
|
|
|
if ( SetPixelFormat( glw_state.hDC, pixelformat, pPFD ) == FALSE )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...SetPixelFormat failed\n", glw_state.hDC );
|
|
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 )
|
|
{
|
|
if ( ( glw_state.hGLRC = qwglCreateContext( glw_state.hDC ) ) == 0 )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...GL context creation failure\n" );
|
|
return TRY_PFD_FAIL_HARD;
|
|
}
|
|
ri.Printf( PRINT_DEVELOPER, "...GL context created\n" );
|
|
|
|
if ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) )
|
|
{
|
|
qwglDeleteContext( glw_state.hGLRC );
|
|
glw_state.hGLRC = NULL;
|
|
ri.Printf( PRINT_ALL, "...GL context creation currency failure\n" );
|
|
return TRY_PFD_FAIL_HARD;
|
|
}
|
|
ri.Printf( PRINT_DEVELOPER, "...GL context creation made current\n" );
|
|
}
|
|
|
|
return TRY_PFD_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
** - get a DC if one doesn't exist
|
|
** - create an HGLRC if one doesn't exist
|
|
*/
|
|
static qbool GLW_InitDriver( int colorbits )
|
|
{
|
|
int tpfd;
|
|
int depthbits, stencilbits;
|
|
static PIXELFORMATDESCRIPTOR pfd; // save between frames since 'tr' gets cleared
|
|
|
|
ri.Printf( PRINT_DEVELOPER, "Initializing OpenGL driver\n" );
|
|
|
|
//
|
|
// get a DC for our window if we don't already have one allocated
|
|
//
|
|
if ( glw_state.hDC == NULL )
|
|
{
|
|
if ( ( glw_state.hDC = GetDC( g_wv.hWnd ) ) == NULL )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...Get DC failed\n" );
|
|
return qfalse;
|
|
}
|
|
ri.Printf( PRINT_DEVELOPER, "...Get DC succeeded\n" );
|
|
}
|
|
|
|
if ( colorbits == 0 )
|
|
{
|
|
colorbits = glw_state.desktopBPP;
|
|
}
|
|
|
|
//
|
|
// implicitly assume Z-buffer depth == desktop color depth
|
|
//
|
|
if ( r_depthbits->integer == 0 ) {
|
|
if ( colorbits > 16 ) {
|
|
depthbits = 24;
|
|
} else {
|
|
depthbits = 16;
|
|
}
|
|
} else {
|
|
depthbits = r_depthbits->integer;
|
|
}
|
|
|
|
//
|
|
// do not allow stencil if Z-buffer depth likely won't contain it
|
|
//
|
|
stencilbits = r_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, (qbool)r_stereo->integer );
|
|
if ( ( tpfd = GLW_MakeContext( &pfd ) ) != TRY_PFD_SUCCESS )
|
|
{
|
|
if ( tpfd == TRY_PFD_FAIL_HARD )
|
|
{
|
|
ri.Printf( PRINT_WARNING, "...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.desktopBPP ) && !stencilbits )
|
|
{
|
|
ReleaseDC( g_wv.hWnd, glw_state.hDC );
|
|
glw_state.hDC = NULL;
|
|
ri.Printf( PRINT_ALL, "...failed to find an appropriate PIXELFORMAT\n" );
|
|
return qfalse;
|
|
}
|
|
|
|
//
|
|
// second attempt: desktop's color bits and no stencil
|
|
//
|
|
if ( colorbits > glw_state.desktopBPP )
|
|
colorbits = glw_state.desktopBPP;
|
|
|
|
GLW_CreatePFD( &pfd, colorbits, depthbits, 0, (qbool)r_stereo->integer );
|
|
if ( GLW_MakeContext( &pfd ) != TRY_PFD_SUCCESS )
|
|
{
|
|
if ( glw_state.hDC )
|
|
{
|
|
ReleaseDC( g_wv.hWnd, glw_state.hDC );
|
|
glw_state.hDC = NULL;
|
|
}
|
|
ri.Printf( PRINT_ALL, "...failed to find an appropriate PIXELFORMAT\n" );
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
// report if stereo is desired but unavailable
|
|
//
|
|
if ( r_stereo->integer && !( pfd.dwFlags & PFD_STEREO ) )
|
|
{
|
|
ri.Printf( PRINT_ALL, "...failed to select stereo pixel format\n" );
|
|
glConfig.stereoEnabled = qfalse;
|
|
}
|
|
}
|
|
|
|
// store PFD specifics
|
|
//
|
|
glConfig.colorBits = ( int ) pfd.cColorBits;
|
|
glConfig.depthBits = ( int ) pfd.cDepthBits;
|
|
glConfig.stencilBits = ( int ) pfd.cStencilBits;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
// responsible for creating the Win32 window and initializing the OpenGL driver.
|
|
|
|
static qbool GLW_CreateWindow( int width, int height, int colorbits )
|
|
{
|
|
static qbool s_classRegistered = qfalse;
|
|
|
|
if ( !s_classRegistered )
|
|
{
|
|
WNDCLASS wc;
|
|
memset( &wc, 0, sizeof( wc ) );
|
|
|
|
wc.style = CS_OWNDC;
|
|
wc.lpfnWndProc = 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)GetStockObject(BLACK_BRUSH);
|
|
wc.lpszMenuName = 0;
|
|
wc.lpszClassName = CLIENT_WINDOW_TITLE;
|
|
|
|
if ( !RegisterClass( &wc ) )
|
|
ri.Error( ERR_FATAL, "GLW_CreateWindow: could not register window class" );
|
|
|
|
s_classRegistered = qtrue;
|
|
ri.Printf( PRINT_DEVELOPER, "...registered window class\n" );
|
|
}
|
|
|
|
//
|
|
// create the HWND if one does not already exist
|
|
//
|
|
if ( !g_wv.hWnd )
|
|
{
|
|
RECT r;
|
|
r.left = 0;
|
|
r.top = 0;
|
|
r.right = width;
|
|
r.bottom = height;
|
|
|
|
int style = WS_VISIBLE | WS_CLIPCHILDREN;
|
|
int exstyle;
|
|
|
|
if ( glInfo.isFullscreen )
|
|
{
|
|
// WS_BORDER fixes the single black flash that happens when reloading a map
|
|
// right after switching from windowed mode to full-screen
|
|
style |= WS_POPUP | WS_BORDER;
|
|
exstyle = WS_EX_TOPMOST;
|
|
}
|
|
else
|
|
{
|
|
style |= WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU;
|
|
exstyle = 0;
|
|
AdjustWindowRect( &r, style, FALSE );
|
|
}
|
|
|
|
const int w = r.right - r.left;
|
|
const int h = r.bottom - r.top;
|
|
|
|
WIN_GetStartUpMonitorIndex();
|
|
const RECT monRect = g_wv.monitorRects[g_wv.monitor];
|
|
|
|
int dx = 0;
|
|
int dy = 0;
|
|
|
|
if ( !glInfo.isFullscreen )
|
|
{
|
|
dx = ri.Cvar_Get( "vid_xpos", "0", 0 )->integer;
|
|
dy = ri.Cvar_Get( "vid_ypos", "0", 0 )->integer;
|
|
dx = Com_ClampInt( 0, max( 0, monRect.right - monRect.left - w ), dx );
|
|
dy = Com_ClampInt( 0, max( 0, monRect.bottom - monRect.top - h ), dy );
|
|
}
|
|
|
|
const int x = monRect.left + dx;
|
|
const int y = monRect.top + dy;
|
|
|
|
g_wv.hWnd = CreateWindowEx( exstyle, CLIENT_WINDOW_TITLE, " "CLIENT_WINDOW_TITLE, style,
|
|
x, y, w, h, NULL, NULL, g_wv.hInstance, NULL );
|
|
|
|
if ( !g_wv.hWnd )
|
|
ri.Error( ERR_FATAL, "GLW_CreateWindow() - Couldn't create window" );
|
|
|
|
ShowWindow( g_wv.hWnd, SW_SHOW );
|
|
UpdateWindow( g_wv.hWnd );
|
|
ri.Printf( PRINT_DEVELOPER, "...created window@%d,%d (%dx%d)\n", x, y, w, h );
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...window already present, CreateWindowEx skipped\n" );
|
|
}
|
|
|
|
if ( !GLW_InitDriver( colorbits ) )
|
|
{
|
|
ShowWindow( g_wv.hWnd, SW_HIDE );
|
|
DestroyWindow( g_wv.hWnd );
|
|
g_wv.hWnd = NULL;
|
|
return qfalse;
|
|
}
|
|
|
|
SetForegroundWindow( g_wv.hWnd );
|
|
SetFocus( g_wv.hWnd );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static qbool GLW_Fullscreen( DEVMODE& dm )
|
|
{
|
|
int cds = ChangeDisplaySettings( &dm, CDS_FULLSCREEN );
|
|
|
|
if (cds == DISP_CHANGE_SUCCESSFUL)
|
|
return qtrue;
|
|
|
|
ri.Printf( PRINT_ALL, "...CDS: %ix%i (C%i) failed: ", dm.dmPelsWidth, dm.dmPelsHeight, dm.dmBitsPerPel );
|
|
|
|
#define CDS_ERROR(x) case x: ri.Printf( PRINT_ALL, #x##"\n" ); break;
|
|
switch (cds) {
|
|
default:
|
|
ri.Printf( PRINT_ALL, "unknown error %d\n", cds );
|
|
break;
|
|
CDS_ERROR( DISP_CHANGE_RESTART );
|
|
CDS_ERROR( DISP_CHANGE_BADPARAM );
|
|
CDS_ERROR( DISP_CHANGE_BADFLAGS );
|
|
CDS_ERROR( DISP_CHANGE_FAILED );
|
|
CDS_ERROR( DISP_CHANGE_BADMODE );
|
|
CDS_ERROR( DISP_CHANGE_NOTUPDATED );
|
|
}
|
|
#undef CDS_ERROR
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
// GL_multisample is SUCH a fucking mess >:(
|
|
|
|
static void GLW_AttemptFSAA()
|
|
{
|
|
static const float ar[] = { 0, 0 };
|
|
// ignore r_xyzbits vars - FSAA requires 32-bit color, and anyone using it is implicitly on decent HW
|
|
static int anAttributes[] = {
|
|
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
|
|
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
|
|
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
|
|
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
|
|
WGL_COLOR_BITS_ARB, 32,
|
|
WGL_ALPHA_BITS_ARB, 0,
|
|
WGL_DEPTH_BITS_ARB, 24,
|
|
WGL_STENCIL_BITS_ARB, 8,
|
|
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
|
|
WGL_SAMPLES_ARB, 4,
|
|
0, 0
|
|
};
|
|
|
|
qwglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)qwglGetProcAddress( "wglChoosePixelFormatARB" );
|
|
if (!r_ext_multisample->integer || !qwglChoosePixelFormatARB) {
|
|
glDisable(GL_MULTISAMPLE_ARB);
|
|
return;
|
|
}
|
|
|
|
int iPFD;
|
|
UINT cPFD;
|
|
anAttributes[19] = r_ext_multisample->integer; // !!! UGH
|
|
if (!qwglChoosePixelFormatARB(glw_state.hDC, anAttributes, ar, 1, &iPFD, &cPFD) || !cPFD)
|
|
return;
|
|
|
|
// now bounce the ENTIRE fucking subsytem thanks to WGL stupidity
|
|
// we can't use GLimp_Shutdown() for this, because that does CDS poking that we don't want
|
|
assert( glw_state.hGLRC && glw_state.hDC && g_wv.hWnd );
|
|
|
|
qwglMakeCurrent( glw_state.hDC, NULL );
|
|
|
|
if ( glw_state.hGLRC ) {
|
|
qwglDeleteContext( glw_state.hGLRC );
|
|
glw_state.hGLRC = NULL;
|
|
}
|
|
|
|
if ( glw_state.hDC ) {
|
|
ReleaseDC( g_wv.hWnd, glw_state.hDC );
|
|
glw_state.hDC = NULL;
|
|
}
|
|
|
|
if ( g_wv.hWnd ) {
|
|
DestroyWindow( g_wv.hWnd );
|
|
g_wv.hWnd = NULL;
|
|
}
|
|
|
|
ri.Printf( PRINT_ALL, "...enabling FSAA\n" );
|
|
|
|
glw_state.nPendingPF = iPFD;
|
|
glw_state.pixelFormatSet = qfalse;
|
|
GLW_CreateWindow( glConfig.vidWidth, glConfig.vidHeight, glConfig.colorBits );
|
|
glw_state.nPendingPF = 0;
|
|
|
|
glEnable(GL_MULTISAMPLE_ARB);
|
|
}
|
|
|
|
|
|
static qbool GLW_SetMode( qbool cdsFullscreen )
|
|
{
|
|
HDC hDC = GetDC( GetDesktopWindow() );
|
|
glw_state.desktopBPP = GetDeviceCaps( hDC, BITSPIXEL );
|
|
glw_state.desktopWidth = GetDeviceCaps( hDC, HORZRES );
|
|
glw_state.desktopHeight = GetDeviceCaps( hDC, VERTRES );
|
|
ReleaseDC( GetDesktopWindow(), hDC );
|
|
|
|
glInfo.isFullscreen = cdsFullscreen;
|
|
if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect ) ) {
|
|
glConfig.vidWidth = glw_state.desktopWidth;
|
|
glConfig.vidHeight = glw_state.desktopHeight;
|
|
glConfig.windowAspect = (float)glConfig.vidWidth / glConfig.vidHeight;
|
|
cdsFullscreen = qfalse;
|
|
}
|
|
//ri.Printf( PRINT_DEVELOPER, "...setting mode %dx%d %s\n", glConfig.vidWidth, glConfig.vidHeight, cdsFullscreen ? "FS" : "W" );
|
|
|
|
DEVMODE dm;
|
|
memset( &dm, 0, sizeof( dm ) );
|
|
dm.dmSize = sizeof( dm );
|
|
|
|
if (cdsFullscreen != glw_state.cdsFullscreen) {
|
|
if (cdsFullscreen) {
|
|
dm.dmPelsWidth = glConfig.vidWidth;
|
|
dm.dmPelsHeight = glConfig.vidHeight;
|
|
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
|
|
|
|
if ( r_displayRefresh->integer ) {
|
|
dm.dmDisplayFrequency = r_displayRefresh->integer;
|
|
dm.dmFields |= DM_DISPLAYFREQUENCY;
|
|
}
|
|
|
|
if ( r_colorbits->integer ) {
|
|
dm.dmBitsPerPel = r_colorbits->integer;
|
|
dm.dmFields |= DM_BITSPERPEL;
|
|
}
|
|
|
|
glInfo.isFullscreen = qtrue;
|
|
glw_state.cdsFullscreen = qtrue;
|
|
|
|
if (!GLW_Fullscreen( dm )) {
|
|
glInfo.isFullscreen = qfalse;
|
|
glw_state.cdsFullscreen = qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ChangeDisplaySettings( 0, 0 );
|
|
glw_state.cdsFullscreen = qfalse;
|
|
}
|
|
}
|
|
|
|
if (!GLW_CreateWindow( glConfig.vidWidth, glConfig.vidHeight, glConfig.colorBits ))
|
|
return qfalse;
|
|
|
|
if (EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &dm ))
|
|
glInfo.displayFrequency = dm.dmDisplayFrequency;
|
|
|
|
GLW_AttemptFSAA();
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static void GLW_InitExtensions()
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "Initializing OpenGL extensions\n" );
|
|
|
|
#define QGL_EXT(T, fn) q##fn = (T)qwglGetProcAddress( #fn ); \
|
|
if (!q##fn) Com_Error( ERR_FATAL, "QGL_EXT: required extension "#fn" not found" );
|
|
|
|
QGL_EXT( PFNGLLOCKARRAYSEXTPROC, glLockArraysEXT );
|
|
QGL_EXT( PFNGLUNLOCKARRAYSEXTPROC, glUnlockArraysEXT );
|
|
QGL_EXT( PFNGLACTIVETEXTUREARBPROC, glActiveTextureARB );
|
|
QGL_EXT( PFNGLCLIENTACTIVETEXTUREARBPROC, glClientActiveTextureARB );
|
|
|
|
#undef QGL_EXT
|
|
|
|
// WGL_EXT_swap_control
|
|
qwglSwapIntervalEXT = ( BOOL (WINAPI *)(int)) qwglGetProcAddress( "wglSwapIntervalEXT" );
|
|
if ( qwglSwapIntervalEXT )
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...using WGL_EXT_swap_control\n" );
|
|
r_swapInterval->modified = qtrue; // force a set next frame
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...WGL_EXT_swap_control not found\n" );
|
|
}
|
|
|
|
int maxAnisotropy = 0;
|
|
if ( strstr( glConfig.extensions_string, "GL_EXT_texture_filter_anisotropic" ) )
|
|
{
|
|
if (r_ext_max_anisotropy->integer > 1) {
|
|
qglGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy );
|
|
if ( maxAnisotropy <= 0 ) {
|
|
ri.Printf( PRINT_DEVELOPER, "...GL_EXT_texture_filter_anisotropic not properly supported!\n" );
|
|
maxAnisotropy = 0;
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...using GL_EXT_texture_filter_anisotropic (max: %i)\n", maxAnisotropy );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...ignoring GL_EXT_texture_filter_anisotropic\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...GL_EXT_texture_filter_anisotropic not found\n" );
|
|
}
|
|
Cvar_Set( "r_ext_max_anisotropy", va("%i", maxAnisotropy) );
|
|
|
|
}
|
|
|
|
|
|
static qbool GLW_LoadOpenGL()
|
|
{
|
|
// only real GL implementations are acceptable
|
|
const char* OPENGL_DRIVER_NAME = "opengl32";
|
|
|
|
// load the driver and bind our function pointers to it
|
|
if ( QGL_Init( OPENGL_DRIVER_NAME ) ) {
|
|
// create the window and set up the context
|
|
if ( GLW_SetMode( (qbool)r_fullscreen->integer ) ) {
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
QGL_Shutdown();
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
void GLimp_EndFrame()
|
|
{
|
|
if ( r_swapInterval->modified ) {
|
|
r_swapInterval->modified = qfalse;
|
|
|
|
if ( !glConfig.stereoEnabled && qwglSwapIntervalEXT ) {
|
|
qwglSwapIntervalEXT( r_swapInterval->integer );
|
|
}
|
|
}
|
|
|
|
// don't flip if drawing to front buffer
|
|
if ( Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) {
|
|
SwapBuffers( glw_state.hDC );
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
static unsigned short s_oldHardwareGamma[3][256];
|
|
|
|
|
|
static void GLW_CheckHardwareGamma()
|
|
{
|
|
glConfig.deviceSupportsGamma = qfalse;
|
|
|
|
if (r_ignorehwgamma->integer)
|
|
return;
|
|
|
|
HDC hDC = GetDC( GetDesktopWindow() );
|
|
glConfig.deviceSupportsGamma = (qbool)GetDeviceGammaRamp( hDC, s_oldHardwareGamma );
|
|
ReleaseDC( GetDesktopWindow(), hDC );
|
|
|
|
if (!glConfig.deviceSupportsGamma)
|
|
return;
|
|
|
|
// do a sanity check on the gamma values
|
|
if ( ( HIBYTE( s_oldHardwareGamma[0][255] ) <= HIBYTE( s_oldHardwareGamma[0][0] ) ) ||
|
|
( HIBYTE( s_oldHardwareGamma[1][255] ) <= HIBYTE( s_oldHardwareGamma[1][0] ) ) ||
|
|
( HIBYTE( s_oldHardwareGamma[2][255] ) <= HIBYTE( s_oldHardwareGamma[2][0] ) ) )
|
|
{
|
|
glConfig.deviceSupportsGamma = qfalse;
|
|
ri.Printf( PRINT_WARNING, "WARNING: device has broken gamma support\n" );
|
|
}
|
|
|
|
// make sure that we didn't have a prior crash in the game:
|
|
// if so, we need to restore the gamma values to at least a linear value
|
|
if ( ( HIBYTE( s_oldHardwareGamma[0][181] ) == 255 ) )
|
|
{
|
|
ri.Printf( PRINT_WARNING, "WARNING: suspicious gamma tables, using linear ramp for restoration\n" );
|
|
for (unsigned short g = 0; g < 255; ++g)
|
|
{
|
|
s_oldHardwareGamma[0][g] = g << 8;
|
|
s_oldHardwareGamma[1][g] = g << 8;
|
|
s_oldHardwareGamma[2][g] = g << 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void GLW_UpdateGammaMonitorInfo()
|
|
{
|
|
const int screenCount = GetSystemMetrics( SM_CMONITORS );
|
|
if ( screenCount <= 1 )
|
|
{
|
|
g_wv.hGammaMonitor = NULL;
|
|
g_wv.gammaMonitorName[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
RECT rect;
|
|
GetWindowRect( g_wv.hWnd, &rect );
|
|
const HMONITOR hMonitor = MonitorFromRect( &rect, MONITOR_DEFAULTTONEAREST );
|
|
|
|
MONITORINFOEX info;
|
|
ZeroMemory( &info, sizeof( info ) );
|
|
info.cbSize = sizeof( MONITORINFOEX );
|
|
if ( GetMonitorInfo( hMonitor, &info ) )
|
|
{
|
|
g_wv.hGammaMonitor = hMonitor;
|
|
Q_strncpyz( g_wv.gammaMonitorName, info.szDevice, sizeof( g_wv.gammaMonitorName ) );
|
|
}
|
|
}
|
|
|
|
|
|
static void GLW_RestoreGammaRamp( HDC hDC )
|
|
{
|
|
if ( SetDeviceGammaRamp( hDC, s_oldHardwareGamma ) )
|
|
glw_state.gammaRampSet = qfalse;
|
|
}
|
|
|
|
|
|
void GLW_RestoreGamma()
|
|
{
|
|
if (!glConfig.deviceSupportsGamma || !glw_state.gammaRampSet)
|
|
return;
|
|
|
|
if ( g_wv.hGammaMonitor )
|
|
{
|
|
const HDC hDC = CreateDC( "DISPLAY", g_wv.gammaMonitorName, NULL, NULL );
|
|
GLW_RestoreGammaRamp( hDC );
|
|
DeleteDC( hDC );
|
|
}
|
|
else
|
|
{
|
|
GLW_RestoreGammaRamp( glw_state.hDC );
|
|
}
|
|
}
|
|
|
|
|
|
static void GLW_SetGammaRamp( HDC hDC, LPVOID lpRamp )
|
|
{
|
|
if ( SetDeviceGammaRamp( hDC, lpRamp ) )
|
|
{
|
|
glw_state.gammaRampSet = qtrue;
|
|
}
|
|
else
|
|
{
|
|
Com_Printf( "SetDeviceGammaRamp failed.\n" );
|
|
}
|
|
}
|
|
|
|
|
|
void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] )
|
|
{
|
|
unsigned short table[3][256];
|
|
int i, j;
|
|
|
|
if ( !glConfig.deviceSupportsGamma || r_ignorehwgamma->integer || !glw_state.hDC )
|
|
return;
|
|
|
|
for ( i = 0; i < 256; i++ ) {
|
|
table[0][i] = ( ( ( unsigned short ) red[i] ) << 8 ) | red[i];
|
|
table[1][i] = ( ( ( unsigned short ) green[i] ) << 8 ) | green[i];
|
|
table[2][i] = ( ( ( unsigned short ) blue[i] ) << 8 ) | blue[i];
|
|
}
|
|
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
for ( i = 0 ; i < 128 ; i++ ) {
|
|
if ( table[j][i] > ( (128+i) << 8 ) ) {
|
|
table[j][i] = (128+i) << 8;
|
|
}
|
|
}
|
|
if ( table[j][127] > 254<<8 ) {
|
|
table[j][127] = 254<<8;
|
|
}
|
|
}
|
|
|
|
GLW_UpdateGammaMonitorInfo();
|
|
if ( g_wv.hGammaMonitor )
|
|
{
|
|
const HDC hDC = CreateDC( "DISPLAY", g_wv.gammaMonitorName, NULL, NULL );
|
|
GLW_SetGammaRamp( hDC, table );
|
|
DeleteDC( hDC );
|
|
}
|
|
else
|
|
{
|
|
GLW_SetGammaRamp( glw_state.hDC, table );
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
/*
|
|
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()
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "Initializing OpenGL subsystem\n" );
|
|
|
|
// save off hInstance for the subsystems
|
|
const cvar_t* cv = ri.Cvar_Get( "win_hinstance", "", 0 );
|
|
sscanf( cv->string, "%i", (int *)&g_wv.hInstance );
|
|
|
|
// load appropriate DLL and initialize subsystem
|
|
if (!GLW_LoadOpenGL())
|
|
ri.Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem\n" );
|
|
|
|
// get our config strings
|
|
Q_strncpyz( glConfig.vendor_string, (const char*)qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) );
|
|
Q_strncpyz( glConfig.renderer_string, (const char*)qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) );
|
|
Q_strncpyz( glConfig.version_string, (const char*)qglGetString (GL_VERSION), sizeof( glConfig.version_string ) );
|
|
Q_strncpyz( glConfig.extensions_string, (const char*)qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) );
|
|
|
|
GLW_InitExtensions();
|
|
GLW_CheckHardwareGamma();
|
|
|
|
if (GLW_InitARB() && QGL_InitARB())
|
|
return;
|
|
|
|
ri.Error( ERR_FATAL, "GLimp_Init() - could not find an acceptable OpenGL subsystem\n" );
|
|
}
|
|
|
|
|
|
// do all OS specific shutdown procedures for the OpenGL subsystem
|
|
|
|
void GLimp_Shutdown()
|
|
{
|
|
const char* success[] = { "failed", "success" };
|
|
int retVal;
|
|
|
|
// FIXME: Brian, we need better fallbacks from partially initialized failures
|
|
if ( !qwglMakeCurrent ) {
|
|
return;
|
|
}
|
|
|
|
ri.Printf( PRINT_DEVELOPER, "Shutting down OpenGL subsystem\n" );
|
|
|
|
GLW_RestoreGamma();
|
|
|
|
// set current context to NULL
|
|
if ( qwglMakeCurrent )
|
|
{
|
|
retVal = qwglMakeCurrent( NULL, NULL ) != 0;
|
|
ri.Printf( PRINT_DEVELOPER, "...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] );
|
|
}
|
|
|
|
// delete HGLRC
|
|
if ( glw_state.hGLRC )
|
|
{
|
|
retVal = qwglDeleteContext( glw_state.hGLRC ) != 0;
|
|
ri.Printf( PRINT_DEVELOPER, "...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;
|
|
ri.Printf( PRINT_DEVELOPER, "...releasing DC: %s\n", success[retVal] );
|
|
glw_state.hDC = NULL;
|
|
}
|
|
|
|
// destroy window
|
|
if ( g_wv.hWnd )
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...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 )
|
|
{
|
|
ri.Printf( PRINT_DEVELOPER, "...resetting display\n" );
|
|
ChangeDisplaySettings( 0, 0 );
|
|
glw_state.cdsFullscreen = qfalse;
|
|
}
|
|
|
|
// shutdown QGL subsystem
|
|
QGL_Shutdown();
|
|
|
|
memset( &glConfig, 0, sizeof( glConfig ) );
|
|
memset( &glState, 0, sizeof( glState ) );
|
|
}
|
|
|
|
|
|
/*
|
|
===========================================================
|
|
|
|
SMP acceleration
|
|
|
|
===========================================================
|
|
*/
|
|
|
|
static HANDLE renderCommandsEvent;
|
|
static HANDLE renderCompletedEvent;
|
|
static HANDLE renderActiveEvent;
|
|
|
|
static void (*glimpRenderThread)( void );
|
|
|
|
static void GLimp_RenderThreadWrapper()
|
|
{
|
|
glimpRenderThread();
|
|
// unbind the context before we die
|
|
qwglMakeCurrent( glw_state.hDC, NULL );
|
|
}
|
|
|
|
qbool 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;
|
|
|
|
DWORD renderThreadId;
|
|
HANDLE 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 );
|
|
|
|
return (renderThreadHandle != NULL);
|
|
}
|
|
|
|
|
|
static void *smpData;
|
|
static int wglErrors;
|
|
|
|
void *GLimp_RendererSleep( void ) {
|
|
void *data;
|
|
|
|
if ( !qwglMakeCurrent( glw_state.hDC, NULL ) ) {
|
|
wglErrors++;
|
|
}
|
|
|
|
ResetEvent( renderActiveEvent );
|
|
|
|
// after this, the front end can exit GLimp_FrontEndSleep
|
|
SetEvent( renderCompletedEvent );
|
|
|
|
WaitForSingleObject( renderCommandsEvent, INFINITE );
|
|
|
|
if ( !qwglMakeCurrent( 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 ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) {
|
|
wglErrors++;
|
|
}
|
|
}
|
|
|
|
|
|
void GLimp_WakeRenderer( void *data ) {
|
|
smpData = data;
|
|
|
|
if ( !qwglMakeCurrent( glw_state.hDC, NULL ) ) {
|
|
wglErrors++;
|
|
}
|
|
|
|
// after this, the renderer can continue through GLimp_RendererSleep
|
|
SetEvent( renderCommandsEvent );
|
|
|
|
WaitForSingleObject( renderActiveEvent, INFINITE );
|
|
}
|
|
|
|
|
|
void WIN_UpdateHardwareGammaRamp( qbool enable )
|
|
{
|
|
if ( !glConfig.deviceSupportsGamma )
|
|
return;
|
|
|
|
if ( enable )
|
|
R_SetColorMappings();
|
|
else
|
|
GLW_RestoreGamma();
|
|
}
|