/*
===========================================================================
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 );

	// 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 )
{
	//
	// 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.
		//
		int pixelformat = GLW_ChoosePFD( glw_state.hDC, pPFD );
		if ( pixelformat == 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 < 16 ) { // was < 24 before, some win9X drivers have 16/8 depth/stencil buffers
		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.cRedBits + ( int ) pfd.cGreenBits + ( int ) pfd.cBlueBits;
	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
