/* =========================================================================== 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 . 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 "sys/platform.h" #include "idlib/containers/List.h" #include "framework/EventLoop.h" #include "framework/Session.h" #include "framework/DemoFile.h" #include "renderer/ModelManager.h" #include "renderer/Material.h" #include "renderer/GuiModel.h" #include "renderer/VertexCache.h" #include "renderer/RenderWorld_local.h" #include "renderer/tr_local.h" idRenderSystemLocal tr; idRenderSystem *renderSystem = &tr; /* ===================== R_PerformanceCounters This prints both front and back end counters, so it should only be called when the back end thread is idle. ===================== */ static void R_PerformanceCounters( void ) { if ( r_showPrimitives.GetInteger() != 0 ) { float megaBytes = globalImages->SumOfUsedImages() / ( 1024*1024.0 ); if ( r_showPrimitives.GetInteger() > 1 ) { common->Printf( "v:%i ds:%i t:%i/%i v:%i/%i st:%i sv:%i image:%5.1f MB\n", tr.pc.c_numViews, backEnd.pc.c_drawElements + backEnd.pc.c_shadowElements, backEnd.pc.c_drawIndexes / 3, ( backEnd.pc.c_drawIndexes - backEnd.pc.c_drawRefIndexes ) / 3, backEnd.pc.c_drawVertexes, ( backEnd.pc.c_drawVertexes - backEnd.pc.c_drawRefVertexes ), backEnd.pc.c_shadowIndexes / 3, backEnd.pc.c_shadowVertexes, megaBytes ); } else { common->Printf( "views:%i draws:%i tris:%i (shdw:%i) (vbo:%i) image:%5.1f MB\n", tr.pc.c_numViews, backEnd.pc.c_drawElements + backEnd.pc.c_shadowElements, ( backEnd.pc.c_drawIndexes + backEnd.pc.c_shadowIndexes ) / 3, backEnd.pc.c_shadowIndexes / 3, backEnd.pc.c_vboIndexes / 3, megaBytes ); } } if ( r_showDynamic.GetBool() ) { common->Printf( "callback:%i md5:%i dfrmVerts:%i dfrmTris:%i tangTris:%i guis:%i\n", tr.pc.c_entityDefCallbacks, tr.pc.c_generateMd5, tr.pc.c_deformedVerts, tr.pc.c_deformedIndexes/3, tr.pc.c_tangentIndexes/3, tr.pc.c_guiSurfs ); } if ( r_showCull.GetBool() ) { common->Printf( "%i sin %i sclip %i sout %i bin %i bout\n", tr.pc.c_sphere_cull_in, tr.pc.c_sphere_cull_clip, tr.pc.c_sphere_cull_out, tr.pc.c_box_cull_in, tr.pc.c_box_cull_out ); } if ( r_showAlloc.GetBool() ) { common->Printf( "alloc:%i free:%i\n", tr.pc.c_alloc, tr.pc.c_free ); } if ( r_showInteractions.GetBool() ) { common->Printf( "createInteractions:%i createLightTris:%i createShadowVolumes:%i\n", tr.pc.c_createInteractions, tr.pc.c_createLightTris, tr.pc.c_createShadowVolumes ); } if ( r_showDefs.GetBool() ) { common->Printf( "viewEntities:%i shadowEntities:%i viewLights:%i\n", tr.pc.c_visibleViewEntities, tr.pc.c_shadowViewEntities, tr.pc.c_viewLights ); } if ( r_showUpdates.GetBool() ) { common->Printf( "entityUpdates:%i entityRefs:%i lightUpdates:%i lightRefs:%i\n", tr.pc.c_entityUpdates, tr.pc.c_entityReferences, tr.pc.c_lightUpdates, tr.pc.c_lightReferences ); } if ( r_showMemory.GetBool() ) { int m1 = frameData ? frameData->memoryHighwater : 0; common->Printf( "frameData: %i (%i)\n", R_CountFrameData(), m1 ); } if ( r_showLightScale.GetBool() ) { common->Printf( "lightScale: %f\n", backEnd.pc.maxLightValue ); } memset( &tr.pc, 0, sizeof( tr.pc ) ); memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); } /* ==================== R_IssueRenderCommands Called by R_EndFrame each frame ==================== */ static void R_IssueRenderCommands( void ) { if ( frameData->cmdHead->commandId == RC_NOP && !frameData->cmdHead->next ) { // nothing to issue return; } // r_skipBackEnd allows the entire time of the back end // to be removed from performance measurements, although // nothing will be drawn to the screen. If the prints // are going to a file, or r_skipBackEnd is later disabled, // usefull data can be received. // r_skipRender is usually more usefull, because it will still // draw 2D graphics if ( !r_skipBackEnd.GetBool() ) { RB_ExecuteBackEndCommands( frameData->cmdHead ); } R_ClearCommandChain(); } /* ============ R_GetCommandBuffer Returns memory for a command buffer (stretchPicCommand_t, drawSurfsCommand_t, etc) and links it to the end of the current command chain. ============ */ void *R_GetCommandBuffer( int bytes ) { emptyCommand_t *cmd; cmd = (emptyCommand_t *)R_FrameAlloc( bytes ); cmd->next = NULL; frameData->cmdTail->next = &cmd->commandId; frameData->cmdTail = cmd; return (void *)cmd; } /* ==================== R_ClearCommandChain Called after every buffer submission and by R_ToggleSmpFrame ==================== */ void R_ClearCommandChain( void ) { // clear the command chain frameData->cmdHead = frameData->cmdTail = (emptyCommand_t *)R_FrameAlloc( sizeof( *frameData->cmdHead ) ); frameData->cmdHead->commandId = RC_NOP; frameData->cmdHead->next = NULL; } /* ================= R_ViewStatistics ================= */ static void R_ViewStatistics( viewDef_t *parms ) { // report statistics about this view if ( !r_showSurfaces.GetBool() ) { return; } common->Printf( "view:%p surfs:%i\n", parms, parms->numDrawSurfs ); } /* ============= R_AddDrawViewCmd This is the main 3D rendering command. A single scene may have multiple views if a mirror, portal, or dynamic texture is present. ============= */ void R_AddDrawViewCmd( viewDef_t *parms ) { drawSurfsCommand_t *cmd; cmd = (drawSurfsCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); cmd->commandId = RC_DRAW_VIEW; cmd->viewDef = parms; tr.pc.c_numViews++; R_ViewStatistics( parms ); } //================================================================================= /* ============= R_CheckCvars See if some cvars that we watch have changed ============= */ static void R_CheckCvars( void ) { globalImages->CheckCvars(); // gamma stuff if ( r_gamma.IsModified() || r_brightness.IsModified() ) { r_gamma.ClearModified(); r_brightness.ClearModified(); R_SetColorMappings(); } if ( r_gammaInShader.IsModified() ) { r_gammaInShader.ClearModified(); // reload shaders so they either add or remove the code for setting gamma/brightness in shader R_ReloadARBPrograms_f( idCmdArgs() ); if ( r_gammaInShader.GetBool() ) { common->Printf( "Will apply r_gamma and r_brightness in shaders\n" ); GLimp_ResetGamma(); // reset hardware gamma } else { common->Printf( "Will apply r_gamma and r_brightness in hardware (possibly on all screens)\n" ); R_SetColorMappings(); } } } /* ============= idRenderSystemLocal::idRenderSystemLocal ============= */ idRenderSystemLocal::idRenderSystemLocal( void ) { Clear(); } /* ============= idRenderSystemLocal::~idRenderSystemLocal ============= */ idRenderSystemLocal::~idRenderSystemLocal( void ) { } /* ============= SetColor This can be used to pass general information to the current material, not just colors ============= */ void idRenderSystemLocal::SetColor( const idVec4 &rgba ) { guiModel->SetColor( rgba[0], rgba[1], rgba[2], rgba[3] ); } /* ============= SetColor4 ============= */ void idRenderSystemLocal::SetColor4( float r, float g, float b, float a ) { guiModel->SetColor( r, g, b, a ); } /* ============= DrawStretchPic ============= */ void idRenderSystemLocal::DrawStretchPic( const idDrawVert *verts, const glIndex_t *indexes, int vertCount, int indexCount, const idMaterial *material, bool clip, float min_x, float min_y, float max_x, float max_y ) { guiModel->DrawStretchPic( verts, indexes, vertCount, indexCount, material, clip, min_x, min_y, max_x, max_y ); } /* ============= DrawStretchPic x/y/w/h are in the 0,0 to 640,480 range ============= */ void idRenderSystemLocal::DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) { guiModel->DrawStretchPic( x, y, w, h, s1, t1, s2, t2, material ); } /* ============= DrawStretchTri x/y/w/h are in the 0,0 to 640,480 range ============= */ void idRenderSystemLocal::DrawStretchTri( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ) { tr.guiModel->DrawStretchTri( p1, p2, p3, t1, t2, t3, material ); } /* ============= GlobalToNormalizedDeviceCoordinates ============= */ void idRenderSystemLocal::GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ) { R_GlobalToNormalizedDeviceCoordinates( global, ndc ); } /* ============= GlobalToNormalizedDeviceCoordinates ============= */ void idRenderSystemLocal::GetGLSettings( int& width, int& height ) { width = glConfig.vidWidth; height = glConfig.vidHeight; } /* ===================== idRenderSystemLocal::DrawSmallChar small chars are drawn at native screen resolution ===================== */ void idRenderSystemLocal::DrawSmallChar( int x, int y, int ch, const idMaterial *material ) { int row, col; float frow, fcol; float size; ch &= 255; if ( ch == ' ' ) { return; } if ( y < -SMALLCHAR_HEIGHT ) { return; } row = ch >> 4; col = ch & 15; frow = row * 0.0625f; fcol = col * 0.0625f; size = 0.0625f; DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, fcol, frow, fcol + size, frow + size, material ); } /* ================== idRenderSystemLocal::DrawSmallString[Color] Draws a multi-colored string with a drop shadow, optionally forcing to a fixed color. Coordinates are at 640 by 480 virtual resolution ================== */ void idRenderSystemLocal::DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) { idVec4 color; const unsigned char *s; int xx; // draw the colored text s = (const unsigned char*)string; xx = x; SetColor( setColor ); while ( *s ) { if ( idStr::IsColor( (const char*)s ) ) { if ( !forceColor ) { if ( *(s+1) == C_COLOR_DEFAULT ) { SetColor( setColor ); } else { color = idStr::ColorForIndex( *(s+1) ); color[3] = setColor[3]; SetColor( color ); } } s += 2; continue; } DrawSmallChar( xx, y, *s, material ); xx += SMALLCHAR_WIDTH; s++; } SetColor( colorWhite ); } /* ===================== idRenderSystemLocal::DrawBigChar ===================== */ void idRenderSystemLocal::DrawBigChar( int x, int y, int ch, const idMaterial *material ) { int row, col; float frow, fcol; float size; ch &= 255; if ( ch == ' ' ) { return; } if ( y < -BIGCHAR_HEIGHT ) { return; } row = ch >> 4; col = ch & 15; frow = row * 0.0625f; fcol = col * 0.0625f; size = 0.0625f; DrawStretchPic( x, y, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, fcol, frow, fcol + size, frow + size, material ); } /* ================== idRenderSystemLocal::DrawBigString[Color] Draws a multi-colored string with a drop shadow, optionally forcing to a fixed color. Coordinates are at 640 by 480 virtual resolution ================== */ void idRenderSystemLocal::DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) { idVec4 color; const char *s; int xx; // draw the colored text s = string; xx = x; SetColor( setColor ); while ( *s ) { if ( idStr::IsColor( s ) ) { if ( !forceColor ) { if ( *(s+1) == C_COLOR_DEFAULT ) { SetColor( setColor ); } else { color = idStr::ColorForIndex( *(s+1) ); color[3] = setColor[3]; SetColor( color ); } } s += 2; continue; } DrawBigChar( xx, y, *s, material ); xx += BIGCHAR_WIDTH; s++; } SetColor( colorWhite ); } //====================================================================================== /* ================== SetBackEndRenderer Check for changes in the back end renderSystem, possibly invalidating cached data ================== */ void idRenderSystemLocal::SetBackEndRenderer() { if ( !r_renderer.IsModified() ) { return; } bool oldVPstate = backEndRendererHasVertexPrograms; backEndRenderer = BE_BAD; if ( idStr::Icmp( r_renderer.GetString(), "arb2" ) == 0 ) { if ( glConfig.allowARB2Path ) { backEndRenderer = BE_ARB2; } } // fallback if ( backEndRenderer == BE_BAD ) { // choose the best if ( glConfig.allowARB2Path ) { backEndRenderer = BE_ARB2; } } backEndRendererHasVertexPrograms = false; backEndRendererMaxLight = 1.0; switch( backEndRenderer ) { case BE_ARB2: common->Printf( "using ARB2 renderSystem\n" ); backEndRendererHasVertexPrograms = true; backEndRendererMaxLight = 999; break; default: common->FatalError( "SetbackEndRenderer: bad back end" ); } // clear the vertex cache if we are changing between // using vertex programs and not, because specular and // shadows will be different data if ( oldVPstate != backEndRendererHasVertexPrograms ) { vertexCache.PurgeAll(); if ( primaryWorld ) { primaryWorld->FreeInteractions(); } } r_renderer.ClearModified(); } /* ==================== BeginFrame ==================== */ void idRenderSystemLocal::BeginFrame( int windowWidth, int windowHeight ) { setBufferCommand_t *cmd; if ( !glConfig.isInitialized ) { return; } // DG: r_lockSurfaces only works properly if r_useScissor is disabled if ( r_lockSurfaces.IsModified() ) { static bool origUseScissor = true; r_lockSurfaces.ClearModified(); if ( r_lockSurfaces.GetBool() ) { origUseScissor = r_useScissor.GetBool(); r_useScissor.SetBool( false ); } else { r_useScissor.SetBool( origUseScissor ); } } // DG end // determine which back end we will use SetBackEndRenderer(); guiModel->Clear(); // for the larger-than-window tiled rendering screenshots if ( tiledViewport[0] ) { windowWidth = tiledViewport[0]; windowHeight = tiledViewport[1]; } // DG: save the original size, so editors don't mess up the game viewport // with their tiny (texture-preview etc) viewports. origWidth = glConfig.vidWidth; origHeight = glConfig.vidHeight; glConfig.vidWidth = windowWidth; glConfig.vidHeight = windowHeight; renderCrops[0].x = 0; renderCrops[0].y = 0; renderCrops[0].width = windowWidth; renderCrops[0].height = windowHeight; currentRenderCrop = 0; // screenFraction is just for quickly testing fill rate limitations if ( r_screenFraction.GetInteger() != 100 ) { int w = SCREEN_WIDTH * r_screenFraction.GetInteger() / 100.0f; int h = SCREEN_HEIGHT * r_screenFraction.GetInteger() / 100.0f; CropRenderSize( w, h ); } // this is the ONLY place this is modified frameCount++; // just in case we did a common->Error while this // was set guiRecursionLevel = 0; // the first rendering will be used for commands like // screenshot, rather than a possible subsequent remote // or mirror render // primaryWorld = NULL; // set the time for shader effects in 2D rendering frameShaderTime = eventLoop->Milliseconds() * 0.001; // // draw buffer stuff // cmd = (setBufferCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); cmd->commandId = RC_SET_BUFFER; cmd->frameCount = frameCount; if ( r_frontBuffer.GetBool() ) { cmd->buffer = (int)GL_FRONT; } else { cmd->buffer = (int)GL_BACK; } } void idRenderSystemLocal::WriteDemoPics() { session->writeDemo->WriteInt( DS_RENDER ); session->writeDemo->WriteInt( DC_GUI_MODEL ); guiModel->WriteToDemo( session->writeDemo ); } void idRenderSystemLocal::DrawDemoPics() { demoGuiModel->EmitFullScreen(); } /* ============= EndFrame Returns the number of msec spent in the back end ============= */ void idRenderSystemLocal::EndFrame( int *frontEndMsec, int *backEndMsec ) { emptyCommand_t *cmd; if ( !glConfig.isInitialized ) { return; } // close any gui drawing guiModel->EmitFullScreen(); guiModel->Clear(); // save out timing information if ( frontEndMsec ) { *frontEndMsec = pc.frontEndMsec; } if ( backEndMsec ) { *backEndMsec = backEnd.pc.msec; } // print any other statistics and clear all of them R_PerformanceCounters(); // check for dynamic changes that require some initialization R_CheckCvars(); // check for errors GL_CheckErrors(); // add the swapbuffers command cmd = (emptyCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); cmd->commandId = RC_SWAP_BUFFERS; // start the back end up again with the new command list R_IssueRenderCommands(); // use the other buffers next frame, because another CPU // may still be rendering into the current buffers R_ToggleSmpFrame(); // we can now release the vertexes used this frame vertexCache.EndFrame(); if ( session->writeDemo ) { session->writeDemo->WriteInt( DS_RENDER ); session->writeDemo->WriteInt( DC_END_FRAME ); if ( r_showDemo.GetBool() ) { common->Printf( "write DC_END_FRAME\n" ); } } // DG: restore the original size that was set before BeginFrame() overwrote it // with its function-arguments, so editors don't mess up our viewport. // (unsure why/how this at least *kinda* worked in original Doom3, // maybe glConfig.vidWidth/Height was reset if the window gained focus or sth) glConfig.vidWidth = origWidth; glConfig.vidHeight = origHeight; } /* ===================== RenderViewToViewport Converts from SCREEN_WIDTH / SCREEN_HEIGHT coordinates to current cropped pixel coordinates ===================== */ void idRenderSystemLocal::RenderViewToViewport( const renderView_t *renderView, idScreenRect *viewport ) { renderCrop_t *rc = &renderCrops[currentRenderCrop]; float wRatio = (float)rc->width / SCREEN_WIDTH; float hRatio = (float)rc->height / SCREEN_HEIGHT; viewport->x1 = idMath::Ftoi( rc->x + renderView->x * wRatio ); viewport->x2 = idMath::Ftoi( rc->x + floor( ( renderView->x + renderView->width ) * wRatio + 0.5f ) - 1 ); viewport->y1 = idMath::Ftoi( ( rc->y + rc->height ) - floor( ( renderView->y + renderView->height ) * hRatio + 0.5f ) ); viewport->y2 = idMath::Ftoi( ( rc->y + rc->height ) - floor( renderView->y * hRatio + 0.5f ) - 1 ); } static int RoundDownToPowerOfTwo( int v ) { int i; for ( i = 0 ; i < 20 ; i++ ) { if ( ( 1 << i ) == v ) { return v; } if ( ( 1 << i ) > v ) { return 1 << ( i-1 ); } } return 1<EmitFullScreen(); guiModel->Clear(); if ( width < 1 || height < 1 ) { common->Error( "CropRenderSize: bad sizes" ); } if ( session->writeDemo ) { session->writeDemo->WriteInt( DS_RENDER ); session->writeDemo->WriteInt( DC_CROP_RENDER ); session->writeDemo->WriteInt( width ); session->writeDemo->WriteInt( height ); session->writeDemo->WriteInt( makePowerOfTwo ); if ( r_showDemo.GetBool() ) { common->Printf( "write DC_CROP_RENDER\n" ); } } // convert from virtual SCREEN_WIDTH/SCREEN_HEIGHT coordinates to physical OpenGL pixels renderView_t renderView; renderView.x = 0; renderView.y = 0; renderView.width = width; renderView.height = height; idScreenRect r; RenderViewToViewport( &renderView, &r ); width = r.x2 - r.x1 + 1; height = r.y2 - r.y1 + 1; if ( forceDimensions ) { // just give exactly what we ask for width = renderView.width; height = renderView.height; } // if makePowerOfTwo, drop to next lower power of two after scaling to physical pixels if ( makePowerOfTwo ) { width = RoundDownToPowerOfTwo( width ); height = RoundDownToPowerOfTwo( height ); // FIXME: megascreenshots with offset viewports don't work right with this yet } renderCrop_t *rc = &renderCrops[currentRenderCrop]; // we might want to clip these to the crop window instead while ( width > glConfig.vidWidth ) { width >>= 1; } while ( height > glConfig.vidHeight ) { height >>= 1; } if ( currentRenderCrop == MAX_RENDER_CROPS ) { common->Error( "idRenderSystemLocal::CropRenderSize: currentRenderCrop == MAX_RENDER_CROPS" ); } currentRenderCrop++; rc = &renderCrops[currentRenderCrop]; rc->x = 0; rc->y = 0; rc->width = width; rc->height = height; } /* ================ UnCrop ================ */ void idRenderSystemLocal::UnCrop() { if ( !glConfig.isInitialized ) { return; } if ( currentRenderCrop < 1 ) { common->Error( "idRenderSystemLocal::UnCrop: currentRenderCrop < 1" ); } // close any gui drawing guiModel->EmitFullScreen(); guiModel->Clear(); currentRenderCrop--; if ( session->writeDemo ) { session->writeDemo->WriteInt( DS_RENDER ); session->writeDemo->WriteInt( DC_UNCROP_RENDER ); if ( r_showDemo.GetBool() ) { common->Printf( "write DC_UNCROP\n" ); } } } /* ================ CaptureRenderToImage ================ */ void idRenderSystemLocal::CaptureRenderToImage( const char *imageName ) { if ( !glConfig.isInitialized ) { return; } guiModel->EmitFullScreen(); guiModel->Clear(); if ( session->writeDemo ) { session->writeDemo->WriteInt( DS_RENDER ); session->writeDemo->WriteInt( DC_CAPTURE_RENDER ); session->writeDemo->WriteHashString( imageName ); if ( r_showDemo.GetBool() ) { common->Printf( "write DC_CAPTURE_RENDER: %s\n", imageName ); } } // look up the image before we create the render command, because it // may need to sync to create the image idImage *image = globalImages->ImageFromFile(imageName, TF_DEFAULT, true, TR_REPEAT, TD_DEFAULT); renderCrop_t *rc = &renderCrops[currentRenderCrop]; copyRenderCommand_t *cmd = (copyRenderCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); cmd->commandId = RC_COPY_RENDER; cmd->x = rc->x; cmd->y = rc->y; cmd->imageWidth = rc->width; cmd->imageHeight = rc->height; cmd->image = image; guiModel->Clear(); } /* ============== CaptureRenderToFile ============== */ void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) { if ( !glConfig.isInitialized ) { return; } renderCrop_t *rc = &renderCrops[currentRenderCrop]; guiModel->EmitFullScreen(); guiModel->Clear(); R_IssueRenderCommands(); qglReadBuffer( GL_BACK ); // include extra space for OpenGL padding to word boundaries int c = ( rc->width + 3 ) * rc->height; byte *data = (byte *)R_StaticAlloc( c * 3 ); qglReadPixels( rc->x, rc->y, rc->width, rc->height, GL_RGB, GL_UNSIGNED_BYTE, data ); byte *data2 = (byte *)R_StaticAlloc( c * 4 ); for ( int i = 0 ; i < c ; i++ ) { data2[ i * 4 ] = data[ i * 3 ]; data2[ i * 4 + 1 ] = data[ i * 3 + 1 ]; data2[ i * 4 + 2 ] = data[ i * 3 + 2 ]; data2[ i * 4 + 3 ] = 0xff; } R_WriteTGA( fileName, data2, rc->width, rc->height, true ); R_StaticFree( data ); R_StaticFree( data2 ); } /* ============== AllocRenderWorld ============== */ idRenderWorld *idRenderSystemLocal::AllocRenderWorld() { idRenderWorldLocal *rw; rw = new idRenderWorldLocal; worlds.Append( rw ); return rw; } /* ============== FreeRenderWorld ============== */ void idRenderSystemLocal::FreeRenderWorld( idRenderWorld *rw ) { if ( primaryWorld == rw ) { primaryWorld = NULL; } worlds.Remove( static_cast(rw) ); delete rw; } /* ============== PrintMemInfo ============== */ void idRenderSystemLocal::PrintMemInfo( MemInfo_t *mi ) { // sum up image totals globalImages->PrintMemInfo( mi ); // sum up model totals renderModelManager->PrintMemInfo( mi ); // compute render totals } /* =============== idRenderSystemLocal::UploadImage =============== */ bool idRenderSystemLocal::UploadImage( const char *imageName, const byte *data, int width, int height ) { idImage *image = globalImages->GetImage( imageName ); if ( !image ) { return false; } image->UploadScratch( data, width, height ); image->SetImageFilterAndRepeat(); return true; }