quadrilateralcowboy/sys/linux/glimp.cpp

678 lines
17 KiB
C++

/*
===========================================================================
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.
===========================================================================
*/
#ifndef USE_SDL
#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");
}
#ifdef ID_GL_HARDLINK
void GLimp_EnableLogging(bool log) {
static bool logging;
if (log != logging)
{
common->DPrintf("GLimp_EnableLogging - disabled at compile time (ID_GL_HARDLINK)\n");
logging = log;
}
}
#endif
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 );
#if !defined( ID_GL_HARDLINK )
GLimp_dlclose();
#endif
XDestroyWindow( dpy, win );
if ( vidmode_active ) {
XF86VidModeSwitchToMode( dpy, scrnum, vidmodes[0] );
}
XFlush( dpy );
// FIXME: that's going to crash
//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() {
int dga_MajorVersion = 0, dga_MinorVersion = 0;
assert( dpy );
#if defined( ID_ENABLE_DGA )
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;
}
#ifndef ID_GL_HARDLINK
if ( !GLimp_dlopen() ) {
return false;
}
#endif
if (!GLX_Init(a)) {
return false;
}
return true;
}
/*
===================
GLimp_SetScreenParms
===================
*/
bool GLimp_SetScreenParms( glimpParms_t parms ) {
return true;
}
#endif
/*
================
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 ) {
#ifdef USE_SDL
return 128;
#else
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;
#endif
}