/* gl_rmain.cpp - renderer main loop Copyright (C) 2013 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "hud.h" #include "cl_util.h" #include "camera.h" #include "entity_types.h" #include "gl_local.h" #include #include "gl_aurora.h" #include "gl_rpart.h" #include "gl_studio.h" #include "gl_sprite.h" #include "event_api.h" #include "gl_world.h" #include "gl_grass.h" extern "C" int DLLEXPORT HUD_GetRenderInterface( int version, render_api_t *renderfuncs, render_interface_t *callback ); ref_globals_t tr; ref_instance_t *RI = NULL; ref_stats_t r_stats; ref_buildstats_t r_buildstats; char r_speeds_msg[2048]; char r_depth_msg[2048]; model_t *worldmodel = NULL; float gldepthmin, gldepthmax; int sunSize[MAX_SHADOWMAPS] = { 1024, 1024, 1024, 1024 }; bool R_SkyIsVisible( void ) { return FBitSet( RI->view.flags, RF_SKYVISIBLE ) ? true : false; } void R_InitRefState( void ) { glState.stack_position = 0; RI = &glState.stack[glState.stack_position]; } const char *R_GetNameForView( void ) { static char passName[256]; passName[0] = '\0'; if( FBitSet( RI->params, RP_MIRRORVIEW )) Q_strncat( passName, "mirror ", sizeof( passName )); if( FBitSet( RI->params, RP_ENVVIEW )) Q_strncat( passName, "cubemap ", sizeof( passName )); if( FBitSet( RI->params, RP_SKYVIEW )) Q_strncat( passName, "skybox ", sizeof( passName )); if( FBitSet( RI->params, RP_SHADOWVIEW )) Q_strncat( passName, "shadow ", sizeof( passName )); if( RP_NORMALPASS( )) Q_strncat( passName, "general ", sizeof( passName )); return passName; } /* =============== R_BuildViewPassHierarchy debug thing =============== */ void R_BuildViewPassHierarchy( void ) { char empty[MAX_REF_STACK]; if( (int)r_speeds->value == 8 ) { int num_faces = 0; if( glState.stack_position > 0 ) num_faces = R_GetPrevInstance()->frame.num_subview_faces; unsigned int i; for( i = 0; i < glState.stack_position; i++ ) empty[i] = ' '; empty[i] = '\0'; // build pass hierarchy const char *string = va( "%s->%d %s (%d subview)\n", empty, glState.stack_position, R_GetNameForView( ), num_faces ); Q_strncat( r_depth_msg, string, sizeof( r_depth_msg )); } } void R_ClearFrameLists( void ) { RI->frame.solid_faces.Purge(); RI->frame.solid_meshes.Purge(); RI->frame.grass_list.Purge(); RI->frame.trans_list.Purge(); RI->frame.light_meshes.Purge(); RI->frame.light_faces.Purge(); RI->frame.light_grass.Purge(); RI->frame.primverts.Purge(); RI->frame.num_subview_faces = 0; } void R_ResetRefState( void ) { ref_instance_t *prevRI; ASSERT( glState.stack_position > 0 ); prevRI = &glState.stack[glState.stack_position - 1]; RI = &glState.stack[glState.stack_position]; // copy params from old refresh RI->params = 0; memcpy( &RI->view, &prevRI->view, sizeof( ref_viewcache_t )); memcpy( &RI->glstate, &prevRI->glstate, sizeof( ref_glstate_t )); RI->view.client_frame = 0; R_ClearFrameLists(); // send info about visible lights if( FBitSet( prevRI->view.flags, RF_HASDYNLIGHTS )) SetBits( RI->view.flags, RF_HASDYNLIGHTS ); RI->currententity = NULL; RI->currentmodel = NULL; RI->currentlight = NULL; GL_BindShader( NULL ); } void R_PushRefState( void ) { if( ++glState.stack_position >= MAX_REF_STACK ) HOST_ERROR( "Refresh stack overflow\n" ); RI = &glState.stack[glState.stack_position]; R_ResetRefState(); } void R_PopRefState( void ) { if( --glState.stack_position < 0 ) HOST_ERROR( "Refresh stack underflow\n" ); RI = &glState.stack[glState.stack_position]; // frametime is valid only for normal pass if( RP_NORMALPASS( )) tr.frametime = tr.saved_frametime; else tr.frametime = 0.0; // need to rebuild scissors R_SetupDynamicLights(); } ref_instance_t *R_GetPrevInstance( void ) { ASSERT( glState.stack_position > 0 ); return &glState.stack[glState.stack_position - 1]; } const Vector gl_state_t :: GetModelOrigin( void ) { if( m_bSkyEntity ) { if( tr.sky_speed ) { Vector trans = RI->view.origin - tr.sky_origin; trans = trans - (RI->view.origin - tr.sky_world_origin) / tr.sky_speed; Vector skypos = tr.sky_origin + (RI->view.origin - tr.sky_world_origin) / tr.sky_speed; return skypos - transform.GetOrigin(); } return tr.sky_origin - transform.GetOrigin(); } return transform.VectorITransform( RI->view.origin ); } /* =============== GL_CacheState build matrix pack or find already existed =============== */ unsigned short GL_CacheState( const Vector &origin, const Vector &angles, bool skyentity ) { gl_state_t state; int i; state.transform = matrix4x4( origin, angles, 1.0f ); state.transform.CopyToArray( state.modelMatrix ); state.m_bSkyEntity = skyentity; for( i = 0; i < tr.cached_state.Count(); i++ ) { // NOTE: (MVP == MV == M). So no reason to compare all the matrices if( !memcmp( tr.cached_state[i].modelMatrix, state.modelMatrix, sizeof( state.modelMatrix ))) { if( tr.cached_state[i].m_bSkyEntity == skyentity ) return i; } } // store results tr.cached_state.AddToTail( state ); return tr.cached_state.Count() - 1; } /* =============== GL_CacheState build matrix pack or find already existed =============== */ gl_state_t *GL_GetCache( word hCachedMatrix ) { static gl_state_t skycache; gl_state_t *glm; if( hCachedMatrix >= tr.cached_state.Count( )) { ASSERT( tr.cached_state.Count() > 0 ); ALERT( at_aiconsole, "request invalid cachenum %d\n", hCachedMatrix ); return &tr.cached_state[WORLD_MATRIX]; } glm = &tr.cached_state[hCachedMatrix]; // sky entities can't be cached properly... if( glm->m_bSkyEntity ) { Vector origin = glm->transform.GetOrigin(); Vector trans = GetVieworg() - tr.sky_origin; skycache.transform = matrix4x4( origin + trans, g_vecZero, 1.0f ); skycache.transform.CopyToArray( skycache.modelMatrix ); skycache.m_bSkyEntity = glm->m_bSkyEntity; return &skycache; } return glm; } /* =============== R_MarkWorldVisibleFaces instead of RecursiveWorldNode =============== */ void R_MarkWorldVisibleFaces( model_t *model ) { float maxdist = 0.0f; msurface_t **mark; mleaf_t *leaf; int i, j; memset( RI->view.visfaces, 0x00, (worldmodel->numsurfaces + 7) >> 3 ); memset( RI->view.vislight, 0x00, (world->numworldlights + 7) >> 3 ); ClearBounds( RI->view.visMins, RI->view.visMaxs ); if( !model ) return; // just clear visibility and out // always skip the leaf 0, because is outside leaf for( i = 1, leaf = &model->leafs[1]; i < model->numleafs + 1; i++, leaf++ ) { mextraleaf_t *eleaf = LEAF_INFO( leaf, model ); // named like personal vaporisers manufacturer he-he if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces )) { if( RI->view.frustum.CullBox( eleaf->mins, eleaf->maxs )) continue; // do additional culling in dev_overview mode if( FBitSet( RI->params, RP_DRAW_OVERVIEW ) && R_CullNodeTopView( (mnode_t *)leaf )) continue; // deal with model fragments in this leaf if( leaf->efrags && RP_NORMALPASS( )) STORE_EFRAGS( &leaf->efrags, tr.realframecount ); if( leaf->contents == CONTENTS_EMPTY ) { // unrolled for speedup reasons RI->view.visMins[0] = Q_min( RI->view.visMins[0], eleaf->mins[0] ); RI->view.visMaxs[0] = Q_max( RI->view.visMaxs[0], eleaf->maxs[0] ); RI->view.visMins[1] = Q_min( RI->view.visMins[1], eleaf->mins[1] ); RI->view.visMaxs[1] = Q_max( RI->view.visMaxs[1], eleaf->maxs[1] ); RI->view.visMins[2] = Q_min( RI->view.visMins[2], eleaf->mins[2] ); RI->view.visMaxs[2] = Q_max( RI->view.visMaxs[2], eleaf->maxs[2] ); } r_stats.c_world_leafs++; if( leaf->nummarksurfaces ) { for( j = 0, mark = leaf->firstmarksurface; j < leaf->nummarksurfaces; j++, mark++ ) { msurface_t *surf = *mark; // construct grass and update leaf bounds if( surf->info->grasscount && RP_NORMALPASS( )) R_PrecacheGrass( surf, eleaf ); SETVISBIT( RI->view.visfaces, *mark - model->surfaces ); } } } } // now we have actual vismins\vismaxs and can calc farplane distance for( i = 0; i < 8; i++ ) { Vector v, dir; float dist; v[0] = ( i & 1 ) ? RI->view.visMins[0] : RI->view.visMaxs[0]; v[1] = ( i & 2 ) ? RI->view.visMins[1] : RI->view.visMaxs[1]; v[2] = ( i & 4 ) ? RI->view.visMins[2] : RI->view.visMaxs[2]; dir = v - RI->view.origin; dist = DotProduct( dir, dir ); maxdist = Q_max( dist, maxdist ); } // RI->view.farClip = sqrt( maxdist ) + 64.0f; // add some bias } /* =============== R_SetupViewCache check for changes and build visibility =============== */ static void R_SetupViewCache( const ref_viewpass_t *rvp ) { const ref_overview_t *ov = GET_OVERVIEW_PARMS(); model_t *model = worldmodel; RI->view.changed = 0; // always clearing changes at start of frame if( !model && FBitSet( RI->params, RP_DRAW_WORLD )) HOST_ERROR( "R_SetupViewCache: NULL worldmodel\n" ); // client frame was changed: refresh the draw lists // FIXME: get rid of this when renderer will be finalized if( RI->view.client_frame != tr.realframecount ) SetBits( RI->view.changed, RC_FORCE_UPDATE ); if( tr.params_changed ) SetBits( RI->view.changed, RC_FORCE_UPDATE ); // viewentity was changed if( RI->view.entity != rvp->viewentity ) { SetBits( RI->view.changed, RC_FORCE_UPDATE ); RI->view.entity = rvp->viewentity; } // viewport was changed, recompute if( memcmp( RI->view.port, rvp->viewport, sizeof( RI->view.port ))) { memcpy( RI->view.port, rvp->viewport, sizeof( RI->view.port )); if( RP_NORMALPASS( ) && !FBitSet( RI->params, RP_DEFERREDLIGHT )) { int x, x2, y, y2; // set up viewport (main, playersetup) x = floor( RI->view.port[0] * glState.width / glState.width ); x2 = ceil(( RI->view.port[0] + RI->view.port[2] ) * glState.width / glState.width ); y = floor( glState.height - RI->view.port[1] * glState.height / glState.height ); y2 = ceil( glState.height - ( RI->view.port[1] + RI->view.port[3] ) * glState.height / glState.height ); RI->glstate.viewport[0] = x; RI->glstate.viewport[1] = y2; RI->glstate.viewport[2] = x2 - x; RI->glstate.viewport[3] = y - y2; } else memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport )); // additional passes } // vieworigin was changed if( RI->view.origin != rvp->vieworigin ) { if( !FBitSet( RI->params, RP_MERGE_PVS )) RI->view.pvspoint = rvp->vieworigin; SetBits( RI->view.changed, RC_ORIGIN_CHANGED ); RI->view.origin = rvp->vieworigin; } if( RI->view.angles != rvp->viewangles ) { SetBits( RI->view.changed, RC_ANGLES_CHANGED ); RI->view.angles = rvp->viewangles; } // check if overview settings was changed if( FBitSet( RI->params, RP_DRAW_OVERVIEW ) && memcmp( &RI->view.over, ov, sizeof( RI->view.over ))) { SetBits( RI->view.changed, RC_ORIGIN_CHANGED|RC_ANGLES_CHANGED|RC_FOV_CHANGED ); memcpy( &RI->view.over, ov, sizeof( RI->view.over )); } if( FBitSet( RI->view.changed, RC_ORIGIN_CHANGED ) && ( model != NULL )) { mleaf_t *leaf = Mod_PointInLeaf( RI->view.pvspoint, model->nodes ); if(( RI->view.leaf == NULL ) || ( RI->view.leaf != leaf )) { SetBits( RI->view.changed, RC_VIEWLEAF_CHANGED ); RI->view.leaf = leaf; } } // development cvars invoke to recompute PVS if(( RI->view.novis_cached != CVAR_TO_BOOL( r_novis )) || ( RI->view.lockpvs_cached != CVAR_TO_BOOL( r_lockpvs ))) { SetBits( RI->view.changed, RC_VIEWLEAF_CHANGED ); RI->view.novis_cached = CVAR_TO_BOOL( r_novis ); RI->view.lockpvs_cached = CVAR_TO_BOOL( r_lockpvs ); } // now setup the frame visibility if( FBitSet( RI->view.changed, RC_VIEWLEAF_CHANGED )) { bool mergevis = FBitSet( RI->params, RP_MERGE_PVS ) ? true : false; bool fullvis = false; if( CVAR_TO_BOOL( r_novis ) || FBitSet( RI->params, RP_DRAW_OVERVIEW ) || ( !RI->view.leaf )) fullvis = true; ENGINE_SET_PVS( RI->view.pvspoint, REFPVS_RADIUS, RI->view.pvsarray, mergevis, fullvis ); // add skyorigin in each pass in case it was specified if( tr.sky_origin != g_vecZero && !fullvis ) ENGINE_SET_PVS( tr.sky_origin, REFPVS_RADIUS, RI->view.pvsarray, true, fullvis ); SetBits( RI->view.changed, RC_PVS_CHANGED ); } // check FOV for changes if(( RI->view.fov_x != rvp->fov_x ) || ( RI->view.fov_y != rvp->fov_y )) { SetBits( RI->view.changed, RC_FOV_CHANGED ); RI->view.fov_x = rvp->fov_x; RI->view.fov_y = rvp->fov_y; if( RI->view.fov_x <= 0.0f || RI->view.fov_y <= 0.0f ) HOST_ERROR( "R_SetupViewCache: bad fov!\n" ); if( IS_NAN( RI->view.fov_x ) || IS_NAN( RI->view.fov_y )) HOST_ERROR( "R_SetupViewCache: NAN fov!\n" ); RI->view.lodScale = tan( DEG2RAD( RI->view.fov_x ) * 0.5f ); } if( FBitSet( RI->view.changed, RC_ORIGIN_CHANGED|RC_ANGLES_CHANGED )) { // build the transformation matrix for the given view angles and view origin RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles, 1.0f ); RI->view.planedist = DotProduct( GetVieworg(), GetVForward() ); // create modelview RI->view.worldMatrix.CreateModelview(); // init quake world orientation RI->view.worldMatrix.ConcatRotate( -RI->view.angles[2], 1, 0, 0 ); RI->view.worldMatrix.ConcatRotate( -RI->view.angles[0], 0, 1, 0 ); RI->view.worldMatrix.ConcatRotate( -RI->view.angles[1], 0, 0, 1 ); RI->view.worldMatrix.ConcatTranslate( -RI->view.origin[0], -RI->view.origin[1], -RI->view.origin[2] ); RI->view.worldMatrix.CopyToArray( RI->glstate.modelviewMatrix ); } // first calculation frustum ignores precision farclip, and will be recomputed with actual farclip if( FBitSet( RI->view.changed, RC_ORIGIN_CHANGED|RC_ANGLES_CHANGED|RC_FOV_CHANGED )) { // compute initial farclip RI->view.farClip = tr.farclip * 1.74f; if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) { RI->view.frustum.InitOrthogonal( RI->view.matrix, ov->xLeft, ov->xRight, ov->yBottom, ov->yTop, ov->zNear, ov->zFar ); RI->view.projectionMatrix.CreateOrtho( ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); } else RI->view.frustum.InitProjection( RI->view.matrix, 0.0f, RI->view.farClip, RI->view.fov_x, RI->view.fov_y ); SetBits( RI->view.changed, RC_FRUSTUM_CHANGED ); } // time to determine visible leafs and recalc farclip if( FBitSet( RI->view.changed, RC_PVS_CHANGED|RC_FRUSTUM_CHANGED|RC_FORCE_UPDATE )) { CFrustum *frustum = &RI->view.frustum; float maxdist = 0.0f; msurface_t *surf; mextrasurf_t *esrf; int i, j; if( FBitSet( RI->params, RP_SKYVIEW )) { for( i = 0; i < 6; i++ ) { RI->view.skyMins[0][i] = RI->view.skyMins[1][i] = -1.0f; RI->view.skyMaxs[0][i] = RI->view.skyMaxs[1][i] = 1.0f; } // force to draw skybox (used for create default cubemap) SetBits( RI->view.flags, RF_SKYVISIBLE ); // force to ignore draw the world model = NULL; } else { for( i = 0; i < 6; i++ ) { RI->view.skyMins[0][i] = RI->view.skyMins[1][i] = 9999999.0f; RI->view.skyMaxs[0][i] = RI->view.skyMaxs[1][i] = -9999999.0f; } ClearBits( RI->view.flags, RF_SKYVISIBLE ); // now sky is invisible } // we have dynamic lights for this frame if( HasDynamicLights( )) SetBits( RI->view.flags, RF_HASDYNLIGHTS ); else ClearBits( RI->view.flags, RF_HASDYNLIGHTS ); R_MarkWorldVisibleFaces( model ); // update the frustum if( !FBitSet( RI->params, RP_DRAW_OVERVIEW )) { RI->view.frustum.InitProjection( RI->view.matrix, 0.0f, RI->view.farClip, RI->view.fov_x, RI->view.fov_y ); RI->view.projectionMatrix.CreateProjection( RI->view.fov_x, RI->view.fov_y, Z_NEAR, RI->view.farClip ); RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); SetBits( RI->view.changed, RC_FRUSTUM_CHANGED ); } // recompute worldview projection RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); R_ClearFrameLists(); // update the split frustum if( model != NULL && RP_NORMALPASS() && !FBitSet( RI->params, RP_DRAW_OVERVIEW ) && FBitSet( RI->view.changed, RC_FRUSTUM_CHANGED )) { float farClip = RI->view.farClip; float lambda = bound( 0.1f, r_shadow_split_weight->value, 1.0f ); float ratio = farClip / Z_NEAR; Vector planeOrigin; float zNear, zFar; int i; // initialize all the frustums for( i = 0; i < MAX_SHADOWMAPS; i++ ) RI->view.splitFrustum[i] = RI->view.frustum; for( i = 1; i < MAX_SHADOWMAPS; i++ ) { float si = i / (float)(MAX_SHADOWMAPS); zNear = lambda * (Z_NEAR * powf( ratio, si )) + (1.0f - lambda) * (Z_NEAR + (farClip - Z_NEAR) * si); zFar = zNear * 1.005f; planeOrigin = GetVieworg() + GetVForward() * zNear; RI->view.splitFrustum[i].SetPlane( FRUSTUM_NEAR, GetVForward(), DotProduct( planeOrigin, GetVForward())); planeOrigin = GetVieworg() + GetVForward() * zFar; RI->view.splitFrustum[i-1].SetPlane( FRUSTUM_FAR, -GetVForward(), DotProduct( planeOrigin, -GetVForward())); RI->view.parallelSplitDistances[i - 1] = zFar; } planeOrigin = GetVieworg() + GetVForward() * farClip; RI->view.splitFrustum[NUM_SHADOW_SPLITS].SetPlane( FRUSTUM_FAR, -GetVForward(), DotProduct( planeOrigin, -GetVForward())); RI->view.parallelSplitDistances[NUM_SHADOW_SPLITS] = farClip; } // don't draw the entities while render skybox or cubemap if( !FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) { // before process of tr.draw_entities // we can add muzzleflashes here R_RunViewmodelEvents(); // brush faces not added here! // only marks as visible in RI->view.visfaces array for( i = 0; i < tr.num_draw_entities; i++ ) { RI->currententity = tr.draw_entities[i]; RI->currentmodel = RI->currententity->model; switch( RI->currentmodel->type ) { case mod_brush: R_MarkSubmodelVisibleFaces(); break; case mod_studio: R_AddStudioToDrawList( RI->currententity ); break; case mod_sprite: R_AddSpriteToDrawList( RI->currententity ); break; default: // HOST_ERROR( "R_SetupViewCache: mod_bad\n" ); break; } } // add particles to deferred list g_pParticleSystems.UpdateSystems(); g_pParticles.Update(); } // create drawlist for faces, do additional culling for world faces for( i = 0; model != NULL && i < world->numsortedfaces; i++ ) { ASSERT( world->sortedfaces != NULL ); j = world->sortedfaces[i]; ASSERT( j >= 0 && j < model->numsurfaces ); if( CHECKVISBIT( RI->view.visfaces, j )) { surf = model->surfaces + j; esrf = surf->info; // submodel faces already passed through this // operation but world is not if( FBitSet( surf->flags, SURF_OF_SUBMODEL )) { RI->currententity = esrf->parent; RI->currentmodel = RI->currententity->model; R_AddGrassToDrawList( surf, DRAWLIST_SOLID ); } else { RI->currententity = GET_ENTITY( 0 ); RI->currentmodel = RI->currententity->model; esrf->parent = RI->currententity; // setup dynamic upcast R_AddGrassToDrawList( surf, DRAWLIST_SOLID ); if( R_CullSurface( surf, GetVieworg(), frustum )) { CLEARVISBIT( RI->view.visfaces, j ); // not visible continue; } // surface has passed all visibility checks // and can be update some data (lightmaps, mirror matrix, etc) R_UpdateSurfaceParams( surf ); } // store world translucent watery (if transparent water is support) if( FBitSet( surf->flags, SURF_DRAWTURB ) && !FBitSet( surf->flags, SURF_OF_SUBMODEL )) { if( FBitSet( world->features, WORLD_WATERALPHA )) R_AddSurfaceToDrawList( surf, DRAWLIST_TRANS ); else R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID ); } else if( FBitSet( surf->flags, SURF_DRAWSKY )) { SetBits( RI->view.flags, RF_SKYVISIBLE ); R_AddSkyBoxSurface( surf ); } else if( R_OpaqueEntity( RI->currententity )) { R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID ); } else { R_AddSurfaceToDrawList( surf, DRAWLIST_TRANS ); } // and store faces that required additional pass from another point into separate list if( FBitSet( surf->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE ) && CVAR_TO_BOOL( r_allow_mirrors )) { if( !FBitSet( RI->currentmodel->flags, BIT( 2 )) || tr.waterlevel < 3 ) R_AddSurfaceToDrawList( surf, DRAWLIST_SUBVIEW ); } } } } else if( !FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) { // before process of tr.draw_entities // we can add muzzleflashes here R_RunViewmodelEvents(); // update entity params for every frame for( int i = 0; i < tr.num_draw_entities; i++ ) { RI->currententity = tr.draw_entities[i]; RI->currentmodel = RI->currententity->model; switch( RI->currentmodel->type ) { case mod_brush: R_UpdateSubmodelParams(); break; case mod_studio: R_AddStudioToDrawList( RI->currententity, true ); break; case mod_sprite: R_AddSpriteToDrawList( RI->currententity, true ); break; default: HOST_ERROR( "R_SetupViewCache: mod_bad\n" ); } } } // setup dynamic lights if( !FBitSet( RI->params, RP_DEFERREDLIGHT )) { Mod_InitBSPModelsTexture(); R_SetupDynamicLights(); } // cache client frame RI->view.client_frame = tr.realframecount; } /* =============== R_SetupGLstate shared our matrices with gl-machine =============== */ void R_SetupGLstate( void ) { pglViewport( RI->glstate.viewport[0], RI->glstate.viewport[1], RI->glstate.viewport[2], RI->glstate.viewport[3] ); if( RP_NORMALPASS() && tr.sunlight != NULL && CVAR_TO_BOOL( r_pssm_show_split )) { GLfloat debugSplitProjection[16], debugSplitModelview[16]; int split = bound( 0, (int)r_pssm_show_split->value - 1, NUM_SHADOW_SPLITS ); tr.sunlight->textureMatrix[split].CopyToArray( debugSplitProjection ); tr.sunlight->modelviewMatrix.CopyToArray( debugSplitModelview ); pglMatrixMode( GL_PROJECTION ); pglLoadMatrixf( debugSplitProjection ); pglMatrixMode( GL_MODELVIEW ); pglLoadMatrixf( debugSplitModelview ); } else { pglMatrixMode( GL_PROJECTION ); pglLoadMatrixf( RI->glstate.projectionMatrix ); pglMatrixMode( GL_MODELVIEW ); pglLoadMatrixf( RI->glstate.modelviewMatrix ); } if( FBitSet( RI->params, RP_CLIPPLANE )) GL_ClipPlane( true ); } /* =============== R_ResetGLstate disable frame specifics =============== */ void R_ResetGLstate( void ) { if( FBitSet( RI->params, RP_CLIPPLANE )) GL_ClipPlane( false ); GL_CleanupDrawState(); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_TRUE ); GL_Blend( GL_FALSE ); } /* ============= R_Clear ============= */ void R_Clear( int bitMask ) { int bits = GL_DEPTH_BUFFER_BIT; // NOTE: mask should be enabled to properly clear buffer GL_DepthMask( GL_TRUE ); if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) pglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules) else pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); // force to clearing screen to avoid ugly blur if( tr.fClearScreen && !CVAR_TO_BOOL( r_clear )) bits |= GL_COLOR_BUFFER_BIT; bits &= bitMask; pglClear( bits ); pglDepthFunc( GL_LEQUAL ); GL_Cull( GL_FRONT ); // change ordering for overview if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) { gldepthmin = 0.8f; gldepthmax = 0.0f; } else { gldepthmin = 0.0f; gldepthmax = 0.8f; } GL_DepthRange( gldepthmin, gldepthmax ); } /* ============= R_SetupProjectionMatrix ============= */ void R_SetupProjectionMatrix( float fov_x, float fov_y, matrix4x4 &m ) { GLdouble xMax, yMax, zFar; zFar = Q_max( 256.0, RI->view.farClip ); xMax = Z_NEAR * tan( fov_x * M_PI / 360.0 ); yMax = Z_NEAR * tan( fov_y * M_PI / 360.0 ); m.CreateProjection( xMax, -xMax, yMax, -yMax, Z_NEAR, zFar ); } /* ============= R_DrawParticles NOTE: particles are drawing with engine methods ============= */ void R_DrawParticles( qboolean trans ) { ref_viewpass_t rvp; if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) return; rvp.viewport[0] = RI->view.port[0]; rvp.viewport[1] = RI->view.port[1]; rvp.viewport[2] = RI->view.port[2]; rvp.viewport[3] = RI->view.port[3]; rvp.viewangles = RI->view.angles; rvp.vieworigin = RI->view.origin; rvp.fov_x = RI->view.fov_x; rvp.fov_y = RI->view.fov_y; rvp.flags = 0; if( FBitSet( RI->params, RP_DRAW_WORLD )) SetBits( rvp.flags, RF_DRAW_WORLD ); if( FBitSet( RI->params, RP_ENVVIEW )) SetBits( rvp.flags, RF_DRAW_CUBEMAP ); if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) SetBits( rvp.flags, RF_DRAW_OVERVIEW ); DRAW_PARTICLES( &rvp, trans, tr.frametime ); } /* ================= R_SortTransMeshes compare translucent meshes ================= */ static int R_SortTransMeshes( const CTransEntry *a, const CTransEntry *b ) { if( a->m_flViewDist > b->m_flViewDist ) return -1; if( a->m_flViewDist < b->m_flViewDist ) return 1; return 0; } /* =============== R_RenderTransList =============== */ void R_RenderTransList( void ) { if( !RI->frame.trans_list.Count() ) return; GL_Blend( GL_FALSE ); // mixing screencopy with diffuse so we don't need blend GL_AlphaTest( GL_FALSE ); // yes, we rendering translucent objects with enabdled depthwrite GL_DepthMask( GL_TRUE ); if( GL_Support( R_SEAMLESS_CUBEMAP )) pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); // sorting by distance RI->frame.trans_list.Sort( R_SortTransMeshes ); for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) { CTransEntry *entry = &RI->frame.trans_list[i]; switch( entry->m_bDrawType ) { case DRAWTYPE_SURFACE: R_RenderTransSurface( entry ); break; case DRAWTYPE_MESH: R_RenderTransMesh( entry ); break; case DRAWTYPE_QUAD: R_RenderQuadPrimitive( entry ); break; } } R_RenderDecalsTransList( DRAWLIST_SOLID ); R_RenderDynLightList( false ); R_RenderLightForTransMeshes(); R_RenderDecalsTransList( DRAWLIST_TRANS ); if( GL_Support( R_SEAMLESS_CUBEMAP )) pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); GL_DepthRange( gldepthmin, gldepthmax ); GL_CleanupDrawState(); GL_ClipPlane( true ); GL_Cull( GL_FRONT ); DBG_DrawGlassScissors(); } /* =============== R_RenderScene =============== */ void R_RenderScene( const ref_viewpass_t *rvp, int params ) { int err; // now we know about pass specific RI->params = params; // frametime is valid only for normal pass if( RP_NORMALPASS( )) tr.frametime = tr.saved_frametime; else tr.frametime = 0.0; R_BuildViewPassHierarchy(); R_SetupViewCache( rvp ); // prepare subview frames R_RenderSubview(); // draw all the shadowmaps R_RenderShadowmaps(); R_SetupGLstate(); R_Clear( ~0 ); R_DrawSkyBox(); R_RenderSolidBrushList(); R_RenderSolidStudioList(); R_RenderSurfOcclusionList(); R_DrawParticles( false ); R_RenderDebugStudioList( false ); if(( err = pglGetError( )) != GL_NO_ERROR ) ALERT( at_error, "OpenGL: error %x while render solid objects\n", err ); // restore right depthrange here GL_DepthRange( gldepthmin, gldepthmax ); R_RenderTransList(); R_DrawParticles( true ); R_DrawWeather(); if(( err = pglGetError( )) != GL_NO_ERROR ) ALERT( at_error, "OpenGL: error %x while render trans objects\n", err ); GL_BindShader( NULL ); R_ResetGLstate(); } /* =============== R_RenderDeferredScene =============== */ void R_RenderDeferredScene( const ref_viewpass_t *rvp, int params ) { int err; // now we know about pass specific RI->params = params; R_SetupViewCache( rvp ); // draw all the shadowmaps if( FBitSet( RI->params, RP_DEFERREDSCENE )) R_RenderShadowmaps(); GL_SetupGBuffer(); R_SetupGLstate(); R_Clear( ~0 ); R_DrawSkyBox(); R_RenderDeferredBrushList(); R_RenderDeferredStudioList(); R_PushRefState(); RI->params = params; R_DrawViewModel(); R_PopRefState(); GL_CleanupDrawState(); GL_ResetGBuffer(); if(( err = pglGetError( )) != GL_NO_ERROR ) ALERT( at_error, "OpenGL: error %x while render solid objects\n", err ); GL_DrawDeferredPass(); R_ResetGLstate(); } /* =============== HUD_RenderFrame A callback that replaces RenderFrame engine function. Return 1 if you override ALL the rendering and return 0 if we don't want draw from the client (e.g. playersetup preview) =============== */ int HUD_RenderFrame( const struct ref_viewpass_s *rvp ) { int refParams = RP_NONE; ref_viewpass_t defVP = *rvp; // setup some renderer flags if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP )) { if( FBitSet( rvp->flags, RF_DRAW_OVERVIEW )) SetBits( refParams, RP_DRAW_OVERVIEW ); if( cam_thirdperson ) SetBits( refParams, RP_THIRDPERSON ); } else { if( world->build_default_cubemap ) SetBits( refParams, RP_SKYVIEW ); else SetBits( refParams, RP_ENVVIEW ); tr.fClearScreen = true; // now all uber-shaders are invalidate // and possible need for recompile tr.params_changed = true; tr.glsl_valid_sequence++; } if( FBitSet( rvp->flags, RF_DRAW_WORLD )) SetBits( refParams, RP_DRAW_WORLD ); if( !GL_BackendStartFrame( &defVP, refParams )) return 0; if( CVAR_TO_BOOL( cv_deferred )) { if( !CVAR_TO_BOOL( cv_deferred_full )) { defVP.viewport[2] = glState.defWidth; defVP.viewport[3] = glState.defHeight; R_RenderDeferredScene( &defVP, RP_DEFERREDLIGHT ); defVP = *rvp; } R_RenderDeferredScene( &defVP, RP_DEFERREDSCENE ); } else { R_RenderScene( &defVP, refParams ); } defVP = *rvp; GL_BackendEndFrame( &defVP, refParams ); return 1; } void HUD_ProcessModelData( model_t *mod, qboolean create, const byte *buffer ) { if( !g_fRenderInitialized ) { // we needs CRC anyway if( mod->type == mod_studio && create ) { // compute model CRC to verify vertexlighting data // NOTE: source buffer is not equal to Mod_Extradata! studiohdr_t *src = (studiohdr_t *)buffer; mod->modelCRC = FILE_CRC32( buffer, src->length ); } return; } // g-cont. probably this is redundant :-) if( RENDER_GET_PARM( PARM_DEDICATED_SERVER, 0 )) return; if( FBitSet( mod->flags, MODEL_WORLD )) R_ProcessWorldData( mod, create, buffer ); else R_ProcessStudioData( mod, create, buffer ); } BOOL HUD_SpeedsMessage( char *out, size_t size ) { if( !g_fRenderInitialized || !CVAR_TO_BOOL( cv_renderer )) return false; // let the engine use built-in counters if( r_speeds->value <= 0 ) return false; if( !out || !size ) return false; Q_strncpy( out, r_speeds_msg, size ); return true; } void HUD_ProcessEntData( qboolean allocate ) { if( allocate ) Mod_PrepareModelInstances(); else Mod_ThrowModelInstances(); } void HUD_BuildLightmaps( void ) { int i; if( !g_fRenderInitialized ) return; // put the gamma into GLSL-friendly array for( i = 0; i < 256; i++ ) tr.gamma_table[i/4][i%4] = (float)TEXTURE_TO_TEXGAMMA( i ) / 255.0f; for( i = 0; i < worldmodel->numsurfaces; i++ ) SetBits( worldmodel->surfaces[i].flags, SURF_LM_UPDATE|SURF_GRASS_UPDATE ); R_StudioClearLightCache(); } // // Xash3D render interface // static render_interface_t gRenderInterface = { CL_RENDER_INTERFACE_VERSION, HUD_RenderFrame, HUD_BuildLightmaps, Mod_SetOrthoBounds, R_CreateStudioDecalList, R_ClearStudioDecals, HUD_SpeedsMessage, HUD_ProcessModelData, HUD_ProcessEntData, Mod_GetCurrentVis, GL_MapChanged, R_ClearScene, R_UpdateLatchedVars, }; int HUD_GetRenderInterface( int version, render_api_t *renderfuncs, render_interface_t *callback ) { if ( !callback || !renderfuncs || version != CL_RENDER_INTERFACE_VERSION ) { return FALSE; } size_t iImportSize = sizeof( render_interface_t ); // copy new physics interface memcpy( &gRenderfuncs, renderfuncs, sizeof( render_api_t )); // fill engine callbacks memcpy( callback, &gRenderInterface, iImportSize ); // get pointer to movevars tr.movevars = gEngfuncs.pEventAPI->EV_GetMovevars(); g_fRenderInterfaceValid = TRUE; g_fRenderInitialized = TRUE; return TRUE; }