/* ============== 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 ); }
/* ================= R_ClearedStaticAlloc ================= */ void *R_ClearedStaticAlloc( int bytes ) { void *buf; buf = R_StaticAlloc( bytes ); SIMDProcessor->Memset( buf, 0, bytes ); return buf; }
/* ================= R_HeightmapToNormalMap it is not possible to convert a heightmap into a normal map properly without knowing the texture coordinate stretching. We can assume constant and equal ST vectors for walls, but not for characters. ================= */ static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale ) { int i, j; byte *depth; scale = scale / 256; // copy and convert to grey scale j = width * height; depth = ( byte * ) R_StaticAlloc( j ); for( i = 0; i < j; i++ ) { depth[i] = ( data[i * 4] + data[i * 4 + 1] + data[i * 4 + 2] ) / 3; } idVec3 dir, dir2; for( i = 0; i < height; i++ ) { for( j = 0; j < width; j++ ) { int d1, d2, d3, d4; int a1, a2, a3, a4; // FIXME: look at five points? // look at three points to estimate the gradient a1 = d1 = depth[( i * width + j )]; a2 = d2 = depth[( i * width + ( ( j + 1 ) & ( width - 1 ) ) )]; a3 = d3 = depth[( ( ( i + 1 ) & ( height - 1 ) ) * width + j )]; a4 = d4 = depth[( ( ( i + 1 ) & ( height - 1 ) ) * width + ( ( j + 1 ) & ( width - 1 ) ) )]; d2 -= d1; d3 -= d1; dir[0] = -d2 * scale; dir[1] = -d3 * scale; dir[2] = 1; dir.NormalizeFast(); a1 -= a3; a4 -= a3; dir2[0] = -a4 * scale; dir2[1] = a1 * scale; dir2[2] = 1; dir2.NormalizeFast(); dir += dir2; dir.NormalizeFast(); a1 = ( i * width + j ) * 4; data[a1 + 0] = ( byte )( dir[0] * 127 + 128 ); data[a1 + 1] = ( byte )( dir[1] * 127 + 128 ); data[a1 + 2] = ( byte )( dir[2] * 127 + 128 ); data[a1 + 3] = 255; } } R_StaticFree( depth ); }
/* ================ R_CalcInteractionFacing Determines which triangles of the surface are facing towards the light origin. The facing array should be allocated with one extra index than the number of surface triangles, which will be used to handle dangling edge silhouettes. ================ */ void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { SCOPED_PROFILE_EVENT( "R_CalcInteractionFacing" ); if ( cullInfo.facing != NULL ) { return; } idVec3 localLightOrigin; R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); const int numFaces = tri->numIndexes / 3; cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ), TAG_RENDER_INTERACTION ); // exact geometric cull against face for ( int i = 0, face = 0; i < tri->numIndexes; i += 3, face++ ) { const idDrawVert & v0 = tri->verts[tri->indexes[i + 0]]; const idDrawVert & v1 = tri->verts[tri->indexes[i + 1]]; const idDrawVert & v2 = tri->verts[tri->indexes[i + 2]]; const idPlane plane( v0.xyz, v1.xyz, v2.xyz ); const float d = plane.Distance( localLightOrigin ); cullInfo.facing[face] = ( d >= 0.0f ); } cullInfo.facing[numFaces] = 1; // for dangling edges to reference }
/* ================ R_CalcInteractionFacing Determines which triangles of the surface are facing towards the light origin. The facing array should be allocated with one extra index than the number of surface triangles, which will be used to handle dangling edge silhouettes. ================ */ void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { idVec3 localLightOrigin; if ( cullInfo.facing != NULL ) { return; } R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); int numFaces = tri->numIndexes / 3; if ( !tri->facePlanes || !tri->facePlanesCalculated ) { R_DeriveFacePlanes( const_cast<srfTriangles_t *>(tri) ); } cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ) ); // calculate back face culling float *planeSide = (float *) _alloca16( numFaces * sizeof( float ) ); // exact geometric cull against face SIMDProcessor->Dot( planeSide, localLightOrigin, tri->facePlanes, numFaces ); SIMDProcessor->CmpGE( cullInfo.facing, planeSide, 0.0f, numFaces ); cullInfo.facing[ numFaces ] = 1; // for dangling edges to reference }
/* ==================== GenerateMegaPreview Make a 2k x 2k preview image for a mega texture that can be used in modeling programs ==================== */ void idMegaTexture::GenerateMegaPreview( const char *fileName ) { idFile *fileHandle = fileSystem->OpenFileRead( fileName ); if ( !fileHandle ) { common->Printf( "idMegaTexture: failed to open %s\n", fileName ); return; } idStr outName = fileName; outName.StripFileExtension(); outName += "_preview.tga"; common->Printf( "Creating %s.\n", outName.c_str() ); megaTextureHeader_t header; fileHandle->Read( &header, sizeof( header ) ); if ( header.tileSize < 64 || header.tilesWide < 1 || header.tilesHigh < 1 ) { common->Printf( "idMegaTexture: bad header on %s\n", fileName ); return; } int tileSize = header.tileSize; int width = header.tilesWide; int height = header.tilesHigh; int tileOffset = 1; int tileBytes = tileSize * tileSize * 4; // find the level that fits while ( width * tileSize > 2048 || height * tileSize > 2048 ) { tileOffset += width * height; width >>= 1; if ( width < 1 ) { width = 1; } height >>= 1; if ( height < 1 ) { height = 1; } } byte *pic = (byte *)R_StaticAlloc( width * height * tileBytes ); byte *oldBlock = (byte *)_alloca( tileBytes ); for ( int y = 0 ; y < height ; y++ ) { for ( int x = 0 ; x < width ; x++ ) { int tileNum = tileOffset + y * width + x; fileHandle->Seek( tileNum * tileBytes, FS_SEEK_SET ); fileHandle->Read( oldBlock, tileBytes ); for ( int yy = 0 ; yy < tileSize ; yy++ ) { memcpy( pic + ( ( y * tileSize + yy ) * width * tileSize + x * tileSize ) * 4, oldBlock + yy * tileSize * 4, tileSize * 4 ); } } } R_WriteTGA( outName.c_str(), pic, width * tileSize, height * tileSize, false ); R_StaticFree( pic ); delete fileHandle; }
/* ================ R_SmoothNormalMap ================ */ static void R_SmoothNormalMap( byte* data, int width, int height ) { byte* orig; int i, j, k, l; idVec3 normal; byte* out; static float factors[3][3] = { { 1, 1, 1 }, { 1, 1, 1 }, { 1, 1, 1 } }; orig = ( byte* )R_StaticAlloc( width * height * 4, TAG_IMAGE ); memcpy( orig, data, width * height * 4 ); for( i = 0 ; i < width ; i++ ) { for( j = 0 ; j < height ; j++ ) { normal = vec3_origin; for( k = -1 ; k < 2 ; k++ ) { for( l = -1 ; l < 2 ; l++ ) { byte* in; in = orig + ( ( ( j + l ) & ( height - 1 ) ) * width + ( ( i + k ) & ( width - 1 ) ) ) * 4; // ignore 000 and -1 -1 -1 if( in[0] == 0 && in[1] == 0 && in[2] == 0 ) { continue; } if( in[0] == 128 && in[1] == 128 && in[2] == 128 ) { continue; } normal[0] += factors[k + 1][l + 1] * ( in[0] - 128 ); normal[1] += factors[k + 1][l + 1] * ( in[1] - 128 ); normal[2] += factors[k + 1][l + 1] * ( in[2] - 128 ); } } normal.Normalize(); out = data + ( j * width + i ) * 4; out[0] = ( byte )( 128 + 127 * normal[0] ); out[1] = ( byte )( 128 + 127 * normal[1] ); out[2] = ( byte )( 128 + 127 * normal[2] ); } } R_StaticFree( orig ); }
/* ===================== R_CalcInteractionCullBits We want to cull a little on the sloppy side, because the pre-clipping of geometry to the lights in dmap will give many cases that are right at the border. We throw things out on the border, because if any one vertex is clearly inside, the entire triangle will be accepted. ===================== */ void R_CalcInteractionCullBits( const idRenderEntityLocal* ent, const srfTriangles_t* tri, const idRenderLightLocal* light, srfCullInfo_t& cullInfo ) { SCOPED_PROFILE_EVENT( "R_CalcInteractionCullBits" ); if( cullInfo.cullBits != NULL ) { return; } idPlane frustumPlanes[6]; idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); int frontBits = 0; // cull the triangle surface bounding box for( int i = 0; i < 6; i++ ) { R_GlobalPlaneToLocal( ent->modelMatrix, frustumPlanes[i], cullInfo.localClipPlanes[i] ); // get front bits for the whole surface if( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) { frontBits |= 1 << i; } } // if the surface is completely inside the light frustum if( frontBits == ( ( 1 << 6 ) - 1 ) ) { cullInfo.cullBits = LIGHT_CULL_ALL_FRONT; return; } cullInfo.cullBits = ( byte* ) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ), TAG_RENDER_INTERACTION ); memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) ); for( int i = 0; i < 6; i++ ) { // if completely infront of this clipping plane if( frontBits & ( 1 << i ) ) { continue; } for( int j = 0; j < tri->numVerts; j++ ) { float d = cullInfo.localClipPlanes[i].Distance( tri->verts[j].xyz ); cullInfo.cullBits[j] |= ( d < LIGHT_CLIP_EPSILON ) << i; } } }
/* ============== idRenderSystemLocal::CaptureRenderToFile ============== */ void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) { if ( !R_IsInitialized() ) { return; } idScreenRect & rc = renderCrops[currentRenderCrop]; guiModel->EmitFullScreen(); guiModel->Clear(); RenderCommandBuffers( frameData->cmdHead ); if ( !vr->useFBO ) // koz { glReadBuffer( GL_BACK ); } // include extra space for OpenGL padding to word boundaries int c = ( rc.GetWidth() + 3 ) * rc.GetHeight(); byte *data = (byte *)R_StaticAlloc( c * 3 ); qglReadPixels( rc.x1, rc.y1, rc.GetWidth(), rc.GetHeight(), 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.GetWidth(), rc.GetHeight(), true ); R_StaticFree( data ); R_StaticFree( data2 ); }
/* =================== RB_ShowIntensity Debugging tool to see how much dynamic range a scene is using. The greatest of the rgb values at each pixel will be used, with the resulting color shading from red at 0 to green at 128 to blue at 255 =================== */ static void RB_ShowIntensity() { byte *colorReadback; int i, j, c; if ( !r_showIntensity.GetBool() ) { return; } colorReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight() * 4, TAG_RENDER_TOOLS ); qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, colorReadback ); c = renderSystem->GetWidth() * renderSystem->GetHeight() * 4; for ( i = 0; i < c; i+=4 ) { j = colorReadback[i]; if ( colorReadback[i+1] > j ) { j = colorReadback[i+1]; } if ( colorReadback[i+2] > j ) { j = colorReadback[i+2]; } if ( j < 128 ) { colorReadback[i+0] = 2*(128-j); colorReadback[i+1] = 2*j; colorReadback[i+2] = 0; } else { colorReadback[i+0] = 0; colorReadback[i+1] = 2*(255-j); colorReadback[i+2] = 2*(j-128); } } // draw it back to the screen qglLoadIdentity(); qglMatrixMode( GL_PROJECTION ); GL_State( GLS_DEPTHFUNC_ALWAYS ); qglPushMatrix(); qglLoadIdentity(); qglOrtho( 0, 1, 0, 1, -1, 1 ); qglRasterPos2f( 0, 0 ); qglPopMatrix(); GL_Color( 1, 1, 1 ); globalImages->BindNull(); qglMatrixMode( GL_MODELVIEW ); qglDrawPixels( renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA , GL_UNSIGNED_BYTE, colorReadback ); R_StaticFree( colorReadback ); }
/* =================== RB_CountStencilBuffer Print an overdraw count based on stencil index values =================== */ static void RB_CountStencilBuffer() { int count; int i; byte *stencilReadback; stencilReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight(), TAG_RENDER_TOOLS ); qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); count = 0; for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) { count += stencilReadback[i]; } R_StaticFree( stencilReadback ); // print some stats (not supposed to do from back end in SMP...) common->Printf( "overdraw: %5.1f\n", (float)count/(renderSystem->GetWidth() * renderSystem->GetHeight()) ); }
/* ===================== R_CalcInteractionCullBits We want to cull a little on the sloppy side, because the pre-clipping of geometry to the lights in dmap will give many cases that are right at the border we throw things out on the border, because if any one vertex is clearly inside, the entire triangle will be accepted. ===================== */ void R_CalcInteractionCullBits( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { int i, frontBits; if ( cullInfo.cullBits != NULL ) { return; } frontBits = 0; // cull the triangle surface bounding box for ( i = 0; i < 6; i++ ) { R_GlobalPlaneToLocal( ent->modelMatrix, -light->frustum[i], cullInfo.localClipPlanes[i] ); // get front bits for the whole surface if ( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) { frontBits |= 1<<i; } } // if the surface is completely inside the light frustum if ( frontBits == ( ( 1 << 6 ) - 1 ) ) { cullInfo.cullBits = LIGHT_CULL_ALL_FRONT; return; } cullInfo.cullBits = (byte *) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ) ); SIMDProcessor->Memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) ); float *planeSide = (float *) _alloca16( tri->numVerts * sizeof( float ) ); for ( i = 0; i < 6; i++ ) { // if completely infront of this clipping plane if ( frontBits & ( 1 << i ) ) { continue; } SIMDProcessor->Dot( planeSide, cullInfo.localClipPlanes[i], tri->verts, tri->numVerts ); SIMDProcessor->CmpLT( cullInfo.cullBits, i, planeSide, LIGHT_CLIP_EPSILON, tri->numVerts ); } }
/* =================== RB_ShowDepthBuffer Draw the depth buffer as colors =================== */ static void RB_ShowDepthBuffer() { void *depthReadback; if ( !r_showDepth.GetBool() ) { return; } qglPushMatrix(); qglLoadIdentity(); qglMatrixMode( GL_PROJECTION ); qglPushMatrix(); qglLoadIdentity(); qglOrtho( 0, 1, 0, 1, -1, 1 ); qglRasterPos2f( 0, 0 ); qglPopMatrix(); qglMatrixMode( GL_MODELVIEW ); qglPopMatrix(); GL_State( GLS_DEPTHFUNC_ALWAYS ); GL_Color( 1, 1, 1 ); globalImages->BindNull(); depthReadback = R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight()*4, TAG_RENDER_TOOLS ); memset( depthReadback, 0, renderSystem->GetWidth() * renderSystem->GetHeight()*4 ); qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_DEPTH_COMPONENT , GL_FLOAT, depthReadback ); #if 0 for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) { ((byte *)depthReadback)[i*4] = ((byte *)depthReadback)[i*4+1] = ((byte *)depthReadback)[i*4+2] = 255 * ((float *)depthReadback)[i]; ((byte *)depthReadback)[i*4+3] = 1; } #endif qglDrawPixels( renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA , GL_UNSIGNED_BYTE, depthReadback ); R_StaticFree( depthReadback ); }
/* =================== RB_ScanStencilBuffer Debugging tool to see what values are in the stencil buffer =================== */ void RB_ScanStencilBuffer() { int counts[256]; int i; byte *stencilReadback; memset( counts, 0, sizeof( counts ) ); stencilReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight(), TAG_RENDER_TOOLS ); qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) { counts[ stencilReadback[i] ]++; } R_StaticFree( stencilReadback ); // print some stats (not supposed to do from back end in SMP...) common->Printf( "stencil values:\n" ); for ( i = 0; i < 255; i++ ) { if ( counts[i] ) { common->Printf( "%i: %i\n", i, counts[i] ); } } }
/* ================= R_ClearedStaticAlloc ================= */ void *R_ClearedStaticAlloc( int bytes ) { void * buf = R_StaticAlloc( bytes ); memset( buf, 0, bytes ); return buf; }
void fhBaseRenderList::Init() { assert(!renderlistMemory); renderlistMemory = R_StaticAlloc(renderlistMaxSize); }
/* ============== RenderBump_f ============== */ void RenderBump_f( const idCmdArgs &args ) { idRenderModel *lowPoly; idStr source; int i, j; const char *cmdLine; int numRenderBumps; renderBump_t *renderBumps, *rb = NULL; renderBump_t opt; int startTime, endTime; // update the screen as we print common->SetRefreshOnPrint( true ); // there should be a single parameter, the filename for a game loadable low-poly model if ( args.Argc() != 2 ) { common->Error( "Usage: renderbump <lowPolyModel>" ); } common->Printf( "----- Renderbump %s -----\n", args.Argv( 1 ) ); startTime = Sys_Milliseconds(); // get the lowPoly model source = args.Argv( 1 ); lowPoly = renderModelManager->CheckModel( source ); if ( !lowPoly ) { common->Error( "Can't load model %s", source.c_str() ); } renderBumps = (renderBump_t *)R_StaticAlloc( lowPoly->NumSurfaces() * sizeof( *renderBumps ) ); numRenderBumps = 0; for ( i = 0 ; i < lowPoly->NumSurfaces() ; i++ ) { const modelSurface_t *ms = lowPoly->Surface( i ); // default options memset( &opt, 0, sizeof( opt ) ); opt.width = 512; opt.height = 512; opt.antiAlias = 1; opt.outline = 8; opt.traceFrac = 0.05f; // parse the renderbump parameters for this surface cmdLine = ms->shader->GetRenderBump(); common->Printf( "surface %i, shader %s\nrenderBump = %s ", i, ms->shader->GetName(), cmdLine ); if ( !ms->geometry ) { common->Printf( "(no geometry)\n" ); continue; } idCmdArgs localArgs; localArgs.TokenizeString( cmdLine, false ); if ( localArgs.Argc() < 2 ) { common->Printf( "(no action)\n" ); continue; } common->Printf( "(rendering)\n" ); for ( j = 0 ; j < localArgs.Argc() - 2; j++ ) { const char *s; s = localArgs.Argv( j ); if ( s[0] == '-' ) { j++; s = localArgs.Argv( j ); if ( s[0] == '\0' ) { continue; } } if ( !idStr::Icmp( s, "size" ) ) { if ( j + 2 >= localArgs.Argc() ) { j = localArgs.Argc(); break; } opt.width = atoi( localArgs.Argv( j + 1 ) ); opt.height = atoi( localArgs.Argv( j + 2 ) ); j += 2; } else if ( !idStr::Icmp( s, "trace" ) ) { opt.traceFrac = atof( localArgs.Argv( j + 1 ) ); j += 1; } else if ( !idStr::Icmp( s, "globalMap" ) ) { opt.saveGlobalMap = true; } else if ( !idStr::Icmp( s, "colorMap" ) ) { opt.saveColorMap = true; } else if ( !idStr::Icmp( s, "outline" ) ) { opt.outline = atoi( localArgs.Argv( j + 1 ) ); j += 1; } else if ( !idStr::Icmp( s, "aa" ) ) { opt.antiAlias = atoi( localArgs.Argv( j + 1 ) ); j += 1; } else { common->Printf( "WARNING: Unknown option \"%s\"\n", s ); break; } } if ( j != ( localArgs.Argc() - 2 ) ) { common->Error( "usage: renderBump [-size width height] [-aa <1-2>] [globalMap] [colorMap] [-trace <0.01 - 1.0>] normalMapImageFile highPolyAseFile" ); } idStr::Copynz( opt.outputName, localArgs.Argv( j ), sizeof( opt.outputName ) ); idStr::Copynz( opt.highName, localArgs.Argv( j + 1 ), sizeof( opt.highName ) ); // adjust size for anti-aliasing opt.width <<= opt.antiAlias; opt.height <<= opt.antiAlias; // see if we already have a renderbump going for another surface that this should use for ( j = 0 ; j < numRenderBumps ; j++ ) { rb = &renderBumps[j]; if ( idStr::Icmp( rb->outputName, opt.outputName ) ) { continue; } // all the other parameters must match, or it is an error if ( idStr::Icmp( rb->highName, opt.highName) || rb->width != opt.width || rb->height != opt.height || rb->antiAlias != opt.antiAlias || rb->traceFrac != opt.traceFrac ) { common->Error( "mismatched renderbump parameters on image %s", rb->outputName ); continue; } // saveGlobalMap will be a sticky option rb->saveGlobalMap = rb->saveGlobalMap | opt.saveGlobalMap; break; } // create a new renderbump if needed if ( j == numRenderBumps ) { numRenderBumps++; rb = &renderBumps[j]; *rb = opt; InitRenderBump( rb ); } // render the triangles for this surface RenderBumpTriangles( ms->geometry, rb ); } // // anti-alias and write out all renderbumps that we have completed // for ( i = 0 ; i < numRenderBumps ; i++ ) { WriteRenderBump( &renderBumps[i], opt.outline << opt.antiAlias ); } R_StaticFree( renderBumps ); endTime = Sys_Milliseconds(); common->Printf( "%5.2f seconds for renderBump\n", ( endTime - startTime ) / 1000.0 ); common->Printf( "---------- RenderBump Completed ----------\n" ); // stop updating the screen as we print common->SetRefreshOnPrint( false ); }