/* gl_world.cpp - world and bmodel rendering Copyright (C) 2016 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 "pm_defs.h" #include "event_api.h" #include #include "gl_studio.h" #include "gl_shader.h" #include "gl_world.h" #include "gl_grass.h" #include "gl_occlusion.h" #include "vertex_fmt.h" static gl_world_t worlddata; gl_world_t *world = &worlddata; static Vector env_dir[] = { Vector( 1.0f, 0.0f, 0.0f ), Vector( -1.0f, 0.0f, 0.0f ), Vector( 0.0f, 1.0f, 0.0f ), Vector( 0.0f, -1.0f, 0.0f ), Vector( 0.0f, 0.0f, 1.0f ), Vector( 0.0f, 0.0f, -1.0f ) }; /* ================== Mod_SampleSizeForFace return the current lightmap resolution per face ================== */ int Mod_SampleSizeForFace( msurface_t *surf ) { if( !surf || !surf->texinfo ) return LM_SAMPLE_SIZE; // world luxels has more priority if( FBitSet( surf->texinfo->flags, TEX_WORLD_LUXELS )) return 1; if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP )) return LM_SAMPLE_EXTRASIZE; if( surf->texinfo->faceinfo ) return surf->texinfo->faceinfo->texture_step; return LM_SAMPLE_SIZE; } gl_texbuffer_t *Surf_GetSubview( mextrasurf_t *es ) { ASSERT( glState.stack_position >= 0 && glState.stack_position < MAX_REF_STACK ); int handle = es->subtexture[glState.stack_position]; if( handle > 0 && handle <= MAX_SUBVIEW_TEXTURES ) return &tr.subviewTextures[handle-1]; return NULL; } bool Surf_CheckSubview( mextrasurf_t *es, bool puddle ) { ASSERT( glState.stack_position >= 0 && glState.stack_position < MAX_REF_STACK ); int handle = es->subtexture[glState.stack_position]; if( !handle ) return false; ASSERT( handle > 0 && handle <= MAX_SUBVIEW_TEXTURES ); // we can't directly compare here if(( tr.realframecount - tr.subviewTextures[handle-1].texframe ) <= 1 ) { if( puddle && FBitSet( es->surf->flags, SURF_REFLECT_PUDDLE )) return true; if( !puddle && FBitSet( es->surf->flags, SURF_REFLECT )) return true; } return false; } /* ============================================================= CUBEMAP HANDLING & PROCESSING ============================================================= */ /* ================== Mod_FreeCubemap unload a given cubemap ================== */ static void Mod_FreeCubemap( mcubemap_t *m ) { if( m->valid && m->texture && m->texture != tr.whiteCubeTexture ) FREE_TEXTURE( m->texture ); memset( m, 0, sizeof( *m )); } /* ================== Mod_FreeCubemaps purge all the cubemaps from current level ================== */ static void Mod_FreeCubemaps( void ) { for( int i = 0; i < world->num_cubemaps; i++ ) Mod_FreeCubemap( &world->cubemaps[i] ); Mod_FreeCubemap( &world->defaultCubemap ); world->rebuilding_cubemaps = CMREBUILD_INACTIVE; world->build_default_cubemap = false; world->loading_cubemaps = false; world->cubemap_build_number = 0; world->num_cubemaps = 0; } /* ================== Mod_CheckCubemap check cubemap sides for valid ================== */ static bool Mod_CheckCubemap( const char *name ) { char *suf[6] = { "px", "nx", "py", "ny", "pz", "nz" }; int valid_sides = 0; char sidename[64]; int iCompare; // FIXME: potentially unsafe checking: looking for DDS_CUBEMAP bit? if( FILE_EXISTS( va( "maps/env/%s/%s.dds", world->name, name ))) return true; for( int i = 0; i < 6; i++ ) { Q_snprintf( sidename, sizeof( sidename ), "maps/env/%s/%s%s.tga", world->name, name, suf[i] ); if( COMPARE_FILE_TIME( worldmodel->name, sidename, &iCompare ) && iCompare <= 0 ) valid_sides++; } return (valid_sides == 6) ? true : false; } /* ================== Mod_DeleteCubemap remove cubemap images from HDD ================== */ static void Mod_DeleteCubemap( const char *name ) { char *suf[6] = { "px", "nx", "py", "ny", "pz", "nz" }; char sidename[64]; for( int i = 0; i < 6; i++ ) { Q_snprintf( sidename, sizeof( sidename ), "maps/env/%s/%s%s.tga", world->name, name, suf[i] ); if( FILE_EXISTS( sidename )) Sys_RemoveFile( sidename ); } } /* ================== Mod_LoadCubemap load cubemap into video memory ================== */ static bool Mod_LoadCubemap( mcubemap_t *m ) { int flags = 0; if( m->valid && !m->texture ) { if( GL_Support( R_SEAMLESS_CUBEMAP )) SetBits( flags, TF_BORDER ); // seamless cubemaps have support for border else SetBits( flags, TF_CLAMP ); // default method m->texture = LOAD_TEXTURE( m->name, NULL, 0, flags ); // make sure what is really cubemap if( RENDER_GET_PARM( PARM_TEX_TARGET, m->texture ) == GL_TEXTURE_CUBE_MAP_ARB ) m->valid = true; else m->valid = false; // NOTE: old DDS cubemaps has no mip-levels m->numMips = RENDER_GET_PARM( PARM_TEX_MIPCOUNT, m->texture ); } return m->valid; } /* ================== GL_LoadAndRebuildCubemaps rebuild cubemaps that older than bspfile loading actual cubemaps into videomemory ================== */ void GL_LoadAndRebuildCubemaps( int refParams ) { if( !world->loading_cubemaps && world->rebuilding_cubemaps == CMREBUILD_INACTIVE ) return; // job is done // we are in cubemap-rendering mode if( FBitSet( refParams, RP_ENVVIEW|RP_SKYVIEW )) return; if( world->rebuilding_cubemaps != CMREBUILD_INACTIVE ) { if( world->build_default_cubemap ) { mcubemap_t *cm = &world->defaultCubemap; if( world->rebuilding_cubemaps == CMREBUILD_WAITING ) { cm->valid = Mod_CheckCubemap( "default" ); // need for rebuild? world->rebuilding_cubemaps = CMREBUILD_CHECKING; if( !cm->valid ) ALERT( at_error, "GL_RebuildCubemaps: can't build default cubemap\n" ); world->build_default_cubemap = false; // done } if( world->build_default_cubemap ) { Mod_DeleteCubemap( "default" ); // NOTE: engine function EnvShot not makes shots immediately // but create a queue. So we will wait for a next frame ENV_SHOT( cm->origin, va( "%s.tga", cm->name ), false, cm->size ); world->rebuilding_cubemaps = CMREBUILD_WAITING; return; } } for( ; world->cubemap_build_number < world->num_cubemaps; world->cubemap_build_number++ ) { mcubemap_t *cm = &world->cubemaps[world->cubemap_build_number]; if( world->rebuilding_cubemaps == CMREBUILD_WAITING ) { cm->valid = Mod_CheckCubemap( va( "cube#%i", world->cubemap_build_number )); // need for rebuild? world->rebuilding_cubemaps = CMREBUILD_CHECKING; if( !cm->valid ) { ALERT( at_error, "GL_RebuildCubemaps: can't build cube#%i\n", world->cubemap_build_number ); continue; // to avoid infinity cycle } } if( cm->valid ) continue; // it's valid cubemap Mod_DeleteCubemap( va( "cube#%i", world->cubemap_build_number )); // NOTE: engine function EnvShot not makes shots immediately // but create a queue. So we will wait for a next frame ENV_SHOT( cm->origin, va( "%s.tga", cm->name ), false, cm->size ); world->rebuilding_cubemaps = CMREBUILD_WAITING; return; } // we reached the end of list // next frame will be restored gamma SetBits( cv_brightness->flags, FCVAR_CHANGED ); SetBits( cv_gamma->flags, FCVAR_CHANGED ); world->rebuilding_cubemaps = CMREBUILD_INACTIVE; world->cubemap_build_number = 0; tr.params_changed = true; tr.glsl_valid_sequence++; tr.fClearScreen = false; } // now all the cubemaps are recreated, so we can starts to upload them if( world->loading_cubemaps ) { Mod_LoadCubemap( &world->defaultCubemap ); for( int i = 0; i < world->num_cubemaps; i++ ) { mcubemap_t *cm = &world->cubemaps[i]; Vector vecStart, vecEnd; pmtrace_t pmtrace; Mod_LoadCubemap( cm ); // compute viewbox size for( int j = 0; j < 6; j++ ) { vecStart = cm->origin; vecEnd = vecStart + env_dir[j] * 4096.0f; gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); gEngfuncs.pEventAPI->EV_PlayerTrace( vecStart, vecStart + vecEnd, PM_WORLD_ONLY, -1, &pmtrace ); AddPointToBounds( pmtrace.endpos, cm->mins, cm->maxs ); } } // bind cubemaps onto world surfaces // so we don't need to search them again for( i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[i]; mextrasurf_t *es = surf->info; CL_FindTwoNearestCubeMapForSurface( es->origin, surf, &es->cubemap[0], &es->cubemap[1] ); // compute lerp factor float dist0 = ( es->cubemap[0]->origin - es->origin ).Length(); float dist1 = ( es->cubemap[1]->origin - es->origin ).Length(); es->lerpFactor = dist0 / (dist0 + dist1); } world->loading_cubemaps = false; } } /* ============================================================= WORLD LOADING ============================================================= */ /* ================= Mod_LoadCubemaps ================= */ static void Mod_LoadCubemaps( const byte *base, const dlump_t *l ) { dcubemap_t *in; mcubemap_t *out; int i, count; in = (dcubemap_t *)(base + l->fileofs); if( l->filelen % sizeof( *in )) HOST_ERROR( "Mod_LoadCubemaps: funny lump size\n" ); count = l->filelen / sizeof( *in ); if( count >= MAX_MAP_CUBEMAPS ) { ALERT( at_error, "Mod_LoadCubemaps: map contain too many cubemaps. Will handle only first %i items\n", MAX_MAP_CUBEMAPS ); count = MAX_MAP_CUBEMAPS; } out = world->cubemaps; world->num_cubemaps = count; world->loading_cubemaps = (count > 0) ? true : false; // makes an default cubemap from skybox if( FBitSet( world->features, WORLD_HAS_SKYBOX ) && (count > 0)) { mcubemap_t *cm = &world->defaultCubemap; Q_snprintf( cm->name, sizeof( cm->name ), "maps/env/%s/default", world->name ); cm->origin = (worldmodel->mins + worldmodel->maxs) * 0.5f; cm->size = 256; // default cubemap larger than others cm->valid = Mod_CheckCubemap( "default" ); // need for rebuild? world->loading_cubemaps = true; if( !cm->valid ) { world->rebuilding_cubemaps = CMREBUILD_CHECKING; world->build_default_cubemap = true; } } else { // using stub as default cubemap mcubemap_t *cm = &world->defaultCubemap; Q_snprintf( cm->name, sizeof( cm->name ), "*whiteCube" ); cm->origin = (worldmodel->mins + worldmodel->maxs) * 0.5f; cm->texture = tr.whiteCubeTexture; cm->valid = true; cm->size = 4; } for( i = 0; i < count; i++, in++, out++ ) { // build a cubemap name like enum Q_snprintf( out->name, sizeof( out->name ), "maps/env/%s/cube#%i", world->name, i ); out->valid = Mod_CheckCubemap( va( "cube#%i", i )); // need for rebuild? if( !out->valid ) world->rebuilding_cubemaps = CMREBUILD_CHECKING; VectorCopy( in->origin, out->origin ); ClearBounds( out->mins, out->maxs ); out->size = in->size; if( out->size <= 0 ) out->size = DEFAULT_CUBEMAP_SIZE; out->size = NearestPOW( out->size, false ); out->size = bound( 1, out->size, 512 ); } // user request for disable autorebuild if( gEngfuncs.CheckParm( "-noautorebuildcubemaps", NULL )) { world->rebuilding_cubemaps = CMREBUILD_INACTIVE; world->build_default_cubemap = false; } } /* ======================== Mod_CopyMaterialDesc copy params from description to real material struct ======================== */ static void Mod_CopyMaterialDesc( material_t *mat, matdesc_t *desc ) { mat->smoothness = desc->smoothness; mat->detailScale[0] = desc->detailScale[0]; mat->detailScale[1] = desc->detailScale[1]; mat->reflectScale = desc->reflectScale; mat->refractScale = desc->refractScale; mat->aberrationScale = desc->aberrationScale; mat->reliefScale = desc->reliefScale; mat->effects = desc->effects; } /* ======================== Mod_LoadWorldMaterials build a material for each world texture ======================== */ static void Mod_LoadWorldMaterials( void ) { char diffuse[128], bumpmap[128]; char glossmap[128], glowmap[128]; char heightmap[128]; world->materials = (material_t *)Mem_Alloc( sizeof( material_t ) * worldmodel->numtextures ); for( int i = 0; i < worldmodel->numtextures; i++ ) { texture_t *tx = worldmodel->textures[i]; material_t *mat = &world->materials[i]; // bad texture? if( !tx || !tx->name[0] ) continue; // make cross-links for consistency tx->material = mat; mat->pSource = tx; // build material names Q_snprintf( diffuse, sizeof( diffuse ), "textures/%s", tx->name ); Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s_norm", tx->name ); Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s_gloss", tx->name ); Q_snprintf( glowmap, sizeof( glowmap ), "textures/%s_luma", tx->name ); Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s_hmap", tx->name ); if( IMAGE_EXISTS( diffuse )) { mat->gl_diffuse_id = LOAD_TEXTURE( diffuse, NULL, 0, 0 ); if( tx->gl_texturenum != tr.defaultTexture ) FREE_TEXTURE( tx->gl_texturenum ); // release wad-texture // so engine can be draw HQ image for gl_renderer 0 // FIXME: what about detail texture scales ? tx->gl_texturenum = mat->gl_diffuse_id; if( RENDER_GET_PARM( PARM_TEX_FLAGS, tx->gl_texturenum ) & TF_HAS_ALPHA ) mat->flags |= BRUSH_HAS_ALPHA; } else { // use texture from wad mat->gl_diffuse_id = tx->gl_texturenum; } if( IMAGE_EXISTS( bumpmap )) { mat->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); } else { // try alternate suffix Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s_local", tx->name ); if( IMAGE_EXISTS( bumpmap )) mat->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); else mat->gl_normalmap_id = tr.normalmapTexture; // blank bumpy } if( IMAGE_EXISTS( glossmap )) { mat->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); } else { // try alternate suffix Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s_spec", tx->name ); if( IMAGE_EXISTS( glossmap )) mat->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); else mat->gl_specular_id = tr.blackTexture; } if( IMAGE_EXISTS( heightmap )) { mat->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); } else { // try alternate suffix Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s_bump", tx->name ); if( IMAGE_EXISTS( heightmap )) mat->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); else mat->gl_heightmap_id = tr.blackTexture; } if( IMAGE_EXISTS( glowmap )) mat->gl_glowmap_id = LOAD_TEXTURE( glowmap, NULL, 0, 0 ); else mat->gl_glowmap_id = tr.blackTexture; // setup material flags if( mat->gl_normalmap_id > 0 && mat->gl_normalmap_id != tr.normalmapTexture ) SetBits( mat->flags, BRUSH_HAS_BUMP ); if( mat->gl_specular_id > 0 && mat->gl_specular_id != tr.blackTexture ) SetBits( mat->flags, BRUSH_HAS_SPECULAR ); if( mat->gl_glowmap_id > 0 && mat->gl_glowmap_id != tr.blackTexture ) SetBits( mat->flags, BRUSH_HAS_LUMA ); if( mat->gl_heightmap_id > 0 && mat->gl_heightmap_id != tr.blackTexture ) SetBits( mat->flags, BRUSH_HAS_HEIGHTMAP ); if( tx->name[0] == '{' ) SetBits( mat->flags, BRUSH_TRANSPARENT ); if( !Q_strnicmp( tx->name, "scroll", 6 )) SetBits( mat->flags, BRUSH_CONVEYOR ); if( !Q_strnicmp( tx->name, "{scroll", 7 )) SetBits( mat->flags, BRUSH_CONVEYOR|BRUSH_TRANSPARENT ); if( !Q_strncmp( tx->name, "mirror", 6 ) || !Q_strncmp( tx->name, "reflect", 7 )) SetBits( mat->flags, BRUSH_REFLECT ); if( !Q_strncmp( tx->name, "movie", 5 )) SetBits( mat->flags, BRUSH_FULLBRIGHT ); if( tx->name[0] == '!' || !Q_strncmp( tx->name, "water", 5 )) { SetBits( mat->flags, BRUSH_REFLECT|BRUSH_LIQUID ); if( tr.waterTextures[0] ) SetBits( mat->flags, BRUSH_HAS_BUMP ); } if( !Q_strncmp( tx->name, "sky", 3 )) SetBits( world->features, WORLD_HAS_SKYBOX ); // setup material constants matdesc_t *desc = CL_FindMaterial( tx->name ); Mod_CopyMaterialDesc( mat, desc ); mat->gl_detailmap_id = desc->dt_texturenum; if( mat->smoothness <= 0.0f ) // don't waste time ClearBits( mat->flags, BRUSH_HAS_SPECULAR ); if( mat->gl_detailmap_id > 0 && mat->gl_detailmap_id != tr.grayTexture ) SetBits( mat->flags, BRUSH_HAS_DETAIL ); tx->effects = mat->effects; } } static void Mod_SetupLeafExtradata( const dlump_t *l, const dlump_t *vis, const byte *buf ) { dleaf_t *in = (dleaf_t *)(buf + l->fileofs); mextraleaf_t *out; world->numleafs = worldmodel->numleafs + 1; // world leafs + outside common leaf world->leafs = out = (mextraleaf_t *)Mem_Alloc( sizeof( mextraleaf_t ) * world->numleafs ); world->totalleafs = l->filelen / sizeof( *in ); // keep the total leaf counting for( int i = 0; i < world->numleafs; i++, in++, out++ ) { VectorCopy( in->mins, out->mins ); VectorCopy( in->maxs, out->maxs ); } } /* ================= Mod_SetupLeafLights ================= */ static void Mod_SetupLeafLights( void ) { mextraleaf_t *out; int i, j, k; mleaf_t *leaf; mworldlight_t *wl; mlightprobe_t *lp; out = (mextraleaf_t *)world->leafs; wl = world->worldlights; lp = world->leaflights; j = k = 0; for( i = 0; i < world->numleafs; i++, out++ ) { leaf = INFO_LEAF( out, worldmodel ); // NOTE: lights already sorted by leafs if( world->numworldlights > 0 && wl->leaf == leaf ) { out->direct_lights = wl; // pointer to first light in the array that belong to this leaf for( ;( j < world->numworldlights ) && (wl->leaf == leaf); j++, wl++ ) out->num_directlights++; } if( world->numleaflights > 0 && lp->leaf == leaf ) { out->ambient_light = lp; // pointer to first light in the array that belong to this leaf for( ;( k < world->numleaflights ) && (lp->leaf == leaf); k++, lp++ ) out->num_lightprobes++; } } } /* ================= Mod_LoadVertNormals ================= */ static void Mod_LoadVertNormals( const byte *base, const dlump_t *l ) { dnormallump_t *nhdr; byte *data; if( !l->filelen ) return; data = (byte *)(base + l->fileofs); nhdr = (dnormallump_t *)data; // indexed normals if( nhdr->ident == NORMIDENT ) { int table_size = worldmodel->numsurfedges * sizeof( dvertnorm_t ); int data_size = nhdr->numnormals * sizeof( dnormal_t ); int total_size = sizeof( dnormallump_t ) + table_size + data_size; if( l->filelen != total_size ) HOST_ERROR( "Mod_LoadVertNormals: funny lump size\n" ); data += sizeof( dnormallump_t ); // alloc remap table world->surfnormals = (dvertnorm_t *)Mem_Alloc( table_size ); memcpy( world->surfnormals, data, table_size ); data += table_size; // copy normals data world->normals = (dnormal_t *)Mem_Alloc( data_size ); memcpy( world->normals, data, data_size ); world->numnormals = nhdr->numnormals; } else { // old method... int count; dnormal_t *in; in = (dnormal_t *)(base + l->fileofs); if( l->filelen % sizeof( *in )) HOST_ERROR( "Mod_LoadVertNormals: funny lump size\n" ); count = l->filelen / sizeof( *in ); // all the other counts are invalid if( count == worldmodel->numvertexes ) { world->normals = (dnormal_t *)Mem_Alloc( count * sizeof( dnormal_t )); memcpy( world->normals, in, count * sizeof( dnormal_t )); } } } /* ============= BuildVisForDLight create visibility cache for dlight ============= */ static int Mod_BuildVisForDLight( mworldlight_t *wl ) { int leafnum; if( wl->emittype == emit_skylight ) { // all leafs that contain skyface should be added to sun visibility for( leafnum = 0; leafnum < worldmodel->numleafs; leafnum++ ) { msurface_t **mark = worldmodel->leafs[leafnum + 1].firstmarksurface; for( int markface = 0; markface < worldmodel->leafs[leafnum + 1].nummarksurfaces; markface++, mark++ ) { msurface_t *surf = *mark; if( FBitSet( surf->flags, SURF_DRAWSKY )) { MergeDLightVis( wl, leafnum + 1 ); break; // no reason to check all faces, go to next leaf } } } // technically light_environment is outside of world return -1; } else { leafnum = Mod_PointInLeaf( wl->origin, worldmodel->nodes ) - worldmodel->leafs; SetDLightVis( wl, leafnum ); return leafnum; } } /* ================= Mod_LoadWorldLights ================= */ static void Mod_LoadWorldLights( const byte *base, const dlump_t *l ) { dworldlight_t *in; mworldlight_t *out, *out2; int i, count, dup = 0; int total = 0; if( !l->filelen ) return; in = (dworldlight_t *)(base + l->fileofs); if( l->filelen % sizeof( *in )) { ALERT( at_error, "Mod_LoadWorldLights: funny lump size in %s\n", world->name ); return; } count = l->filelen / sizeof( *in ); world->worldlights = out = (mworldlight_t *)Mem_Alloc( count * sizeof( *out )); world->numworldlights = count; for( i = 0; i < count; i++, in++, out++ ) { out->emittype = (emittype_t)in->emittype; out->style = in->style; VectorCopy( in->origin, out->origin ); VectorCopy( in->intensity, out->intensity ); VectorCopy( in->normal, out->normal ); out->stopdot = in->stopdot; out->stopdot2 = in->stopdot2; out->fade = in->fade; out->leaf = &worldmodel->leafs[in->leafnum]; out->radius = in->radius; out->falloff = in->falloff; if( out->emittype == emit_surface ) out->surface = worldmodel->surfaces + in->facenum; out->modelnum = in->modelnumber; out->lightnum = i; // !!! out->shadow_x = 0xFFFF; out->shadow_y = 0xFFFF; if( out->emittype == emit_skylight ) out->stopdot2 = -1.0f; } out = world->worldlights; for( i = dup = 0; i < count; i++, out++ ) { if( out->intensity == g_vecZero ) out->emittype = emit_ignored; if( out->emittype != emit_surface ) continue; VectorMA( out->origin, 1.0f, out->normal, out->origin ); SetBits( out->surface->flags, SURF_FULLBRIGHT ); // emit faces is always fullbright SetBits( out->surface->texinfo->texture->material->flags, BRUSH_FULLBRIGHT ); out2 = world->worldlights; for( int j = 0; j < count; j++, out2++ ) { if( out == out2 ) continue; // himself if( out2->emittype != emit_surface ) continue; if( out->surface == out2->surface ) { out2->emittype = emit_ignored; dup++; } } } out = world->worldlights; for( i = 0; i < count; i++, out++ ) { if( out->emittype == emit_ignored ) continue; Mod_BuildVisForDLight( out ); total++; } // alloc shadow occlusion buffers world->shadowzbuffers = (lightzbuffer_t *)Mem_Alloc( count * sizeof( lightzbuffer_t )); ALERT( at_console, "%d world lights\n", total ); } static void Mod_LoadVertexLighting( const byte *base, const dlump_t *l ) { dvlightlump_t *vl; if( !l->filelen ) return; vl = (dvlightlump_t *)(base + l->fileofs); if( vl->ident != VLIGHTIDENT ) return; // probably it's LUMP_LEAF_LIGHTING if( vl->version != VLIGHT_VERSION ) return; // old version? if( vl->nummodels <= 0 ) return; world->vertex_lighting = (dvlightlump_t *)Mem_Alloc( l->filelen ); memcpy( world->vertex_lighting, vl, l->filelen ); } static void Mod_LoadSurfaceLighting( const byte *base, const dlump_t *l ) { dvlightlump_t *vl; if( !l->filelen ) return; vl = (dvlightlump_t *)(base + l->fileofs); if( vl->ident != FLIGHTIDENT ) return; // probably it's LUMP_LEAF_LIGHTING if( vl->version != FLIGHT_VERSION ) return; // old version? if( vl->nummodels <= 0 ) return; world->surface_lighting = (dvlightlump_t *)Mem_Alloc( l->filelen ); memcpy( world->surface_lighting, vl, l->filelen ); } /* ================= Mod_LoadVisLightData worldlights visibility per face ================= */ static void Mod_LoadVisLightData( const byte *base, const dlump_t *l ) { if( !l->filelen ) return; world->vislightdata = (byte *)Mem_Alloc( l->filelen ); memcpy( world->vislightdata, (byte *)(base + l->fileofs), l->filelen ); } /* ================= Mod_LoadLeafAmbientLighting and link into leafs ================= */ static void Mod_LoadLeafAmbientLighting( const byte *base, const dlump_t *l ) { dleafsample_t *in; dvlightlump_t *vl; mlightprobe_t *out; int i, count; short curleaf = -1; mextraleaf_t *leaf = NULL; if( !l->filelen ) return; vl = (dvlightlump_t *)(base + l->fileofs); if( vl->ident == VLIGHTIDENT ) { // probably it's LUMP_VERTEX_LIGHTING Mod_LoadVertexLighting( base, l ); return; } in = (dleafsample_t *)(base + l->fileofs); if( l->filelen % sizeof( *in )) { ALERT( at_warning, "Mod_LoadLeafAmbientLighting: funny lump size in %s\n", world->name ); return; } count = l->filelen / sizeof( *in ); world->leaflights = out = (mlightprobe_t *)Mem_Alloc( count * sizeof( *out )); world->numleaflights = count; for( i = 0; i < count; i++, in++, out++ ) { memcpy( &out->cube, &in->ambient, sizeof( dlightcube_t )); out->leaf = &worldmodel->leafs[in->leafnum]; VectorCopy( in->origin, out->origin ); } } /* ================= Mod_SurfaceCompareBuild sort faces before lightmap building ================= */ static int Mod_SurfaceCompareBuild( const unsigned short **a, const unsigned short **b ) { msurface_t *surf1, *surf2; surf1 = &worldmodel->surfaces[(unsigned short)*a]; surf2 = &worldmodel->surfaces[(unsigned short)*b]; if( FBitSet( surf1->flags, SURF_DRAWSKY ) && !FBitSet( surf2->flags, SURF_DRAWSKY )) return -1; if( !FBitSet( surf1->flags, SURF_DRAWSKY ) && FBitSet( surf2->flags, SURF_DRAWSKY )) return 1; if( FBitSet( surf1->flags, SURF_DRAWTURB ) && !FBitSet( surf2->flags, SURF_DRAWTURB )) return -1; if( !FBitSet( surf1->flags, SURF_DRAWTURB ) && FBitSet( surf2->flags, SURF_DRAWTURB )) return 1; // there faces owned with model in local space, so it *always* have non-identity transform matrix. // move them to end of the list if( FBitSet( surf1->flags, SURF_LOCAL_SPACE ) && !FBitSet( surf2->flags, SURF_LOCAL_SPACE )) return 1; if( !FBitSet( surf1->flags, SURF_LOCAL_SPACE ) && FBitSet( surf2->flags, SURF_LOCAL_SPACE )) return -1; return 0; } /* ================= Mod_SurfaceCompareInGame sort faces to reduce shader switches ================= */ static int Mod_SurfaceCompareInGame( const unsigned short **a, const unsigned short **b ) { msurface_t *surf1, *surf2; mextrasurf_t *esrf1, *esrf2; surf1 = &worldmodel->surfaces[(unsigned short)*a]; surf2 = &worldmodel->surfaces[(unsigned short)*b]; esrf1 = surf1->info; esrf2 = surf2->info; if( esrf1->forwardScene[0].GetHandle() > esrf2->forwardScene[0].GetHandle() ) return 1; if( esrf1->forwardScene[0].GetHandle() < esrf2->forwardScene[0].GetHandle() ) return -1; if( surf1->texinfo->texture->gl_texturenum > surf2->texinfo->texture->gl_texturenum ) return 1; if( surf1->texinfo->texture->gl_texturenum < surf2->texinfo->texture->gl_texturenum ) return -1; if( esrf1->lightmaptexturenum > esrf2->lightmaptexturenum ) return 1; if( esrf1->lightmaptexturenum < esrf2->lightmaptexturenum ) return -1; if( !esrf1->parent || !esrf2->parent ) return 0; if( esrf1->parent->hCachedMatrix > esrf2->parent->hCachedMatrix ) return 1; if( esrf1->parent->hCachedMatrix < esrf2->parent->hCachedMatrix ) return -1; return 0; } /* ================= Mod_FinalizeWorld build representation table of surfaces sorted by texture then alloc lightmaps ================= */ static void Mod_FinalizeWorld( void ) { int i; world->sortedfaces = (unsigned short *)Mem_Alloc( worldmodel->numsurfaces * sizeof( unsigned short )); world->numsortedfaces = worldmodel->numsurfaces; // initial filling for( i = 0; i < worldmodel->numsurfaces; i++ ) world->sortedfaces[i] = i; qsort( world->sortedfaces, worldmodel->numsurfaces, sizeof( unsigned short ), (cmpfunc)Mod_SurfaceCompareBuild ); // alloc surface lightmaps and compute lm coords (for sorted list) for( i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[world->sortedfaces[i]]; // allocate the lightmap coords, create lightmap textures (empty at this moment) GL_AllocLightmapForFace( surf ); } } /* ================= Mod_ShaderSceneForward compute albedo with static lighting ================= */ static word Mod_ShaderSceneForward( msurface_t *s ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; mextrasurf_t *es = s->info; cl_entity_t *e = es->parent ? es->parent : GET_ENTITY( 0 ); // don't cache shader for skyfaces! if( FBitSet( s->flags, SURF_DRAWSKY )) return 0; // mirror is actual only if we has actual screen texture! bool mirror = Surf_CheckSubview( s->info ); if( es->forwardScene[mirror].IsValid() && es->lastRenderMode == e->curstate.rendermode ) return es->forwardScene[mirror].GetHandle(); // valid Q_strncpy( glname, "forward/scene_bmodel", sizeof( glname )); memset( options, 0, sizeof( options )); mfaceinfo_t *landscape = landscape = s->texinfo->faceinfo; material_t *mat = R_TextureAnimation( s )->material; bool shader_translucent = false; bool shader_additive = false; bool using_cubemaps = false; bool fullbright = false; if(( FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( ) || mirror ) && !FBitSet( mat->flags, BRUSH_LIQUID )) fullbright = true; if( e->curstate.rendermode == kRenderTransAdd ) { shader_additive = true; fullbright = true; } if( fullbright ) { GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); } else { // process lightstyles for( int i = 0; i < MAXLIGHTMAPS && s->styles[i] != LS_NONE; i++ ) { if( tr.sun_light_enabled && s->styles[i] == LS_SKY ) continue; // skip the sunlight due realtime sun is enabled GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i )); } if( CVAR_TO_BOOL( cv_brdf )) GL_AddShaderDirective( options, "APPLY_PBS" ); // NOTE: deluxemap and normalmap are separate because some modes may using // normalmap directly e.g. for mirror distorsion if( es->normals ) { GL_AddShaderDirective( options, "HAS_DELUXEMAP" ); GL_AddShaderDirective( options, "COMPUTE_TBN" ); } if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) { if( r_lightmap->value == 1.0f && worldmodel && worldmodel->lightdata ) GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); } if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, BRUSH_HAS_SPECULAR ))) GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if( FBitSet( mat->flags, BRUSH_HAS_LUMA )) GL_AddShaderDirective( options, "HAS_LUMA" ); } if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && landscape && landscape->terrain ) { GL_AddShaderDirective( options, va( "TERRAIN_NUM_LAYERS %i", landscape->terrain->numLayers )); GL_AddShaderDirective( options, "APPLY_TERRAIN" ); if( landscape->terrain->indexmap.gl_diffuse_id != 0 && CVAR_TO_BOOL( r_detailtextures )) GL_AddShaderDirective( options, "HAS_DETAIL" ); } else { if( FBitSet( mat->flags, BRUSH_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) GL_AddShaderDirective( options, "HAS_DETAIL" ); } if( !RP_CUBEPASS() && ( FBitSet( mat->flags, BRUSH_HAS_BUMP ) && (CVAR_TO_BOOL( cv_bump ) || FBitSet( mat->flags, BRUSH_LIQUID )))) { // FIXME: all the waternormals should be encoded as first frame if( FBitSet( mat->flags, BRUSH_LIQUID )) GL_EncodeNormal( options, tr.waterTextures[0] ); else GL_EncodeNormal( options, mat->gl_normalmap_id ); GL_AddShaderDirective( options, "HAS_NORMALMAP" ); } // and finally select the render-mode if( FBitSet( mat->flags, BRUSH_LIQUID )) { GL_AddShaderDirective( options, "LIQUID_SURFACE" ); if( tr.waterlevel >= 3 ) GL_AddShaderDirective( options, "LIQUID_UNDERWATER" ); // world watery with compiler feature if( !FBitSet( s->flags, SURF_OF_SUBMODEL ) && FBitSet( world->features, WORLD_WATERALPHA )) shader_translucent = true; else if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) shader_translucent = true; } else if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !RP_CUBEPASS( )) { if( !FBitSet( mat->flags, BRUSH_REFLECT )) { GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); using_cubemaps = true; } } if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) shader_translucent = true; if( shader_translucent ) GL_AddShaderDirective( options, "TRANSLUCENT" ); if( shader_additive ) GL_AddShaderDirective( options, "ADDITIVE" ); if( mirror ) GL_AddShaderDirective( options, "PLANAR_REFLECTION" ); if( shader_translucent && FBitSet( mat->flags, BRUSH_HAS_ALPHA )) GL_AddShaderDirective( options, "ALPHA_GLASS" ); if( mat->refractScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) GL_AddShaderDirective( options, "APPLY_REFRACTION" ); if( mat->aberrationScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) GL_AddShaderDirective( options, "APPLY_ABERRATION" ); if( tr.fogEnabled ) GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { tr.fClearScreen = true; // to avoid ugly blur SetBits( s->flags, SURF_NODRAW ); return 0; // something bad happens } if( shader_translucent ) GL_AddShaderFeature( shaderNum, SHADER_TRANSLUCENT|SHADER_USE_SCREENCOPY ); if( shader_additive ) GL_AddShaderFeature( shaderNum, SHADER_ADDITIVE ); if( using_cubemaps ) GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); es->lastRenderMode = e->curstate.rendermode; ClearBits( s->flags, SURF_NODRAW ); es->forwardScene[mirror].SetShader( shaderNum ); return shaderNum; } /* ================= Mod_ShaderLightForward compute dynamic lighting ================= */ static word Mod_ShaderLightForward( CDynLight *dl, msurface_t *s ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; mfaceinfo_t *landscape = NULL; mextrasurf_t *es = s->info; switch( dl->type ) { case LIGHT_SPOT: if( es->forwardLightSpot.IsValid( )) return es->forwardLightSpot.GetHandle(); // valid break; case LIGHT_OMNI: if( es->forwardLightOmni.IsValid( )) return es->forwardLightOmni.GetHandle(); // valid break; case LIGHT_DIRECTIONAL: if( es->forwardLightProj.IsValid( )) return es->forwardLightProj.GetHandle(); // valid break; } Q_strncpy( glname, "forward/light_bmodel", sizeof( glname )); memset( options, 0, sizeof( options )); switch( dl->type ) { case LIGHT_SPOT: GL_AddShaderDirective( options, "LIGHT_SPOT" ); break; case LIGHT_OMNI: GL_AddShaderDirective( options, "LIGHT_OMNI" ); break; case LIGHT_DIRECTIONAL: GL_AddShaderDirective( options, "LIGHT_PROJ" ); break; } // mirror is actual only if we has actual screen texture! bool mirror = Surf_CheckSubview( s->info ); material_t *mat = R_TextureAnimation( s )->material; landscape = s->texinfo->faceinfo; if( CVAR_TO_BOOL( cv_brdf )) GL_AddShaderDirective( options, "APPLY_PBS" ); if( CVAR_TO_BOOL( cv_bump ) || FBitSet( mat->flags, BRUSH_LIQUID )) { if( FBitSet( mat->flags, BRUSH_HAS_BUMP ) && !FBitSet( dl->flags, DLF_NOBUMP )) { // FIXME: all the waternormals should be encoded as first frame if( FBitSet( mat->flags, BRUSH_LIQUID )) GL_EncodeNormal( options, tr.waterTextures[0] ); else GL_EncodeNormal( options, mat->gl_normalmap_id ); GL_AddShaderDirective( options, "HAS_NORMALMAP" ); GL_AddShaderDirective( options, "COMPUTE_TBN" ); } } if( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, BRUSH_HAS_SPECULAR )) GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && landscape && landscape->terrain ) { GL_AddShaderDirective( options, va( "TERRAIN_NUM_LAYERS %i", landscape->terrain->numLayers )); GL_AddShaderDirective( options, "APPLY_TERRAIN" ); if( landscape->terrain->indexmap.gl_diffuse_id != 0 && CVAR_TO_BOOL( r_detailtextures ) && glConfig.max_varying_floats > 48 ) GL_AddShaderDirective( options, "HAS_DETAIL" ); } else { if( FBitSet( mat->flags, BRUSH_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures ) && glConfig.max_varying_floats > 48 ) GL_AddShaderDirective( options, "HAS_DETAIL" ); } if( mirror && glConfig.max_varying_floats > 48 ) GL_AddShaderDirective( options, "PLANAR_REFLECTION" ); // and finally select the render-mode if( FBitSet( mat->flags, BRUSH_LIQUID )) { GL_AddShaderDirective( options, "LIQUID_SURFACE" ); if( tr.waterlevel >= 3 ) GL_AddShaderDirective( options, "LIQUID_UNDERWATER" ); } if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS )) { // shadow cubemaps only support if GL_EXT_gpu_shader4 is support if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows )) { GL_AddShaderDirective( options, "APPLY_SHADOW" ); } else if( dl->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" ); } } word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { if( dl->type == LIGHT_DIRECTIONAL ) SetBits( s->flags, SURF_NOSUNLIGHT ); else SetBits( s->flags, SURF_NODLIGHT ); return 0; // something bad happens } // done switch( dl->type ) { case LIGHT_SPOT: es->forwardLightSpot.SetShader( shaderNum ); ClearBits( s->flags, SURF_NODLIGHT ); break; case LIGHT_OMNI: es->forwardLightOmni.SetShader( shaderNum ); ClearBits( s->flags, SURF_NODLIGHT ); break; case LIGHT_DIRECTIONAL: es->forwardLightProj.SetShader( shaderNum ); ClearBits( s->flags, SURF_NOSUNLIGHT ); break; } return shaderNum; } /* ================= Mod_ShaderSceneDeferred compute deferred albedo ================= */ static word Mod_ShaderSceneDeferred( msurface_t *s ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; bool using_cubemaps = false; mextrasurf_t *es = s->info; if( es->deferredScene.IsValid( )) return es->deferredScene.GetHandle(); // valid Q_strncpy( glname, "deferred/scene_bmodel", sizeof( glname )); memset( options, 0, sizeof( options )); material_t *mat = s->texinfo->texture->material; mfaceinfo_t *landscape = s->texinfo->faceinfo; if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && landscape && landscape->terrain ) { GL_AddShaderDirective( options, va( "TERRAIN_NUM_LAYERS %i", landscape->terrain->numLayers )); GL_AddShaderDirective( options, "APPLY_TERRAIN" ); if( landscape->terrain->indexmap.gl_diffuse_id != 0 && CVAR_TO_BOOL( r_detailtextures )) GL_AddShaderDirective( options, "HAS_DETAIL" ); } else { if( FBitSet( mat->flags, BRUSH_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) GL_AddShaderDirective( options, "HAS_DETAIL" ); } if( FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( )) GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); if( FBitSet( mat->flags, BRUSH_HAS_LUMA )) GL_AddShaderDirective( options, "HAS_LUMA" ); if( !RP_CUBEPASS() && ( FBitSet( mat->flags, BRUSH_HAS_BUMP ) && CVAR_TO_BOOL( cv_bump ))) { GL_AddShaderDirective( options, "HAS_NORMALMAP" ); GL_EncodeNormal( options, mat->gl_normalmap_id ); GL_AddShaderDirective( options, "COMPUTE_TBN" ); } if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, BRUSH_HAS_SPECULAR ))) { GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && !RP_CUBEPASS( )) { GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); using_cubemaps = true; } } word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { tr.fClearScreen = true; // to avoid ugly blur SetBits( s->flags, SURF_NODRAW ); return 0; // something bad happens } if( using_cubemaps ) GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); // done es->deferredScene.SetShader( shaderNum ); ClearBits( s->flags, SURF_NODRAW ); return shaderNum; } /* ================= Mod_ShaderLightDeferred compute deferred lighting ================= */ static word Mod_ShaderLightDeferred( msurface_t *s ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; mextrasurf_t *es = s->info; if( es->deferredLight.IsValid( )) return es->deferredLight.GetHandle(); // valid Q_strncpy( glname, "deferred/light_bmodel", sizeof( glname )); memset( options, 0, sizeof( options )); material_t *mat = s->texinfo->texture->material; if( FBitSet( mat->flags, BRUSH_HAS_LUMA ) || FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( )) GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { tr.fClearScreen = true; // to avoid ugly blur SetBits( s->flags, SURF_NODRAW ); return 0; // something bad happens } // done es->deferredLight.SetShader( shaderNum ); ClearBits( s->flags, SURF_NODRAW ); return shaderNum; } /* ================= Mod_ShaderSceneDepth return bmodel depth-shader ================= */ static word Mod_ShaderSceneDepth( msurface_t *s ) { mextrasurf_t *es = s->info; if( es->forwardDepth.IsValid( )) return es->forwardDepth.GetHandle(); word shaderNum = GL_FindUberShader( "forward/depth_bmodel" ); es->forwardDepth.SetShader( shaderNum ); return shaderNum; } /* ================= Mod_PrecacheShaders precache shaders to reduce freezes in-game ================= */ static void Mod_PrecacheShaders( void ) { msurface_t *surf; int i; // preload shaders for all the world faces (but ignore watery faces) for( i = 0; i < worldmodel->submodels[0].numfaces; i++ ) { surf = &worldmodel->surfaces[world->sortedfaces[i]]; if( !FBitSet( surf->flags, SURF_DRAWTURB|SURF_DRAWSKY )) { Mod_ShaderSceneForward( surf ); // also precache a default light shaders Mod_ShaderLightForward( &tr.defaultlightSpot, surf ); Mod_ShaderLightForward( &tr.defaultlightOmni, surf ); Mod_ShaderLightForward( &tr.defaultlightProj, surf ); } } tr.params_changed = true; #if 0 Msg( "sorted faces:\n" ); for( i = 0; i < worldmodel->numsurfaces; i++ ) { surf = &worldmodel->surfaces[world->sortedfaces[i]]; mextrasurf_t *esrf = SURF_INFO( surf, worldmodel ); Msg( "face %i (local %s), style[1] %i\n", i, FBitSet( surf->flags, SURF_LOCAL_SPACE ) ? "Yes" : "No", surf->styles[1] ); } #endif } /* ================= Mod_ResortFaces if shaders was changed we need to resort them ================= */ void Mod_ResortFaces( void ) { int i; if( !tr.params_changed ) return; // rebuild shaders for( i = 0; i < worldmodel->submodels[0].numfaces; i++ ) Mod_ShaderSceneForward( &worldmodel->surfaces[i] ); // resort faces qsort( world->sortedfaces, worldmodel->numsurfaces, sizeof( unsigned short ), (cmpfunc)Mod_SurfaceCompareInGame ); #if 0 Msg( "resorted faces:\n" ); for( i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[world->sortedfaces[i]]; mextrasurf_t *esrf = SURF_INFO( surf, worldmodel ); Msg( "face %i (submodel %s) (local %s), shader %i, lightmap %i, style[1] %i\n", i, FBitSet( surf->flags, SURF_OF_SUBMODEL ) ? "Yes" : "No", FBitSet( surf->flags, SURF_LOCAL_SPACE ) ? "Yes" : "No", esrf->shaderNum, esrf->lightmaptexturenum, surf->styles[1] ); } #endif } /* ================= Mod_ComputeFaceTBN compute smooth TBN with baked normals ================= */ static void Mod_ComputeFaceTBN( msurface_t *surf, mextrasurf_t *esrf ) { Vector texdirections[2]; Vector directionnormals[2]; Vector faceNormal, vertNormal; int side; for( int i = 0; i < esrf->numverts; i++ ) { bvert_t *v = &world->vertexes[esrf->firstvertex + i]; int l = worldmodel->surfedges[surf->firstedge + i]; int vert = worldmodel->edges[abs(l)].v[(l > 0) ? 0 : 1]; if( world->surfnormals != NULL && world->normals != NULL ) { l = world->surfnormals[surf->firstedge + i]; if( l >= 0 || l < world->numnormals ) vertNormal = Vector( world->normals[l].normal ); else ALERT( at_error, "normal index %d out of range (max %d)\n", l, world->numnormals ); } else if( world->normals != NULL ) vertNormal = Vector( world->normals[vert].normal ); // calc unsmoothed tangent space if( FBitSet( surf->flags, SURF_PLANEBACK )) faceNormal = -surf->plane->normal; else faceNormal = surf->plane->normal; // fallback if( vertNormal == g_vecZero ) vertNormal = faceNormal; vertNormal = vertNormal.Normalize(); for( side = 0; side < 2; side++ ) { texdirections[side] = CrossProduct( faceNormal, surf->info->lmvecs[!side] ).Normalize(); if( DotProduct( texdirections[side], surf->info->lmvecs[side] ) < 0.0f ) texdirections[side] = -texdirections[side]; } for( side = 0; side < 2; side++ ) { float dot = DotProduct( texdirections[side], vertNormal ); VectorMA( texdirections[side], -dot, vertNormal, directionnormals[side] ); directionnormals[side] = directionnormals[side].Normalize(); } v->tangent = directionnormals[0]; v->binormal = -directionnormals[1]; v->normal = vertNormal; } } /* ================== GetLayerIndexForPoint this function came from q3map2 ================== */ static byte Mod_GetLayerIndexForPoint( indexMap_t *im, const Vector &mins, const Vector &maxs, const Vector &point ) { Vector size; if( !im->pixels ) return 0; for( int i = 0; i < 3; i++ ) size[i] = ( maxs[i] - mins[i] ); float s = ( point[0] - mins[0] ) / size[0]; float t = ( maxs[1] - point[1] ) / size[1]; int x = s * im->width; int y = t * im->height; x = bound( 0, x, ( im->width - 1 )); y = bound( 0, y, ( im->height - 1 )); return im->pixels[y * im->width + x]; } /* ================= Mod_LayerNameForPixel return layer name per pixel ================= */ bool Mod_CheckLayerNameForPixel( mfaceinfo_t *land, const Vector &point, const char *checkName ) { terrain_t *terra; layerMap_t *lm; indexMap_t *im; if( !land ) return true; // no landscape specified terra = land->terrain; if( !terra ) return true; im = &terra->indexmap; lm = &terra->layermap; if( !Q_stricmp( checkName, lm->names[Mod_GetLayerIndexForPoint( im, land->mins, land->maxs, point )] )) return true; return false; } /* ================= Mod_CheckLayerNameForSurf return layer name per face ================= */ bool Mod_CheckLayerNameForSurf( msurface_t *surf, const char *checkName ) { mtexinfo_t *tx = surf->texinfo; mfaceinfo_t *land = tx->faceinfo; terrain_t *terra; layerMap_t *lm; if( land != NULL && land->terrain != NULL ) { terra = land->terrain; lm = &terra->layermap; for( int i = 0; i < terra->numLayers; i++ ) { if( !Q_stricmp( checkName, lm->names[i] )) return true; } } else { const char *texname = surf->texinfo->texture->name; if( !Q_stricmp( checkName, texname )) return true; } return false; } /* ================= Mod_ProcessLandscapes handle all the landscapes per level ================= */ static void Mod_ProcessLandscapes( msurface_t *surf, mextrasurf_t *esrf ) { mtexinfo_t *tx = surf->texinfo; mfaceinfo_t *land = tx->faceinfo; if( !land || land->groupid == 0 || !land->landname[0] ) return; // no landscape specified, just lightmap resolution if( !land->terrain ) { land->terrain = R_FindTerrain( land->landname ); if( !land->terrain ) { // land name was specified in bsp but not declared in script file ALERT( at_error, "Mod_ProcessLandscapes: %s missing description\n", land->landname ); land->landname[0] = '\0'; // clear name to avoid trying to find invalid terrain return; } // prepare new landscape params ClearBounds( land->mins, land->maxs ); // setup shared pointers for( int i = 0; i < land->terrain->numLayers; i++ ) land->effects[i] = land->terrain->layermap.material[i]->effects; land->heightmap = land->terrain->indexmap.pixels; land->heightmap_width = land->terrain->indexmap.width; land->heightmap_height = land->terrain->indexmap.height; } // update terrain bounds AddPointToBounds( esrf->mins, land->mins, land->maxs ); AddPointToBounds( esrf->maxs, land->mins, land->maxs ); for( int j = 0; j < esrf->numverts; j++ ) { bvert_t *v = &world->vertexes[esrf->firstvertex + j]; AddPointToBounds( v->vertex, land->mins, land->maxs ); } } /* ================= Mod_MappingLandscapes now landscape AABB is actual mappping the surfaces ================= */ static void Mod_MappingLandscapes( msurface_t *surf, mextrasurf_t *esrf ) { mtexinfo_t *tx = surf->texinfo; mfaceinfo_t *land = tx->faceinfo; float mappingScale; terrain_t *terra; bvert_t *v; if( !land ) return; // no landscape specified terra = land->terrain; if( !terra ) return; // ooops! something bad happens! // now we have landscape info! SetBits( surf->flags, SURF_LANDSCAPE ); mappingScale = terra->texScale; // setup layers here if( surf->texinfo && surf->texinfo->texture && surf->texinfo->texture->material ) { material_t *mat = surf->texinfo->texture->material; ASSERT( mat != NULL ); mat->gl_diffuse_id = terra->layermap.gl_diffuse_id; mat->gl_normalmap_id = terra->layermap.gl_normalmap_id; mat->gl_specular_id = terra->layermap.gl_specular_id; mat->gl_glowmap_id = tr.blackTexture; mat->flags |= BRUSH_MULTI_LAYERS; if( mat->gl_normalmap_id > 0 && mat->gl_normalmap_id != tr.normalmapTexture ) mat->flags |= BRUSH_HAS_BUMP; if( mat->gl_specular_id > 0 && mat->gl_specular_id != tr.blackTexture ) mat->flags |= BRUSH_HAS_SPECULAR; if( mat->gl_glowmap_id > 0 && mat->gl_glowmap_id != tr.blackTexture ) mat->flags |= BRUSH_HAS_LUMA; if( RENDER_GET_PARM( PARM_TEX_FLAGS, mat->gl_specular_id ) & TF_HAS_ALPHA ) mat->flags |= BRUSH_GLOSSPOWER; // refresh material constants matdesc_t *desc = CL_FindMaterial( terra->indexmap.diffuse ); Mod_CopyMaterialDesc( mat, desc ); } // mapping global diffuse texture for( int i = 0; i < esrf->numverts; i++ ) { v = &world->vertexes[esrf->firstvertex + i]; v->stcoord0[0] *= mappingScale; v->stcoord0[1] *= mappingScale; R_GlobalCoords( surf, v->vertex, v->stcoord0 ); } } /* ================= Mod_FindStaticLights find a mark lights that affected to this face ================= */ void Mod_FindStaticLights( byte *vislight, byte lights[MAXDYNLIGHTS], const Vector &origin ) { mworldlight_t *wl = world->worldlights; int indexes[32]; int i, count; memset( lights, 255, sizeof( byte ) * MAXDYNLIGHTS ); count = 0; // failed to vislightdata... if( !vislight ) return; // mark all the lights that can lit this face for( i = 0; i < world->numworldlights; i++, wl++ ) { if( wl->emittype == emit_ignored ) continue; // bad light? // this face is invisible for this light if( !CHECKVISBIT( vislight, i )) continue; if( count >= ARRAYSIZE( indexes )) { ALERT( at_aiconsole, "too many lights on a face\n" ); break; } indexes[count++] = i;// member this light } get_next_light: float maxPhotons = 0.0; int ignored = -1; int light = 255; for( i = 0; i < count; i++ ) { if( indexes[i] == -1 ) continue; wl = world->worldlights + indexes[i]; Vector delta = (wl->origin - origin); float dist = Q_max( delta.Length(), 1.0 ); delta = delta.Normalize(); float ratio = 1.0 / (dist * dist); Vector add = wl->intensity * ratio; float photons = VectorMax( add ); // skylight has a maximum priority if( photons > maxPhotons ) { maxPhotons = photons; light = indexes[i]; ignored = i; } } if( ignored == -1 ) { if( count > (int)cv_deferred_maxlights->value ) ALERT( at_aiconsole, "total %i lights affected to face\n", count ); return; } for( i = 0; i < (int)cv_deferred_maxlights->value && lights[i] != 255; i++ ); if( i < (int)cv_deferred_maxlights->value ) lights[i] = light; // nearest light for surf indexes[ignored] = -1; // this light is handled // if( count > (int)cv_deferred_maxlights->value && i == (int)cv_deferred_maxlights->value ) // Msg( "skipped light %i intensity %g, type %d\n", light, maxPhotons, world->worldlights[light].emittype ); goto get_next_light; } /* ================= Mod_InitLightTexture ================= */ static void Mod_InitLightTexture( void ) { mworldlight_t *wl = world->worldlights; int height = ((world->numworldlights / 256) + 1) * 3; int lightnum = 0; int width = 256; if( !world->numworldlights ) return; Vector4D *worldlights = (Vector4D *)Mem_Alloc( sizeof( Vector4D ) * width * height ); // Msg( "light: %d %d\n", width, height ); for( int y = 0; y < height; y += 3 ) { for( int x = 0; x < width; x++, lightnum++ ) { if( lightnum == world->numworldlights ) break; wl = &world->worldlights[lightnum]; worldlights[((y+0)*width)+x] = Vector4D( NormalToFloat( wl->normal ), wl->stopdot, wl->stopdot2, wl->emittype ); worldlights[((y+1)*width)+x] = Vector4D( wl->origin[0], wl->origin[1], wl->origin[2], wl->falloff ); worldlights[((y+2)*width)+x] = Vector4D( wl->intensity[0], wl->intensity[1], wl->intensity[2], wl->style ); } } tr.packed_lights_texture = CREATE_TEXTURE( "*worldlights", width, height, (byte *)worldlights, TF_STORAGE ); Mem_Free( worldlights ); } /* ================= Mod_InitBSPTreeTexture ================= */ static void Mod_InitBSPTreeTexture( void ) { int planenum = 0; int height = 256; int width = 256; int nodenum = 0; mnode_t *cn, *child; int x, y; dclipnode_t out; mplane_t *pl; // planes are 4-th components float texture 256x56 (max 65536 planes) Vector4D *worldplanes = (Vector4D *)Mem_Alloc( sizeof( Vector4D ) * width * height ); for( y = 0; y < height; y++ ) { for( x = 0; x < width; x++, planenum++ ) { if( planenum == worldmodel->numplanes ) break; pl = &worldmodel->planes[planenum]; // we not enough free space to store type or signbits but these optimizations doesn't matter on GPU anyway worldplanes[(y*width)+x] = Vector4D( pl->normal.x, pl->normal.y, pl->normal.z, pl->dist ); } } tr.packed_planes_texture = CREATE_TEXTURE( "*worldplanes", width, height, (byte *)worldplanes, TF_STORAGE ); Mem_Free( worldplanes ); // nodes are 4-th components float texture 256x256 (max 65536 nodes) Vector4D *worldnodes = (Vector4D *)Mem_Alloc( sizeof( Vector4D ) * width * height ); for( y = 0; y < height; y++ ) { for( x = 0; x < width; x++, nodenum++ ) { if( nodenum == worldmodel->numnodes ) break; // all nodes are stored cn = &worldmodel->nodes[nodenum]; out.planenum = cn->plane - worldmodel->planes; // convert nodes to clipnodes for( int j = 0; j < 2; j++ ) { child = cn->children[j]; if( child->contents < 0 ) out.children[j] = child->contents; else out.children[j] = child - worldmodel->nodes; } // FIXME: store into 4-th component something useful :) worldnodes[(y*width)+x] = Vector4D( out.children[0], out.children[1], out.planenum, 0.0f ); // unused } } tr.packed_nodes_texture = CREATE_TEXTURE( "*worldnodes", width, height, (byte *)worldnodes, TF_STORAGE ); Mem_Free( worldnodes ); // Msg( "bsp structure placed into textures (%i nodes, %i planes)\n", worldmodel->numnodes, worldmodel->numplanes ); } /* ================= Mod_InitBSPTreeTexture ================= */ void Mod_InitBSPModelsTexture( void ) { cl_entity_t *visible_ents[MAX_VISIBLE_ENTS]; Vector absmin, absmax; static double lastupdate; gl_state_t *glm; if( !CVAR_TO_BOOL( cv_deferred_tracebmodels )) return; if( !FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) return; if( lastupdate > Sys_DoubleTime( )) return; // don't upload models too often lastupdate = Sys_DoubleTime() + 0.01; world->num_visible_models = 0; for( int i = 0; i < tr.num_draw_entities; i++ ) { cl_entity_t *e = tr.draw_entities[i]; if( !e || !e->model || e->model->type != mod_brush ) continue; if( e->curstate.rendermode != kRenderNormal ) continue; glm = GL_GetCache( e->hCachedMatrix ); if( glm->m_bSkyEntity ) continue; visible_ents[world->num_visible_models] = e; world->num_visible_models++; } int height = worldmodel->numsubmodels; int width = 8, flags = 0; // data representation [width = 26][height = nummodels] // first 6 floats - bounding box, next 16 floats - actual model matrix, // next 2 float - rootnode, totalnodes, last 2 floats - first face, Vector4D *worldmodels = (Vector4D *)stackalloc( sizeof( Vector4D ) * width * height ); for( int y = 0; y < world->num_visible_models; y++ ) { cl_entity_t *e = visible_ents[y]; // grab the transformed vieworg glm = GL_GetCache( e->hCachedMatrix ); matrix4x4 im = glm->transform.Invert(); model_t *m = e->model; if( e->angles != g_vecZero ) { TransformAABB( glm->transform, e->model->mins, e->model->maxs, absmin, absmax ); } else { absmin = e->origin + e->model->mins; absmax = e->origin + e->model->maxs; } // first 2 pixels - transformed bounding box, identity flag, rootnode worldmodels[(y*width)+0] = Vector4D( absmin.x, absmin.y, absmin.z, e->hCachedMatrix > 0 ? 1.0f : 0.0f ); worldmodels[(y*width)+1] = Vector4D( absmax.x, absmax.y, absmax.z, m->hulls[0].firstclipnode ); // next 4 pixels - actual inverted model matrix worldmodels[(y*width)+2] = Vector4D( im[0][0], im[1][0], im[2][0], im[3][0] ); worldmodels[(y*width)+3] = Vector4D( im[0][1], im[1][1], im[2][1], im[3][1] ); worldmodels[(y*width)+4] = Vector4D( im[0][2], im[1][2], im[2][2], im[3][2] ); worldmodels[(y*width)+5] = Vector4D( im[0][3], im[1][3], im[2][3], im[3][3] ); } // if texture already present - update it if( tr.packed_models_texture != 0 ) SetBits( flags, TF_UPDATE ); // it will automatically called glSubImage on next calls tr.packed_models_texture = CREATE_TEXTURE( "*worldmodels", width, height, (byte *)worldmodels, TF_STORAGE|flags ); } static void CreateBufferBaseGL21( bvert_t *arrayxvert ) { static bvert_v0_gl21_t arraybvert[MAX_MAP_VERTS*4]; // convert to GLSL-compacted array for( int i = 0; i < world->numvertexes; i++ ) { arraybvert[i].vertex[0] = arrayxvert[i].vertex[0]; arraybvert[i].vertex[1] = arrayxvert[i].vertex[1]; arraybvert[i].vertex[2] = arrayxvert[i].vertex[2]; arraybvert[i].tangent[0] = arrayxvert[i].tangent[0]; arraybvert[i].tangent[1] = arrayxvert[i].tangent[1]; arraybvert[i].tangent[2] = arrayxvert[i].tangent[2]; arraybvert[i].binormal[0] = arrayxvert[i].binormal[0]; arraybvert[i].binormal[1] = arrayxvert[i].binormal[1]; arraybvert[i].binormal[2] = arrayxvert[i].binormal[2]; arraybvert[i].normal[0] = arrayxvert[i].normal[0]; arraybvert[i].normal[1] = arrayxvert[i].normal[1]; arraybvert[i].normal[2] = arrayxvert[i].normal[2]; arraybvert[i].stcoord0[0] = arrayxvert[i].stcoord0[0]; arraybvert[i].stcoord0[1] = arrayxvert[i].stcoord0[1]; arraybvert[i].stcoord0[2] = arrayxvert[i].stcoord0[2]; arraybvert[i].stcoord0[3] = arrayxvert[i].stcoord0[3]; arraybvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; arraybvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; arraybvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; arraybvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; arraybvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; arraybvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; arraybvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; arraybvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; memcpy( arraybvert[i].styles, arrayxvert[i].styles, MAXLIGHTMAPS ); } world->cacheSize = world->numvertexes * sizeof( bvert_v0_gl21_t ); // create world vertex buffer pglGenBuffersARB( 1, &world->vertex_buffer_object ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); pglBufferDataARB( GL_ARRAY_BUFFER_ARB, world->cacheSize, &arraybvert[0], GL_STATIC_DRAW_ARB ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); } static void BindBufferBaseGL21( void ) { pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, vertex )); pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, tangent )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, binormal )); pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, normal )); pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, stcoord0 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, lmcoord0 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, lmcoord1 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, styles )); pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); } static void CreateBufferBaseGL30( bvert_t *arrayxvert ) { static bvert_v0_gl30_t arraybvert[MAX_MAP_VERTS*4]; // convert to GLSL-compacted array for( int i = 0; i < world->numvertexes; i++ ) { arraybvert[i].vertex[0] = arrayxvert[i].vertex[0]; arraybvert[i].vertex[1] = arrayxvert[i].vertex[1]; arraybvert[i].vertex[2] = arrayxvert[i].vertex[2]; CompressNormalizedVector( arraybvert[i].normal, arrayxvert[i].normal ); CompressNormalizedVector( arraybvert[i].tangent, arrayxvert[i].tangent ); CompressNormalizedVector( arraybvert[i].binormal, arrayxvert[i].binormal ); arraybvert[i].stcoord0[0] = arrayxvert[i].stcoord0[0]; arraybvert[i].stcoord0[1] = arrayxvert[i].stcoord0[1]; arraybvert[i].stcoord0[2] = arrayxvert[i].stcoord0[2]; arraybvert[i].stcoord0[3] = arrayxvert[i].stcoord0[3]; arraybvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; arraybvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; arraybvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; arraybvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; arraybvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; arraybvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; arraybvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; arraybvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; memcpy( arraybvert[i].styles, arrayxvert[i].styles, MAXLIGHTMAPS ); memcpy( arraybvert[i].lights0, arrayxvert[i].lights0, MAXLIGHTMAPS ); memcpy( arraybvert[i].lights1, arrayxvert[i].lights1, MAXLIGHTMAPS ); } world->cacheSize = world->numvertexes * sizeof( bvert_v0_gl30_t ); // create world vertex buffer pglGenBuffersARB( 1, &world->vertex_buffer_object ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); pglBufferDataARB( GL_ARRAY_BUFFER_ARB, world->cacheSize, &arraybvert[0], GL_STATIC_DRAW_ARB ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); } static void BindBufferBaseGL30( void ) { pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, vertex )); pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, tangent )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, binormal )); pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, normal )); pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, stcoord0 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lmcoord0 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lmcoord1 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, styles )); pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_NUMS0, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lights0 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_NUMS0 ); pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_NUMS1, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lights1 )); pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_NUMS1 ); } /* ================= Mod_CreateBufferObject ================= */ static void Mod_CreateBufferObject( void ) { if( world->vertex_buffer_object ) return; // already created // calculate number of used faces and vertexes msurface_t *surf = worldmodel->surfaces; int i, j, curVert = 0; byte *vislight = NULL; mvertex_t *dv; bvert_t *mv; world->numvertexes = 0; // compute totalvertex count for VBO but ignore sky polys for( i = 0; i < worldmodel->numsurfaces; i++, surf++ ) { if( FBitSet( surf->flags, SURF_DRAWSKY )) continue; world->numvertexes += surf->numedges; } // temporary array will be removed at end of this function // g-cont. i'm leave local copy of vertexes for some debug purpoces world->vertexes = (bvert_t *)Mem_Alloc( sizeof( bvert_t ) * world->numvertexes ); surf = worldmodel->surfaces; // create VBO-optimized vertex array (single for world and all brush-models) for( i = 0; i < worldmodel->numsurfaces; i++, surf++ ) { Vector t, b, n; if( FBitSet( surf->flags, SURF_DRAWSKY )) continue; // ignore sky polys it was never be drawed mv = &world->vertexes[curVert]; // request vislightdata for this surface if( world->vislightdata ) vislight = world->vislightdata + i * ((world->numworldlights + 7) / 8); Mod_FindStaticLights( vislight, surf->info->lights, surf->info->origin ); // NOTE: all polygons stored as source (no tesselation anyway) for( j = 0; j < surf->numedges; j++, mv++ ) { int l = worldmodel->surfedges[surf->firstedge + j]; int vert = worldmodel->edges[abs(l)].v[(l > 0) ? 0 : 1]; memcpy( mv->styles, surf->styles, sizeof( surf->styles )); memcpy( mv->lights0, surf->info->lights, sizeof( surf->info->lights )); dv = &worldmodel->vertexes[vert]; mv->vertex = dv->position; R_TextureCoords( surf, mv->vertex, mv->stcoord0 ); R_LightmapCoords( surf, mv->vertex, mv->lmcoord0, 0 ); // styles 0-1 R_LightmapCoords( surf, mv->vertex, mv->lmcoord1, 2 ); // styles 2-3 } // NOTE: now firstvertex are handled in world->vertexes[] array, not in world->tbn_vectors[] !!! surf->info->firstvertex = curVert; surf->info->numverts = surf->numedges; curVert += surf->numedges; Mod_ComputeFaceTBN( surf, surf->info ); } // compute water global coords for( i = 1; i < worldmodel->numsubmodels; i++ ) { Vector absmin, absmax; ClearBounds( absmin, absmax ); // first iteration - compute water bbox for( j = 0; j < worldmodel->submodels[i].numfaces; j++ ) { surf = &worldmodel->surfaces[worldmodel->submodels[i].firstface + j]; if( !FBitSet( surf->flags, SURF_DRAWTURB )) continue; AddPointToBounds( surf->info->mins, absmin, absmax ); AddPointToBounds( surf->info->maxs, absmin, absmax ); } if( BoundsIsCleared( absmin, absmax )) continue; // not a water float scale = sqrt( (absmax - absmin).Average()) * 0.3f; // FIXME: tune this constant? // second iteration - mapping global coords for( j = 0; j < worldmodel->submodels[i].numfaces; j++ ) { surf = &worldmodel->surfaces[worldmodel->submodels[i].firstface + j]; if( !FBitSet( surf->flags, SURF_DRAWTURB )) continue; for( int k = 0; k < surf->info->numverts; k++ ) { mv = &world->vertexes[surf->info->firstvertex + k]; R_GlobalCoords( surf, mv->vertex, absmin, absmax, scale, mv->stcoord0 ); } } } // time to prepare landscapes for( i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[i]; Mod_ProcessLandscapes( surf, surf->info ); } for( i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[i]; Mod_MappingLandscapes( surf, surf->info ); } // now normals are merged into single array world->vertexes[] if( world->normals != NULL ) Mem_Free( world->normals ); world->normals = NULL; GL_CheckVertexArrayBinding(); // create world vertex buffer if( glConfig.version < ACTUAL_GL_VERSION ) CreateBufferBaseGL21( world->vertexes ); else CreateBufferBaseGL30( world->vertexes ); // create vertex array object pglGenVertexArrays( 1, &world->vertex_array_object ); pglBindVertexArray( world->vertex_array_object ); if( glConfig.version < ACTUAL_GL_VERSION ) BindBufferBaseGL21(); else BindBufferBaseGL30(); pglBindVertexArray( GL_FALSE ); // update stats tr.total_vbo_memory += world->cacheSize; } /* ================= Mod_DeleteBufferObject ================= */ static void Mod_DeleteBufferObject( void ) { if( world->vertex_array_object ) pglDeleteVertexArrays( 1, &world->vertex_array_object ); if( world->vertex_buffer_object ) pglDeleteBuffersARB( 1, &world->vertex_buffer_object ); tr.total_vbo_memory -= world->cacheSize; world->vertex_array_object = world->vertex_buffer_object = 0; world->cacheSize = 0; } /* ================= Mod_PrepareModelInstances throw all the instances before loading the new map ================= */ void Mod_PrepareModelInstances( void ) { // invalidate model handles for( int i = 1; i < RENDER_GET_PARM( PARM_MAX_ENTITIES, 0 ); i++ ) { cl_entity_t *e = GET_ENTITY( i ); if( !e ) break; e->modelhandle = INVALID_HANDLE; } if( gHUD.m_pHeadShieldEnt != NULL ) { gHUD.m_pHeadShieldEnt->modelhandle = INVALID_HANDLE; gHUD.m_pHeadShieldEnt->curstate.movetype = MOVETYPE_WALK; } GET_VIEWMODEL()->modelhandle = INVALID_HANDLE; memset( tr.draw_entities, 0, sizeof( tr.draw_entities )); } /* ================= Mod_ThrowModelInstances throw all the instances before loading the new map ================= */ void Mod_ThrowModelInstances( void ) { // engine already released entity array so we can't release // model instance for each entity personally g_StudioRenderer.DestroyAllModelInstances(); // may caused by Host_Error, so clear gl state if( g_fRenderInitialized ) GL_SetDefaultState(); } /* ================= Mod_LoadWorld ================= */ static void Mod_LoadWorld( model_t *mod, const byte *buf ) { dheader_t *header; dextrahdr_t *extrahdr; int i; header = (dheader_t *)buf; extrahdr = (dextrahdr_t *)((byte *)buf + sizeof( dheader_t )); if( RENDER_GET_PARM( PARM_FEATURES, 0 ) & ENGINE_LARGE_LIGHTMAPS ) glConfig.block_size = BLOCK_SIZE_MAX; else glConfig.block_size = BLOCK_SIZE_DEFAULT; if( RENDER_GET_PARM( PARM_MAP_HAS_DELUXE, 0 )) SetBits( world->features, WORLD_HAS_DELUXEMAP ); if( RENDER_GET_PARM( PARM_WATER_ALPHA, 0 )) SetBits( world->features, WORLD_WATERALPHA ); COM_FileBase( worldmodel->name, world->name ); ALERT( at_aiconsole, "Mod_LoadWorld: %s\n", world->name ); R_CheckChanges(); // catch all the cvar changes tr.glsl_valid_sequence = 1; tr.params_changed = false; // precache and upload cinematics R_InitCinematics(); // prepare visibility and setup leaf extradata Mod_SetupLeafExtradata( &header->lumps[LUMP_LEAFS], &header->lumps[LUMP_VISIBILITY], buf ); // all the old lightmaps are freed GL_BeginBuildingLightmaps(); // process landscapes first R_LoadLandscapes( world->name ); // load material textures Mod_LoadWorldMaterials(); // mark submodel faces for( i = worldmodel->submodels[0].numfaces; i < worldmodel->numsurfaces; i++ ) SetBits( worldmodel->surfaces[i].flags, SURF_OF_SUBMODEL ); // detect surfs in local space for( i = 0; i < worldmodel->numsubmodels; i++ ) { dmodel_t *bm = &worldmodel->submodels[i]; if( bm->origin == g_vecZero ) continue; // abs space // mark surfs in local space msurface_t *surf = worldmodel->surfaces + bm->firstface; for( int j = 0; j < bm->numfaces; j++, surf++ ) SetBits( surf->flags, SURF_LOCAL_SPACE ); } if( extrahdr->id == IDEXTRAHEADER ) { if( extrahdr->version == EXTRA_VERSION ) { // new Xash3D extended format if( GL_Support( R_TEXTURECUBEMAP_EXT )) // loading cubemaps only when it's support Mod_LoadCubemaps( buf, &extrahdr->lumps[LUMP_CUBEMAPS] ); Mod_LoadVertNormals( buf, &extrahdr->lumps[LUMP_VERTNORMALS] ); Mod_LoadWorldLights( buf, &extrahdr->lumps[LUMP_WORLDLIGHTS] ); Mod_LoadLeafAmbientLighting( buf, &extrahdr->lumps[LUMP_LEAF_LIGHTING] ); Mod_LoadVertexLighting( buf, &extrahdr->lumps[LUMP_VERTEX_LIGHT] ); Mod_LoadSurfaceLighting( buf, &extrahdr->lumps[LUMP_SURFACE_LIGHT] ); Mod_LoadVisLightData( buf, &extrahdr->lumps[LUMP_VISLIGHTDATA] ); } else if( extrahdr->version == EXTRA_VERSION_OLD ) { // P2: Savior regular format if( GL_Support( R_TEXTURECUBEMAP_EXT )) // loading cubemaps only when it's support Mod_LoadCubemaps( buf, &extrahdr->lumps[LUMP_CUBEMAPS] ); Mod_LoadWorldLights( buf, &extrahdr->lumps[LUMP_WORLDLIGHTS] ); } Mod_SetupLeafLights(); } // mark surfaces for world features for( i = 0; i < worldmodel->numsurfaces; i++ ) { msurface_t *surf = &worldmodel->surfaces[i]; if( FBitSet( surf->flags, SURF_DRAWSKY )) SetBits( world->features, WORLD_HAS_SKYBOX ); if( FBitSet( surf->flags, SURF_DRAWTURB )) { SetBits( surf->flags, SURF_REFLECT ); } if( !Q_strncmp( surf->texinfo->texture->name, "movie", 5 )) { SetBits( world->features, WORLD_HAS_MOVIES ); SetBits( surf->flags, SURF_MOVIE ); } if( !Q_strncmp( surf->texinfo->texture->name, "mirror", 6 ) || !Q_strncmp( surf->texinfo->texture->name, "reflect", 7 )) { SetBits( surf->flags, SURF_REFLECT ); } if( !Q_strncmp( surf->texinfo->texture->name, "water", 5 )) { SetBits( surf->flags, SURF_REFLECT ); } if( FBitSet( surf->flags, SURF_REFLECT )) GL_AllocOcclusionQuery( surf ); } world->deluxedata = (color24 *)RENDER_GET_PARM( PARM_DELUXEDATA, 0 ); world->shadowdata = (byte *)RENDER_GET_PARM( PARM_SHADOWDATA, 0 ); Mod_FinalizeWorld(); Mod_CreateBufferObject(); // helper to precache shaders R_InitDefaultLights(); // time to place grass for( i = 0; i < worldmodel->numsurfaces; i++ ) { // place to initialize our grass R_GrassInitForSurface( &worldmodel->surfaces[i] ); } // precache world shaders Mod_PrecacheShaders(); // creating float texture from worldlights Mod_InitLightTexture(); // put bsp-tree into two texture (planes and nodes) Mod_InitBSPTreeTexture(); } static void Mod_FreeWorld( model_t *mod ) { Mod_FreeCubemaps(); // destroy VBO & VAO Mod_DeleteBufferObject(); if( world->leafs ) Mem_Free( world->leafs ); world->leafs = NULL; if( world->vertexes ) Mem_Free( world->vertexes ); world->vertexes = NULL; if( world->vertex_lighting ) Mem_Free( world->vertex_lighting ); world->vertex_lighting = NULL; if( world->surface_lighting ) Mem_Free( world->surface_lighting ); world->surface_lighting = NULL; // free old cinematics R_FreeCinematics(); // free landscapes R_FreeLandscapes(); if( tr.packed_lights_texture ) { FREE_TEXTURE( tr.packed_lights_texture ); tr.packed_lights_texture = 0; } if( tr.packed_planes_texture ) { FREE_TEXTURE( tr.packed_planes_texture ); tr.packed_planes_texture = 0; } if( tr.packed_nodes_texture ) { FREE_TEXTURE( tr.packed_nodes_texture ); tr.packed_nodes_texture = 0; } if( tr.packed_models_texture ) { FREE_TEXTURE( tr.packed_models_texture ); tr.packed_models_texture = 0; } if( world->materials ) { for( int i = 0; i < worldmodel->numtextures; i++ ) { texture_t *tx = worldmodel->textures[i]; material_t *mat = &world->materials[i]; if( !mat->pSource ) continue; // not initialized? if( !FBitSet( mat->flags, BRUSH_MULTI_LAYERS )) { if( tx->gl_texturenum != mat->gl_diffuse_id ) FREE_TEXTURE( mat->gl_diffuse_id ); if( mat->gl_normalmap_id != tr.normalmapTexture ) FREE_TEXTURE( mat->gl_normalmap_id ); if( mat->gl_specular_id != tr.blackTexture ) FREE_TEXTURE( mat->gl_specular_id ); } if( mat->gl_glowmap_id != tr.blackTexture ) FREE_TEXTURE( mat->gl_glowmap_id ); } Mem_Free( world->materials ); world->materials = NULL; } // delete queries for( int i = 0; i < worldmodel->numsurfaces; i++ ) GL_DeleteOcclusionQuery( &worldmodel->surfaces[i] ); if( FBitSet( world->features, WORLD_HAS_GRASS )) { // throw grass vbo's for( int i = 0; i < worldmodel->numsurfaces; i++ ) R_RemoveGrassForSurface( worldmodel->surfaces[i].info ); } memset( world, 0, sizeof( gl_world_t )); } /* ================== Mod_SetOrthoBounds setup ortho min\maxs for draw overview ================== */ void Mod_SetOrthoBounds( const float *mins, const float *maxs ) { if( !g_fRenderInitialized ) return; world->orthocenter.x = ((maxs[0] + mins[0]) * 0.5f); world->orthocenter.y = ((maxs[1] + mins[1]) * 0.5f); world->orthohalf.x = maxs[0] - world->orthocenter.x; world->orthohalf.y = maxs[1] - world->orthocenter.y; } /* ================== R_ProcessWorldData resource management ================== */ void R_ProcessWorldData( model_t *mod, qboolean create, const byte *buffer ) { worldmodel = mod; if( create ) { double start = Sys_DoubleTime(); Mod_LoadWorld( mod, buffer ); double end = Sys_DoubleTime(); r_buildstats.create_buffer_object += (end - start); r_buildstats.total_buildtime += (end - start); } else Mod_FreeWorld( mod ); } /* ============================================================================= WORLD RENDERING ============================================================================= */ static unsigned int tempElems[MAX_MAP_ELEMS]; static unsigned int numTempElems; /* ================= R_MarkSubmodelVisibleFaces do all the visibility checks, update surface params, and mark them as visible ================= */ void R_MarkSubmodelVisibleFaces( void ) { cl_entity_t *e = RI->currententity; model_t *model = RI->currentmodel; Vector absmin, absmax; CFrustum *frustum; mplane_t plane; msurface_t *surf; mextrasurf_t *esrf; gl_state_t *glm; int i; // grab the transformed vieworg glm = GL_GetCache( e->hCachedMatrix ); if( e->angles != g_vecZero ) { TransformAABB( glm->transform, model->mins, model->maxs, absmin, absmax ); } else { absmin = e->origin + model->mins; absmax = e->origin + model->maxs; } if( !Mod_CheckBoxVisible( absmin, absmax )) { r_stats.c_culled_entities++; return; } // frustum checking if( R_CullBrushModel( e )) { r_stats.c_culled_entities++; return; } // mark all the visible faces (will added later) for( i = 0; model != NULL && i < model->nummodelsurfaces; i++ ) { surf = model->surfaces + model->firstmodelsurface + i; esrf = surf->info; esrf->parent = RI->currententity; // setup dynamic upcast if( FBitSet( surf->flags, SURF_DRAWTURB )) { if( FBitSet( surf->flags, SURF_PLANEBACK )) SetPlane( &plane, -surf->plane->normal, -surf->plane->dist ); else SetPlane( &plane, surf->plane->normal, surf->plane->dist ); glm->transform.TransformPositivePlane( plane, plane ); if( surf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES )) continue; if( absmin[2] + 1.0 >= plane.dist ) continue; } // non-moved bmodels can be culled by frustum if( R_StaticEntity( e )) frustum = &RI->view.frustum; else frustum = NULL; int cull_type = R_CullSurface( surf, tr.modelorg, frustum ); if( cull_type >= CULL_FRUSTUM ) continue; if( cull_type == CULL_BACKSIDE ) { if( !FBitSet( surf->flags, SURF_DRAWTURB )) continue; } SETVISBIT( RI->view.visfaces, model->firstmodelsurface + i ); // surface has passed all visibility checks // and can be update some data (lightmaps, mirror matrix, etc) if( RP_NORMALPASS( )) R_UpdateSurfaceParams( surf ); } if( e->origin != e->baseline.vuser4 ) { R_FindWorldLights( e->origin, e->model->mins, e->model->maxs, e->lights, true ); #if 0 Msg( "%s gather lights: %d %d %d %d %d %d %d %d\n", e->model->name, e->lights[0], e->lights[1], e->lights[2], e->lights[3], e->lights[4], e->lights[4], e->lights[6], e->lights[7] ); #endif e->baseline.vuser4 = e->origin; } } /* ================= R_UpdateSubmodelParams run cinematic, evaluate conveyor etc ================= */ void R_UpdateSubmodelParams( void ) { cl_entity_t *e = RI->currententity; model_t *model = RI->currentmodel; msurface_t *surf; mextrasurf_t *esrf; // mark all the visible faces (will added later) for( int i = 0; model != NULL && i < model->nummodelsurfaces; i++ ) { surf = model->surfaces + model->firstmodelsurface + i; esrf = surf->info; esrf->parent = RI->currententity; // setup dynamic upcast // and can be update some data (lightmaps, mirror matrix, etc) R_UpdateSurfaceParams( surf ); } } _forceinline void R_DrawSurface( mextrasurf_t *es ) { // accumulate the indices for( int j = 0; j < es->numverts - 2; j++ ) { ASSERT( numTempElems < ( MAX_MAP_ELEMS - 3 )); tempElems[numTempElems++] = es->firstvertex; tempElems[numTempElems++] = es->firstvertex + j + 1; tempElems[numTempElems++] = es->firstvertex + j + 2; } } void R_MarkVisibleLights( byte lights[MAXDYNLIGHTS] ) { // mark lights that visible for this frame for( int i = 0; i < (int)cv_deferred_maxlights->value && lights[i] != 255; i++ ) SETVISBIT( RI->view.vislight, lights[i] ); } /* ================= R_AddSurfaceToDrawList add specified face into sorted drawlist ================= */ bool R_AddSurfaceToDrawList( msurface_t *surf, drawlist_t drawlist_type ) { cl_entity_t *e = RI->currententity; mextrasurf_t *es = surf->info; word hProgram = 0; CSolidEntry entry_s; CTransEntry entry_t; switch( drawlist_type ) { case DRAWLIST_SOLID: if( FBitSet( surf->flags, SURF_NODRAW )) return false; if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) { // precache shaders Mod_ShaderSceneDeferred( surf ); Mod_ShaderLightDeferred( surf ); } else hProgram = Mod_ShaderSceneForward( surf ); R_MarkVisibleLights( es->lights ); entry_s.SetRenderSurface( surf, hProgram ); RI->frame.solid_faces.AddToTail( entry_s ); break; case DRAWLIST_TRANS: if( FBitSet( surf->flags, SURF_NODRAW )) return false; hProgram = Mod_ShaderSceneForward( surf ); entry_t.SetRenderSurface( surf, hProgram ); if( ScreenCopyRequired( &glsl_programs[hProgram] )) { Vector mins, maxs; gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); TransformAABB( glm->transform, es->mins, es->maxs, mins, maxs ); // create sentinel border for refractions ExpandBounds( mins, maxs, 2.0f ); entry_t.ComputeScissor( mins, maxs ); } RI->frame.trans_list.AddToTail( entry_t ); break; case DRAWLIST_SHADOW: if( FBitSet( surf->flags, SURF_NODRAW )) return false; hProgram = Mod_ShaderSceneDepth( surf ); entry_s.SetRenderSurface( surf, hProgram ); RI->frame.solid_faces.AddToTail( entry_s ); break; case DRAWLIST_LIGHT: if( RI->currentlight->type == LIGHT_DIRECTIONAL ) { if( FBitSet( surf->flags, SURF_NOSUNLIGHT )) return false; } else { if( FBitSet( surf->flags, SURF_NODLIGHT )) return false; } hProgram = Mod_ShaderLightForward( RI->currentlight, surf ); entry_s.SetRenderSurface( surf, hProgram ); RI->frame.light_faces.AddToTail( entry_s ); break; case DRAWLIST_SUBVIEW: if( RI->frame.num_subview_faces >= MAX_SUBVIEW_FACES ) return false; if( !FBitSet( surf->flags, SURF_REFLECT )) { if( !FBitSet( surf->flags, SURF_REFLECT_PUDDLE ) || !CVAR_TO_BOOL( cv_realtime_puddles )) return false; // if realtime puddles not allowed } RI->frame.subview_faces[RI->frame.num_subview_faces] = surf; RI->frame.num_subview_faces++; GL_SurfaceOccluded( surf );// fetch queries result break; default: HOST_ERROR( "R_AddSurfaceToDrawList: unknown drawlist( %i )\n", drawlist_type ); break; } return true; } /* ================ R_SetSurfaceUniforms ================ */ void R_SetSurfaceUniforms( word hProgram, msurface_t *surface, bool force ) { mextrasurf_t *es = surface->info; Vector4D lightstyles, lightdir; cl_entity_t *e = es->parent; msurface_t *s = surface; float r, g, b, a, *v; int map; // begin draw the sorted list if( force || ( RI->currentshader != &glsl_programs[hProgram] )) { // force to bind new shader GL_BindShader( &glsl_programs[hProgram] ); } material_t *mat = R_TextureAnimation( s )->material; glsl_program_t *shader = RI->currentshader; CDynLight *pl = RI->currentlight; // may be NULL gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); mtexinfo_t *tx = s->texinfo; mfaceinfo_t *land = tx->faceinfo; GLfloat viewMatrix[16]; if( !FBitSet( RI->params, RP_SHADOWVIEW )) { // setup some GL-states if( e->curstate.renderfx == SKYBOX_ENTITY ) { GL_DepthRange( 0.8f, 0.9f ); GL_ClipPlane( false ); } else { GL_DepthRange( gldepthmin, gldepthmax ); GL_ClipPlane( true ); } if( tr.waterlevel >= 3 && RP_NORMALPASS() && FBitSet( s->flags, SURF_DRAWTURB )) GL_Cull( GL_BACK ); else GL_Cull( GL_FRONT ); } if( !FBitSet( RI->params, RP_DEFERREDLIGHT )) { if( FBitSet( mat->flags, BRUSH_TRANSPARENT|BRUSH_HAS_ALPHA )) GL_AlphaTest( GL_TRUE ); else GL_AlphaTest( GL_FALSE ); } tr.modelorg = glm->GetModelOrigin(); // setup specified uniforms (and texture bindings) for( int i = 0; i < shader->numUniforms; i++ ) { uniform_t *u = &shader->uniforms[i]; switch( u->type ) { case UT_COLORMAP: if( Surf_CheckSubview( es )) u->SetValue( Surf_GetSubview( es )->texturenum ); else if( FBitSet( s->flags, SURF_MOVIE ) && RI->currententity->curstate.body ) u->SetValue( tr.cinTextures[es->cintexturenum-1] ); else u->SetValue( mat->gl_diffuse_id ); break; case UT_NORMALMAP: if( FBitSet( mat->flags, BRUSH_LIQUID ) && tr.waterTextures[0] > 0 ) u->SetValue( tr.waterTextures[(int)( tr.time * WATER_ANIMTIME ) % WATER_TEXTURES] ); else u->SetValue( mat->gl_normalmap_id ); break; case UT_GLOSSMAP: u->SetValue( mat->gl_specular_id ); break; case UT_DETAILMAP: if( land && land->terrain && land->terrain->indexmap.gl_diffuse_id != 0 ) u->SetValue( land->terrain->indexmap.gl_diffuse_id ); else u->SetValue( mat->gl_detailmap_id ); break; case UT_PROJECTMAP: if( pl && pl->type == LIGHT_SPOT ) u->SetValue( pl->spotlightTexture ); else u->SetValue( tr.whiteTexture ); break; case UT_SHADOWMAP: case UT_SHADOWMAP0: if( pl ) u->SetValue( pl->shadowTexture[0] ); else u->SetValue( tr.depthTexture ); break; case UT_SHADOWMAP1: if( pl ) u->SetValue( pl->shadowTexture[1] ); else u->SetValue( tr.depthTexture ); break; case UT_SHADOWMAP2: if( pl ) u->SetValue( pl->shadowTexture[2] ); else u->SetValue( tr.depthTexture ); break; case UT_SHADOWMAP3: if( pl ) u->SetValue( pl->shadowTexture[3] ); else u->SetValue( tr.depthTexture ); break; case UT_LIGHTMAP: if( R_FullBright( )) u->SetValue( tr.grayTexture ); else u->SetValue( tr.lightmaps[es->lightmaptexturenum].lightmap ); break; case UT_DELUXEMAP: if( R_FullBright( )) u->SetValue( tr.deluxemapTexture ); else u->SetValue( tr.lightmaps[es->lightmaptexturenum].deluxmap ); break; case UT_DECALMAP: // unacceptable for brushmodels u->SetValue( tr.whiteTexture ); break; case UT_SCREENMAP: u->SetValue( tr.screen_color ); break; case UT_DEPTHMAP: u->SetValue( tr.screen_depth ); break; case UT_ENVMAP0: case UT_ENVMAP: if( es->cubemap[0] != NULL ) u->SetValue( es->cubemap[0]->texture ); else u->SetValue( tr.whiteCubeTexture ); break; case UT_ENVMAP1: if( es->cubemap[1] != NULL ) u->SetValue( es->cubemap[1]->texture ); else u->SetValue( tr.whiteCubeTexture ); break; case UT_GLOWMAP: u->SetValue( mat->gl_glowmap_id ); break; case UT_LAYERMAP: if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && land && land->terrain ) u->SetValue( land->terrain->indexmap.gl_heightmap_id ); else u->SetValue( tr.whiteTexture ); break; case UT_HEIGHTMAP: u->SetValue( mat->gl_heightmap_id ); break; case UT_BSPPLANESMAP: u->SetValue( tr.packed_planes_texture ); break; case UT_BSPNODESMAP: u->SetValue( tr.packed_nodes_texture ); break; case UT_BSPLIGHTSMAP: u->SetValue( tr.packed_lights_texture ); break; case UT_FITNORMALMAP: u->SetValue( tr.normalsFitting ); break; case UT_MODELMATRIX: u->SetValue( &glm->modelMatrix[0] ); break; case UT_REFLECTMATRIX: if( Surf_CheckSubview( es )) Surf_GetSubview( es )->matrix.CopyToArray( viewMatrix ); else memcpy( viewMatrix, glState.identityMatrix, sizeof( float ) * 16 ); u->SetValue( &viewMatrix[0] ); break; case UT_SCREENSIZEINV: u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); break; case UT_ZFAR: u->SetValue( -tr.farclip * 1.74f ); break; case UT_LIGHTSTYLES: for( map = 0; map < MAXLIGHTMAPS; map++ ) { if( s->styles[map] != 255 ) lightstyles[map] = tr.lightstyle[s->styles[map]]; else lightstyles[map] = 0.0f; } u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); break; case UT_LIGHTSTYLEVALUES: u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); break; case UT_REALTIME: u->SetValue( (float)tr.time ); break; case UT_DETAILSCALE: u->SetValue( mat->detailScale[0], mat->detailScale[1] ); break; case UT_FOGPARAMS: if( e->curstate.renderfx == SKYBOX_ENTITY ) u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity ); else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); break; case UT_SHADOWPARMS: if( pl != NULL ) { float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); // depth scale and bias and shadowmap resolution u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); } else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); break; case UT_TEXOFFSET: u->SetValue( es->texofs[0], es->texofs[1] ); break; case UT_VIEWORIGIN: if( pl ) u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); else u->SetValue( tr.modelorg.x, tr.modelorg.y, tr.modelorg.z, e->hCachedMatrix ? 1.0f : 0.0f ); break; case UT_VIEWRIGHT: u->SetValue( GetVRight().x, GetVRight().y, GetVRight().z ); break; case UT_RENDERCOLOR: if( e->curstate.rendermode == kRenderNormal ) { r = g = b = a = 1.0f; } else { int sum = (e->curstate.rendercolor.r + e->curstate.rendercolor.g + e->curstate.rendercolor.b); if(( sum > 0 ) && !FBitSet( s->flags, SURF_CONVEYOR )) { r = e->curstate.rendercolor.r / 255.0f; g = e->curstate.rendercolor.g / 255.0f; b = e->curstate.rendercolor.b / 255.0f; } else { r = g = b = 1.0f; } if( e->curstate.rendermode != kRenderTransAlpha ) a = Q_max( e->curstate.renderamt / 255.0f, 0.25f ); else a = 1.0f; } u->SetValue( r, g, b, a ); break; case UT_SMOOTHNESS: if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && land && land->terrain ) { terrain_t *terra = land->terrain; u->SetValue( &terra->layermap.smoothness[0], terra->numLayers ); } else u->SetValue( mat->smoothness ); break; case UT_SHADOWMATRIX: if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); break; case UT_SHADOWSPLITDIST: v = RI->view.parallelSplitDistances; u->SetValue( v[0], v[1], v[2], v[3] ); break; case UT_TEXELSIZE: u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); break; case UT_GAMMATABLE: u->SetValue( &tr.gamma_table[0][0], 64 ); break; case UT_LIGHTDIR: if( pl ) { if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); } break; case UT_LIGHTDIFFUSE: if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); break; case UT_LIGHTORIGIN: if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); break; case UT_LIGHTVIEWPROJMATRIX: if( pl ) { GLfloat gl_lightViewProjMatrix[16]; pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); u->SetValue( &gl_lightViewProjMatrix[0] ); } break; case UT_DIFFUSEFACTOR: u->SetValue( tr.diffuseFactor ); break; case UT_AMBIENTFACTOR: if( pl && pl->type == LIGHT_DIRECTIONAL ) u->SetValue( tr.sun_ambient ); else u->SetValue( tr.ambientFactor ); break; case UT_SUNREFRACT: u->SetValue( tr.sun_refract ); break; case UT_LERPFACTOR: u->SetValue( es->lerpFactor ); break; case UT_REFRACTSCALE: u->SetValue( bound( 0.0f, mat->refractScale, 1.0f )); break; case UT_REFLECTSCALE: u->SetValue( bound( 0.0f, mat->reflectScale, 1.0f )); break; case UT_ABERRATIONSCALE: u->SetValue( bound( 0.0f, mat->aberrationScale, 1.0f )); break; case UT_BOXMINS: if( world->num_cubemaps > 0 ) { Vector mins[2]; mins[0] = es->cubemap[0]->mins; mins[1] = es->cubemap[1]->mins; u->SetValue( &mins[0][0], 2 ); } break; case UT_BOXMAXS: if( world->num_cubemaps > 0 ) { Vector maxs[2]; maxs[0] = es->cubemap[0]->maxs; maxs[1] = es->cubemap[1]->maxs; u->SetValue( &maxs[0][0], 2 ); } break; case UT_CUBEORIGIN: if( world->num_cubemaps > 0 ) { Vector origin[2]; origin[0] = es->cubemap[0]->origin; origin[1] = es->cubemap[1]->origin; u->SetValue( &origin[0][0], 2 ); } break; case UT_CUBEMIPCOUNT: if( world->num_cubemaps > 0 ) { r = Q_max( 1, es->cubemap[0]->numMips - cv_cube_lod_bias->value ); g = Q_max( 1, es->cubemap[1]->numMips - cv_cube_lod_bias->value ); u->SetValue( r, g ); } break; case UT_LIGHTNUMS0: u->SetValue( (float)e->lights[0], (float)e->lights[1], (float)e->lights[2], (float)e->lights[3] ); break; case UT_LIGHTNUMS1: u->SetValue( (float)e->lights[4], (float)e->lights[5], (float)e->lights[6], (float)e->lights[7] ); break; default: ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); break; } } } /* ================ R_RenderDynLightList ================ */ void R_BuildFaceListForLight( CDynLight *pl, bool solid ) { int i; RI->currententity = GET_ENTITY( 0 ); RI->currentmodel = RI->currententity->model; RI->frame.light_faces.Purge(); RI->frame.light_grass.Purge(); tr.modelorg = pl->origin; if( solid ) { // only visible polys passed through the light list for( i = 0; i < RI->frame.solid_faces.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_faces[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; gl_state_t *glm = GL_GetCache( es->parent->hCachedMatrix ); RI->currententity = es->parent; RI->currentmodel = RI->currententity->model; if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) continue; // fast reject if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) continue; bool worldpos = R_StaticEntity( es->parent ) ? true : false; CFrustum *frustum = (worldpos) ? &pl->frustum : NULL; tr.modelorg = glm->GetModelOrigin(); R_AddGrassToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); if( R_CullSurface( entry->m_pSurf, tr.modelorg, frustum )) continue; // move from main list into light list R_AddSurfaceToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); } } else { // only visible polys passed through the light list for( i = 0; i < RI->frame.trans_list.Count(); i++ ) { CTransEntry *entry = &RI->frame.trans_list[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; gl_state_t *glm = GL_GetCache( es->parent->hCachedMatrix ); RI->currententity = es->parent; RI->currentmodel = RI->currententity->model; if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) continue; // fast reject if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) continue; bool worldpos = R_StaticEntity( es->parent ) ? true : false; CFrustum *frustum = (worldpos) ? &pl->frustum : NULL; tr.modelorg = glm->GetModelOrigin(); R_AddGrassToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); if( R_CullSurface( entry->m_pSurf, tr.modelorg, frustum )) continue; // move from main list into light list R_AddSurfaceToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); } } } /* ================ R_DrawLightForSurfList setup light projection for each ================ */ void R_DrawLightForSurfList( CDynLight *pl ) { material_t *cached_material = NULL; cl_entity_t *cached_entity = NULL; bool flush_buffer = false; int startv, endv; pglBindVertexArray( world->vertex_array_object ); pglBlendFunc( GL_ONE, GL_ONE ); float y2 = (float)RI->view.port[3] - pl->h - pl->y; pglScissor( pl->x, y2, pl->w, pl->h ); numTempElems = 0; for( int i = 0; i < RI->frame.light_faces.Count(); i++ ) { CSolidEntry *entry = &RI->frame.light_faces[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; cl_entity_t *e = RI->currententity = es->parent; RI->currentmodel = e->model; msurface_t *s = entry->m_pSurf; if( !entry->m_hProgram ) continue; material_t *mat = R_TextureAnimation( s )->material; if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) flush_buffer = true; if( cached_entity != RI->currententity ) flush_buffer = true; if( cached_material != mat ) flush_buffer = true; if( flush_buffer ) { if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; numTempElems = 0; } flush_buffer = false; startv = MAX_MAP_ELEMS; endv = 0; } // now cache values cached_entity = RI->currententity; cached_material = mat; if( numTempElems == 0 ) // new chain has started, apply uniforms R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); startv = Q_min( startv, es->firstvertex ); endv = Q_max( es->firstvertex + es->numverts, endv ); R_DrawSurface( es ); } if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; startv = MAX_MAP_ELEMS; numTempElems = 0; endv = 0; } R_DrawLightForGrass( pl ); } /* ================ R_RenderDynLightList draw dynamic lights for world and bmodels ================ */ void R_RenderDynLightList( bool solid ) { if( FBitSet( RI->params, RP_ENVVIEW|RP_SKYVIEW )) return; if( !FBitSet( RI->view.flags, RF_HASDYNLIGHTS )) return; if( R_FullBright( )) return; GL_Blend( GL_TRUE ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_FALSE ); CDynLight *pl = tr.dlights; for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) { if( pl->Expired( )) continue; if( pl->type == LIGHT_SPOT || pl->type == LIGHT_OMNI ) { if( !pl->Active( )) continue; if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) continue; if( R_CullFrustum( &pl->frustum )) continue; pglEnable( GL_SCISSOR_TEST ); } else { // couldn't use scissor for sunlight pglDisable( GL_SCISSOR_TEST ); } RI->currentlight = pl; // draw world from light position R_BuildFaceListForLight( pl, solid ); if( !RI->frame.light_faces.Count() && !RI->frame.light_grass.Count() ) continue; // no interaction with this light? R_DrawLightForSurfList( pl ); } GL_CleanupDrawState(); pglDisable( GL_SCISSOR_TEST ); RI->currentlight = NULL; } /* ================ R_RenderDeferredBrushList ================ */ void R_RenderDeferredBrushList( void ) { cl_entity_t *cached_entity = NULL; material_t *cached_material = NULL; mcubemap_t *cached_cubemap[2]; bool flush_buffer = false; int startv, endv; if( !RI->frame.solid_faces.Count() ) return; GL_Blend( GL_FALSE ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_TRUE ); pglAlphaFunc( GL_GREATER, 0.25f ); numTempElems = 0; if( GL_Support( R_SEAMLESS_CUBEMAP )) pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); pglBindVertexArray( world->vertex_array_object ); cached_cubemap[0] = &world->defaultCubemap; cached_cubemap[1] = &world->defaultCubemap; for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_faces[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; cl_entity_t *e = RI->currententity = es->parent; RI->currentmodel = e->model; msurface_t *s = entry->m_pSurf; if( FBitSet( RI->params, RP_DEFERREDSCENE )) entry->m_hProgram = es->deferredScene.GetHandle(); else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) entry->m_hProgram = es->deferredLight.GetHandle(); else entry->m_hProgram = 0; if( !entry->m_hProgram ) continue; material_t *mat = R_TextureAnimation( s )->material; if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) flush_buffer = true; if( cached_entity != RI->currententity ) flush_buffer = true; if( cached_material != mat ) flush_buffer = true; if( IsReflectShader( RI->currentshader ) && ( cached_cubemap[0] != es->cubemap[0] || cached_cubemap[1] != es->cubemap[1] )) flush_buffer = true; if( flush_buffer ) { if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; numTempElems = 0; } flush_buffer = false; startv = MAX_MAP_ELEMS; endv = 0; } // now cache values cached_entity = RI->currententity = es->parent; RI->currentmodel = es->parent->model; cached_cubemap[0] = es->cubemap[0]; cached_cubemap[1] = es->cubemap[1]; cached_material = mat; if( numTempElems == 0 ) // new chain has started, apply uniforms R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); startv = Q_min( startv, es->firstvertex ); endv = Q_max( es->firstvertex + es->numverts, endv ); R_DrawSurface( es ); } if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; startv = MAX_MAP_ELEMS; numTempElems = 0; endv = 0; } if( GL_Support( R_SEAMLESS_CUBEMAP )) pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); GL_DepthRange( gldepthmin, gldepthmax ); GL_SelectTexture( glConfig.max_texture_units - 1 ); // force to cleanup all the units GL_CleanUpTextureUnits( 0 ); GL_AlphaTest( GL_FALSE ); GL_ClipPlane( true ); if( FBitSet( RI->params, RP_DEFERREDSCENE )) { // render all decals with alpha-channel R_RenderDecalsSolidList( DRAWLIST_SOLID ); // render all decals with gray base R_RenderDecalsSolidList( DRAWLIST_TRANS ); } // draw grass on visible surfaces R_RenderGrassOnList(); GL_CleanupDrawState(); } /* ================ R_RenderSolidBrushList ================ */ void R_RenderSolidBrushList( void ) { cl_entity_t *cached_entity = NULL; material_t *cached_material = NULL; int cached_mirror = -1; int cached_lightmap = -1; qboolean flush_buffer = false; mcubemap_t *cached_cubemap[2]; int startv, endv; if( !RI->frame.solid_faces.Count() ) return; GL_Blend( GL_FALSE ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_TRUE ); pglAlphaFunc( GL_GREATER, 0.25f ); numTempElems = 0; if( GL_Support( R_SEAMLESS_CUBEMAP )) pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); pglBindVertexArray( world->vertex_array_object ); cached_cubemap[0] = &world->defaultCubemap; cached_cubemap[1] = &world->defaultCubemap; for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_faces[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; cl_entity_t *e = RI->currententity = es->parent; RI->currentmodel = e->model; msurface_t *s = entry->m_pSurf; if( !entry->m_hProgram ) continue; material_t *mat = R_TextureAnimation( s )->material; if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) flush_buffer = true; if( cached_entity != RI->currententity ) flush_buffer = true; if( cached_material != mat ) flush_buffer = true; if( cached_lightmap != es->lightmaptexturenum ) flush_buffer = true; if( cached_mirror != es->subtexture[glState.stack_position] ) flush_buffer = true; if( IsReflectShader( RI->currentshader ) && ( cached_cubemap[0] != es->cubemap[0] || cached_cubemap[1] != es->cubemap[1] )) flush_buffer = true; if( flush_buffer ) { if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; numTempElems = 0; } flush_buffer = false; startv = MAX_MAP_ELEMS; endv = 0; } // now cache values cached_entity = RI->currententity = es->parent; cached_lightmap = es->lightmaptexturenum; RI->currentmodel = es->parent->model; cached_mirror = es->subtexture[glState.stack_position]; cached_cubemap[0] = es->cubemap[0]; cached_cubemap[1] = es->cubemap[1]; cached_material = mat; if( numTempElems == 0 ) // new chain has started, apply uniforms R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); startv = Q_min( startv, es->firstvertex ); endv = Q_max( es->firstvertex + es->numverts, endv ); R_DrawSurface( es ); } if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; startv = MAX_MAP_ELEMS; numTempElems = 0; endv = 0; } if( GL_Support( R_SEAMLESS_CUBEMAP )) pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); GL_DepthRange( gldepthmin, gldepthmax ); GL_CleanupDrawState(); GL_AlphaTest( GL_FALSE ); GL_ClipPlane( true ); GL_Cull( GL_FRONT ); // render all decals with alpha-channel R_RenderDecalsSolidList( DRAWLIST_SOLID ); // draw grass on visible surfaces R_RenderGrassOnList(); // draw dynamic lighting for world and bmodels R_RenderDynLightList( true ); GL_CleanupDrawState(); // render all decals with gray base R_RenderDecalsSolidList( DRAWLIST_TRANS ); } void R_RenderTransSurface( CTransEntry *entry ) { if( entry->m_bDrawType != DRAWTYPE_SURFACE ) return; if( !entry->m_hProgram ) return; mextrasurf_t *es = entry->m_pSurf->info; cl_entity_t *e = RI->currententity = es->parent; msurface_t *s = entry->m_pSurf; RI->currentmodel = e->model; int startv = MAX_MAP_ELEMS; numTempElems = 0; int endv = 0; if( ScreenCopyRequired( &glsl_programs[entry->m_hProgram] )) { if( !FBitSet( s->flags, SURF_OCCLUDED )) { entry->RequestScreenColor(); // underwater fog if( R_WaterEntity( RI->currentmodel )) entry->RequestScreenDepth(); r_stats.c_screen_copy++; } } pglBindVertexArray( world->vertex_array_object ); R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, true ); startv = Q_min( startv, es->firstvertex ); endv = Q_max( es->firstvertex + es->numverts, endv ); if( FBitSet( glsl_programs[entry->m_hProgram].status, SHADER_ADDITIVE )) { GL_DepthMask( GL_FALSE ); GL_Blend( GL_TRUE ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); } else { GL_DepthMask( GL_TRUE ); GL_Blend( GL_FALSE ); } R_DrawSurface( es ); if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; numTempElems = 0; } GL_DepthMask( GL_TRUE ); GL_Blend( GL_FALSE ); GL_ClipPlane( true ); GL_Cull( GL_FRONT ); } /* ================ R_RenderShadowBrushList ================ */ void R_RenderShadowBrushList( void ) { int cached_matrix = -1; material_t *cached_material = NULL; qboolean flush_buffer = false; int startv, endv; if( !RI->frame.solid_faces.Count() ) return; pglBindVertexArray( world->vertex_array_object ); pglAlphaFunc( GL_GREATER, 0.25f ); numTempElems = 0; for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_faces[i]; if( entry->m_bDrawType != DRAWTYPE_SURFACE ) continue; mextrasurf_t *es = entry->m_pSurf->info; cl_entity_t *e = RI->currententity = es->parent; RI->currentmodel = e->model; msurface_t *s = entry->m_pSurf; if( !entry->m_hProgram ) continue; material_t *mat = s->texinfo->texture->material; if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) flush_buffer = true; if( cached_matrix != es->parent->hCachedMatrix ) flush_buffer = true; if( cached_material != mat ) flush_buffer = true; if( flush_buffer ) { if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; numTempElems = 0; } flush_buffer = false; startv = MAX_MAP_ELEMS; endv = 0; } // now cache values cached_matrix = es->parent->hCachedMatrix; cached_material = mat; if( numTempElems == 0 ) // new chain has started, apply uniforms R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); startv = Q_min( startv, es->firstvertex ); endv = Q_max( es->firstvertex + es->numverts, endv ); R_DrawSurface( es ); } if( numTempElems ) { pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); r_stats.c_total_tris += (numTempElems / 3); r_stats.num_flushes++; startv = MAX_MAP_ELEMS; numTempElems = 0; endv = 0; } R_RenderShadowGrassOnList(); GL_CleanupDrawState(); }