/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition 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 3 of the License, or (at your option) any later version. Doom 3 BFG Edition 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 Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 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_SwapBuffers ** GLimp_Init ** GLimp_Shutdown ** GLimp_SetGamma ** ** Note that the GLW_xxx functions are Windows specific GL-subsystem ** related functions that are relevant ONLY to win_glimp.c */ #pragma hdrstop #include "../../idlib/precompiled.h" #include "win_local.h" #include "rc/doom_resource.h" #include "../../renderer/tr_local.h" // WGL_ARB_extensions_string PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; // WGL_EXT_swap_interval PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; // WGL_ARB_pixel_format PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB; PFNWGLGETPIXELFORMATATTRIBFVARBPROC wglGetPixelFormatAttribfvARB; PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; // WGL_ARB_create_context PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; idCVar r_useOpenGL32( "r_useOpenGL32", "1", CVAR_INTEGER, "0 = OpenGL 2.0, 1 = OpenGL 3.2 compatibility profile, 2 = OpenGL 3.2 core profile", 0, 2 ); // // function declaration // bool QGL_Init( const char* dllname ); void QGL_Shutdown(); /* ======================== GLimp_TestSwapBuffers ======================== */ void GLimp_TestSwapBuffers( const idCmdArgs& args ) { idLib::Printf( "GLimp_TimeSwapBuffers\n" ); static const int MAX_FRAMES = 5; uint64 timestamps[MAX_FRAMES]; qglDisable( GL_SCISSOR_TEST ); int frameMilliseconds = 16; for( int swapInterval = 2 ; swapInterval >= -1 ; swapInterval-- ) { wglSwapIntervalEXT( swapInterval ); for( int i = 0 ; i < MAX_FRAMES ; i++ ) { if( swapInterval == -1 ) { Sys_Sleep( frameMilliseconds ); } if( i & 1 ) { qglClearColor( 0, 1, 0, 1 ); } else { qglClearColor( 1, 0, 0, 1 ); } qglClear( GL_COLOR_BUFFER_BIT ); qwglSwapBuffers( win32.hDC ); qglFinish(); timestamps[i] = Sys_Microseconds(); } idLib::Printf( "\nswapinterval %i\n", swapInterval ); for( int i = 1 ; i < MAX_FRAMES ; i++ ) { idLib::Printf( "%i microseconds\n", ( int )( timestamps[i] - timestamps[i - 1] ) ); } } } /* ======================== GLimp_GetOldGammaRamp ======================== */ static void GLimp_SaveGamma() { HDC hDC; BOOL success; hDC = GetDC( GetDesktopWindow() ); success = GetDeviceGammaRamp( hDC, win32.oldHardwareGamma ); common->DPrintf( "...getting default gamma ramp: %s\n", success ? "success" : "failed" ); ReleaseDC( GetDesktopWindow(), hDC ); } /* ======================== GLimp_RestoreGamma ======================== */ static void GLimp_RestoreGamma() { HDC hDC; BOOL success; // if we never read in a reasonable looking // table, don't write it out if( win32.oldHardwareGamma[0][255] == 0 ) { return; } hDC = GetDC( GetDesktopWindow() ); success = SetDeviceGammaRamp( hDC, win32.oldHardwareGamma ); common->DPrintf( "...restoring hardware gamma: %s\n", success ? "success" : "failed" ); ReleaseDC( GetDesktopWindow(), hDC ); } /* ======================== GLimp_SetGamma The renderer calls this when the user adjusts r_gamma or r_brightness ======================== */ void GLimp_SetGamma( unsigned short red[256], unsigned short green[256], unsigned short blue[256] ) { unsigned short table[3][256]; int i; if( !win32.hDC ) { return; } for( i = 0; i < 256; i++ ) { table[0][i] = red[i]; table[1][i] = green[i]; table[2][i] = blue[i]; } if( !SetDeviceGammaRamp( win32.hDC, table ) ) { common->Printf( "WARNING: SetDeviceGammaRamp failed.\n" ); } } /* ============================================================================= WglExtension Grabbing This is gross -- creating a window just to get a context to get the wgl extensions ============================================================================= */ /* ======================== R_CheckWinExtension ======================== */ bool R_CheckWinExtension( const char* name ) { if( !strstr( glConfig.wgl_extensions_string, name ) ) { idLib::Printf( "X..%s not found\n", name ); return false; } idLib::Printf( "...using %s\n", name ); return true; } /* ==================== FakeWndProc Only used to get wglExtensions ==================== */ LONG WINAPI FakeWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if( uMsg == WM_DESTROY ) { PostQuitMessage( 0 ); } if( uMsg != WM_CREATE ) { return DefWindowProc( hWnd, uMsg, wParam, lParam ); } const static PIXELFORMATDESCRIPTOR pfd = { sizeof( PIXELFORMATDESCRIPTOR ), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 24, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0, }; int pixelFormat; HDC hDC; HGLRC hGLRC; hDC = GetDC( hWnd ); // Set up OpenGL pixelFormat = ChoosePixelFormat( hDC, &pfd ); SetPixelFormat( hDC, pixelFormat, &pfd ); hGLRC = qwglCreateContext( hDC ); qwglMakeCurrent( hDC, hGLRC ); // free things wglMakeCurrent( NULL, NULL ); wglDeleteContext( hGLRC ); ReleaseDC( hWnd, hDC ); return DefWindowProc( hWnd, uMsg, wParam, lParam ); } /* ================== GLW_GetWGLExtensionsWithFakeWindow ================== */ void GLW_CheckWGLExtensions( HDC hDC ) { wglGetExtensionsStringARB = ( PFNWGLGETEXTENSIONSSTRINGARBPROC ) GLimp_ExtensionPointer( "wglGetExtensionsStringARB" ); if( wglGetExtensionsStringARB ) { glConfig.wgl_extensions_string = ( const char* ) wglGetExtensionsStringARB( hDC ); } else { glConfig.wgl_extensions_string = ""; } // WGL_EXT_swap_control wglSwapIntervalEXT = ( PFNWGLSWAPINTERVALEXTPROC ) GLimp_ExtensionPointer( "wglSwapIntervalEXT" ); r_swapInterval.SetModified(); // force a set next frame // WGL_EXT_swap_control_tear glConfig.swapControlTearAvailable = R_CheckWinExtension( "WGL_EXT_swap_control_tear" ); // WGL_ARB_pixel_format wglGetPixelFormatAttribivARB = ( PFNWGLGETPIXELFORMATATTRIBIVARBPROC )GLimp_ExtensionPointer( "wglGetPixelFormatAttribivARB" ); wglGetPixelFormatAttribfvARB = ( PFNWGLGETPIXELFORMATATTRIBFVARBPROC )GLimp_ExtensionPointer( "wglGetPixelFormatAttribfvARB" ); wglChoosePixelFormatARB = ( PFNWGLCHOOSEPIXELFORMATARBPROC )GLimp_ExtensionPointer( "wglChoosePixelFormatARB" ); // wglCreateContextAttribsARB wglCreateContextAttribsARB = ( PFNWGLCREATECONTEXTATTRIBSARBPROC )wglGetProcAddress( "wglCreateContextAttribsARB" ); } /* ================== GLW_GetWGLExtensionsWithFakeWindow ================== */ static void GLW_GetWGLExtensionsWithFakeWindow() { HWND hWnd; MSG msg; // Create a window for the sole purpose of getting // a valid context to get the wglextensions hWnd = CreateWindow( WIN32_FAKE_WINDOW_CLASS_NAME, GAME_NAME, WS_OVERLAPPEDWINDOW, 40, 40, 640, 480, NULL, NULL, win32.hInstance, NULL ); if( !hWnd ) { common->FatalError( "GLW_GetWGLExtensionsWithFakeWindow: Couldn't create fake window" ); } HDC hDC = GetDC( hWnd ); HGLRC gRC = wglCreateContext( hDC ); wglMakeCurrent( hDC, gRC ); GLW_CheckWGLExtensions( hDC ); wglDeleteContext( gRC ); ReleaseDC( hWnd, hDC ); DestroyWindow( hWnd ); while( GetMessage( &msg, NULL, 0, 0 ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } //============================================================================= /* ==================== GLW_WM_CREATE ==================== */ void GLW_WM_CREATE( HWND hWnd ) { } /* ======================== CreateOpenGLContextOnDC ======================== */ static HGLRC CreateOpenGLContextOnDC( const HDC hdc, const bool debugContext ) { int useOpenGL32 = r_useOpenGL32.GetInteger(); HGLRC m_hrc = NULL; for( int i = 0; i < 2; i++ ) { const int glMajorVersion = ( useOpenGL32 != 0 ) ? 3 : 2; const int glMinorVersion = ( useOpenGL32 != 0 ) ? 2 : 0; const int glDebugFlag = debugContext ? WGL_CONTEXT_DEBUG_BIT_ARB : 0; const int glProfileMask = ( useOpenGL32 != 0 ) ? WGL_CONTEXT_PROFILE_MASK_ARB : 0; const int glProfile = ( useOpenGL32 == 1 ) ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : ( ( useOpenGL32 == 2 ) ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : 0 ); const int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, glMajorVersion, WGL_CONTEXT_MINOR_VERSION_ARB, glMinorVersion, WGL_CONTEXT_FLAGS_ARB, glDebugFlag, glProfileMask, glProfile, 0 }; m_hrc = wglCreateContextAttribsARB( hdc, 0, attribs ); if( m_hrc != NULL ) { idLib::Printf( "created OpenGL %d.%d context\n", glMajorVersion, glMinorVersion ); break; } idLib::Printf( "failed to create OpenGL %d.%d context\n", glMajorVersion, glMinorVersion ); useOpenGL32 = 0; // fall back to OpenGL 2.0 } if( m_hrc == NULL ) { int err = GetLastError(); switch( err ) { case ERROR_INVALID_VERSION_ARB: idLib::Printf( "ERROR_INVALID_VERSION_ARB\n" ); break; case ERROR_INVALID_PROFILE_ARB: idLib::Printf( "ERROR_INVALID_PROFILE_ARB\n" ); break; default: idLib::Printf( "unknown error: 0x%x\n", err ); break; } } return m_hrc; } /* ==================== GLW_ChoosePixelFormat Returns -1 on failure, or a pixel format ==================== */ static int GLW_ChoosePixelFormat( const HDC hdc, const int multisamples, const bool stereo3D ) { FLOAT fAttributes[] = { 0, 0 }; int iAttributes[] = { WGL_SAMPLE_BUFFERS_ARB, ( ( multisamples > 1 ) ? 1 : 0 ), WGL_SAMPLES_ARB, multisamples, WGL_DOUBLE_BUFFER_ARB, TRUE, WGL_STENCIL_BITS_ARB, 8, WGL_DEPTH_BITS_ARB, 24, WGL_RED_BITS_ARB, 8, WGL_BLUE_BITS_ARB, 8, WGL_GREEN_BITS_ARB, 8, WGL_ALPHA_BITS_ARB, 8, WGL_STEREO_ARB, ( stereo3D ? TRUE : FALSE ), 0, 0 }; int pixelFormat; UINT numFormats; if( !wglChoosePixelFormatARB( hdc, iAttributes, fAttributes, 1, &pixelFormat, &numFormats ) ) { return -1; } return pixelFormat; } /* ==================== GLW_InitDriver Set the pixelformat for the window before it is shown, and create the rendering context ==================== */ static bool GLW_InitDriver( glimpParms_t parms ) { 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 32, // 32-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 8, // 8 bit destination alpha 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 }; common->Printf( "Initializing OpenGL driver\n" ); // // get a DC for our window if we don't already have one allocated // if( win32.hDC == NULL ) { common->Printf( "...getting DC: " ); if( ( win32.hDC = GetDC( win32.hWnd ) ) == NULL ) { common->Printf( "^3failed^0\n" ); return false; } common->Printf( "succeeded\n" ); } // the multisample path uses the wgl if( wglChoosePixelFormatARB ) { win32.pixelformat = GLW_ChoosePixelFormat( win32.hDC, parms.multiSamples, parms.stereo ); } else { // this is the "classic" choose pixel format path common->Printf( "Using classic ChoosePixelFormat\n" ); // eventually we may need to have more fallbacks, but for // now, ask for everything if( parms.stereo ) { common->Printf( "...attempting to use stereo\n" ); src.dwFlags |= PFD_STEREO; } // // 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( ( win32.pixelformat = ChoosePixelFormat( win32.hDC, &src ) ) == 0 ) { common->Printf( "...^3GLW_ChoosePFD failed^0\n" ); return false; } common->Printf( "...PIXELFORMAT %d selected\n", win32.pixelformat ); } // get the full info DescribePixelFormat( win32.hDC, win32.pixelformat, sizeof( win32.pfd ), &win32.pfd ); glConfig.colorBits = win32.pfd.cColorBits; glConfig.depthBits = win32.pfd.cDepthBits; glConfig.stencilBits = win32.pfd.cStencilBits; // XP seems to set this incorrectly if( !glConfig.stencilBits ) { glConfig.stencilBits = 8; } // the same SetPixelFormat is used either way if( SetPixelFormat( win32.hDC, win32.pixelformat, &win32.pfd ) == FALSE ) { common->Printf( "...^3SetPixelFormat failed^0\n", win32.hDC ); return false; } // // startup the OpenGL subsystem by creating a context and making it current // common->Printf( "...creating GL context: " ); win32.hGLRC = CreateOpenGLContextOnDC( win32.hDC, r_debugContext.GetBool() ); if( win32.hGLRC == 0 ) { common->Printf( "^3failed^0\n" ); return false; } common->Printf( "succeeded\n" ); common->Printf( "...making context current: " ); if( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) { qwglDeleteContext( win32.hGLRC ); win32.hGLRC = NULL; common->Printf( "^3failed^0\n" ); return false; } common->Printf( "succeeded\n" ); return true; } /* ==================== GLW_CreateWindowClasses ==================== */ static void GLW_CreateWindowClasses() { WNDCLASS wc; // // register the window class if necessary // if( win32.windowClassRegistered ) { return; } memset( &wc, 0, sizeof( wc ) ); wc.style = 0; wc.lpfnWndProc = ( WNDPROC ) MainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = win32.hInstance; wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE( IDI_ICON1 ) ); wc.hCursor = NULL; wc.hbrBackground = ( struct HBRUSH__* )COLOR_GRAYTEXT; wc.lpszMenuName = 0; wc.lpszClassName = WIN32_WINDOW_CLASS_NAME; if( !RegisterClass( &wc ) ) { common->FatalError( "GLW_CreateWindow: could not register window class" ); } common->Printf( "...registered window class\n" ); // now register the fake window class that is only used // to get wgl extensions wc.style = 0; wc.lpfnWndProc = ( WNDPROC ) FakeWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = win32.hInstance; wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE( IDI_ICON1 ) ); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = ( struct HBRUSH__* )COLOR_GRAYTEXT; wc.lpszMenuName = 0; wc.lpszClassName = WIN32_FAKE_WINDOW_CLASS_NAME; if( !RegisterClass( &wc ) ) { common->FatalError( "GLW_CreateWindow: could not register window class" ); } common->Printf( "...registered fake window class\n" ); win32.windowClassRegistered = true; } /* ======================== GetDisplayName ======================== */ static const char* GetDisplayName( const int deviceNum ) { static DISPLAY_DEVICE device; device.cb = sizeof( device ); if( !EnumDisplayDevices( 0, // lpDevice deviceNum, &device, 0 /* dwFlags */ ) ) { return NULL; } return device.DeviceName; } /* ======================== GetDeviceName ======================== */ static idStr GetDeviceName( const int deviceNum ) { DISPLAY_DEVICE device = {}; device.cb = sizeof( device ); if( !EnumDisplayDevices( 0, // lpDevice deviceNum, &device, 0 /* dwFlags */ ) ) { return false; } // get the monitor for this display if( !( device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP ) ) { return false; } return idStr( device.DeviceName ); } /* ======================== GetDisplayCoordinates ======================== */ static bool GetDisplayCoordinates( const int deviceNum, int& x, int& y, int& width, int& height, int& displayHz ) { idStr deviceName = GetDeviceName( deviceNum ); if( deviceName.Length() == 0 ) { return false; } DISPLAY_DEVICE device = {}; device.cb = sizeof( device ); if( !EnumDisplayDevices( 0, // lpDevice deviceNum, &device, 0 /* dwFlags */ ) ) { return false; } DISPLAY_DEVICE monitor; monitor.cb = sizeof( monitor ); if( !EnumDisplayDevices( deviceName.c_str(), 0, &monitor, 0 /* dwFlags */ ) ) { return false; } DEVMODE devmode; devmode.dmSize = sizeof( devmode ); if( !EnumDisplaySettings( deviceName.c_str(), ENUM_CURRENT_SETTINGS, &devmode ) ) { return false; } common->Printf( "display device: %i\n", deviceNum ); common->Printf( " DeviceName : %s\n", device.DeviceName ); common->Printf( " DeviceString: %s\n", device.DeviceString ); common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); common->Printf( " DeviceID : %s\n", device.DeviceID ); common->Printf( " DeviceKey : %s\n", device.DeviceKey ); common->Printf( " DeviceName : %s\n", monitor.DeviceName ); common->Printf( " DeviceString: %s\n", monitor.DeviceString ); common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); common->Printf( " DeviceID : %s\n", monitor.DeviceID ); common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); common->Printf( " dmDisplayFrequency: %i\n", devmode.dmDisplayFrequency ); x = devmode.dmPosition.x; y = devmode.dmPosition.y; width = devmode.dmPelsWidth; height = devmode.dmPelsHeight; displayHz = devmode.dmDisplayFrequency; return true; } /* ==================== DMDFO ==================== */ static const char* DMDFO( int dmDisplayFixedOutput ) { switch( dmDisplayFixedOutput ) { case DMDFO_DEFAULT: return "DMDFO_DEFAULT"; case DMDFO_CENTER: return "DMDFO_CENTER"; case DMDFO_STRETCH: return "DMDFO_STRETCH"; } return "UNKNOWN"; } /* ==================== PrintDevMode ==================== */ static void PrintDevMode( DEVMODE& devmode ) { common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); common->Printf( " dmDisplayFixedOutput: %s\n", DMDFO( devmode.dmDisplayFixedOutput ) ); common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); common->Printf( " dmDisplayFrequency : %i\n", devmode.dmDisplayFrequency ); } /* ==================== DumpAllDisplayDevices ==================== */ void DumpAllDisplayDevices() { common->Printf( "\n" ); for( int deviceNum = 0 ; ; deviceNum++ ) { DISPLAY_DEVICE device = {}; device.cb = sizeof( device ); if( !EnumDisplayDevices( 0, // lpDevice deviceNum, &device, 0 /* dwFlags */ ) ) { break; } common->Printf( "display device: %i\n", deviceNum ); common->Printf( " DeviceName : %s\n", device.DeviceName ); common->Printf( " DeviceString: %s\n", device.DeviceString ); common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); common->Printf( " DeviceID : %s\n", device.DeviceID ); common->Printf( " DeviceKey : %s\n", device.DeviceKey ); for( int monitorNum = 0 ; ; monitorNum++ ) { DISPLAY_DEVICE monitor = {}; monitor.cb = sizeof( monitor ); if( !EnumDisplayDevices( device.DeviceName, monitorNum, &monitor, 0 /* dwFlags */ ) ) { break; } common->Printf( " DeviceName : %s\n", monitor.DeviceName ); common->Printf( " DeviceString: %s\n", monitor.DeviceString ); common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); common->Printf( " DeviceID : %s\n", monitor.DeviceID ); common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); DEVMODE currentDevmode = {}; if( !EnumDisplaySettings( device.DeviceName, ENUM_CURRENT_SETTINGS, ¤tDevmode ) ) { common->Printf( "ERROR: EnumDisplaySettings(ENUM_CURRENT_SETTINGS) failed!\n" ); } common->Printf( " -------------------\n" ); common->Printf( " ENUM_CURRENT_SETTINGS\n" ); PrintDevMode( currentDevmode ); DEVMODE registryDevmode = {}; if( !EnumDisplaySettings( device.DeviceName, ENUM_REGISTRY_SETTINGS, ®istryDevmode ) ) { common->Printf( "ERROR: EnumDisplaySettings(ENUM_CURRENT_SETTINGS) failed!\n" ); } common->Printf( " -------------------\n" ); common->Printf( " ENUM_CURRENT_SETTINGS\n" ); PrintDevMode( registryDevmode ); for( int modeNum = 0 ; ; modeNum++ ) { DEVMODE devmode = {}; if( !EnumDisplaySettings( device.DeviceName, modeNum, &devmode ) ) { break; } if( devmode.dmBitsPerPel != 32 ) { continue; } if( devmode.dmDisplayFrequency < 60 ) { continue; } if( devmode.dmPelsHeight < 720 ) { continue; } common->Printf( " -------------------\n" ); common->Printf( " modeNum : %i\n", modeNum ); PrintDevMode( devmode ); } } } common->Printf( "\n" ); } // RB: moved out of R_GetModeListForDisplay class idSort_VidMode : public idSort_Quick< vidMode_t, idSort_VidMode > { public: int Compare( const vidMode_t& a, const vidMode_t& b ) const { int wd = a.width - b.width; int hd = a.height - b.height; int fd = a.displayHz - b.displayHz; return ( hd != 0 ) ? hd : ( wd != 0 ) ? wd : fd; } }; // RB end /* ==================== R_GetModeListForDisplay ==================== */ bool R_GetModeListForDisplay( const int requestedDisplayNum, idList& modeList ) { modeList.Clear(); bool verbose = false; for( int displayNum = requestedDisplayNum; ; displayNum++ ) { DISPLAY_DEVICE device; device.cb = sizeof( device ); if( !EnumDisplayDevices( 0, // lpDevice displayNum, &device, 0 /* dwFlags */ ) ) { return false; } // get the monitor for this display if( !( device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP ) ) { continue; } DISPLAY_DEVICE monitor; monitor.cb = sizeof( monitor ); if( !EnumDisplayDevices( device.DeviceName, 0, &monitor, 0 /* dwFlags */ ) ) { continue; } DEVMODE devmode; devmode.dmSize = sizeof( devmode ); if( verbose ) { common->Printf( "display device: %i\n", displayNum ); common->Printf( " DeviceName : %s\n", device.DeviceName ); common->Printf( " DeviceString: %s\n", device.DeviceString ); common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); common->Printf( " DeviceID : %s\n", device.DeviceID ); common->Printf( " DeviceKey : %s\n", device.DeviceKey ); common->Printf( " DeviceName : %s\n", monitor.DeviceName ); common->Printf( " DeviceString: %s\n", monitor.DeviceString ); common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); common->Printf( " DeviceID : %s\n", monitor.DeviceID ); common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); } for( int modeNum = 0 ; ; modeNum++ ) { if( !EnumDisplaySettings( device.DeviceName, modeNum, &devmode ) ) { break; } if( devmode.dmBitsPerPel != 32 ) { continue; } if( ( devmode.dmDisplayFrequency != 60 ) && ( devmode.dmDisplayFrequency != 120 ) ) { continue; } if( devmode.dmPelsHeight < 720 ) { continue; } if( verbose ) { common->Printf( " -------------------\n" ); common->Printf( " modeNum : %i\n", modeNum ); common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); common->Printf( " dmDisplayFixedOutput: %s\n", DMDFO( devmode.dmDisplayFixedOutput ) ); common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); common->Printf( " dmDisplayFrequency : %i\n", devmode.dmDisplayFrequency ); } vidMode_t mode; mode.width = devmode.dmPelsWidth; mode.height = devmode.dmPelsHeight; mode.displayHz = devmode.dmDisplayFrequency; modeList.AddUnique( mode ); } if( modeList.Num() > 0 ) { // sort with lowest resolution first modeList.SortWithTemplate( idSort_VidMode() ); return true; } } // Never gets here } /* ==================== GLW_GetWindowDimensions ==================== */ static bool GLW_GetWindowDimensions( const glimpParms_t parms, int& x, int& y, int& w, int& h ) { // // compute width and height // if( parms.fullScreen != 0 ) { if( parms.fullScreen == -1 ) { // borderless window at specific location, as for spanning // multiple monitor outputs x = parms.x; y = parms.y; w = parms.width; h = parms.height; } else { // get the current monitor position and size on the desktop, assuming // any required ChangeDisplaySettings has already been done int displayHz = 0; if( !GetDisplayCoordinates( parms.fullScreen - 1, x, y, w, h, displayHz ) ) { return false; } } } else { RECT r; // adjust width and height for window border r.bottom = parms.height; r.left = 0; r.top = 0; r.right = parms.width; AdjustWindowRect( &r, WINDOW_STYLE | WS_SYSMENU, FALSE ); w = r.right - r.left; h = r.bottom - r.top; x = parms.x; y = parms.y; } return true; } /* ======================= GLW_CreateWindow Responsible for creating the Win32 window. If fullscreen, it won't have a border ======================= */ static bool GLW_CreateWindow( glimpParms_t parms ) { int x, y, w, h; if( !GLW_GetWindowDimensions( parms, x, y, w, h ) ) { return false; } int stylebits; int exstyle; if( parms.fullScreen != 0 ) { exstyle = WS_EX_TOPMOST; stylebits = WS_POPUP | WS_VISIBLE | WS_SYSMENU; } else { exstyle = 0; stylebits = WINDOW_STYLE | WS_SYSMENU; } win32.hWnd = CreateWindowEx( exstyle, WIN32_WINDOW_CLASS_NAME, GAME_NAME, stylebits, x, y, w, h, NULL, NULL, win32.hInstance, NULL ); if( !win32.hWnd ) { common->Printf( "^3GLW_CreateWindow() - Couldn't create window^0\n" ); return false; } ::SetTimer( win32.hWnd, 0, 100, NULL ); ShowWindow( win32.hWnd, SW_SHOW ); UpdateWindow( win32.hWnd ); common->Printf( "...created window @ %d,%d (%dx%d)\n", x, y, w, h ); // makeCurrent NULL frees the DC, so get another win32.hDC = GetDC( win32.hWnd ); if( !win32.hDC ) { common->Printf( "^3GLW_CreateWindow() - GetDC()failed^0\n" ); return false; } // Check to see if we can get a stereo pixel format, even if we aren't going to use it, // so the menu option can be if( GLW_ChoosePixelFormat( win32.hDC, parms.multiSamples, true ) != -1 ) { glConfig.stereoPixelFormatAvailable = true; } else { glConfig.stereoPixelFormatAvailable = false; } if( !GLW_InitDriver( parms ) ) { ShowWindow( win32.hWnd, SW_HIDE ); DestroyWindow( win32.hWnd ); win32.hWnd = NULL; return false; } SetForegroundWindow( win32.hWnd ); SetFocus( win32.hWnd ); glConfig.isFullscreen = parms.fullScreen; return true; } /* =================== PrintCDSError =================== */ static void PrintCDSError( int value ) { switch( value ) { case DISP_CHANGE_RESTART: common->Printf( "restart required\n" ); break; case DISP_CHANGE_BADPARAM: common->Printf( "bad param\n" ); break; case DISP_CHANGE_BADFLAGS: common->Printf( "bad flags\n" ); break; case DISP_CHANGE_FAILED: common->Printf( "DISP_CHANGE_FAILED\n" ); break; case DISP_CHANGE_BADMODE: common->Printf( "bad mode\n" ); break; case DISP_CHANGE_NOTUPDATED: common->Printf( "not updated\n" ); break; default: common->Printf( "unknown error %d\n", value ); break; } } /* =================== GLW_ChangeDislaySettingsIfNeeded Optionally ChangeDisplaySettings to get a different fullscreen resolution. Default uses the full desktop resolution. =================== */ static bool GLW_ChangeDislaySettingsIfNeeded( glimpParms_t parms ) { // If we had previously changed the display settings on a different monitor, // go back to standard. if( win32.cdsFullscreen != 0 && win32.cdsFullscreen != parms.fullScreen ) { win32.cdsFullscreen = 0; ChangeDisplaySettings( 0, 0 ); Sys_Sleep( 1000 ); // Give the driver some time to think about this change } // 0 is dragable mode on desktop, -1 is borderless window on desktop if( parms.fullScreen <= 0 ) { return true; } // if we are already in the right resolution, don't do a ChangeDisplaySettings int x, y, width, height, displayHz; if( !GetDisplayCoordinates( parms.fullScreen - 1, x, y, width, height, displayHz ) ) { return false; } if( width == parms.width && height == parms.height && ( displayHz == parms.displayHz || parms.displayHz == 0 ) ) { return true; } DEVMODE dm = {}; dm.dmSize = sizeof( dm ); dm.dmPelsWidth = parms.width; dm.dmPelsHeight = parms.height; dm.dmBitsPerPel = 32; dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; if( parms.displayHz != 0 ) { dm.dmDisplayFrequency = parms.displayHz; dm.dmFields |= DM_DISPLAYFREQUENCY; } common->Printf( "...calling CDS: " ); const char* const deviceName = GetDisplayName( parms.fullScreen - 1 ); int cdsRet; if( ( cdsRet = ChangeDisplaySettingsEx( deviceName, &dm, NULL, CDS_FULLSCREEN, NULL ) ) == DISP_CHANGE_SUCCESSFUL ) { common->Printf( "ok\n" ); win32.cdsFullscreen = parms.fullScreen; return true; } common->Printf( "^3failed^0, " ); PrintCDSError( cdsRet ); return false; } /* =================== GLimp_Init This is the platform specific OpenGL initialization function. It is responsible for loading OpenGL, initializing it, 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. If there is any failure, the renderer will revert back to safe parameters and try again. =================== */ bool GLimp_Init( glimpParms_t parms ) { const char* driverName; HDC hDC; cmdSystem->AddCommand( "testSwapBuffers", GLimp_TestSwapBuffers, CMD_FL_SYSTEM, "Times swapbuffer options" ); common->Printf( "Initializing OpenGL subsystem with multisamples:%i stereo:%i fullscreen:%i\n", parms.multiSamples, parms.stereo, parms.fullScreen ); // check our desktop attributes hDC = GetDC( GetDesktopWindow() ); win32.desktopBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); win32.desktopWidth = GetDeviceCaps( hDC, HORZRES ); win32.desktopHeight = GetDeviceCaps( hDC, VERTRES ); ReleaseDC( GetDesktopWindow(), hDC ); // we can't run in a window unless it is 32 bpp if( win32.desktopBitsPixel < 32 && parms.fullScreen <= 0 ) { common->Printf( "^3Windowed mode requires 32 bit desktop depth^0\n" ); return false; } // save the hardware gamma so it can be // restored on exit GLimp_SaveGamma(); // create our window classes if we haven't already GLW_CreateWindowClasses(); // this will load the dll and set all our qgl* function pointers, // but doesn't create a window // r_glDriver is only intended for using instrumented OpenGL // dlls. Normal users should never have to use it, and it is // not archived. driverName = r_glDriver.GetString()[0] ? r_glDriver.GetString() : "opengl32"; if( !QGL_Init( driverName ) ) { common->Printf( "^3GLimp_Init() could not load r_glDriver \"%s\"^0\n", driverName ); return false; } // getting the wgl extensions involves creating a fake window to get a context, // which is pretty disgusting, and seems to mess with the AGP VAR allocation GLW_GetWGLExtensionsWithFakeWindow(); // Optionally ChangeDisplaySettings to get a different fullscreen resolution. if( !GLW_ChangeDislaySettingsIfNeeded( parms ) ) { GLimp_Shutdown(); return false; } // try to create a window with the correct pixel format // and init the renderer context if( !GLW_CreateWindow( parms ) ) { GLimp_Shutdown(); return false; } glConfig.isFullscreen = parms.fullScreen; glConfig.isStereoPixelFormat = parms.stereo; glConfig.nativeScreenWidth = parms.width; glConfig.nativeScreenHeight = parms.height; glConfig.multisamples = parms.multiSamples; glConfig.pixelAspect = 1.0f; // FIXME: some monitor modes may be distorted // should side-by-side stereo modes be consider aspect 0.5? // get the screen size, which may not be reliable... // If we use the windowDC, I get my 30" monitor, even though the window is // on a 27" monitor, so get a dedicated DC for the full screen device name. const idStr deviceName = GetDeviceName( Max( 0, parms.fullScreen - 1 ) ); HDC deviceDC = CreateDC( deviceName.c_str(), deviceName.c_str(), NULL, NULL ); const int mmWide = GetDeviceCaps( win32.hDC, HORZSIZE ); DeleteDC( deviceDC ); if( mmWide == 0 ) { glConfig.physicalScreenWidthInCentimeters = 100.0f; } else { glConfig.physicalScreenWidthInCentimeters = 0.1f * mmWide; } // wglSwapinterval, etc GLW_CheckWGLExtensions( win32.hDC ); // check logging GLimp_EnableLogging( ( r_logFile.GetInteger() != 0 ) ); return true; } /* =================== GLimp_SetScreenParms Sets up the screen based on passed parms.. =================== */ bool GLimp_SetScreenParms( glimpParms_t parms ) { // Optionally ChangeDisplaySettings to get a different fullscreen resolution. if( !GLW_ChangeDislaySettingsIfNeeded( parms ) ) { return false; } int x, y, w, h; if( !GLW_GetWindowDimensions( parms, x, y, w, h ) ) { return false; } int exstyle; int stylebits; if( parms.fullScreen ) { exstyle = WS_EX_TOPMOST; stylebits = WS_POPUP | WS_VISIBLE | WS_SYSMENU; } else { exstyle = 0; stylebits = WINDOW_STYLE | WS_SYSMENU; } SetWindowLong( win32.hWnd, GWL_STYLE, stylebits ); SetWindowLong( win32.hWnd, GWL_EXSTYLE, exstyle ); SetWindowPos( win32.hWnd, parms.fullScreen ? HWND_TOPMOST : HWND_NOTOPMOST, x, y, w, h, SWP_SHOWWINDOW ); glConfig.isFullscreen = parms.fullScreen; glConfig.pixelAspect = 1.0f; // FIXME: some monitor modes may be distorted glConfig.isFullscreen = parms.fullScreen; glConfig.nativeScreenWidth = parms.width; glConfig.nativeScreenHeight = parms.height; return true; } /* =================== GLimp_Shutdown This routine does all OS specific shutdown procedures for the OpenGL subsystem. =================== */ void GLimp_Shutdown() { const char* success[] = { "failed", "success" }; int retVal; common->Printf( "Shutting down OpenGL subsystem\n" ); // set current context to NULL if( qwglMakeCurrent ) { retVal = qwglMakeCurrent( NULL, NULL ) != 0; common->Printf( "...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] ); } // delete HGLRC if( win32.hGLRC ) { retVal = qwglDeleteContext( win32.hGLRC ) != 0; common->Printf( "...deleting GL context: %s\n", success[retVal] ); win32.hGLRC = NULL; } // release DC if( win32.hDC ) { retVal = ReleaseDC( win32.hWnd, win32.hDC ) != 0; common->Printf( "...releasing DC: %s\n", success[retVal] ); win32.hDC = NULL; } // destroy window if( win32.hWnd ) { common->Printf( "...destroying window\n" ); ShowWindow( win32.hWnd, SW_HIDE ); DestroyWindow( win32.hWnd ); win32.hWnd = NULL; } // reset display settings if( win32.cdsFullscreen ) { common->Printf( "...resetting display\n" ); ChangeDisplaySettings( 0, 0 ); win32.cdsFullscreen = 0; } // close the thread so the handle doesn't dangle if( win32.renderThreadHandle ) { common->Printf( "...closing smp thread\n" ); CloseHandle( win32.renderThreadHandle ); win32.renderThreadHandle = NULL; } // restore gamma GLimp_RestoreGamma(); // shutdown QGL subsystem QGL_Shutdown(); } /* ===================== GLimp_SwapBuffers ===================== */ void GLimp_SwapBuffers() { if( r_swapInterval.IsModified() ) { r_swapInterval.ClearModified(); int interval = 0; if( r_swapInterval.GetInteger() == 1 ) { interval = ( glConfig.swapControlTearAvailable ) ? -1 : 1; } else if( r_swapInterval.GetInteger() == 2 ) { interval = 1; } if( wglSwapIntervalEXT ) { wglSwapIntervalEXT( interval ); } } qwglSwapBuffers( win32.hDC ); } /* =========================================================== SMP acceleration =========================================================== */ /* =================== GLimp_ActivateContext =================== */ void GLimp_ActivateContext() { if( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) { win32.wglErrors++; } } /* =================== GLimp_DeactivateContext =================== */ void GLimp_DeactivateContext() { qglFinish(); if( !qwglMakeCurrent( win32.hDC, NULL ) ) { win32.wglErrors++; } } /* =================== GLimp_RenderThreadWrapper =================== */ static void GLimp_RenderThreadWrapper() { win32.glimpRenderThread(); // unbind the context before we die qwglMakeCurrent( win32.hDC, NULL ); } /* ======================= GLimp_SpawnRenderThread Returns false if the system only has a single processor ======================= */ bool GLimp_SpawnRenderThread( void ( *function )() ) { SYSTEM_INFO info; // check number of processors GetSystemInfo( &info ); if( info.dwNumberOfProcessors < 2 ) { return false; } // create the IPC elements win32.renderCommandsEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); win32.renderCompletedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); win32.renderActiveEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); win32.glimpRenderThread = function; win32.renderThreadHandle = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa, 0, // DWORD cbStack, ( LPTHREAD_START_ROUTINE )GLimp_RenderThreadWrapper, // LPTHREAD_START_ROUTINE lpStartAddr, 0, // LPVOID lpvThreadParm, 0, // DWORD fdwCreate, &win32.renderThreadId ); if( !win32.renderThreadHandle ) { common->Error( "GLimp_SpawnRenderThread: failed" ); } SetThreadPriority( win32.renderThreadHandle, THREAD_PRIORITY_ABOVE_NORMAL ); #if 0 // make sure they always run on different processors SetThreadAffinityMask( GetCurrentThread, 1 ); SetThreadAffinityMask( win32.renderThreadHandle, 2 ); #endif return true; } //#define DEBUG_PRINTS /* =================== GLimp_BackEndSleep =================== */ void* GLimp_BackEndSleep() { void* data; #ifdef DEBUG_PRINTS OutputDebugString( "-->GLimp_BackEndSleep\n" ); #endif ResetEvent( win32.renderActiveEvent ); // after this, the front end can exit GLimp_FrontEndSleep SetEvent( win32.renderCompletedEvent ); WaitForSingleObject( win32.renderCommandsEvent, INFINITE ); ResetEvent( win32.renderCompletedEvent ); ResetEvent( win32.renderCommandsEvent ); data = win32.smpData; // after this, the main thread can exit GLimp_WakeRenderer SetEvent( win32.renderActiveEvent ); #ifdef DEBUG_PRINTS OutputDebugString( "<--GLimp_BackEndSleep\n" ); #endif return data; } /* =================== GLimp_FrontEndSleep =================== */ void GLimp_FrontEndSleep() { #ifdef DEBUG_PRINTS OutputDebugString( "-->GLimp_FrontEndSleep\n" ); #endif WaitForSingleObject( win32.renderCompletedEvent, INFINITE ); #ifdef DEBUG_PRINTS OutputDebugString( "<--GLimp_FrontEndSleep\n" ); #endif } volatile bool renderThreadActive; /* =================== GLimp_WakeBackEnd =================== */ void GLimp_WakeBackEnd( void* data ) { int r; #ifdef DEBUG_PRINTS OutputDebugString( "-->GLimp_WakeBackEnd\n" ); #endif win32.smpData = data; if( renderThreadActive ) { common->FatalError( "GLimp_WakeBackEnd: already active" ); } r = WaitForSingleObject( win32.renderActiveEvent, 0 ); if( r == WAIT_OBJECT_0 ) { common->FatalError( "GLimp_WakeBackEnd: already signaled" ); } r = WaitForSingleObject( win32.renderCommandsEvent, 0 ); if( r == WAIT_OBJECT_0 ) { common->FatalError( "GLimp_WakeBackEnd: commands already signaled" ); } // after this, the renderer can continue through GLimp_RendererSleep SetEvent( win32.renderCommandsEvent ); r = WaitForSingleObject( win32.renderActiveEvent, 5000 ); if( r == WAIT_TIMEOUT ) { common->FatalError( "GLimp_WakeBackEnd: WAIT_TIMEOUT" ); } #ifdef DEBUG_PRINTS OutputDebugString( "<--GLimp_WakeBackEnd\n" ); #endif } /* =================== GLimp_ExtensionPointer Returns a function pointer for an OpenGL extension entry point =================== */ GLExtension_t GLimp_ExtensionPointer( const char* name ) { void ( *proc )(); proc = ( GLExtension_t )qwglGetProcAddress( name ); if( !proc ) { common->Printf( "Couldn't find proc address for: %s\n", name ); } return proc; }