This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/vid_gl/r_cull.c

573 lines
13 KiB
C

/*
Copyright (C) 2007 Victor Luchits
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 2
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "r_local.h"
#include "mathlib.h"
/*
=============================================================
FRUSTUM AND PVS CULLING
=============================================================
*/
/*
=================
R_CullBox
Returns true if the box is completely outside the frustum
=================
*/
bool R_CullBox( const vec3_t mins, const vec3_t maxs, const unsigned int clipflags )
{
uint i, bit;
const cplane_t *p;
if( r_nocull->integer )
return false;
for( i = sizeof( RI.frustum )/sizeof( RI.frustum[0] ), bit = 1, p = RI.frustum; i > 0; i--, bit<<=1, p++ )
{
if( !( clipflags & bit ) )
continue;
switch( p->signbits )
{
case 0:
if( p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist )
return true;
break;
case 1:
if( p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist )
return true;
break;
case 2:
if( p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist )
return true;
break;
case 3:
if( p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist )
return true;
break;
case 4:
if( p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist )
return true;
break;
case 5:
if( p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist )
return true;
break;
case 6:
if( p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist )
return true;
break;
case 7:
if( p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist )
return true;
break;
default:
Com_Assert( 1 );
return false;
}
}
return false;
}
/*
=================
R_CullSphere
Returns true if the sphere is completely outside the frustum
=================
*/
bool R_CullSphere( const vec3_t centre, const float radius, const uint clipflags )
{
uint i, bit;
const cplane_t *p;
if( r_nocull->integer )
return false;
for( i = sizeof( RI.frustum ) / sizeof( RI.frustum[0] ), bit = 1, p = RI.frustum; i > 0; i--, bit <<= 1, p++ )
{
if(!( clipflags & bit )) continue;
if( DotProduct( centre, p->normal ) - p->dist <= -radius )
return true;
}
return false;
}
/*
===================
R_VisCullBox
===================
*/
bool R_VisCullBox( const vec3_t mins, const vec3_t maxs )
{
int s, stackdepth = 0;
vec3_t extmins, extmaxs;
mnode_t *node, *localstack[2048];
if( !r_worldmodel || ( RI.refdef.flags & RDF_NOWORLDMODEL ) )
return false;
if( r_novis->integer )
return false;
for( s = 0; s < 3; s++ )
{
extmins[s] = mins[s] - 4;
extmaxs[s] = maxs[s] + 4;
}
for( node = r_worldbrushmodel->nodes;; )
{
if( node->pvsframe != r_pvsframecount )
{
if( !stackdepth )
return true;
node = localstack[--stackdepth];
continue;
}
if( !node->plane )
return false;
s = BoxOnPlaneSide( extmins, extmaxs, node->plane ) - 1;
if( s < 2 )
{
node = node->children[s];
continue;
}
// go down both sides
if( stackdepth < sizeof( localstack )/sizeof( mnode_t * ) )
localstack[stackdepth++] = node->children[0];
node = node->children[1];
}
return true;
}
/*
===================
R_VisCullSphere
===================
*/
bool R_VisCullSphere( const vec3_t origin, float radius )
{
float dist;
int stackdepth = 0;
mnode_t *node, *localstack[2048];
if( !r_worldmodel || ( RI.refdef.flags & RDF_NOWORLDMODEL ) )
return false;
if( r_novis->integer )
return false;
radius += 4;
for( node = r_worldbrushmodel->nodes;; )
{
if( node->pvsframe != r_pvsframecount )
{
if( !stackdepth )
return true;
node = localstack[--stackdepth];
continue;
}
if( !node->plane )
return false;
dist = PlaneDiff( origin, node->plane );
if( dist > radius )
{
node = node->children[0];
continue;
}
else if( dist < -radius )
{
node = node->children[1];
continue;
}
// go down both sides
if( stackdepth < sizeof( localstack )/sizeof( mnode_t * ) )
localstack[stackdepth++] = node->children[0];
node = node->children[1];
}
return true;
}
/*
=============
R_CullModel
=============
*/
int R_CullModel( ref_entity_t *e, vec3_t mins, vec3_t maxs, float radius )
{
if( e->ent_type == ED_VIEWMODEL )
{
if( RI.params & RP_NONVIEWERREF )
return 1;
return 0;
}
if( RP_LOCALCLIENT( e ) && !(RI.refdef.flags & RDF_THIRDPERSON))
{
if(!( RI.params & ( RP_MIRRORVIEW|RP_SHADOWMAPVIEW )))
return 1;
}
if( RP_FOLLOWENTITY( e ) && RP_LOCALCLIENT( e->parent ) && !(RI.refdef.flags & RDF_THIRDPERSON ))
{
if(!( RI.params & ( RP_MIRRORVIEW|RP_SHADOWMAPVIEW )))
return 1;
}
if( R_CullSphere( e->origin, radius, RI.clipFlags ))
return 1;
if( RI.refdef.flags & (RDF_PORTALINVIEW|RDF_SKYPORTALINVIEW) || (RI.params & RP_SKYPORTALVIEW))
{
if( R_VisCullSphere( e->origin, radius ))
return 2;
}
return 0;
}
/*
=============================================================
OCCLUSION QUERIES
=============================================================
*/
// occlusion queries for entities
#define BEGIN_OQ_ENTITIES 0
#define MAX_OQ_ENTITIES MAX_ENTITIES
#define END_OQ_ENTITIES ( BEGIN_OQ_ENTITIES+MAX_OQ_ENTITIES )
// occlusion queries for planar shadows
#define BEGIN_OQ_PLANARSHADOWS END_OQ_ENTITIES
#define MAX_OQ_PLANARSHADOWS MAX_ENTITIES
#define END_OQ_PLANARSHADOWS ( BEGIN_OQ_PLANARSHADOWS+MAX_OQ_PLANARSHADOWS )
// occlusion queries for shadowgroups
#define BEGIN_OQ_SHADOWGROUPS END_OQ_PLANARSHADOWS
#define MAX_OQ_SHADOWGROUPS MAX_SHADOWGROUPS
#define END_OQ_SHADOWGROUPS ( BEGIN_OQ_SHADOWGROUPS+MAX_OQ_SHADOWGROUPS )
// custom occlusion queries (portals, mirrors, etc)
#define BEGIN_OQ_CUSTOM END_OQ_SHADOWGROUPS
#define MAX_OQ_CUSTOM MAX_SURF_QUERIES
#define END_OQ_CUSTOM ( BEGIN_OQ_CUSTOM+MAX_OQ_CUSTOM )
#define MAX_OQ_TOTAL ( MAX_OQ_ENTITIES+MAX_OQ_PLANARSHADOWS+MAX_OQ_SHADOWGROUPS+MAX_OQ_CUSTOM )
static byte r_queriesBits[MAX_OQ_TOTAL/8];
static GLuint r_occlusionQueries[MAX_OQ_TOTAL];
static meshbuffer_t r_occluderMB;
static ref_shader_t *r_occlusionShader;
static bool r_occludersQueued;
static ref_entity_t *r_occlusionEntity;
static void R_RenderOccludingSurfaces( void );
/*
===============
R_InitOcclusionQueries
===============
*/
void R_InitOcclusionQueries( void )
{
meshbuffer_t *meshbuf = &r_occluderMB;
if( !r_occlusionShader )
r_occlusionShader = R_LoadShader( "***r_occlusion***", SHADER_OPAQUE_OCCLUDER, false, 0, SHADER_INVALID );
if( !GL_Support( R_OCCLUSION_QUERIES_EXT ))
return;
pglGenQueriesARB( MAX_OQ_TOTAL, r_occlusionQueries );
meshbuf->sortkey = MB_ENTITY2NUM( r_worldent ) | MB_MODEL;
meshbuf->shaderkey = r_occlusionShader->sortkey;
}
/*
===============
R_BeginOcclusionPass
===============
*/
void R_BeginOcclusionPass( void )
{
Com_Assert( OCCLUSION_QUERIES_ENABLED( RI ) == 0 );
r_occludersQueued = false;
r_occlusionEntity = r_worldent;
memset( r_queriesBits, 0, sizeof( r_queriesBits ) );
R_LoadIdentity();
R_BackendResetPassMask();
R_ClearSurfOcclusionQueryKeys();
pglShadeModel( GL_FLAT );
pglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
pglDisable( GL_TEXTURE_2D );
}
/*
===============
R_EndOcclusionPass
===============
*/
void R_EndOcclusionPass( void )
{
Com_Assert( OCCLUSION_QUERIES_ENABLED( RI ) == 0 );
R_RenderOccludingSurfaces();
R_SurfIssueOcclusionQueries();
R_BackendResetPassMask();
R_BackendResetCounters();
if( r_occlusion_queries_finish->integer )
pglFinish();
else
pglFlush();
pglShadeModel( GL_SMOOTH );
pglEnable( GL_TEXTURE_2D );
pglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
}
/*
===============
R_RenderOccludingSurfaces
===============
*/
static void R_RenderOccludingSurfaces( void )
{
if( !r_occludersQueued )
return;
r_occludersQueued = false;
R_RotateForEntity( r_occlusionEntity );
R_BackendResetPassMask();
R_RenderMeshBuffer( &r_occluderMB );
}
/*
===============
R_OcclusionShader
===============
*/
ref_shader_t *R_OcclusionShader( void )
{
return r_occlusionShader;
}
/*
===============
R_AddOccludingSurface
===============
*/
void R_AddOccludingSurface( msurface_t *surf, ref_shader_t *shader )
{
int diff = shader->flags ^ r_occlusionShader->flags;
if( R_MeshOverflow( surf->mesh ) || ( diff & ( SHADER_CULL_FRONT|SHADER_CULL_BACK ) ) || r_occlusionEntity != RI.currententity )
{
R_RenderOccludingSurfaces();
r_occlusionShader->flags ^= diff;
r_occlusionEntity = RI.currententity;
}
r_occludersQueued = true;
R_PushMesh( surf->mesh, 0 );
}
/*
===============
R_GetOcclusionQueryNum
===============
*/
int R_GetOcclusionQueryNum( int type, int key )
{
switch( type )
{
case OQ_ENTITY:
return ( r_occlusion_queries->integer & 1 ) ? BEGIN_OQ_ENTITIES + ( key%MAX_OQ_ENTITIES ) : -1;
case OQ_PLANARSHADOW:
return ( r_occlusion_queries->integer & 1 ) ? BEGIN_OQ_PLANARSHADOWS + ( key%MAX_OQ_PLANARSHADOWS ) : -1;
case OQ_SHADOWGROUP:
return ( r_occlusion_queries->integer & 2 ) ? BEGIN_OQ_SHADOWGROUPS + ( key%MAX_OQ_SHADOWGROUPS ) : -1;
case OQ_CUSTOM:
return BEGIN_OQ_CUSTOM + ( key%MAX_OQ_CUSTOM );
}
return -1;
}
/*
===============
R_IssueOcclusionQuery
===============
*/
int R_IssueOcclusionQuery( int query, ref_entity_t *e, vec3_t mins, vec3_t maxs )
{
static vec4_t verts[8];
static mesh_t mesh;
static elem_t indices[] =
{
0, 1, 2, 1, 2, 3, // top
7, 5, 6, 5, 6, 4, // bottom
4, 0, 6, 0, 6, 2, // front
3, 7, 1, 7, 1, 5, // back
0, 1, 4, 1, 4, 5, // left
7, 6, 3, 6, 3, 2, // right
};
int i;
vec3_t bbox[8];
if( query < 0 )
return -1;
if( r_queriesBits[query>>3] & ( 1<<( query&7 ) ) )
return -1;
r_queriesBits[query>>3] |= ( 1<<( query&7 ) );
R_RenderOccludingSurfaces();
mesh.numVertexes = 8;
mesh.xyzArray = verts;
mesh.numElems = 36;
mesh.elems = indices;
r_occlusionShader->flags &= ~( SHADER_CULL_BACK | SHADER_CULL_FRONT );
pglBeginQueryARB( GL_SAMPLES_PASSED_ARB, r_occlusionQueries[query] );
R_TransformEntityBBox( e, mins, maxs, bbox, false );
for( i = 0; i < 8; i++ )
Vector4Set( verts[i], bbox[i][0], bbox[i][1], bbox[i][2], 1 );
R_RotateForEntity( e );
R_BackendSetPassMask( GLSTATE_MASK & ~GLSTATE_DEPTHWRITE );
R_PushMesh( &mesh, MF_NONBATCHED|MF_NOCULL );
R_RenderMeshBuffer( &r_occluderMB );
pglEndQueryARB( GL_SAMPLES_PASSED_ARB );
return query;
}
/*
===============
R_OcclusionQueryIssued
===============
*/
bool R_OcclusionQueryIssued( int query )
{
Com_Assert((query >= 0 && query < MAX_OQ_TOTAL) == 0 );
return r_queriesBits[query>>3] & ( 1<<( query&7 ) );
}
/*
===============
R_GetOcclusionQueryResult
===============
*/
unsigned int R_GetOcclusionQueryResult( int query, bool wait )
{
GLint available;
GLuint sampleCount;
if( query < 0 )
return 1;
if( !R_OcclusionQueryIssued( query ) )
return 1;
query = r_occlusionQueries[query];
pglGetQueryObjectivARB( query, GL_QUERY_RESULT_AVAILABLE_ARB, &available );
if( !available && wait )
{
int n = 0;
do
{
n++;
pglGetQueryObjectivARB( query, GL_QUERY_RESULT_AVAILABLE_ARB, &available );
} while( !available && ( n < 10000 ) );
}
if( !available )
return 0;
pglGetQueryObjectuivARB( query, GL_QUERY_RESULT_ARB, &sampleCount );
return sampleCount;
}
/*
===============
R_GetOcclusionQueryResultBool
===============
*/
bool R_GetOcclusionQueryResultBool( int type, int key, bool wait )
{
int query = R_GetOcclusionQueryNum( type, key );
if( query >= 0 && R_OcclusionQueryIssued( query ) )
{
if( R_GetOcclusionQueryResult( query, wait ) )
return true;
return false;
}
return true;
}
/*
===============
R_ShutdownOcclusionQueries
===============
*/
void R_ShutdownOcclusionQueries( void )
{
r_occlusionShader = NULL;
if( !GL_Support( R_OCCLUSION_QUERIES_EXT ))
return;
pglDeleteQueriesARB( MAX_OQ_TOTAL, r_occlusionQueries );
r_occludersQueued = false;
}