/* gl_rmisc.cpp - renderer miscellaneous Copyright (C) 2013 Uncle Mike This program 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. This program 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. */ #include "hud.h" #include "cl_util.h" #include "gl_local.h" #include "gl_studio.h" #include "gl_aurora.h" #include "gl_rpart.h" #include "material.h" #include "gl_occlusion.h" #include "gl_world.h" #include "gl_grass.h" #include "gl_shader.h" #define DEFAULT_SMOOTHNESS 0.35f #define FILTER_SIZE 2 /* ================== CL_LoadMaterials parse material from a given file ================== */ void CL_LoadMaterials( const char *path ) { ALERT( at_aiconsole, "loading %s\n", path ); char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)path, 5, NULL ); if( !afile ) { ALERT( at_error, "Cannot open file \"%s\"\n", path ); return; } matdesc_t *oldmaterials = tr.materials; int oldcount = tr.matcount; char *pfile = afile; char token[256]; int depth = 0; float flValue; // count materials while( pfile != NULL ) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) break; if( Q_strlen( token ) > 1 ) continue; if( token[0] == '{' ) { depth++; } else if( token[0] == '}' ) { tr.matcount++; depth--; } } if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", path ); if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", path ); tr.materials = (matdesc_t *)Mem_Alloc( sizeof( matdesc_t ) * tr.matcount ); memcpy( tr.materials, oldmaterials, oldcount * sizeof( matdesc_t )); Mem_Free( oldmaterials ); pfile = afile; // start real parsing int current = oldcount; // starts from while( pfile != NULL ) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) break; if( current >= tr.matcount ) { ALERT ( at_error, "material parse is overrun %d > %d\n", current, tr.matcount ); break; } matdesc_t *mat = &tr.materials[current]; // read the material name Q_strncpy( mat->name, token, sizeof( mat->name )); COM_StripExtension( mat->name ); for( int i = 0; i < current; i++ ) { if( !Q_stricmp( mat->name, tr.materials[i].name )) { ALERT( at_warning, "mat %s was duplicated, second definition will be ignored\n", mat->name ); break; } } // set defaults mat->dt_texturenum = tr.grayTexture; mat->detailScale[0] = 10.0f; mat->detailScale[1] = 10.0f; mat->smoothness = DEFAULT_SMOOTHNESS; // read opening brace pfile = COM_ParseFile( pfile, token ); if( !pfile ) break; if( token[0] != '{' ) { ALERT( at_error, "found %s when expecting {\n", token ); break; } while( pfile != NULL ) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "EOF without closing brace\n" ); goto getout; } // description end goto next material if( token[0] == '}' ) { current++; break; } else if( !Q_stricmp( token, "glossExp" )) // for backward compatibility { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'glossExp'\n" ); goto getout; } flValue = Q_atof( token ); flValue = bound( 0.0f, flValue, 256.0f ); mat->smoothness = sqrt( flValue / 256.0f ); } else if( !Q_stricmp( token, "smoothness" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'smoothness'\n" ); goto getout; } mat->smoothness = Q_atof( token ); mat->smoothness = bound( 0.0f, mat->smoothness, 1.0f ); } else if( !Q_stricmp( token, "detailScale" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'detailScale'\n" ); goto getout; } Q_atov( mat->detailScale, token, 2 ); mat->detailScale[0] = bound( 0.01f, mat->detailScale[0], 100.0f ); mat->detailScale[1] = bound( 0.01f, mat->detailScale[0], 100.0f ); } else if( !Q_stricmp( token, "detailmap" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'detailmap'\n" ); goto getout; } COM_FixSlashes( token ); mat->dt_texturenum = LOAD_TEXTURE( token, NULL, 0, TF_FORCE_COLOR ); } else if( !Q_stricmp( token, "diffuseMap" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'diffusemap'\n" ); goto getout; } COM_FixSlashes( token ); Q_strncpy( mat->diffusemap, token, sizeof( mat->diffusemap )); } else if( !Q_stricmp( token, "normalMap" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'normalmap'\n" ); goto getout; } COM_FixSlashes( token ); Q_strncpy( mat->normalmap, token, sizeof( mat->normalmap )); } else if( !Q_stricmp( token, "glossMap" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'glossmap'\n" ); goto getout; } COM_FixSlashes( token ); Q_strncpy( mat->glossmap, token, sizeof( mat->glossmap )); } else if( !Q_stricmp( token, "AberrationScale" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'AberrationScale'\n" ); goto getout; } mat->aberrationScale = Q_atof( token ) * 0.1f; mat->aberrationScale = bound( 0.0f, mat->aberrationScale, 0.1f ); } else if( !Q_stricmp( token, "ReflectScale" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'ReflectScale'\n" ); goto getout; } mat->reflectScale = Q_atof( token ); mat->reflectScale = bound( 0.0f, mat->reflectScale, 1.0f ); } else if( !Q_stricmp( token, "RefractScale" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'RefractScale'\n" ); goto getout; } mat->refractScale = Q_atof( token ); mat->refractScale = bound( 0.0f, mat->refractScale, 1.0f ); } else if( !Q_stricmp( token, "ReliefScale" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'ReliefScale'\n" ); goto getout; } mat->reliefScale = Q_atof( token ); mat->reliefScale = bound( 0.0f, mat->reliefScale, 1.0f ); } else if( !Q_stricmp( token, "material" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'material'\n" ); goto getout; } mat->effects = COM_FindMatdef( token ); } else ALERT( at_warning, "Unknown material token %s\n", token ); } // apply default values if( !mat->effects ) mat->effects = COM_DefaultMatdef(); if( mat->dt_texturenum == tr.grayTexture && mat->effects->detailName[0] ) { mat->dt_texturenum = LOAD_TEXTURE( mat->effects->detailName, NULL, 0, TF_FORCE_COLOR ); mat->detailScale[0] = mat->effects->detailScale[0]; mat->detailScale[1] = mat->effects->detailScale[0]; } // if detail texture was missed if( mat->dt_texturenum == 0 ) mat->dt_texturenum = tr.grayTexture; } getout: gEngfuncs.COM_FreeFile( afile ); ALERT( at_aiconsole, "%d materials parsed\n", current ); } /* ================== CL_InitMaterials parse all files with .mat extension ================== */ void CL_InitMaterials( void ) { int count = 0; char **filenames = FS_SEARCH( "scripts/*.mat", &count, 0 ); tr.matcount = 0; // sequentially load materials for( int i = 0; i < count; i++ ) CL_LoadMaterials( filenames[i] ); } /* ================== CL_FindMaterial This function never failed ================== */ matdesc_t *CL_FindMaterial( const char *name ) { static matdesc_t defmat; if( !defmat.name[0] ) { // initialize default material Q_strncpy( defmat.name, "*default", sizeof( defmat.name )); defmat.dt_texturenum = tr.grayTexture; defmat.detailScale[0] = 10.0f; defmat.detailScale[1] = 10.0f; defmat.aberrationScale = 0.0f; defmat.reflectScale = 0.0f; defmat.refractScale = 0.0f; defmat.reliefScale = 0.0f; defmat.smoothness = DEFAULT_SMOOTHNESS; // same as glossExp 32.0 defmat.effects = COM_DefaultMatdef(); } for( int i = 0; i < tr.matcount; i++ ) { if( !Q_stricmp( name, tr.materials[i].name )) return &tr.materials[i]; } return &defmat; } /* ======================== LoadHeightMap parse heightmap pixels and remap it to real layer count ======================== */ bool LoadHeightMap( indexMap_t *im, int numLayers ) { unsigned int *src; int i, tex; int depth = 1; if( !GL_Support( R_TEXTURE_ARRAY_EXT ) || numLayers <= 0 ) return false; // loading heightmap and keep the source pixels if( !( tex = LOAD_TEXTURE( im->name, NULL, 0, TF_KEEP_SOURCE|TF_EXPAND_SOURCE ))) return false; if(( src = (unsigned int *)GET_TEXTURE_DATA( tex )) == NULL ) { ALERT( at_error, "LoadHeightMap: couldn't get source pixels for %s\n", im->name ); FREE_TEXTURE( tex ); return false; } im->gl_diffuse_id = LOAD_TEXTURE( im->diffuse, NULL, 0, 0 ); int width = RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, tex ); int height = RENDER_GET_PARM( PARM_TEX_SRC_HEIGHT, tex ); im->pixels = (byte *)Mem_Alloc( width * height ); im->numLayers = bound( 1, numLayers, 255 ); im->height = height; im->width = width; for( i = 0; i < ( im->width * im->height ); i++ ) { byte rawHeight = ( src[i] & 0xFF ); im->maxHeight = Q_max(( 16 * (int)ceil( rawHeight / 16 )), im->maxHeight ); } // merge layers count im->numLayers = (im->maxHeight / 16) + 1; depth = Q_max((int)Q_ceil((float)im->numLayers / 4.0f ), 1 ); // clamp to layers count for( i = 0; i < ( im->width * im->height ); i++ ) im->pixels[i] = (( src[i] & 0xFF ) * ( im->numLayers - 1 )) / im->maxHeight; size_t lay_size = im->width * im->height * 4; size_t img_size = lay_size * depth; byte *layers = (byte *)Mem_Alloc( img_size ); byte *pixels = (byte *)src; for( int x = 0; x < im->width; x++ ) { for( int y = 0; y < im->height; y++ ) { float weights[MAX_LANDSCAPE_LAYERS]; memset( weights, 0, sizeof( weights )); for( int pos_x = 0; pos_x < FILTER_SIZE; pos_x++ ) { for( int pos_y = 0; pos_y < FILTER_SIZE; pos_y++ ) { int img_x = (x - (FILTER_SIZE / 2) + pos_x + im->width) % im->width; int img_y = (y - (FILTER_SIZE / 2) + pos_y + im->height) % im->height; float rawHeight = (float)( src[img_y * im->width + img_x] & 0xFF ); float curLayer = ( rawHeight * ( im->numLayers - 1 )) / (float)im->maxHeight; if( curLayer != (int)curLayer ) { byte layer0 = (int)floor( curLayer ); byte layer1 = (int)ceil( curLayer ); float factor = curLayer - (int)curLayer; weights[layer0] += (1.0 - factor) * (1.0 / (FILTER_SIZE * FILTER_SIZE)); weights[layer1] += (factor ) * (1.0 / (FILTER_SIZE * FILTER_SIZE)); } else { weights[(int)curLayer] += (1.0 / (FILTER_SIZE * FILTER_SIZE)); } } } // encode layers into RGBA channels layers[lay_size * 0 + (y * im->width + x)*4+0] = weights[0] * 255; layers[lay_size * 0 + (y * im->width + x)*4+1] = weights[1] * 255; layers[lay_size * 0 + (y * im->width + x)*4+2] = weights[2] * 255; layers[lay_size * 0 + (y * im->width + x)*4+3] = weights[3] * 255; if( im->numLayers <= 4 ) continue; layers[lay_size * 1 + ((y * im->width + x)*4+0)] = weights[4] * 255; layers[lay_size * 1 + ((y * im->width + x)*4+1)] = weights[5] * 255; layers[lay_size * 1 + ((y * im->width + x)*4+2)] = weights[6] * 255; layers[lay_size * 1 + ((y * im->width + x)*4+3)] = weights[7] * 255; if( im->numLayers <= 8 ) continue; layers[lay_size * 2 + ((y * im->width + x)*4+0)] = weights[8] * 255; layers[lay_size * 2 + ((y * im->width + x)*4+1)] = weights[9] * 255; layers[lay_size * 2 + ((y * im->width + x)*4+2)] = weights[10] * 255; layers[lay_size * 2 + ((y * im->width + x)*4+3)] = weights[11] * 255; if( im->numLayers <= 12 ) continue; layers[lay_size * 3 + ((y * im->width + x)*4+0)] = weights[12] * 255; layers[lay_size * 3 + ((y * im->width + x)*4+1)] = weights[13] * 255; layers[lay_size * 3 + ((y * im->width + x)*4+2)] = weights[14] * 255; layers[lay_size * 3 + ((y * im->width + x)*4+3)] = weights[15] * 255; } } // release source texture FREE_TEXTURE( tex ); tex = CREATE_TEXTURE_ARRAY( im->name, im->width, im->height, depth, layers, TF_CLAMP|TF_HAS_ALPHA ); Mem_Free( layers ); im->gl_heightmap_id = tex; return true; } /* ======================== LoadTerrainLayers loading all the landscape layers into texture arrays ======================== */ bool LoadTerrainLayers( layerMap_t *lm, int numLayers ) { char *texnames[MAX_LANDSCAPE_LAYERS]; char *ptr, buffer[1024], tmpname[64]; size_t nameLen = 64; int i; memset( buffer, 0, sizeof( buffer )); // list must be null terminated // initialize names array for( i = 0, ptr = buffer; i < MAX_LANDSCAPE_LAYERS; i++, ptr += nameLen ) texnames[i] = ptr; // process diffuse textures for( i = 0; i < numLayers; i++ ) { Q_snprintf( texnames[i], nameLen, "textures/%s", lm->pathes[i] ); lm->material[i] = CL_FindMaterial( lm->names[i] ); lm->smoothness[i] = lm->material[i]->smoothness; } if(( lm->gl_diffuse_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, 0 )) == 0 ) return false; memset( buffer, 0, sizeof( buffer )); // list must be null terminated // process normalmaps for( i = 0; i < numLayers; i++ ) { const char *ext = COM_FileExtension( lm->pathes[i] ); COM_FileBase( lm->pathes[i], tmpname ); if( !Q_stricmp( ext, "" )) { Q_snprintf( texnames[i], nameLen, "textures/%s_norm", tmpname ); if( IMAGE_EXISTS( texnames[i] )) continue; ALERT( at_warning, "layer normalmap %s is missed\n", texnames[i] ); break; } else { Q_snprintf( texnames[i], nameLen, "textures/%s_norm.%s", tmpname, ext ); if( FILE_EXISTS( texnames[i] )) continue; ALERT( at_warning, "layer normalmap %s is missed\n", texnames[i] ); break; } } if( i == numLayers ) lm->gl_normalmap_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, TF_NORMALMAP ); if( !lm->gl_normalmap_id ) lm->gl_normalmap_id = tr.normalmapTexture; memset( buffer, 0, sizeof( buffer )); // list must be null terminated // process glossmaps for( i = 0; i < numLayers; i++ ) { const char *ext = COM_FileExtension( lm->pathes[i] ); COM_FileBase( lm->pathes[i], tmpname ); if( !Q_stricmp( ext, "" )) { Q_snprintf( texnames[i], nameLen, "textures/%s_gloss", tmpname ); if( IMAGE_EXISTS( texnames[i] )) continue; ALERT( at_warning, "layer glossmap %s is missed\n", texnames[i] ); break; } else { Q_snprintf( texnames[i], nameLen, "textures/%s_gloss.%s", tmpname, ext ); if( FILE_EXISTS( texnames[i] )) continue; ALERT( at_warning, "layer glossmap %s is missed\n", texnames[i] ); break; } } if( i == numLayers ) lm->gl_specular_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, 0 ); if( !lm->gl_specular_id ) lm->gl_specular_id = tr.blackTexture; return true; } /* ======================== R_FreeLandscapes free the landscape definitions ======================== */ void R_FreeLandscapes( void ) { for( int i = 0; i < world->num_terrains; i++ ) { terrain_t *terra = &world->terrains[i]; indexMap_t *im = &terra->indexmap; if( im->pixels ) Mem_Free( im->pixels ); layerMap_t *lm = &terra->layermap; if( lm->gl_diffuse_id ) FREE_TEXTURE( lm->gl_diffuse_id ); if( lm->gl_normalmap_id != tr.normalmapTexture ) FREE_TEXTURE( lm->gl_normalmap_id ); if( lm->gl_specular_id != tr.blackTexture ) FREE_TEXTURE( lm->gl_specular_id ); if( im->gl_diffuse_id != 0 ) FREE_TEXTURE( im->gl_diffuse_id ); FREE_TEXTURE( im->gl_heightmap_id ); } if( world->terrains ) Mem_Free( world->terrains ); world->num_terrains = 0; world->terrains = NULL; } /* ======================== R_LoadLandscapes load the landscape definitions ======================== */ void R_LoadLandscapes( const char *filename ) { char filepath[256]; Q_snprintf( filepath, sizeof( filepath ), "maps/%s_land.txt", filename ); char *afile = (char *)gEngfuncs.COM_LoadFile( filepath, 5, NULL ); if( !afile ) return; ALERT( at_aiconsole, "loading %s\n", filepath ); char *pfile = afile; char token[256]; int depth = 0; // count materials while( pfile != NULL ) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) break; if( Q_strlen( token ) > 1 ) continue; if( token[0] == '{' ) { depth++; } else if( token[0] == '}' ) { world->num_terrains++; depth--; } } if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", filepath ); if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", filepath ); world->terrains = (terrain_t *)Mem_Alloc( sizeof( terrain_t ) * world->num_terrains ); pfile = afile; // start real parsing int current = 0; while( pfile != NULL ) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) break; if( current >= world->num_terrains ) { ALERT ( at_error, "landscape parse is overrun %d > %d\n", current, world->num_terrains ); break; } terrain_t *terra = &world->terrains[current]; // read the landscape name Q_strncpy( terra->name, token, sizeof( terra->name )); terra->texScale = 1.0f; // read opening brace pfile = COM_ParseFile( pfile, token ); if( !pfile ) break; if( token[0] != '{' ) { ALERT( at_error, "found %s when expecting {\n", token ); break; } while( pfile != NULL ) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "EOF without closing brace\n" ); goto land_getout; } // description end goto next material if( token[0] == '}' ) { current++; break; } else if( !Q_stricmp( token, "indexMap" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'indexMap'\n" ); goto land_getout; } Q_strncpy( terra->indexmap.name, token, sizeof( terra->indexmap.name )); } else if( !Q_stricmp( token, "diffuseMap" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'diffuseMap'\n" ); goto land_getout; } Q_strncpy( terra->indexmap.diffuse, token, sizeof( terra->indexmap.diffuse )); } else if( !Q_strnicmp( token, "layer", 5 )) { int layerNum = Q_atoi( token + 5 ); pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'layer'\n" ); goto land_getout; } if( layerNum < 0 || layerNum > ( MAX_LANDSCAPE_LAYERS - 1 )) { ALERT( at_error, "%s is out of range. Ignored\n", token ); } else { Q_strncpy( terra->layermap.pathes[layerNum], token, sizeof( terra->layermap.pathes[0] )); COM_FileBase( terra->layermap.pathes[layerNum], terra->layermap.names[layerNum] ); } terra->numLayers = Q_max( terra->numLayers, layerNum + 1 ); } else if( !Q_stricmp( token, "tessSize" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'tessSize'\n" ); goto land_getout; } terra->tessSize = Q_atoi( token ); terra->tessSize = bound( 8, terra->tessSize, 256 ); } else if( !Q_stricmp( token, "texScale" )) { pfile = COM_ParseFile( pfile, token ); if( !pfile ) { ALERT( at_error, "hit EOF while parsing 'texScale'\n" ); goto land_getout; } terra->texScale = Q_atof( token ); terra->texScale = 1.0 / (bound( 0.000001f, terra->texScale, 16.0f )); } else ALERT( at_warning, "Unknown landscape token %s\n", token ); } if( LoadHeightMap( &terra->indexmap, terra->numLayers )) { if( LoadTerrainLayers( &terra->layermap, terra->numLayers )) terra->valid = true; // all done } } land_getout: gEngfuncs.COM_FreeFile( afile ); ALERT( at_console, "%d landscapes parsed\n", current ); } /* ======================== R_FindTerrain find the terrain description ======================== */ terrain_t *R_FindTerrain( const char *texname ) { for( int i = 0; i < world->num_terrains; i++ ) { if( !Q_stricmp( texname, world->terrains[i].name ) && world->terrains[i].valid ) return &world->terrains[i]; } return NULL; } /* ================== R_InitShadowTextures Re-create shadow textures if them size was changed ================== */ void R_InitShadowTextures( void ) { int i; for( i = 0; i < MAX_SHADOWS; i++ ) { if( !tr.shadowTextures[i] ) break; FREE_TEXTURE( tr.shadowTextures[i] ); } for( i = 0; i < MAX_SHADOWS; i++ ) { if( !tr.shadowCubemaps[i] ) break; FREE_TEXTURE( tr.shadowCubemaps[i] ); } memset( tr.shadowTextures, 0, sizeof( tr.shadowTextures )); memset( tr.shadowCubemaps, 0, sizeof( tr.shadowCubemaps )); int base_shadow_size = bound( 128, r_shadowmap_size->value, 2048 ); int shadow_size2D = bound( 128, NearestPOW( base_shadow_size, true ), 2048 ); tr.fbo_shadow2D.Init( FBO_DEPTH, shadow_size2D, shadow_size2D, FBO_NOTEXTURE ); int shadow_sizeCM = bound( 128, NearestPOW( base_shadow_size * 0.5f, true ), 1024 ); tr.fbo_shadowCM.Init( FBO_DEPTH, shadow_sizeCM, shadow_sizeCM, FBO_NOTEXTURE ); } /* ================== R_InitCommonTextures Init common textures for renderer that size based on screen size ================== */ void R_InitCommonTextures( void ) { int i; GL_BindFBO( 0 ); R_InitShadowTextures(); // release old subview textures for( i = 0; i < MAX_SUBVIEW_TEXTURES; i++ ) { if( !tr.subviewTextures[i].texturenum ) break; FREE_TEXTURE( tr.subviewTextures[i].texturenum ); } memset( tr.subviewTextures, 0, sizeof( tr.subviewTextures )); for( i = 0; i < tr.num_framebuffers; i++ ) { if( !tr.frame_buffers[i].init ) break; R_FreeFrameBuffer( i ); } for( i = 0; i < MAX_SHADOWMAPS; i++ ) { tr.sunShadowFBO[i].Init( FBO_DEPTH, sunSize[i], sunSize[i] ); } tr.fbo_light.Init( FBO_COLOR, glState.width, glState.height, FBO_LINEAR ); tr.fbo_filter.Init( FBO_COLOR, glState.width, glState.height, FBO_LINEAR ); tr.fbo_shadow.Init( FBO_COLOR, glState.defWidth, glState.defHeight, FBO_LINEAR ); // setup the skybox sides for( i = 0; i < 6; i++ ) tr.skyboxTextures[i] = RENDER_GET_PARM( PARM_TEX_SKYBOX, i ); tr.num_framebuffers = 0; tr.num_subview_used = 0; tr.glsl_valid_sequence++; // refresh shader cache InitPostTextures(); } /* ==================== ED_ParseEdict Parses an edict out of the given string, returning the new position ed should be a properly initialized empty edict. ==================== */ void ED_ParseEdict( char **pfile ) { int vertex_light_cache = -1; int surface_light_cache = -1; char modelname[64]; char token[2048]; // go through all the dictionary pairs while( 1 ) { char keyname[256]; // parse key if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) HOST_ERROR( "ED_ParseEdict: EOF without closing brace\n" ); if( token[0] == '}' ) break; // end of desc Q_strncpy( keyname, token, sizeof( keyname )); // parse value if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) HOST_ERROR( "ED_ParseEdict: EOF without closing brace\n" ); if( token[0] == '}' ) HOST_ERROR( "ED_ParseEdict: closing brace without data\n" ); // ignore attempts to set key "" if( !keyname[0] ) continue; // "wad" field is completely ignored in XashXT if( !Q_strcmp( keyname, "wad" )) continue; // ignore attempts to set value "" if( !token[0] ) continue; // also grab light settings from p2rad if( !Q_strcmp( keyname, "_lightgamma" )) tr.light_gamma = Q_atof( token ); if( !Q_strcmp( keyname, "_dscale" )) tr.direct_scale = Q_atof( token ); if( !Q_strcmp( keyname, "_maxlight" )) tr.light_threshold = Q_atof( token ); if( !Q_strcmp( keyname, "_ambient" )) Q_atov( tr.ambient_color, token, 3 ); if( !Q_strcmp( keyname, "_smooth" )) tr.smoothing_threshold = cos( DEG2RAD( Q_atof( token ))); // only two fields that we needed if( !Q_strcmp( keyname, "model" )) Q_strncpy( modelname, token, sizeof( modelname )); if( !Q_strcmp( keyname, "vlight_cache" )) vertex_light_cache = atoi( token ); if( !Q_strcmp( keyname, "flight_cache" )) surface_light_cache = atoi( token ); } // deal with light cache double start = Sys_DoubleTime(); if( vertex_light_cache > 0 && vertex_light_cache < MAX_LIGHTCACHE ) g_StudioRenderer.CreateStudioCacheVL( modelname, vertex_light_cache - 1 ); if( surface_light_cache > 0 && surface_light_cache < MAX_LIGHTCACHE ) g_StudioRenderer.CreateStudioCacheFL( modelname, surface_light_cache - 1 ); double end = Sys_DoubleTime(); r_buildstats.create_light_cache += (end - start); r_buildstats.total_buildtime += (end - start); } /* ================== GL_InitModelLightCache create VBO cache for vertex-lit and lightmapped studio models ================== */ void GL_InitModelLightCache( void ) { char *entities = worldmodel->entities; static char worldname[64]; char token[2048]; if( !Q_stricmp( world->name, worldname )) return; // just a restart Q_strncpy( worldname, world->name, sizeof( worldname )); RI->currententity = GET_ENTITY( 0 ); // to have something valid here // parse ents to find vertex light cache while(( entities = COM_ParseFile( entities, token )) != NULL ) { if( token[0] != '{' ) HOST_ERROR( "ED_LoadFromFile: found %s when expecting {\n", token ); ED_ParseEdict( &entities ); } // create lightmap pages right after studiomodel alloc their lightmaps (empty at this moment) GL_EndBuildingLightmaps( (worldmodel->lightdata != NULL), FBitSet( world->features, WORLD_HAS_DELUXEMAP ) ? true : false ); } /* ================== R_NewMap Called always when map is changed or restarted ================== */ void R_NewMap( void ) { // setup special flags for( int i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[i]; mextrasurf_t *info = surf->info; // clear the in-game set flags ClearBits( surf->flags, SURF_NODRAW|SURF_NODLIGHT|SURF_NOSUNLIGHT|SURF_REFLECT_PUDDLE ); // clear occlusion queries results ClearBits( surf->flags, SURF_QUEUED|SURF_OCCLUDED ); memset( info->subtexture, 0, sizeof( info->subtexture )); info->cubemap[0] = &world->defaultCubemap; info->cubemap[1] = &world->defaultCubemap; info->checkcount = -1; } // we need to reapply cubemaps to surfaces after restart level if( world->num_cubemaps > 0 ) world->loading_cubemaps = true; // reset sky settings tr.sky_origin = tr.sky_world_origin = g_vecZero; tr.sky_speed = 0; // reset light settings tr.light_gamma = 0.5; tr.direct_scale = 2.0; tr.light_threshold = 196; tr.ambient_color = g_vecZero; tr.smoothing_threshold = 0.642788f; // 50 degrees } /* ================== R_InitDynLightShader changed dynamic lighting shaders ================== */ void R_InitDynLightShader( int type ) { char options[MAX_OPTIONS_LENGTH]; switch( type ) { case LIGHT_SPOT: GL_SetShaderDirective( options, "LIGHT_SPOT" ); break; case LIGHT_OMNI: GL_SetShaderDirective( options, "LIGHT_OMNI" ); break; default: return; break; } if( CVAR_TO_BOOL( cv_brdf )) GL_AddShaderDirective( options, "APPLY_PBS" ); if( CVAR_TO_BOOL( cv_specular )) GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if( CVAR_TO_BOOL( r_shadows )) { // shadow cubemaps only support if GL_EXT_gpu_shader4 is support if( type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 )) { GL_AddShaderDirective( options, "APPLY_SHADOW" ); if( r_shadows->value == 2.0f ) GL_AddShaderDirective( options, "SHADOW_PCF2X2" ); else if( r_shadows->value >= 3.0f ) GL_AddShaderDirective( options, "SHADOW_PCF3X3" ); } } tr.defDynLightShader[type] = GL_FindShader( "deferred/dynlight", "deferred/generic", "deferred/dynlight", options ); } /* ================== R_InitDynLightShaders changed dynamic lighting shaders ================== */ void R_InitDynLightShaders( void ) { char options[MAX_OPTIONS_LENGTH]; GL_CleanupDrawState(); R_InitDynLightShader( LIGHT_SPOT ); R_InitDynLightShader( LIGHT_OMNI ); options[0] = '\0'; if( CVAR_TO_BOOL( cv_deferred_tracebmodels )) GL_AddShaderDirective( options, "BSPTRACE_BMODELS" ); // init deferred shaders tr.defSceneShader[0] = GL_FindShader( "deferred/scene_sep", "deferred/generic", "deferred/scene_sep", options ); tr.defSceneShader[1] = GL_FindShader( "deferred/scene_all", "deferred/generic", "deferred/scene_all", options ); tr.defLightShader = GL_FindShader( "deferred/light", "deferred/generic", "deferred/light", options ); tr.bilateralShader = GL_FindUberShader( "deferred/bilateral" ); } /* ================== R_VidInit Called always when "vid_mode" or "fullscreen" cvars is changed ================== */ void R_VidInit( void ) { // get the actual screen size glState.width = RENDER_GET_PARM( PARM_SCREEN_WIDTH, 0 ); glState.height = RENDER_GET_PARM( PARM_SCREEN_HEIGHT, 0 ); // TESTTEST glState.defWidth = 256; glState.defHeight = 192; R_InitCommonTextures(); GL_VidInitDrawBuffers(); } /* ================== GL_MapChanged Called always when map is changed or restarted ================== */ void GL_MapChanged( void ) { if( !g_fRenderInitialized ) return; R_InitRefState(); tr.glsl_valid_sequence = 1; tr.grassunloadframe = 0; tr.fClearScreen = false; tr.realframecount = 1; tr.num_cin_used = 0; // catch changes of screen R_VidInit (); g_pParticleSystems.ClearSystems(); // buz g_pParticles.Clear(); CL_ClearDlights(); ClearDecals(); ResetRain(); // don't flush shaders for each map - save time to recompile if( num_glsl_programs >= ( MAX_GLSL_PROGRAMS * 0.9f )) GL_FreeUberShaders(); R_NewMap (); // tell the renderer what a new map started g_StudioRenderer.VidInit(); GL_InitModelLightCache(); }