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