/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>. In addition, the Doom 3 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 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. =========================================================================== */ #include "../../idlib/precompiled.h" #include "../../renderer/tr_local.h" #include "local.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> extern "C" { # include "libXNVCtrl/NVCtrlLib.h" } idCVar sys_videoRam( "sys_videoRam", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "Texture memory on the video card (in megabytes) - 0: autodetect", 0, 512 ); Display *dpy = NULL; static int scrnum = 0; Window win = 0; bool dga_found = false; static GLXContext ctx = NULL; static bool vidmode_ext = false; static int vidmode_MajorVersion = 0, vidmode_MinorVersion = 0; // major and minor of XF86VidExtensions static XF86VidModeModeInfo **vidmodes; static int num_vidmodes; static bool vidmode_active = false; // backup gamma ramp static int save_rampsize = 0; static unsigned short *save_red, *save_green, *save_blue; void GLimp_WakeBackEnd(void *a) { common->DPrintf("GLimp_WakeBackEnd stub\n"); } void GLimp_EnableLogging(bool log) { static bool logging; if (log != logging) { common->DPrintf("GLimp_EnableLogging - not available\n"); logging = log; } } void GLimp_FrontEndSleep() { common->DPrintf("GLimp_FrontEndSleep stub\n"); } void *GLimp_BackEndSleep() { common->DPrintf("GLimp_BackEndSleep stub\n"); return 0; } bool GLimp_SpawnRenderThread(void (*a) ()) { common->DPrintf("GLimp_SpawnRenderThread stub\n"); return false; } void GLimp_ActivateContext() { assert( dpy ); assert( ctx ); qglXMakeCurrent( dpy, win, ctx ); } void GLimp_DeactivateContext() { assert( dpy ); qglXMakeCurrent( dpy, None, NULL ); } /* ================= GLimp_SaveGamma save and restore the original gamma of the system ================= */ void GLimp_SaveGamma() { if ( save_rampsize ) { return; } assert( dpy ); XF86VidModeGetGammaRampSize( dpy, scrnum, &save_rampsize); save_red = (unsigned short *)malloc(save_rampsize*sizeof(unsigned short)); save_green = (unsigned short *)malloc(save_rampsize*sizeof(unsigned short)); save_blue = (unsigned short *)malloc(save_rampsize*sizeof(unsigned short)); XF86VidModeGetGammaRamp( dpy, scrnum, save_rampsize, save_red, save_green, save_blue); } /* ================= GLimp_RestoreGamma save and restore the original gamma of the system ================= */ void GLimp_RestoreGamma() { if (!save_rampsize) return; XF86VidModeSetGammaRamp( dpy, scrnum, save_rampsize, save_red, save_green, save_blue); free(save_red); free(save_green); free(save_blue); save_rampsize = 0; } /* ================= GLimp_SetGamma gamma ramp is generated by the renderer from r_gamma and r_brightness for 256 elements the size of the gamma ramp can not be changed on X (I need to confirm this) ================= */ void GLimp_SetGamma(unsigned short red[256], unsigned short green[256], unsigned short blue[256]) { if ( dpy ) { int size; GLimp_SaveGamma(); XF86VidModeGetGammaRampSize( dpy, scrnum, &size); common->DPrintf("XF86VidModeGetGammaRampSize: %d\n", size); if ( size > 256 ) { // silly generic resample int i; unsigned short *l_red, *l_green, *l_blue; l_red = (unsigned short *)malloc(size*sizeof(unsigned short)); l_green = (unsigned short *)malloc(size*sizeof(unsigned short)); l_blue = (unsigned short *)malloc(size*sizeof(unsigned short)); //int r_size = 256; int r_i; float r_f; for(i=0; i<size-1; i++) { r_f = (float)i*255.0f/(float)(size-1); r_i = (int)floor(r_f); r_f -= (float)r_i; l_red[i] = (int)round((1.0f-r_f)*(float)red[r_i]+r_f*(float)red[r_i+1]); l_green[i] = (int)round((1.0f-r_f)*(float)green[r_i]+r_f*(float)green[r_i+1]); l_blue[i] = (int)round((1.0f-r_f)*(float)blue[r_i]+r_f*(float)blue[r_i+1]); } l_red[size-1] = red[255]; l_green[size-1] = green[255]; l_blue[size-1] = blue[255]; XF86VidModeSetGammaRamp( dpy, scrnum, size, l_red, l_green, l_blue ); free(l_red); free(l_green); free(l_blue); } else { XF86VidModeSetGammaRamp( dpy, scrnum, size, red, green, blue ); } } } void GLimp_Shutdown() { if ( dpy ) { Sys_XUninstallGrabs(); GLimp_RestoreGamma(); qglXDestroyContext( dpy, ctx ); XDestroyWindow( dpy, win ); if ( vidmode_active ) { XF86VidModeSwitchToMode( dpy, scrnum, vidmodes[0] ); } // FIXME: that's going to crash //XFlush( dpy ); //XCloseDisplay( dpy ); vidmode_active = false; dpy = NULL; win = 0; ctx = NULL; } } void GLimp_SwapBuffers() { assert( dpy ); qglXSwapBuffers( dpy, win ); } /* GLX_TestDGA Check for DGA - update in_dgamouse if needed */ void GLX_TestDGA() { #if defined( ID_ENABLE_DGA ) int dga_MajorVersion = 0, dga_MinorVersion = 0; assert( dpy ); if ( !XF86DGAQueryVersion( dpy, &dga_MajorVersion, &dga_MinorVersion ) ) { // unable to query, probalby not supported common->Printf( "Failed to detect DGA DirectVideo Mouse\n" ); cvarSystem->SetCVarBool( "in_dgamouse", false ); dga_found = false; } else { common->Printf( "DGA DirectVideo Mouse (Version %d.%d) initialized\n", dga_MajorVersion, dga_MinorVersion ); dga_found = true; } #else dga_found = false; #endif } /* ** XErrorHandler ** the default X error handler exits the application ** I found out that on some hosts some operations would raise X errors (GLXUnsupportedPrivateRequest) ** but those don't seem to be fatal .. so the default would be to just ignore them ** our implementation mimics the default handler behaviour (not completely cause I'm lazy) */ int idXErrorHandler(Display * l_dpy, XErrorEvent * ev) { char buf[1024]; common->Printf( "Fatal X Error:\n" ); common->Printf( " Major opcode of failed request: %d\n", ev->request_code ); common->Printf( " Minor opcode of failed request: %d\n", ev->minor_code ); common->Printf( " Serial number of failed request: %lu\n", ev->serial ); XGetErrorText( l_dpy, ev->error_code, buf, 1024 ); common->Printf( "%s\n", buf ); return 0; } bool GLimp_OpenDisplay( void ) { if ( dpy ) { return true; } if ( cvarSystem->GetCVarInteger( "net_serverDedicated" ) == 1 ) { common->DPrintf( "not opening the display: dedicated server\n" ); return false; } common->Printf( "Setup X display connection\n" ); // that should be the first call into X if ( !XInitThreads() ) { common->Printf("XInitThreads failed\n"); return false; } // set up our custom error handler for X failures XSetErrorHandler( &idXErrorHandler ); if ( !( dpy = XOpenDisplay(NULL) ) ) { common->Printf( "Couldn't open the X display\n" ); return false; } scrnum = DefaultScreen( dpy ); return true; } /* =============== GLX_Init =============== */ int GLX_Init(glimpParms_t a) { int attrib[] = { GLX_RGBA, // 0 GLX_RED_SIZE, 8, // 1, 2 GLX_GREEN_SIZE, 8, // 3, 4 GLX_BLUE_SIZE, 8, // 5, 6 GLX_DOUBLEBUFFER, // 7 GLX_DEPTH_SIZE, 24, // 8, 9 GLX_STENCIL_SIZE, 8, // 10, 11 GLX_ALPHA_SIZE, 8, // 12, 13 None }; // these match in the array #define ATTR_RED_IDX 2 #define ATTR_GREEN_IDX 4 #define ATTR_BLUE_IDX 6 #define ATTR_DEPTH_IDX 9 #define ATTR_STENCIL_IDX 11 #define ATTR_ALPHA_IDX 13 Window root; XVisualInfo *visinfo; XSetWindowAttributes attr; XSizeHints sizehints; unsigned long mask; int colorbits, depthbits, stencilbits; int tcolorbits, tdepthbits, tstencilbits; int actualWidth, actualHeight; int i; const char *glstring; if ( !GLimp_OpenDisplay() ) { return false; } common->Printf( "Initializing OpenGL display\n" ); root = RootWindow( dpy, scrnum ); actualWidth = glConfig.vidWidth; actualHeight = glConfig.vidHeight; // Get video mode list if ( !XF86VidModeQueryVersion( dpy, &vidmode_MajorVersion, &vidmode_MinorVersion ) ) { vidmode_ext = false; common->Printf("XFree86-VidModeExtension not available\n"); } else { vidmode_ext = true; common->Printf("Using XFree86-VidModeExtension Version %d.%d\n", vidmode_MajorVersion, vidmode_MinorVersion); } GLX_TestDGA(); if ( vidmode_ext ) { int best_fit, best_dist, dist, x, y; XF86VidModeGetAllModeLines( dpy, scrnum, &num_vidmodes, &vidmodes ); // Are we going fullscreen? If so, let's change video mode if ( a.fullScreen ) { best_dist = 9999999; best_fit = -1; for (i = 0; i < num_vidmodes; i++) { if (a.width > vidmodes[i]->hdisplay || a.height > vidmodes[i]->vdisplay) continue; x = a.width - vidmodes[i]->hdisplay; y = a.height - vidmodes[i]->vdisplay; dist = (x * x) + (y * y); if (dist < best_dist) { best_dist = dist; best_fit = i; } } if (best_fit != -1) { actualWidth = vidmodes[best_fit]->hdisplay; actualHeight = vidmodes[best_fit]->vdisplay; // change to the mode XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[best_fit]); vidmode_active = true; // Move the viewport to top left // FIXME: center? XF86VidModeSetViewPort(dpy, scrnum, 0, 0); common->Printf( "Free86-VidModeExtension Activated at %dx%d\n", actualWidth, actualHeight ); } else { a.fullScreen = false; common->Printf( "Free86-VidModeExtension: No acceptable modes found\n" ); } } else { common->Printf( "XFree86-VidModeExtension: not fullscreen, ignored\n" ); } } // color, depth and stencil colorbits = 24; depthbits = 24; stencilbits = 8; for (i = 0; i < 16; i++) { // 0 - default // 1 - minus colorbits // 2 - minus depthbits // 3 - minus stencil if ((i % 4) == 0 && i) { // one pass, reduce switch (i / 4) { case 2: if (colorbits == 24) colorbits = 16; break; case 1: if (depthbits == 24) depthbits = 16; else if (depthbits == 16) depthbits = 8; case 3: if (stencilbits == 24) stencilbits = 16; else if (stencilbits == 16) stencilbits = 8; } } tcolorbits = colorbits; tdepthbits = depthbits; tstencilbits = stencilbits; if ((i % 4) == 3) { // reduce colorbits if (tcolorbits == 24) tcolorbits = 16; } if ((i % 4) == 2) { // reduce depthbits if (tdepthbits == 24) tdepthbits = 16; else if (tdepthbits == 16) tdepthbits = 8; } if ((i % 4) == 1) { // reduce stencilbits if (tstencilbits == 24) tstencilbits = 16; else if (tstencilbits == 16) tstencilbits = 8; else tstencilbits = 0; } if (tcolorbits == 24) { attrib[ATTR_RED_IDX] = 8; attrib[ATTR_GREEN_IDX] = 8; attrib[ATTR_BLUE_IDX] = 8; } else { // must be 16 bit attrib[ATTR_RED_IDX] = 4; attrib[ATTR_GREEN_IDX] = 4; attrib[ATTR_BLUE_IDX] = 4; } attrib[ATTR_DEPTH_IDX] = tdepthbits; // default to 24 depth attrib[ATTR_STENCIL_IDX] = tstencilbits; visinfo = qglXChooseVisual(dpy, scrnum, attrib); if (!visinfo) { continue; } common->Printf( "Using %d/%d/%d Color bits, %d Alpha bits, %d depth, %d stencil display.\n", attrib[ATTR_RED_IDX], attrib[ATTR_GREEN_IDX], attrib[ATTR_BLUE_IDX], attrib[ATTR_ALPHA_IDX], attrib[ATTR_DEPTH_IDX], attrib[ATTR_STENCIL_IDX]); glConfig.colorBits = tcolorbits; glConfig.depthBits = tdepthbits; glConfig.stencilBits = tstencilbits; break; } if (!visinfo) { common->Printf("Couldn't get a visual\n"); return false; } // window attributes attr.background_pixel = BlackPixel(dpy, scrnum); attr.border_pixel = 0; attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone); attr.event_mask = X_MASK; if (vidmode_active) { mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect; attr.override_redirect = True; attr.backing_store = NotUseful; attr.save_under = False; } else { mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; } win = XCreateWindow(dpy, root, 0, 0, actualWidth, actualHeight, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &attr); XStoreName(dpy, win, GAME_NAME); // don't let the window be resized // FIXME: allow resize (win32 does) sizehints.flags = PMinSize | PMaxSize; sizehints.min_width = sizehints.max_width = actualWidth; sizehints.min_height = sizehints.max_height = actualHeight; XSetWMNormalHints(dpy, win, &sizehints); XMapWindow( dpy, win ); if ( vidmode_active ) { XMoveWindow( dpy, win, 0, 0 ); } XFlush(dpy); XSync(dpy, False); ctx = qglXCreateContext(dpy, visinfo, NULL, True); XSync(dpy, False); // Free the visinfo after we're done with it XFree(visinfo); qglXMakeCurrent(dpy, win, ctx); glstring = (const char *) qglGetString(GL_RENDERER); common->Printf("GL_RENDERER: %s\n", glstring); glstring = (const char *) qglGetString(GL_EXTENSIONS); common->Printf("GL_EXTENSIONS: %s\n", glstring); // FIXME: here, software GL test glConfig.isFullscreen = a.fullScreen; if ( glConfig.isFullscreen ) { Sys_GrabMouseCursor( true ); } return true; } /* =================== 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 a ) { if ( !GLimp_OpenDisplay() ) { return false; } if (!GLX_Init(a)) { return false; } return true; } /* =================== GLimp_SetScreenParms =================== */ bool GLimp_SetScreenParms( glimpParms_t parms ) { return true; } /* ================ Sys_GetVideoRam returns in megabytes open your own display connection for the query and close it using the one shared with GLimp_Init is not stable ================ */ int Sys_GetVideoRam( void ) { static int run_once = 0; int major, minor, value; Display *l_dpy; int l_scrnum; if ( run_once ) { return run_once; } if ( sys_videoRam.GetInteger() ) { run_once = sys_videoRam.GetInteger(); return sys_videoRam.GetInteger(); } // try a few strategies to guess the amount of video ram common->Printf( "guessing video ram ( use +set sys_videoRam to force ) ..\n" ); if ( !GLimp_OpenDisplay( ) ) { run_once = 64; return run_once; } l_dpy = dpy; l_scrnum = scrnum; // go for nvidia ext first if ( XNVCTRLQueryVersion( l_dpy, &major, &minor ) ) { common->Printf( "found XNVCtrl extension %d.%d\n", major, minor ); if ( XNVCTRLIsNvScreen( l_dpy, l_scrnum ) ) { if ( XNVCTRLQueryAttribute( l_dpy, l_scrnum, 0, NV_CTRL_VIDEO_RAM, &value ) ) { run_once = value / 1024; return run_once; } else { common->Printf( "XNVCtrlQueryAttribute NV_CTRL_VIDEO_RAM failed\n" ); } } else { common->Printf( "default screen %d is not controlled by NVIDIA driver\n", l_scrnum ); } } // try ATI /proc read ( for the lack of a better option ) int fd; if ( ( fd = open( "/proc/dri/0/umm", O_RDONLY ) ) != -1 ) { int len; char umm_buf[ 1024 ]; char *line; if ( ( len = read( fd, umm_buf, 1024 ) ) != -1 ) { // should be way enough to get the full file // grab "free LFB = " line and "free Inv = " lines umm_buf[ len-1 ] = '\0'; line = umm_buf; line = strtok( umm_buf, "\n" ); int total = 0; while ( line ) { if ( strlen( line ) >= 13 && strstr( line, "max LFB =" ) == line ) { total += atoi( line + 12 ); } else if ( strlen( line ) >= 13 && strstr( line, "max Inv =" ) == line ) { total += atoi( line + 12 ); } line = strtok( NULL, "\n" ); } if ( total ) { run_once = total / 1048576; // round to the lower 16Mb run_once &= ~15; return run_once; } } else { common->Printf( "read /proc/dri/0/umm failed: %s\n", strerror( errno ) ); } } common->Printf( "guess failed, return default low-end VRAM setting ( 64MB VRAM )\n" ); run_once = 64; return run_once; } /* =================== GLimp_ExtensionPointer =================== */ static void StubFunction( void ) { } GLExtension_t GLimp_ExtensionPointer( const char *name ) { if ( strstr( name, "wgl" ) == name ) { common->DPrintf( "WARNING: GLimp_ExtensionPointer for '%s'\n", name ); } #ifdef ID_DEDICATED common->Printf("GLimp_ExtensionPointer %s\n", name); return StubFunction; #else GLExtension_t ret; #if defined(__unix__) // for some reason glXGetProcAddressARB doesn't work on RH9? ret = qglXGetProcAddressARB((const GLubyte *) name); if ( !ret ) { common->Printf("glXGetProcAddressARB failed: \"%s\"\n", name); return StubFunction; } #else #error Need OS define #endif return ret; #endif }