/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "SDL.h" #ifdef SMP #include "SDL_thread.h" #endif #include #include #include #include "../renderer/tr_local.h" #include "../client/client.h" #include "../sys/sys_local.h" #include "sdl_icon.h" /* Just hack it for now. */ #ifdef MACOS_X typedef CGLContextObj QGLContext; #define GLimp_GetCurrentContext() CGLGetCurrentContext() #define GLimp_SetCurrentContext(ctx) CGLSetCurrentContext(ctx) #else typedef void *QGLContext; #define GLimp_GetCurrentContext() (NULL) #define GLimp_SetCurrentContext(ctx) #endif static QGLContext opengl_context; typedef enum { RSERR_OK, RSERR_INVALID_FULLSCREEN, RSERR_INVALID_MODE, RSERR_UNKNOWN } rserr_t; static SDL_Surface *screen = NULL; cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obtained PFNGLMULTITEXCOORD2FARBPROC qglMultiTexCoord2fARB; PFNGLACTIVETEXTUREARBPROC qglActiveTextureARB; PFNGLCLIENTACTIVETEXTUREARBPROC qglClientActiveTextureARB; PFNGLLOCKARRAYSEXTPROC qglLockArraysEXT; PFNGLUNLOCKARRAYSEXTPROC qglUnlockArraysEXT; /* =============== GLimp_Shutdown =============== */ void GLimp_Shutdown( void ) { IN_Shutdown(); SDL_QuitSubSystem( SDL_INIT_VIDEO ); screen = NULL; memset( &glConfig, 0, sizeof( glConfig ) ); memset( &glState, 0, sizeof( glState ) ); } /* =============== GLimp_LogComment =============== */ void GLimp_LogComment( char *comment ) { } /* =============== GLimp_SetMode =============== */ static int GLimp_SetMode( int mode, qboolean fullscreen ) { const char* glstring; int sdlcolorbits; int colorbits, depthbits, stencilbits; int tcolorbits, tdepthbits, tstencilbits; int i = 0; SDL_Surface *vidscreen = NULL; ri.Printf( PRINT_ALL, "Initializing OpenGL display\n"); ri.Printf (PRINT_ALL, "...setting mode %d:", mode ); if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, mode ) ) { ri.Printf( PRINT_ALL, " invalid mode\n" ); return RSERR_INVALID_MODE; } ri.Printf( PRINT_ALL, " %d %d\n", glConfig.vidWidth, glConfig.vidHeight); Uint32 flags = SDL_OPENGL; if (fullscreen) { flags |= SDL_FULLSCREEN; glConfig.isFullscreen = qtrue; } else glConfig.isFullscreen = qfalse; if (!r_colorbits->value) colorbits = 24; else colorbits = r_colorbits->value; if (!r_depthbits->value) depthbits = 24; else depthbits = r_depthbits->value; stencilbits = r_stencilbits->value; 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; } sdlcolorbits = 4; if (tcolorbits == 24) sdlcolorbits = 8; SDL_GL_SetAttribute( SDL_GL_RED_SIZE, sdlcolorbits ); SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, sdlcolorbits ); SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, sdlcolorbits ); SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, tdepthbits ); SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, tstencilbits ); SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); // If not allowing software GL, demand accelerated if( !r_allowSoftwareGL->integer ) { if( SDL_GL_SetAttribute( SDL_GL_ACCELERATED_VISUAL, 1 ) < 0 ) { ri.Printf( PRINT_ALL, "Unable to guarantee accelerated " "visual with libSDL < 1.2.10\n" ); } } if( SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, r_swapInterval->integer ) < 0 ) ri.Printf( PRINT_ALL, "r_swapInterval requires libSDL >= 1.2.10\n" ); #ifdef USE_ICON { SDL_Surface *icon = SDL_CreateRGBSurfaceFrom( (void *)CLIENT_WINDOW_ICON.pixel_data, CLIENT_WINDOW_ICON.width, CLIENT_WINDOW_ICON.height, CLIENT_WINDOW_ICON.bytes_per_pixel * 8, CLIENT_WINDOW_ICON.bytes_per_pixel * CLIENT_WINDOW_ICON.width, #ifdef Q3_LITTLE_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); SDL_WM_SetIcon( icon, NULL ); SDL_FreeSurface( icon ); } #endif SDL_WM_SetCaption(CLIENT_WINDOW_TITLE, CLIENT_WINDOW_MIN_TITLE); SDL_ShowCursor(0); if (!(vidscreen = SDL_SetVideoMode(glConfig.vidWidth, glConfig.vidHeight, colorbits, flags))) { ri.Printf( PRINT_DEVELOPER, "SDL_SetVideoMode failed: %s\n", SDL_GetError( ) ); continue; } opengl_context = GLimp_GetCurrentContext(); ri.Printf( PRINT_ALL, "Using %d/%d/%d Color bits, %d depth, %d stencil display.\n", sdlcolorbits, sdlcolorbits, sdlcolorbits, tdepthbits, tstencilbits); glConfig.colorBits = tcolorbits; glConfig.depthBits = tdepthbits; glConfig.stencilBits = tstencilbits; break; } if (!vidscreen) { ri.Printf( PRINT_ALL, "Couldn't get a visual\n" ); return RSERR_INVALID_MODE; } screen = vidscreen; glstring = (char *) qglGetString (GL_RENDERER); ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glstring ); return RSERR_OK; } /* =============== GLimp_StartDriverAndSetMode =============== */ static qboolean GLimp_StartDriverAndSetMode( int mode, qboolean fullscreen ) { rserr_t err; if (!SDL_WasInit(SDL_INIT_VIDEO)) { ri.Printf( PRINT_ALL, "SDL_Init( SDL_INIT_VIDEO )... "); if (SDL_Init(SDL_INIT_VIDEO) == -1) { ri.Printf( PRINT_ALL, "FAILED (%s)\n", SDL_GetError()); return qfalse; } ri.Printf( PRINT_ALL, "OK\n"); } if (fullscreen && Cvar_VariableIntegerValue( "in_nograb" ) ) { ri.Printf( PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n"); ri.Cvar_Set( "r_fullscreen", "0" ); r_fullscreen->modified = qfalse; fullscreen = qfalse; } err = GLimp_SetMode( mode, fullscreen ); switch ( err ) { case RSERR_INVALID_FULLSCREEN: ri.Printf( PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n" ); return qfalse; case RSERR_INVALID_MODE: ri.Printf( PRINT_ALL, "...WARNING: could not set the given mode (%d)\n", mode ); return qfalse; default: break; } return qtrue; } /* =============== GLimp_InitExtensions =============== */ static void GLimp_InitExtensions( void ) { if ( !r_allowExtensions->integer ) { ri.Printf( PRINT_ALL, "* IGNORING OPENGL EXTENSIONS *\n" ); return; } ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" ); // GL_S3_s3tc if ( Q_stristr( glConfig.extensions_string, "GL_S3_s3tc" ) ) { if ( r_ext_compressed_textures->value ) { glConfig.textureCompression = TC_S3TC; ri.Printf( PRINT_ALL, "...using GL_S3_s3tc\n" ); } else { glConfig.textureCompression = TC_NONE; ri.Printf( PRINT_ALL, "...ignoring GL_S3_s3tc\n" ); } } else { glConfig.textureCompression = TC_NONE; ri.Printf( PRINT_ALL, "...GL_S3_s3tc not found\n" ); } // GL_EXT_texture_env_add glConfig.textureEnvAddAvailable = qfalse; if ( Q_stristr( glConfig.extensions_string, "EXT_texture_env_add" ) ) { if ( r_ext_texture_env_add->integer ) { glConfig.textureEnvAddAvailable = qtrue; ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" ); } else { glConfig.textureEnvAddAvailable = qfalse; ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" ); } } else { ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" ); } // GL_ARB_multitexture qglMultiTexCoord2fARB = NULL; qglActiveTextureARB = NULL; qglClientActiveTextureARB = NULL; if ( Q_stristr( glConfig.extensions_string, "GL_ARB_multitexture" ) ) { if ( r_ext_multitexture->value ) { qglMultiTexCoord2fARB = ( PFNGLMULTITEXCOORD2FARBPROC ) SDL_GL_GetProcAddress( "glMultiTexCoord2fARB" ); qglActiveTextureARB = ( PFNGLACTIVETEXTUREARBPROC ) SDL_GL_GetProcAddress( "glActiveTextureARB" ); qglClientActiveTextureARB = ( PFNGLCLIENTACTIVETEXTUREARBPROC ) SDL_GL_GetProcAddress( "glClientActiveTextureARB" ); if ( qglActiveTextureARB ) { GLint glint = 0; qglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glint ); glConfig.maxActiveTextures = (int) glint; if ( glConfig.maxActiveTextures > 1 ) { ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); } else { qglMultiTexCoord2fARB = NULL; qglActiveTextureARB = NULL; qglClientActiveTextureARB = NULL; ri.Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n" ); } } } else { ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); } } else { ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); } // GL_EXT_compiled_vertex_array if ( Q_stristr( glConfig.extensions_string, "GL_EXT_compiled_vertex_array" ) ) { if ( r_ext_compiled_vertex_array->value ) { ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); qglLockArraysEXT = ( void ( APIENTRY * )( GLint, GLint ) ) SDL_GL_GetProcAddress( "glLockArraysEXT" ); qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) SDL_GL_GetProcAddress( "glUnlockArraysEXT" ); if (!qglLockArraysEXT || !qglUnlockArraysEXT) { ri.Error (ERR_FATAL, "bad getprocaddress"); } } else { ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); } } else { ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); } textureFilterAnisotropic = qfalse; if ( strstr( glConfig.extensions_string, "GL_EXT_texture_filter_anisotropic" ) ) { if ( r_ext_texture_filter_anisotropic->integer ) { qglGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint *)&maxAnisotropy ); if ( maxAnisotropy <= 0 ) { ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not properly supported!\n" ); maxAnisotropy = 0; } else { ri.Printf( PRINT_ALL, "...using GL_EXT_texture_filter_anisotropic (max: %i)\n", maxAnisotropy ); textureFilterAnisotropic = qtrue; } } else { ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_filter_anisotropic\n" ); } } else { ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not found\n" ); } } #define R_MODE_FALLBACK 3 // 640 * 480 /* =============== GLimp_Init This routine is responsible for initializing the OS specific portions of OpenGL =============== */ void GLimp_Init( void ) { qboolean success = qtrue; r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH ); // create the window and set up the context if( !GLimp_StartDriverAndSetMode( r_mode->integer, r_fullscreen->integer ) ) { if( r_mode->integer != R_MODE_FALLBACK ) { ri.Printf( PRINT_ALL, "Setting r_mode %d failed, falling back on r_mode %d", r_mode->integer, R_MODE_FALLBACK ); if( !GLimp_StartDriverAndSetMode( R_MODE_FALLBACK, r_fullscreen->integer ) ) success = qfalse; } else success = qfalse; } if( !success ) ri.Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem\n" ); // This values force the UI to disable driver selection glConfig.driverType = GLDRV_ICD; glConfig.hardwareType = GLHW_GENERIC; glConfig.deviceSupportsGamma = !!( SDL_SetGamma( 1.0f, 1.0f, 1.0f ) >= 0 ); // get our config strings Q_strncpyz( glConfig.vendor_string, (char *) qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) ); Q_strncpyz( glConfig.renderer_string, (char *) qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) ); if (*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n') glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0; Q_strncpyz( glConfig.version_string, (char *) qglGetString (GL_VERSION), sizeof( glConfig.version_string ) ); Q_strncpyz( glConfig.extensions_string, (char *) qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); // initialize extensions GLimp_InitExtensions( ); // This depends on SDL_INIT_VIDEO, hence having it here IN_Init( ); return; } /* =============== GLimp_EndFrame Responsible for doing a swapbuffers =============== */ void GLimp_EndFrame( void ) { // don't flip if drawing to front buffer if ( Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) { SDL_GL_SwapBuffers(); } if( r_fullscreen->modified ) { qboolean fullscreen; qboolean sdlToggled = qfalse; SDL_Surface *s = SDL_GetVideoSurface( ); if( s ) { // Find out the current state if( s->flags & SDL_FULLSCREEN ) fullscreen = qtrue; else fullscreen = qfalse; // Is the state we want different from the current state? if( !!r_fullscreen->integer != fullscreen ) sdlToggled = SDL_WM_ToggleFullScreen( s ); else sdlToggled = qtrue; } // SDL_WM_ToggleFullScreen didn't work, so do it the slow way if( !sdlToggled ) Cbuf_AddText( "vid_restart" ); r_fullscreen->modified = qfalse; } } #ifdef SMP /* =========================================================== SMP acceleration =========================================================== */ /* * I have no idea if this will even work...most platforms don't offer * thread-safe OpenGL libraries, and it looks like the original Linux * code counted on each thread claiming the GL context with glXMakeCurrent(), * which you can't currently do in SDL. We'll just have to hope for the best. */ static SDL_mutex *smpMutex = NULL; static SDL_cond *renderCommandsEvent = NULL; static SDL_cond *renderCompletedEvent = NULL; static void (*glimpRenderThread)( void ) = NULL; static SDL_Thread *renderThread = NULL; /* =============== GLimp_ShutdownRenderThread =============== */ static void GLimp_ShutdownRenderThread(void) { if (smpMutex != NULL) { SDL_DestroyMutex(smpMutex); smpMutex = NULL; } if (renderCommandsEvent != NULL) { SDL_DestroyCond(renderCommandsEvent); renderCommandsEvent = NULL; } if (renderCompletedEvent != NULL) { SDL_DestroyCond(renderCompletedEvent); renderCompletedEvent = NULL; } glimpRenderThread = NULL; } /* =============== GLimp_RenderThreadWrapper =============== */ static int GLimp_RenderThreadWrapper( void *arg ) { Com_Printf( "Render thread starting\n" ); glimpRenderThread(); GLimp_SetCurrentContext(NULL); Com_Printf( "Render thread terminating\n" ); return 0; } /* =============== GLimp_SpawnRenderThread =============== */ qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { static qboolean warned = qfalse; if (!warned) { Com_Printf("WARNING: You enable r_smp at your own risk!\n"); warned = qtrue; } #ifndef MACOS_X return qfalse; /* better safe than sorry for now. */ #endif if (renderThread != NULL) /* hopefully just a zombie at this point... */ { Com_Printf("Already a render thread? Trying to clean it up...\n"); SDL_WaitThread(renderThread, NULL); renderThread = NULL; GLimp_ShutdownRenderThread(); } smpMutex = SDL_CreateMutex(); if (smpMutex == NULL) { Com_Printf( "smpMutex creation failed: %s\n", SDL_GetError() ); GLimp_ShutdownRenderThread(); return qfalse; } renderCommandsEvent = SDL_CreateCond(); if (renderCommandsEvent == NULL) { Com_Printf( "renderCommandsEvent creation failed: %s\n", SDL_GetError() ); GLimp_ShutdownRenderThread(); return qfalse; } renderCompletedEvent = SDL_CreateCond(); if (renderCompletedEvent == NULL) { Com_Printf( "renderCompletedEvent creation failed: %s\n", SDL_GetError() ); GLimp_ShutdownRenderThread(); return qfalse; } glimpRenderThread = function; renderThread = SDL_CreateThread(GLimp_RenderThreadWrapper, NULL); if ( renderThread == NULL ) { ri.Printf( PRINT_ALL, "SDL_CreateThread() returned %s", SDL_GetError() ); GLimp_ShutdownRenderThread(); return qfalse; } else { // tma 01/09/07: don't think this is necessary anyway? // // !!! FIXME: No detach API available in SDL! //ret = pthread_detach( renderThread ); //if ( ret ) { //ri.Printf( PRINT_ALL, "pthread_detach returned %d: %s", ret, strerror( ret ) ); //} } return qtrue; } static volatile void *smpData = NULL; static volatile qboolean smpDataReady; /* =============== GLimp_RendererSleep =============== */ void *GLimp_RendererSleep( void ) { void *data = NULL; GLimp_SetCurrentContext(NULL); SDL_LockMutex(smpMutex); { smpData = NULL; smpDataReady = qfalse; // after this, the front end can exit GLimp_FrontEndSleep SDL_CondSignal(renderCompletedEvent); while ( !smpDataReady ) SDL_CondWait(renderCommandsEvent, smpMutex); data = (void *)smpData; } SDL_UnlockMutex(smpMutex); GLimp_SetCurrentContext(opengl_context); return data; } /* =============== GLimp_FrontEndSleep =============== */ void GLimp_FrontEndSleep( void ) { SDL_LockMutex(smpMutex); { while ( smpData ) SDL_CondWait(renderCompletedEvent, smpMutex); } SDL_UnlockMutex(smpMutex); GLimp_SetCurrentContext(opengl_context); } /* =============== GLimp_WakeRenderer =============== */ void GLimp_WakeRenderer( void *data ) { GLimp_SetCurrentContext(NULL); SDL_LockMutex(smpMutex); { assert( smpData == NULL ); smpData = data; smpDataReady = qtrue; // after this, the renderer can continue through GLimp_RendererSleep SDL_CondSignal(renderCommandsEvent); } SDL_UnlockMutex(smpMutex); } #else // No SMP - stubs void GLimp_RenderThreadWrapper( void *arg ) { } qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { ri.Printf( PRINT_WARNING, "ERROR: SMP support was disabled at compile time\n"); return qfalse; } void *GLimp_RendererSleep( void ) { return NULL; } void GLimp_FrontEndSleep( void ) { } void GLimp_WakeRenderer( void *data ) { } #endif