openmohaa/code/renderer_gl3/tr_world.cpp

2535 lines
57 KiB
C++
Raw Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2008 Robert Beckebans <trebor_7@users.sourceforge.net>
This file is part of XreaL source code.
XreaL source code 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.
XreaL source code 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 XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tr_world.c
#include "tr_local.h"
#include "gl_shader.h"
/*
=================
R_CullTriSurf
Returns true if the grid is completely culled away.
Also sets the clipped hint bit in tess
=================
*/
/*
static qboolean R_CullTriSurf(srfTriangles_t * cv)
{
int boxCull;
boxCull = R_CullLocalBox(cv->bounds);
if(boxCull == CULL_OUT)
{
return qtrue;
}
return qfalse;
}
*/
/*
=================
R_CullGrid
Returns true if the grid is completely culled away.
Also sets the clipped hint bit in tess
=================
*/
/*
static qboolean R_CullGrid(srfGridMesh_t * cv)
{
int boxCull;
int sphereCull;
if(r_nocurves->integer)
{
return qtrue;
}
if(tr.currentEntity != &tr.worldEntity)
{
sphereCull = R_CullLocalPointAndRadius(cv->origin, cv->radius);
}
else
{
sphereCull = R_CullPointAndRadius(cv->origin, cv->radius);
}
boxCull = CULL_OUT;
// check for trivial reject
if(sphereCull == CULL_OUT)
{
tr.pc.c_sphere_cull_patch_out++;
return qtrue;
}
// check bounding box if necessary
else if(sphereCull == CULL_CLIP)
{
tr.pc.c_sphere_cull_patch_clip++;
boxCull = R_CullLocalBox(cv->bounds);
if(boxCull == CULL_OUT)
{
tr.pc.c_box_cull_patch_out++;
return qtrue;
}
else if(boxCull == CULL_IN)
{
tr.pc.c_box_cull_patch_in++;
}
else
{
tr.pc.c_box_cull_patch_clip++;
}
}
else
{
tr.pc.c_sphere_cull_patch_in++;
}
return qfalse;
}
*/
/*
================
R_CullSurface
Tries to back face cull surfaces before they are lighted or
added to the sorting list.
This will also allow mirrors on both sides of a model without recursion.
================
*/
static qboolean R_CullSurface(surfaceType_t * surface, shader_t * shader, int *frontFace)
{
srfGeneric_t *gen;
int cull;
float d;
// force to non-front facing
*frontFace = 0;
// allow culling to be disabled
if(r_nocull->integer)
{
return qfalse;
}
// ydnar: made surface culling generic, inline with q3map2 surface classification
switch (*surface)
{
case SF_FACE:
case SF_TRIANGLES:
break;
case SF_GRID:
if(r_nocurves->integer)
{
return qtrue;
}
break;
/*
case SF_FOLIAGE:
if(!r_drawfoliage->value)
{
return qtrue;
}
break;
*/
default:
return qtrue;
}
// get generic surface
gen = (srfGeneric_t *) surface;
// plane cull
if(gen->plane.type != PLANE_NON_PLANAR && r_facePlaneCull->integer)
{
d = DotProduct(tr.orientation.viewOrigin, gen->plane.normal) - gen->plane.dist;
if(d > 0.0f)
{
*frontFace = 1;
}
// don't cull exactly on the plane, because there are levels of rounding
// through the BSP, ICD, and hardware that may cause pixel gaps if an
// epsilon isn't allowed here
if(shader->cullType == CT_FRONT_SIDED)
{
if(d < -8.0f)
{
tr.pc.c_plane_cull_out++;
return qtrue;
}
}
else if(shader->cullType == CT_BACK_SIDED)
{
if(d > 8.0f)
{
tr.pc.c_plane_cull_out++;
return qtrue;
}
}
tr.pc.c_plane_cull_in++;
}
{
// try sphere cull
if(tr.currentEntity != &tr.worldEntity)
{
cull = R_CullLocalPointAndRadius(gen->origin, gen->radius);
}
else
{
cull = R_CullPointAndRadius(gen->origin, gen->radius);
}
if(cull == CULL_OUT)
{
tr.pc.c_sphere_cull_out++;
return qtrue;
}
tr.pc.c_sphere_cull_in++;
}
// must be visible
return qfalse;
}
static qboolean R_LightSurfaceGeneric(srfGeneric_t * face, trRefLight_t * light, byte * cubeSideBits)
{
// do a quick AABB cull
if(!BoundsIntersect(light->worldBounds[0], light->worldBounds[1], face->bounds[0], face->bounds[1]))
{
return qfalse;
}
// do a more expensive and precise light frustum cull
if(!r_noLightFrustums->integer)
{
if(R_CullLightWorldBounds(light, face->bounds) == CULL_OUT)
{
return qfalse;
}
}
if(r_cullShadowPyramidFaces->integer)
{
*cubeSideBits = R_CalcLightCubeSideBits(light, face->bounds);
}
return qtrue;
}
/*
======================
R_AddInteractionSurface
======================
*/
static void R_AddInteractionSurface(bspSurface_t * surf, trRefLight_t * light)
{
qboolean intersects;
interactionType_t iaType = IA_DEFAULT;
byte cubeSideBits = CUBESIDE_CLIPALL;
// Tr3B - this surface is maybe not in this view but it may still cast a shadow
// into this view
if(surf->viewCount != tr.viewCountNoReset)
{
if(r_shadows->integer <= SHADOWING_BLOB || light->l.noShadows)
return;
else
iaType = IA_SHADOWONLY;
}
if(surf->lightCount == tr.lightCount)
{
// already checked this surface
return;
}
surf->lightCount = tr.lightCount;
// skip all surfaces that don't matter for lighting only pass
if(surf->shader->isSky || (!surf->shader->interactLight && surf->shader->noShadows))
return;
switch (*surf->data)
{
case SF_FACE:
case SF_GRID:
case SF_TRIANGLES:
intersects = R_LightSurfaceGeneric((srfGeneric_t *) surf->data, light, &cubeSideBits);
break;
default:
intersects = qfalse;
};
if(intersects)
{
R_AddLightInteraction(light, surf->data, surf->shader, cubeSideBits, iaType);
if(light->isStatic)
tr.pc.c_slightSurfaces++;
else
tr.pc.c_dlightSurfaces++;
}
else
{
if(!light->isStatic)
tr.pc.c_dlightSurfacesCulled++;
}
}
/*
======================
R_AddWorldSurface
======================
*/
static void R_AddWorldSurface(bspSurface_t * surf, int decalBits)
{
int i, frontFace;
shader_t *shader;
if(surf->viewCount == tr.viewCountNoReset)
return;
surf->viewCount = tr.viewCountNoReset;
shader = surf->shader;
// add decals
if(decalBits)
{
// ydnar: project any decals
for(i = 0; i < tr.refdef.numDecalProjectors; i++)
{
if(decalBits & (1 << i))
{
R_ProjectDecalOntoSurface(&tr.refdef.decalProjectors[i], surf, &tr.world->models[0]);
}
}
}
#if defined(USE_BSP_CLUSTERSURFACE_MERGING)
if(r_mergeClusterSurfaces->integer &&
!r_dynamicBspOcclusionCulling->integer &&
((r_mergeClusterFaces->integer && *surf->data == SF_FACE) ||
(r_mergeClusterCurves->integer && *surf->data == SF_GRID) ||
(r_mergeClusterTriangles->integer && *surf->data == SF_TRIANGLES)) &&
!shader->isSky && !shader->isPortal && !ShaderRequiresCPUDeforms(shader))
return;
#endif
// try to cull before lighting or adding
if(R_CullSurface(surf->data, surf->shader, &frontFace))
{
return;
}
R_AddDrawSurf(surf->data, surf->shader, surf->lightmapNum, surf->fogIndex);
}
/*
=============================================================
BRUSH MODELS
=============================================================
*/
/*
======================
R_AddBrushModelSurface
======================
*/
static void R_AddBrushModelSurface(bspSurface_t * surf, int fogIndex)
{
int frontFace;
if(surf->viewCount == tr.viewCountNoReset)
{
return; // already in this view
}
surf->viewCount = tr.viewCountNoReset;
// try to cull before lighting or adding
if(R_CullSurface(surf->data, surf->shader, &frontFace))
{
return;
}
R_AddDrawSurf(surf->data, surf->shader, surf->lightmapNum, fogIndex);
}
/*
=================
R_AddBSPModelSurfaces
=================
*/
void R_AddBSPModelSurfaces(trRefEntity_t * ent)
{
bspModel_t *bspModel;
model_t *pModel;
int i;
vec3_t v;
vec3_t transformed;
vec3_t boundsCenter;
// float boundsRadius;
int fogNum;
pModel = R_GetModelByHandle(ent->e.hModel);
bspModel = pModel->bsp;
// copy local bounds
for(i = 0; i < 3; i++)
{
ent->localBounds[0][i] = bspModel->bounds[0][i];
ent->localBounds[1][i] = bspModel->bounds[1][i];
}
#if 0
boundsRadius = RadiusFromBounds(bspModel->bounds[0], bspModel->bounds[1]);
ent->cull = R_CullPointAndRadius(ent->e.origin, boundsRadius);
#else
ent->cull = R_CullLocalBox(bspModel->bounds);
if(ent->cull == CULL_OUT)
{
return;
}
#endif
// setup world bounds for intersection tests
ClearBounds(ent->worldBounds[0], ent->worldBounds[1]);
for(i = 0; i < 8; i++)
{
v[0] = ent->localBounds[i & 1][0];
v[1] = ent->localBounds[(i >> 1) & 1][1];
v[2] = ent->localBounds[(i >> 2) & 1][2];
// transform local bounds vertices into world space
R_LocalPointToWorld(v, transformed);
AddPointToBounds(transformed, ent->worldBounds[0], ent->worldBounds[1]);
}
VectorAdd(ent->worldBounds[0], ent->worldBounds[1], boundsCenter);
VectorScale(boundsCenter, 0.5f, boundsCenter);
// Tr3B: BSP inline models should always use vertex lighting
R_SetupEntityLighting(&tr.refdef, ent, boundsCenter);
fogNum = R_FogWorldBox(ent->worldBounds);
if(r_vboModels->integer && bspModel->numVBOSurfaces)
{
int i;
srfVBOMesh_t *vboSurface;
for(i = 0; i < bspModel->numVBOSurfaces; i++)
{
vboSurface = bspModel->vboSurfaces[i];
R_AddDrawSurf((surfaceType_t *) vboSurface, vboSurface->shader, vboSurface->lightmapNum, fogNum);
}
}
else
{
for(i = 0; i < bspModel->numSurfaces; i++)
{
R_AddBrushModelSurface(bspModel->firstSurface + i, fogNum);
}
}
}
/*
=============================================================
WORLD MODEL
=============================================================
*/
static void R_AddLeafSurfaces(bspNode_t * node, int decalBits)
{
int c;
bspSurface_t *surf, **mark;
tr.pc.c_leafs++;
// add to z buffer bounds
if(node->mins[0] < tr.viewParms.visBounds[0][0])
{
tr.viewParms.visBounds[0][0] = node->mins[0];
}
if(node->mins[1] < tr.viewParms.visBounds[0][1])
{
tr.viewParms.visBounds[0][1] = node->mins[1];
}
if(node->mins[2] < tr.viewParms.visBounds[0][2])
{
tr.viewParms.visBounds[0][2] = node->mins[2];
}
if(node->maxs[0] > tr.viewParms.visBounds[1][0])
{
tr.viewParms.visBounds[1][0] = node->maxs[0];
}
if(node->maxs[1] > tr.viewParms.visBounds[1][1])
{
tr.viewParms.visBounds[1][1] = node->maxs[1];
}
if(node->maxs[2] > tr.viewParms.visBounds[1][2])
{
tr.viewParms.visBounds[1][2] = node->maxs[2];
}
// add the individual surfaces
mark = node->markSurfaces;
c = node->numMarkSurfaces;
while(c--)
{
// the surface may have already been added if it
// spans multiple leafs
surf = *mark;
R_AddWorldSurface(surf, decalBits);
mark++;
}
}
/*
================
R_RecursiveWorldNode
================
*/
static void R_RecursiveWorldNode(bspNode_t * node, int planeBits, int decalBits)
{
do
{
// if the node wasn't marked as potentially visible, exit
if(node->visCounts[tr.visIndex] != tr.visCounts[tr.visIndex])
{
return;
}
if(node->contents != -1 && !node->numMarkSurfaces)
{
// don't waste time dealing with this empty leaf
return;
}
// if the bounding volume is outside the frustum, nothing
// inside can be visible OPTIMIZE: don't do this all the way to leafs?
if(!r_nocull->integer)
{
int i;
int r;
for(i = 0; i < FRUSTUM_PLANES; i++)
{
if(planeBits & (1 << i))
{
r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustums[0][i]);
if(r == 2)
{
return; // culled
}
if(r == 1)
{
planeBits &= ~(1 << i); // all descendants will also be in front
}
}
}
}
InsertLink(&node->visChain, &tr.traversalStack);
// ydnar: cull decals
if(decalBits)
{
int i;
for(i = 0; i < tr.refdef.numDecalProjectors; i++)
{
if(decalBits & (1 << i))
{
// test decal bounds against node surface bounds
if(tr.refdef.decalProjectors[i].shader == NULL ||
!R_TestDecalBoundingBox(&tr.refdef.decalProjectors[i], node->surfMins, node->surfMaxs))
{
decalBits &= ~(1 << i);
}
}
}
}
if(node->contents != -1)
{
break;
}
// recurse down the children, front side first
R_RecursiveWorldNode(node->children[0], planeBits, decalBits);
// tail recurse
node = node->children[1];
} while(1);
if(node->numMarkSurfaces)
{
// ydnar: moved off to separate function
R_AddLeafSurfaces(node, decalBits);
}
}
/*
================
R_RecursiveInteractionNode
================
*/
static void R_RecursiveInteractionNode(bspNode_t * node, trRefLight_t * light, int planeBits)
{
int i;
int r;
// if the node wasn't marked as potentially visible, exit
if(node->visCounts[tr.visIndex] != tr.visCounts[tr.visIndex])
{
return;
}
// light already hit node
if(node->lightCount == tr.lightCount)
{
return;
}
node->lightCount = tr.lightCount;
// if the bounding volume is outside the frustum, nothing
// inside can be visible OPTIMIZE: don't do this all the way to leafs?
// Tr3B - even surfaces that belong to nodes that are outside of the view frustum
// can cast shadows into the view frustum
if(!r_nocull->integer && r_shadows->integer <= SHADOWING_BLOB)
{
for(i = 0; i < FRUSTUM_PLANES; i++)
{
if(planeBits & (1 << i))
{
r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustums[0][i]);
if(r == 2)
{
return; // culled
}
if(r == 1)
{
planeBits &= ~(1 << i); // all descendants will also be in front
}
}
}
}
if(node->contents != -1)
{
// leaf node, so add mark surfaces
int c;
bspSurface_t *surf, **mark;
// add the individual surfaces
mark = node->markSurfaces;
c = node->numMarkSurfaces;
while(c--)
{
// the surface may have already been added if it
// spans multiple leafs
surf = *mark;
R_AddInteractionSurface(surf, light);
mark++;
}
return;
}
// node is just a decision point, so go down both sides
// since we don't care about sort orders, just go positive to negative
r = BoxOnPlaneSide(light->worldBounds[0], light->worldBounds[1], node->plane);
switch (r)
{
case 1:
R_RecursiveInteractionNode(node->children[0], light, planeBits);
break;
case 2:
R_RecursiveInteractionNode(node->children[1], light, planeBits);
break;
case 3:
default:
// recurse down the children, front side first
R_RecursiveInteractionNode(node->children[0], light, planeBits);
R_RecursiveInteractionNode(node->children[1], light, planeBits);
break;
}
}
/*
===============
R_PointInLeaf
===============
*/
static bspNode_t *R_PointInLeaf(const vec3_t p)
{
bspNode_t *node;
float d;
cplane_t *plane;
if(!tr.world)
{
ri.Error(ERR_DROP, "R_PointInLeaf: bad model");
}
node = tr.world->nodes;
while(1)
{
if(node->contents != -1)
{
break;
}
plane = node->plane;
d = DotProduct(p, plane->normal) - plane->dist;
if(d > 0)
{
node = node->children[0];
}
else
{
node = node->children[1];
}
}
return node;
}
/*
==============
R_ClusterPVS
==============
*/
static const byte *R_ClusterPVS(int cluster)
{
if(!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters)
{
return tr.world->novis;
}
return tr.world->vis + cluster * tr.world->clusterBytes;
}
/*
=================
R_inPVS
=================
*/
qboolean R_inPVS(const vec3_t p1, const vec3_t p2)
{
bspNode_t *leaf;
const byte *vis;
leaf = R_PointInLeaf(p1);
vis = R_ClusterPVS(leaf->cluster);
leaf = R_PointInLeaf(p2);
if(!(vis[leaf->cluster >> 3] & (1 << (leaf->cluster & 7))))
{
return qfalse;
}
return qtrue;
}
/*
=================
BSPSurfaceCompare
compare function for qsort()
=================
*/
static int BSPSurfaceCompare(const void *a, const void *b)
{
bspSurface_t *aa, *bb;
aa = *(bspSurface_t **) a;
bb = *(bspSurface_t **) b;
// shader first
if(aa->shader < bb->shader)
return -1;
else if(aa->shader > bb->shader)
return 1;
// by lightmap
if(aa->lightmapNum < bb->lightmapNum)
return -1;
else if(aa->lightmapNum > bb->lightmapNum)
return 1;
return 0;
}
/*
===============
R_UpdateClusterSurfaces()
===============
*/
#if defined(USE_BSP_CLUSTERSURFACE_MERGING)
static void R_UpdateClusterSurfaces()
{
int i, k, l;
int numVerts;
int numTriangles;
// static glIndex_t indexes[MAX_MAP_DRAW_INDEXES];
// static byte indexes[MAX_MAP_DRAW_INDEXES * sizeof(glIndex_t)];
glIndex_t *indexes;
int indexesSize;
shader_t *shader, *oldShader;
int lightmapNum, oldLightmapNum;
int numSurfaces;
bspSurface_t *surface, *surface2;
bspSurface_t **surfacesSorted;
bspCluster_t *cluster;
srfVBOMesh_t *vboSurf;
IBO_t *ibo;
vec3_t bounds[2];
if(tr.visClusters[tr.visIndex] < 0 || tr.visClusters[tr.visIndex] >= tr.world->numClusters)
{
// Tr3B: this is not a bug, the super cluster is the last one in the array
cluster = &tr.world->clusters[tr.world->numClusters];
}
else
{
cluster = &tr.world->clusters[tr.visClusters[tr.visIndex]];
}
tr.world->numClusterVBOSurfaces[tr.visIndex] = 0;
// count number of static cluster surfaces
numSurfaces = 0;
for(k = 0; k < cluster->numMarkSurfaces; k++)
{
surface = cluster->markSurfaces[k];
shader = surface->shader;
if(shader->isSky)
continue;
if(shader->isPortal)
continue;
if(ShaderRequiresCPUDeforms(shader))
continue;
numSurfaces++;
}
if(!numSurfaces)
return;
// build interaction caches list
surfacesSorted = (bspSurface_t **) ri.Malloc(numSurfaces * sizeof(surfacesSorted[0]));
numSurfaces = 0;
for(k = 0; k < cluster->numMarkSurfaces; k++)
{
surface = cluster->markSurfaces[k];
shader = surface->shader;
if(shader->isSky)
continue;
if(shader->isPortal)
continue;
if(ShaderRequiresCPUDeforms(shader))
continue;
surfacesSorted[numSurfaces] = surface;
numSurfaces++;
}
// sort surfaces by shader
qsort(surfacesSorted, numSurfaces, sizeof(surfacesSorted), BSPSurfaceCompare);
shader = oldShader = NULL;
lightmapNum = oldLightmapNum = -1;
for(k = 0; k < numSurfaces; k++)
{
surface = surfacesSorted[k];
shader = surface->shader;
lightmapNum = surface->lightmapNum;
if(shader != oldShader || (r_precomputedLighting->integer ? lightmapNum != oldLightmapNum : 0))
{
oldShader = shader;
oldLightmapNum = lightmapNum;
// count vertices and indices
numVerts = 0;
numTriangles = 0;
for(l = k; l < numSurfaces; l++)
{
surface2 = surfacesSorted[l];
if(surface2->shader != shader || surface2->lightmapNum != lightmapNum)
continue;
if(*surface2->data == SF_FACE)
{
srfSurfaceFace_t *face = (srfSurfaceFace_t *) surface2->data;
if(!r_mergeClusterFaces->integer)
continue;
if(face->numVerts)
numVerts += face->numVerts;
if(face->numTriangles)
numTriangles += face->numTriangles;
}
else if(*surface2->data == SF_GRID)
{
srfGridMesh_t *grid = (srfGridMesh_t *) surface2->data;
if(!r_mergeClusterCurves->integer)
continue;
if(grid->numVerts)
numVerts += grid->numVerts;
if(grid->numTriangles)
numTriangles += grid->numTriangles;
}
else if(*surface2->data == SF_TRIANGLES)
{
srfTriangles_t *tri = (srfTriangles_t *) surface2->data;
if(!r_mergeClusterTriangles->integer)
continue;
if(tri->numVerts)
numVerts += tri->numVerts;
if(tri->numTriangles)
numTriangles += tri->numTriangles;
}
}
if(!numVerts || !numTriangles)
continue;
ClearBounds(bounds[0], bounds[1]);
// build triangle indices
indexesSize = numTriangles * 3 * sizeof(glIndex_t);
indexes = (glIndex_t *) ri.Malloc(indexesSize);
numTriangles = 0;
for(l = k; l < numSurfaces; l++)
{
surface2 = surfacesSorted[l];
if(surface2->shader != shader || surface2->lightmapNum != lightmapNum)
continue;
// set up triangle indices
if(*surface2->data == SF_FACE)
{
srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface2->data;
if(!r_mergeClusterFaces->integer)
continue;
if(srf->numTriangles)
{
srfTriangle_t *tri;
for(i = 0, tri = tr.world->triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
{
indexes[numTriangles * 3 + i * 3 + 0] = tri->indexes[0];
indexes[numTriangles * 3 + i * 3 + 1] = tri->indexes[1];
indexes[numTriangles * 3 + i * 3 + 2] = tri->indexes[2];
}
numTriangles += srf->numTriangles;
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
}
}
else if(*surface2->data == SF_GRID)
{
srfGridMesh_t *srf = (srfGridMesh_t *) surface2->data;
if(!r_mergeClusterCurves->integer)
continue;
if(srf->numTriangles)
{
srfTriangle_t *tri;
for(i = 0, tri = tr.world->triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
{
indexes[numTriangles * 3 + i * 3 + 0] = tri->indexes[0];
indexes[numTriangles * 3 + i * 3 + 1] = tri->indexes[1];
indexes[numTriangles * 3 + i * 3 + 2] = tri->indexes[2];
}
numTriangles += srf->numTriangles;
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
}
}
else if(*surface2->data == SF_TRIANGLES)
{
srfTriangles_t *srf = (srfTriangles_t *) surface2->data;
if(!r_mergeClusterTriangles->integer)
continue;
if(srf->numTriangles)
{
srfTriangle_t *tri;
for(i = 0, tri = tr.world->triangles + srf->firstTriangle; i < srf->numTriangles; i++, tri++)
{
indexes[numTriangles * 3 + i * 3 + 0] = tri->indexes[0];
indexes[numTriangles * 3 + i * 3 + 1] = tri->indexes[1];
indexes[numTriangles * 3 + i * 3 + 2] = tri->indexes[2];
}
numTriangles += srf->numTriangles;
BoundsAdd(bounds[0], bounds[1], srf->bounds[0], srf->bounds[1]);
}
}
}
if(tr.world->numClusterVBOSurfaces[tr.visIndex] < tr.world->clusterVBOSurfaces[tr.visIndex].currentElements)
{
vboSurf =
(srfVBOMesh_t *) Com_GrowListElement(&tr.world->clusterVBOSurfaces[tr.visIndex],
tr.world->numClusterVBOSurfaces[tr.visIndex]);
ibo = vboSurf->ibo;
/*
if(ibo->indexesVBO)
{
glDeleteBuffersARB(1, &ibo->indexesVBO);
ibo->indexesVBO = 0;
}
*/
//Com_Dealloc(ibo);
//Com_Dealloc(vboSurf);
}
else
{
vboSurf = (srfVBOMesh_t *) ri.Hunk_Alloc(sizeof(*vboSurf), h_low);
vboSurf->surfaceType = SF_VBO_MESH;
vboSurf->vbo = tr.world->vbo;
vboSurf->ibo = ibo = (IBO_t *) ri.Hunk_Alloc(sizeof(*ibo), h_low);
#if defined(USE_D3D10)
// TODO
#else
glGenBuffersARB(1, &ibo->indexesVBO);
#endif
Com_AddToGrowList(&tr.world->clusterVBOSurfaces[tr.visIndex], vboSurf);
}
//ri.Printf(PRINT_ALL, "creating VBO cluster surface for shader '%s'\n", shader->name);
// update surface properties
vboSurf->numIndexes = numTriangles * 3;
vboSurf->numVerts = numVerts;
vboSurf->shader = shader;
vboSurf->lightmapNum = lightmapNum;
VectorCopy(bounds[0], vboSurf->bounds[0]);
VectorCopy(bounds[1], vboSurf->bounds[1]);
// make sure the render thread is stopped
R_SyncRenderThread();
// update IBO
Q_strncpyz(ibo->name,
va("staticWorldMesh_IBO_visIndex%i_surface%i", tr.visIndex, tr.world->numClusterVBOSurfaces[tr.visIndex]),
sizeof(ibo->name));
ibo->indexesSize = indexesSize;
R_BindIBO(ibo);
#if defined(USE_D3D10)
// TODO
#else
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, GL_DYNAMIC_DRAW_ARB);
#endif
R_BindNullIBO();
//GL_CheckErrors();
ri.Free(indexes);
tr.world->numClusterVBOSurfaces[tr.visIndex]++;
}
}
ri.Free(surfacesSorted);
if(r_showcluster->modified || r_showcluster->integer)
{
r_showcluster->modified = qfalse;
if(r_showcluster->integer)
{
ri.Printf(PRINT_ALL, " surfaces:%i\n", tr.world->numClusterVBOSurfaces[tr.visIndex]);
}
}
}
#endif // #if defined(USE_BSP_CLUSTERSURFACE_MERGING)
/*
===============
R_MarkLeaves
Mark the leaves and nodes that are in the PVS for the current
cluster
===============
*/
static void R_MarkLeaves(void)
{
const byte *vis;
bspNode_t *leaf, *parent;
int i;
int cluster;
// lockpvs lets designers walk around to determine the
// extent of the current pvs
if(r_lockpvs->integer)// || r_dynamicBspOcclusionCulling->integer)
{
return;
}
// current viewcluster
leaf = R_PointInLeaf(tr.viewParms.pvsOrigin);
cluster = leaf->cluster;
// if the cluster is the same and the area visibility matrix
// hasn't changed, we don't need to mark everything again
for(i = 0; i < MAX_VISCOUNTS; i++)
{
if(tr.visClusters[i] == cluster)
{
// if r_showcluster was just turned on, remark everything
if(!tr.refdef.areamaskModified && !r_showcluster->modified)// && !r_dynamicBspOcclusionCulling->modified)
{
if(tr.visClusters[i] != tr.visClusters[tr.visIndex] && r_showcluster->integer)
{
ri.Printf(PRINT_ALL, "found cluster:%i area:%i index:%i\n", cluster, leaf->area, i);
}
tr.visIndex = i;
return;
}
if(tr.refdef.areamaskModified)
{
// invalidate old visclusters so they will be updated next time
tr.visClusters[i] = -1;
}
}
}
tr.visIndex = (tr.visIndex + 1) % MAX_VISCOUNTS;
tr.visCounts[tr.visIndex]++;
tr.visClusters[tr.visIndex] = cluster;
if(r_showcluster->modified || r_showcluster->integer)
{
r_showcluster->modified = qfalse;
if(r_showcluster->integer)
{
ri.Printf(PRINT_ALL, "update cluster:%i area:%i index:%i\n", cluster, leaf->area, tr.visIndex);
}
}
/*
if(r_dynamicBspOcclusionCulling->modified)
{
r_dynamicBspOcclusionCulling->modified = qfalse;
}
*/
#if defined(USE_BSP_CLUSTERSURFACE_MERGING)
if(r_mergeClusterSurfaces->integer && !r_dynamicBspOcclusionCulling->integer)
{
R_UpdateClusterSurfaces();
}
#endif
if(r_novis->integer || tr.visClusters[tr.visIndex] == -1)
{
for(i = 0; i < tr.world->numnodes; i++)
{
if(tr.world->nodes[i].contents != CONTENTS_SOLID)
{
tr.world->nodes[i].visCounts[tr.visIndex] = tr.visCounts[tr.visIndex];
}
}
return;
}
vis = R_ClusterPVS(tr.visClusters[tr.visIndex]);
for(i = 0, leaf = tr.world->nodes; i < tr.world->numnodes; i++, leaf++)
{
if(tr.world->vis)
{
cluster = leaf->cluster;
if(cluster >= 0 && cluster < tr.world->numClusters)
{
// check general pvs
if(!(vis[cluster >> 3] & (1 << (cluster & 7))))
{
continue;
}
}
}
// check for door connection
if((tr.refdef.areamask[leaf->area >> 3] & (1 << (leaf->area & 7))))
{
// not visible
continue;
}
// ydnar: don't want to walk the entire bsp to add skybox surfaces
if(tr.refdef.rdflags & RDF_SKYBOXPORTAL)
{
// this only happens once, as game/cgame know the origin of the skybox
// this also means the skybox portal cannot move, as this list is calculated once and never again
if(tr.world->numSkyNodes < WORLD_MAX_SKY_NODES)
{
tr.world->skyNodes[tr.world->numSkyNodes++] = leaf;
}
R_AddLeafSurfaces(leaf, 0);
continue;
}
parent = leaf;
do
{
if(parent->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex])
break;
parent->visCounts[tr.visIndex] = tr.visCounts[tr.visIndex];
parent = parent->parent;
} while(parent);
}
}
static void DrawLeaf(bspNode_t * node, int decalBits)
{
// leaf node, so add mark surfaces
int c;
bspSurface_t *surf, **mark;
tr.pc.c_leafs++;
// add to z buffer bounds
if(node->mins[0] < tr.viewParms.visBounds[0][0])
{
tr.viewParms.visBounds[0][0] = node->mins[0];
}
if(node->mins[1] < tr.viewParms.visBounds[0][1])
{
tr.viewParms.visBounds[0][1] = node->mins[1];
}
if(node->mins[2] < tr.viewParms.visBounds[0][2])
{
tr.viewParms.visBounds[0][2] = node->mins[2];
}
if(node->maxs[0] > tr.viewParms.visBounds[1][0])
{
tr.viewParms.visBounds[1][0] = node->maxs[0];
}
if(node->maxs[1] > tr.viewParms.visBounds[1][1])
{
tr.viewParms.visBounds[1][1] = node->maxs[1];
}
if(node->maxs[2] > tr.viewParms.visBounds[1][2])
{
tr.viewParms.visBounds[1][2] = node->maxs[2];
}
// add the individual surfaces
mark = node->markSurfaces;
c = node->numMarkSurfaces;
while(c--)
{
// the surface may have already been added if it
// spans multiple leafs
surf = *mark;
R_AddWorldSurface(surf, decalBits);
mark++;
}
}
// ================================================================================================
//
// BSP OCCLUSION CULLING
//
// ================================================================================================
static qboolean InsideViewFrustum(bspNode_t * node, int planeBits)
{
if(!r_nocull->integer)
{
int i;
int r;
for(i = 0; i < FRUSTUM_PLANES; i++)
{
if(planeBits & (1 << i))
{
r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustums[0][i]);
if(r == 2)
{
return qfalse; // culled
}
if(r == 1)
{
planeBits &= ~(1 << i); // all descendants will also be in front
}
}
}
}
return qtrue;
}
//#define DEBUG_CHC 1
static void DrawNode_r(bspNode_t * node, int planeBits)
{
do
{
// if the bounding volume is outside the frustum, nothing
// inside can be visible OPTIMIZE: don't do this all the way to leafs?
if(!r_nocull->integer)
{
int i;
int r;
for(i = 0; i < FRUSTUM_PLANES; i++)
{
if(planeBits & (1 << i))
{
r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustums[0][i]);
if(r == 2)
{
return; // culled
}
if(r == 1)
{
planeBits &= ~(1 << i); // all descendants will also be in front
}
}
}
}
if(r_logFile->integer)
{
GLimp_LogComment(va("--- DrawNode_r( node = %i, isLeaf = %i ) ---\n", node - tr.world->nodes, node->contents == -1));
}
if(node->contents != -1)// && !(node->contents & CONTENTS_TRANSLUCENT))
{
gl_genericShader->SetUniform_Color(colorGreen);
}
else
{
gl_genericShader->SetUniform_Color(colorMdGrey);
}
// draw bsp leave or node
{
R_BindVBO(node->volumeVBO);
R_BindIBO(node->volumeIBO);
GL_VertexAttribsState(ATTR_POSITION);
tess.numVertexes = node->volumeVerts;
tess.numIndexes = node->volumeIndexes;
Tess_DrawElements();
tess.multiDrawPrimitives = 0;
tess.numIndexes = 0;
tess.numVertexes = 0;
}
if(node->contents != -1)
{
break;
}
// recurse down the children, front side first
DrawNode_r(node->children[0], planeBits);
// tail recurse
node = node->children[1];
} while(1);
}
static void IssueOcclusionQuery(link_t * queue, bspNode_t * node, qboolean resetMultiQueryLink)
{
#if defined(DEBUG_CHC)
if(r_logFile->integer)
{
if(node->contents != -1)// && !(node->contents & CONTENTS_TRANSLUCENT))
{
GLimp_LogComment(va("--- IssueOcclusionQuery( leaf = %i ) ---\n", node - tr.world->nodes));
gl_genericShader->SetUniform_Color(colorGreen);
}
else
{
GLimp_LogComment(va("--- IssueOcclusionQuery( node = %i ) ---\n", node - tr.world->nodes));
gl_genericShader->SetUniform_Color(colorMdGrey);
}
}
#endif
EnQueue(queue, node);
// tell GetOcclusionQueryResult that this is not a multi query
if(resetMultiQueryLink)
{
QueueInit(&node->multiQuery);
}
GL_CheckErrors();
#if 0
if(glIsQueryARB(node->occlusionQueryObjects[tr.viewCount]))
{
ri.Error(ERR_FATAL, "IssueOcclusionQuery: node %i has already an occlusion query object in slot %i: %i", node - tr.world->nodes, tr.viewCount, node->occlusionQueryObjects[tr.viewCount]);
}
#endif
// begin the occlusion query
glBeginQueryARB(GL_SAMPLES_PASSED, node->occlusionQueryObjects[tr.viewCount]);
GL_CheckErrors();
R_BindVBO(node->volumeVBO);
R_BindIBO(node->volumeIBO);
GL_VertexAttribsState(ATTR_POSITION);
tess.numVertexes = node->volumeVerts;
tess.numIndexes = node->volumeIndexes;
Tess_DrawElements();
// end the query
glEndQueryARB(GL_SAMPLES_PASSED);
#if 1
if(!glIsQueryARB(node->occlusionQueryObjects[tr.viewCount]))
{
ri.Error(ERR_FATAL, "IssueOcclusionQuery: node %i has no occlusion query object in slot %i: %i", node - tr.world->nodes, tr.viewCount, node->occlusionQueryObjects[tr.viewCount]);
}
#endif
node->occlusionQueryNumbers[tr.viewCount] = tr.pc.c_occlusionQueries;
tr.pc.c_occlusionQueries++;
tess.multiDrawPrimitives = 0;
tess.numIndexes = 0;
tess.numVertexes = 0;
GL_CheckErrors();
GLimp_LogComment("--- IssueOcclusionQuery end ---\n");
}
static void IssueMultiOcclusionQueries(link_t * multiQueue, link_t * individualQueue)
{
bspNode_t *node;
bspNode_t *multiQueryNode;
link_t *l;
if(r_logFile->integer)
{
GLimp_LogComment("IssueMultiOcclusionQueries([");
for(l = multiQueue->prev; l != multiQueue; l = l->prev)
{
node = (bspNode_t *) l->data;
GLimp_LogComment(va("%i, ", (int)(node - tr.world->nodes)));
}
GLimp_LogComment("])");
}
if(QueueEmpty(multiQueue))
return;
multiQueryNode = (bspNode_t *) QueueFront(multiQueue)->data;
// begin the occlusion query
GL_CheckErrors();
#if 0
if(!glIsQueryARB(multiQueryNode->occlusionQueryObjects[tr.viewCount]))
{
ri.Error(ERR_FATAL, "IssueMultiOcclusionQueries: node %i has already occlusion query object in slot %i: %i", multiQueryNode - tr.world->nodes, tr.viewCount, multiQueryNode->occlusionQueryObjects[tr.viewCount]);
}
#endif
glBeginQueryARB(GL_SAMPLES_PASSED, multiQueryNode->occlusionQueryObjects[tr.viewCount]);
GL_CheckErrors();
//GLimp_LogComment("rendering nodes:[");
for(l = multiQueue->prev; l != multiQueue; l = l->prev)
{
node = (bspNode_t *) l->data;
if(node->contents != -1)// && !(node->contents & CONTENTS_TRANSLUCENT))
{
gl_genericShader->SetUniform_Color(colorGreen);
}
else
{
gl_genericShader->SetUniform_Color(colorMdGrey);
}
//if(r_logFile->integer)
//{
// GLimp_LogComment(va("%i, ", node - tr.world->nodes));
//}
//Tess_EndBegin();
R_BindVBO(node->volumeVBO);
R_BindIBO(node->volumeIBO);
GL_VertexAttribsState(ATTR_POSITION);
tess.multiDrawPrimitives = 0;
tess.numVertexes = node->volumeVerts;
tess.numIndexes = node->volumeIndexes;
Tess_DrawElements();
tess.numIndexes = 0;
tess.numVertexes = 0;
}
//GLimp_LogComment("]\n");
multiQueryNode->occlusionQueryNumbers[tr.viewCount] = tr.pc.c_occlusionQueries;
tr.pc.c_occlusionQueries++;
tr.pc.c_occlusionQueriesMulti++;
// end the query
glEndQueryARB(GL_SAMPLES_PASSED);
GL_CheckErrors();
#if 0
if(!glIsQueryARB(multiQueryNode->occlusionQueryObjects[tr.viewCount]))
{
ri.Error(ERR_FATAL, "IssueMultiOcclusionQueries: node %i has no occlusion query object in slot %i: %i", multiQueryNode - tr.world->nodes, tr.viewCount, multiQueryNode->occlusionQueryObjects[tr.viewCount]);
}
#endif
// move queue to node->multiQuery queue
QueueInit(&multiQueryNode->multiQuery);
DeQueue(multiQueue);
while(!QueueEmpty(multiQueue))
{
node = (bspNode_t *) DeQueue(multiQueue);
EnQueue(&multiQueryNode->multiQuery, node);
}
EnQueue(individualQueue, multiQueryNode);
GLimp_LogComment("--- IssueMultiOcclusionQueries end ---\n");
}
static qboolean ResultAvailable(bspNode_t *node)
{
GLint available;
//glFinish();
available = 0;
//if(glIsQueryARB(node->occlusionQueryObjects[tr.viewCount]))
{
glGetQueryObjectivARB(node->occlusionQueryObjects[tr.viewCount], GL_QUERY_RESULT_AVAILABLE_ARB, &available);
GL_CheckErrors();
}
return (qboolean) available;
}
static void GetOcclusionQueryResult(bspNode_t *node)
{
link_t *l, *sentinel;
int ocSamples;
GLint available;
GLimp_LogComment("--- GetOcclusionQueryResult ---\n");
//glFinish();
#if 0
if(!glIsQueryARB(node->occlusionQueryObjects[tr.viewCount]))
{
ri.Error(ERR_FATAL, "GetOcclusionQueryResult: node %i has no occlusion query object in slot %i: %i", node - tr.world->nodes, tr.viewCount, node->occlusionQueryObjects[tr.viewCount]);
}
#endif
available = 0;
while(!available)
{
//if(glIsQueryARB(node->occlusionQueryObjects[tr.viewCount]))
{
glGetQueryObjectivARB(node->occlusionQueryObjects[tr.viewCount], GL_QUERY_RESULT_AVAILABLE_ARB, &available);
//GL_CheckErrors();
}
}
glGetQueryObjectivARB(node->occlusionQueryObjects[tr.viewCount], GL_QUERY_RESULT, &ocSamples);
if(r_logFile->integer)
{
GLimp_LogComment(va("GetOcclusionQueryResult(%i): available = %i, samples = %i\n", (int)(node - tr.world->nodes), available, ocSamples));
}
GL_CheckErrors();
node->occlusionQuerySamples[tr.viewCount] = ocSamples;
node->lastQueried[tr.viewCount] = tr.frameCount;
// copy result to all nodes that were linked to this multi query node
sentinel = &node->multiQuery;
for(l = sentinel->prev; l != sentinel; l = l->prev)
{
node = (bspNode_t *) l->data;
node->occlusionQuerySamples[tr.viewCount] = ocSamples;
node->lastQueried[tr.viewCount] = tr.frameCount;
}
}
static void PullUpVisibility(bspNode_t * node)
{
bspNode_t *parent;
parent = node;
while(parent && !parent->visible[tr.viewCount])
{
parent->visible[tr.viewCount] = qtrue;
parent->lastVisited[tr.viewCount] = tr.frameCount;
parent = parent->parent;
};
}
/*
static void PushNode(link_t * traversalStack, bspNode_t * node)
{
if(node->contents != -1)
{
//DrawLeaf(node, tr.refdef.decalBits);
}
else
{
//float d1, d2;
cplane_t *splitPlane;
splitPlane = node->plane;
//d1 = DistanceSquared(tr.viewParms.orientation.origin, node->children[0]->origin);
//d2 = DistanceSquared(tr.viewParms.orientation.origin, node->children[1]->origin);
//if(d1 <= d2)
#if 0
if(DotProduct(splitPlane->normal, tr.viewParms.orientation.axis[0]) <= 0)
{
StackPush(traversalStack, node->children[0]);
StackPush(traversalStack, node->children[1]);
ri.Printf(PRINT_ALL, "--> %i\n", node->children[0] - tr.world->nodes);
ri.Printf(PRINT_ALL, "--> %i\n", node->children[1] - tr.world->nodes);
}
else
#endif
{
#if 1
StackPush(traversalStack, node->children[0]);
StackPush(traversalStack, node->children[1]);
#else
InsertLink(&((bspNode_t*)node->children[0])->visChain, traversalStack);
InsertLink(&((bspNode_t*)node->children[1])->visChain, traversalStack);
traversalStack->numElements += 2;
#endif
if(r_logFile->integer)
{
GLimp_LogComment(va("traversal-stack <-- node %i\n", node->children[0] - tr.world->nodes));
GLimp_LogComment(va("traversal-stack <-- node %i\n", node->children[1] - tr.world->nodes));
}
}
}
}
*/
static void TraverseNode(link_t * distanceQueue, bspNode_t * node)
{
#if defined(DEBUG_CHC)
if(r_logFile->integer)
{
if(node->contents != -1)
{
GLimp_LogComment(va("--- TraverseNode( leaf = %i ) ---\n", node - tr.world->nodes));
gl_genericShader->SetUniform_Color(colorGreen);
}
else
{
GLimp_LogComment(va("--- TraverseNode( node = %i ) ---\n", node - tr.world->nodes));
gl_genericShader->SetUniform_Color(colorMdGrey);
}
// draw bsp leave or node
{
R_BindVBO(node->volumeVBO);
R_BindIBO(node->volumeIBO);
GL_VertexAttribsState(ATTR_POSITION);
tess.numVertexes = node->volumeVerts;
tess.numIndexes = node->volumeIndexes;
Tess_DrawElements();
tess.multiDrawPrimitives = 0;
tess.numIndexes = 0;
tess.numVertexes = 0;
}
}
#endif
if(node->contents != -1)
{
//DrawLeaf(node, tr.refdef.decalBits);
}
else
{
EnQueue(distanceQueue, node->children[0]);
EnQueue(distanceQueue, node->children[1]);
if(r_logFile->integer)
{
GLimp_LogComment(va("distance-queue <-- node %i\n", (int)(node->children[0] - tr.world->nodes)));
GLimp_LogComment(va("distance-queue <-- node %i\n", (int)(node->children[1] - tr.world->nodes)));
}
}
}
static void BuildNodeTraversalStackPost_r(bspNode_t * node)
{
do
{
if(tr.frameCount != node->lastVisited[tr.viewCount])
{
return;
}
#if defined(DEBUG_CHC)
if(r_logFile->integer)
{
if(node->contents != -1)
{
GLimp_LogComment(va("--- BuildNodeTraversalStackPost_r( leaf = %i, visible = %i ) ---\n", node - tr.world->nodes, node->visible[tr.viewCount]));
}
else
{
GLimp_LogComment(va("--- BuildNodeTraversalStackPost_r( node = %i, visible = %i ) ---\n", node - tr.world->nodes, node->visible[tr.viewCount]));
}
}
#endif
InsertLink(&node->visChain, &tr.traversalStack);
if(node->contents != -1)
{
if(node->visible[tr.viewCount])
DrawLeaf(node, tr.refdef.decalBits);
break;
}
// recurse down the children, front side first
BuildNodeTraversalStackPost_r(node->children[0]);
// tail recurse
node = node->children[1];
} while(1);
}
static bool WasVisible(bspNode_t * node)
{
return (node->visible[tr.viewCount] && ((tr.frameCount - node->lastVisited[tr.viewCount]) <= r_chcMaxVisibleFrames->integer));
}
static bool QueryReasonable(bspNode_t * node)
{
// if r_chcMaxVisibleFrames 10 then range from 5 to 10
//return ((tr.frameCount - node->lastQueried[tr.viewCount]) > r_chcMaxVisibleFrames->integer);
return ((tr.frameCount - node->lastQueried[tr.viewCount]) > Q_min((int)ceil((r_chcMaxVisibleFrames->value * 0.5f) + (r_chcMaxVisibleFrames->value * 0.5f) * random()), r_chcMaxVisibleFrames->integer));
}
static void R_CoherentHierachicalCulling()
{
bspNode_t *node;
bspNode_t *multiQueryNode;
// link_t traversalStack;
link_t distanceQueue;
link_t occlusionQueryQueue;
link_t visibleQueue; // CHC++
link_t invisibleQueue; // CHC++
//link_t renderQueue;
int startTime = 0, endTime = 0;
//ri.Cvar_Set("r_logFile", "1");
GLimp_LogComment("--- R_CoherentHierachicalCulling ---\n");
if(r_logFile->integer)
{
GLimp_LogComment(va("tr.viewCount = %i, tr.viewCountNoReset = %i\n", tr.viewCount, tr.viewCountNoReset));
}
if(r_speeds->integer)
{
glFinish();
startTime = ri.Milliseconds();
}
if(DS_STANDARD_ENABLED())
{
R_BindFBO(tr.geometricRenderFBO);
}
else if(HDR_ENABLED())
{
R_BindFBO(tr.deferredRenderFBO);
}
else
{
R_BindNullFBO();
}
gl_genericShader->DisableAlphaTesting();
gl_genericShader->DisablePortalClipping();
gl_genericShader->DisableVertexSkinning();
gl_genericShader->DisableVertexAnimation();
gl_genericShader->DisableDeformVertexes();
gl_genericShader->DisableTCGenEnvironment();
gl_genericShader->BindProgram();
GL_Cull(CT_TWO_SIDED);
GL_LoadProjectionMatrix(tr.viewParms.projectionMatrix);
GL_Viewport(tr.viewParms.viewportX, tr.viewParms.viewportY,
tr.viewParms.viewportWidth, tr.viewParms.viewportHeight);
GL_Scissor(tr.viewParms.viewportX, tr.viewParms.viewportY,
tr.viewParms.viewportWidth, tr.viewParms.viewportHeight);
// set uniforms
gl_genericShader->SetUniform_ColorModulate(CGEN_CONST, AGEN_CONST);
gl_genericShader->SetUniform_Color(colorWhite);
// set up the transformation matrix
GL_LoadModelViewMatrix(tr.orientation.modelViewMatrix);
gl_genericShader->SetUniform_ModelViewProjectionMatrix(glState.modelViewProjectionMatrix[glState.stackIndex]);
// bind u_ColorMap
GL_SelectTexture(0);
GL_Bind(tr.whiteImage);
gl_genericShader->SetUniform_ColorTextureMatrix(matrixIdentity);
#if 0
GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GL_State(GLS_POLYMODE_LINE | GLS_DEPTHTEST_DISABLE);
// draw BSP leaf volumes to color for debugging
DrawNode_r(&tr.world->nodes[0], FRUSTUM_CLIPALL);
#endif
#if 0
GL_ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_DEPTH_BUFFER_BIT);
GL_State(GLS_COLORMASK_BITS | GLS_DEPTHMASK_TRUE);
// draw BSP leaf volumes to depth
DrawNode_r(&tr.world->nodes[0], FRUSTUM_CLIPALL);
#endif
if(r_logFile->integer)
{
GL_ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
}
else
{
// use the depth buffer of the previous frame for occlusion culling
GL_State(GLS_COLORMASK_BITS);
}
ClearLink(&tr.traversalStack);
QueueInit(&tr.occlusionQueryQueue);
ClearLink(&tr.occlusionQueryList);
//ClearLink(&traversalStack);
QueueInit(&distanceQueue);
QueueInit(&occlusionQueryQueue);
QueueInit(&visibleQueue);
QueueInit(&invisibleQueue);
//QueueInit(&renderQueue);
EnQueue(&distanceQueue, &tr.world->nodes[0]);
//StackPush(&traversalStack, &tr.world->nodes[0]);
/*
ClearLink(&traversalStack);
traversalStack.numElements = 0;
node = &tr.world->nodes[0];
InsertLink(&node->visChain, &traversalStack);
traversalStack.numElements++;
*/
while(!QueueEmpty(&distanceQueue) || !QueueEmpty(&occlusionQueryQueue) || !QueueEmpty(&invisibleQueue) || !QueueEmpty(&visibleQueue))
{
if(r_logFile->integer)
{
GLimp_LogComment(va("--- (distanceQueue = %i, occlusionQueryQueue = %i, invisibleQueue = %i, visibleQueue = %i)\n", QueueSize(&distanceQueue), QueueSize(&occlusionQueryQueue), QueueSize(&invisibleQueue), QueueSize(&visibleQueue)));
}
//--PART 1: process finished occlusion queries
while(!QueueEmpty(&occlusionQueryQueue) && (ResultAvailable((bspNode_t *) QueueFront(&occlusionQueryQueue)->data) || QueueEmpty(&distanceQueue)))
{
if(ResultAvailable((bspNode_t *) QueueFront(&occlusionQueryQueue)->data))
{
node = (bspNode_t *) DeQueue(&occlusionQueryQueue);
// wait if result not available
GetOcclusionQueryResult(node);
if(node->occlusionQuerySamples[tr.viewCount] > r_chcVisibilityThreshold->integer)
{
// if a query of multiple previously invisible objects became visible, we need to
// test all the individual objects ...
if(!QueueEmpty(&node->multiQuery))
{
if(r_logFile->integer)
{
GLimp_LogComment(va("MULTI query node %i visible\n", (int)(node - tr.world->nodes)));
}
multiQueryNode = node;
IssueOcclusionQuery(&occlusionQueryQueue, multiQueryNode, qfalse);
while(!QueueEmpty(&multiQueryNode->multiQuery))
{
node = (bspNode_t *) DeQueue(&multiQueryNode->multiQuery);
// it might be possible that a leaf caused this node to be visible by a PullUpVisibility() call
// so avoid a further query
if(!(node->visible[tr.viewCount] && tr.frameCount == node->lastVisited[tr.viewCount]))
{
IssueOcclusionQuery(&occlusionQueryQueue, node, qtrue);
}
}
}
else
{
if(r_logFile->integer)
{
GLimp_LogComment(va("single query node %i visible\n", (int)(node - tr.world->nodes)));
}
if(r_dynamicBspOcclusionCulling->integer == 1)
{
if(!WasVisible(node))
{
TraverseNode(&distanceQueue, node);
}
}
else
{
TraverseNode(&distanceQueue, node);
}
PullUpVisibility(node);
}
}
else
{
node->visible[tr.viewCount] = qfalse;
if(!QueueEmpty(&node->multiQuery))
{
// node was an invisible multi query node so dequeue all its children
multiQueryNode = node;
while(!QueueEmpty(&multiQueryNode->multiQuery))
{
node = (bspNode_t *) DeQueue(&multiQueryNode->multiQuery);
node->visible[tr.viewCount] = qfalse;
tr.pc.c_occlusionQueriesSaved++;
}
}
}
}
#if 1
else if(r_dynamicBspOcclusionCulling->integer == 1)
{
if(!QueueEmpty(&visibleQueue))
{
node = (bspNode_t *) DeQueue(&visibleQueue);
IssueOcclusionQuery(&occlusionQueryQueue, node, qtrue);
}
}
#endif
} // end while(!QueueEmpty(&occlusionQueryQueue))
//--PART 2: hierarchical traversal
if(!QueueEmpty(&distanceQueue)) //if(!StackEmpty(&traversalStack))
{
node = (bspNode_t *) DeQueue(&distanceQueue);
//node = (bspNode_t *) StackPop(&traversalStack);
/*
link_t* top = traversalStack.next;
RemoveLink(top);
node = (bspNode_t *) top->data;
*/
if(r_logFile->integer)
{
GLimp_LogComment(va("distance-queue --> node %i\n", (int)(node - tr.world->nodes)));
}
if( node->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex] && // node was marked as potentially visible
(node->contents == -1 || (node->contents != -1 && node->numMarkSurfaces)) &&
InsideViewFrustum(node, FRUSTUM_CLIPALL)
)
{
// identify previously visible nodes
bool wasVisible = WasVisible(node);
if(r_dynamicBspOcclusionCulling->integer > 1)
{
// reset node's visibility classification
node->visible[tr.viewCount] = (qboolean) !QueryReasonable(node);
}
// identify nodes that we cannot skip queries for
bool needsQuery;
bool clipsNearPlane = (BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustums[0][FRUSTUM_NEAR]) == 3);
if(clipsNearPlane)
{
// node clips near plane so avoid the occlusion query test
node->occlusionQuerySamples[tr.viewCount] = r_chcVisibilityThreshold->integer + 1;
node->lastQueried[tr.viewCount] = tr.frameCount;
node->visible[tr.viewCount] = qtrue;
needsQuery = false;
}
#if 1
else if(r_chcIgnoreLeaves->integer && node->contents != -1)
{
// NOTE: this is the fastest dynamic occlusion culling path
// only very few leaves are invisible if we don't traverse through all bsp nodes
// so testing these leaves just causes additional occlusion queries which can be avoided
// by setting all reached leaves to visible
node->occlusionQuerySamples[tr.viewCount] = r_chcVisibilityThreshold->integer + 1;
node->lastQueried[tr.viewCount] = tr.frameCount;
node->visible[tr.viewCount] = qtrue;
needsQuery = false;
}
#endif
else
{
// CHC default
needsQuery = !wasVisible || (node->contents != -1);
}
// update node's visited flag
node->lastVisited[tr.viewCount] = tr.frameCount;
// optimization
#if 0
if((node->contents != -1) && node->sameAABBAsParent)
{
node->visible[tr.viewCount] = qtrue;
wasVisible = qtrue;
}
#endif
bool leafThatNeedsQuery = node->contents != -1;
if(leafThatNeedsQuery)
{
if(r_chcIgnoreLeaves->integer)
leafThatNeedsQuery = false;
}
else
{
leafThatNeedsQuery = true;
}
if(r_dynamicBspOcclusionCulling->integer == 1)
{
// CHC++
if(!wasVisible && !clipsNearPlane && leafThatNeedsQuery)
{
if(r_logFile->integer)
{
GLimp_LogComment(va("i-queue <-- node %i\n", (int)(node - tr.world->nodes)));
}
EnQueue(&invisibleQueue, node);
if(QueueSize(&invisibleQueue) >= r_chcMaxPrevInvisNodesBatchSize->integer)
IssueMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue);
}
else
{
#if 1
if((node->contents != -1) && !clipsNearPlane && QueryReasonable(node) && leafThatNeedsQuery)
{
if(r_logFile->integer)
{
GLimp_LogComment(va("v-queue <-- node %i\n", (int)(node - tr.world->nodes)));
}
EnQueue(&visibleQueue, node);
}
#endif
// always traverse a node if it was visible
TraverseNode(&distanceQueue, node);
}
}
else
{
// CHC default
if(needsQuery)//!wasVisible && !clipsNearPlane)
{
IssueOcclusionQuery(&occlusionQueryQueue, node, qtrue);
}
if(wasVisible)
{
// always traverse a node if it was visible
TraverseNode(&distanceQueue, node);
//if(clipsNearPlane)
//{
// PullUpVisibility(node);
//}
}
}
}
}
if(r_dynamicBspOcclusionCulling->integer == 1)
{
if(QueueEmpty(&distanceQueue))
//if(StackEmpty(&traversalStack))
{
// remaining previously visible node queries
if(!QueueEmpty(&invisibleQueue))
{
IssueMultiOcclusionQueries(&invisibleQueue, &occlusionQueryQueue);
}
if(!QueueEmpty(&visibleQueue))
{
while(!QueueEmpty(&visibleQueue))
{
node = (bspNode_t *) DeQueue(&visibleQueue);
IssueOcclusionQuery(&occlusionQueryQueue, node, qtrue);
}
}
}
}
//ri.Printf(PRINT_ALL, "--- (%i, %i, %i)\n", !StackEmpty(&traversalStack), !QueueEmpty(&occlusionQueryQueue), !QueueEmpty(&invisibleQueue));
}
ClearLink(&tr.traversalStack);
BuildNodeTraversalStackPost_r(&tr.world->nodes[0]);
R_BindNullFBO();
// reenable color buffer and depth buffer writes
GL_State(GLS_DEFAULT);
GL_CheckErrors();
//ri.Printf(PRINT_ALL, "--- R_CHC++ end ---\n");
if(r_speeds->integer)
{
glFinish();
endTime = ri.Milliseconds();
tr.pc.c_CHCTime = endTime - startTime;
}
}
/*
=============
R_AddWorldSurfaces
=============
*/
void R_AddWorldSurfaces(void)
{
if(!r_drawworld->integer)
{
return;
}
if(tr.refdef.rdflags & RDF_NOWORLDMODEL)
{
return;
}
tr.currentEntity = &tr.worldEntity;
// clear out the visible min/max
ClearBounds(tr.viewParms.visBounds[0], tr.viewParms.visBounds[1]);
// render sky or world?
if(tr.refdef.rdflags & RDF_SKYBOXPORTAL && tr.world->numSkyNodes > 0)
{
int i;
bspNode_t **node;
for(i = 0, node = tr.world->skyNodes; i < tr.world->numSkyNodes; i++, node++)
R_AddLeafSurfaces(*node, 0); // no decals on skybox nodes
}
else
{
// determine which leaves are in the PVS / areamask
R_MarkLeaves();
// update the bsp nodes with the dynamic occlusion query results
if(glConfig2.occlusionQueryBits && glConfig.driverType != GLDRV_MESA && r_dynamicBspOcclusionCulling->integer)
{
R_CoherentHierachicalCulling();
}
else
{
ClearLink(&tr.traversalStack);
ClearLink(&tr.occlusionQueryQueue);
ClearLink(&tr.occlusionQueryList);
// update visbounds and add surfaces that weren't cached with VBOs
R_RecursiveWorldNode(tr.world->nodes, FRUSTUM_CLIPALL, tr.refdef.decalBits);
}
// ydnar: add decal surfaces
R_AddDecalSurfaces(tr.world->models);
#if defined(USE_BSP_CLUSTERSURFACE_MERGING)
if(r_mergeClusterSurfaces->integer && !r_dynamicBspOcclusionCulling->integer)
{
int j, i;
srfVBOMesh_t *srf;
shader_t *shader;
cplane_t *frust;
int r;
for(j = 0; j < tr.world->numClusterVBOSurfaces[tr.visIndex]; j++)
{
srf = (srfVBOMesh_t *) Com_GrowListElement(&tr.world->clusterVBOSurfaces[tr.visIndex], j);
shader = srf->shader;
for(i = 0; i < FRUSTUM_PLANES; i++)
{
frust = &tr.viewParms.frustums[0][i];
r = BoxOnPlaneSide(srf->bounds[0], srf->bounds[1], frust);
if(r == 2)
{
// completely outside frustum
continue;
}
}
R_AddDrawSurf((surfaceType_t *)srf, shader, srf->lightmapNum, 0);
}
}
#endif
}
}
/*
=============
R_AddWorldInteractions
=============
*/
void R_AddWorldInteractions(trRefLight_t * light)
{
if(!r_drawworld->integer)
{
return;
}
if(tr.refdef.rdflags & RDF_NOWORLDMODEL)
{
return;
}
tr.currentEntity = &tr.worldEntity;
// perform frustum culling and add all the potentially visible surfaces
tr.lightCount++;
R_RecursiveInteractionNode(tr.world->nodes, light, FRUSTUM_CLIPALL);
}
/*
=============
R_AddPrecachedWorldInteractions
=============
*/
void R_AddPrecachedWorldInteractions(trRefLight_t * light)
{
interactionType_t iaType = IA_DEFAULT;
if(!r_drawworld->integer)
{
return;
}
if(tr.refdef.rdflags & RDF_NOWORLDMODEL)
{
return;
}
if(!light->firstInteractionCache)
{
// this light has no interactions precached
return;
}
tr.currentEntity = &tr.worldEntity;
if((r_vboShadows->integer || r_vboLighting->integer))// && light->l.rlType != RL_DIRECTIONAL)
{
interactionCache_t *iaCache;
interactionVBO_t *iaVBO;
srfVBOMesh_t *srf;
shader_t *shader;
bspSurface_t *surface;
// this can be shadow mapping or shadowless lighting
for(iaVBO = light->firstInteractionVBO; iaVBO; iaVBO = iaVBO->next)
{
if(!iaVBO->vboLightMesh)
continue;
srf = iaVBO->vboLightMesh;
shader = iaVBO->shader;
switch (light->l.rlType)
{
case RL_OMNI:
R_AddLightInteraction(light, (surfaceType_t *)srf, shader, CUBESIDE_CLIPALL, IA_LIGHTONLY);
break;
case RL_DIRECTIONAL:
case RL_PROJ:
R_AddLightInteraction(light, (surfaceType_t *)srf, shader, CUBESIDE_CLIPALL, IA_LIGHTONLY);
break;
default:
R_AddLightInteraction(light, (surfaceType_t *)srf, shader, CUBESIDE_CLIPALL, IA_DEFAULT);
break;
}
}
// add meshes for shadowmap generation if any
for(iaVBO = light->firstInteractionVBO; iaVBO; iaVBO = iaVBO->next)
{
if(!iaVBO->vboShadowMesh)
continue;
srf = iaVBO->vboShadowMesh;
shader = iaVBO->shader;
R_AddLightInteraction(light, (surfaceType_t *)srf, shader, iaVBO->cubeSideBits, IA_SHADOWONLY);
}
// add interactions that couldn't be merged into VBOs
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
{
if(iaCache->redundant)
continue;
if(iaCache->mergedIntoVBO)
continue;
surface = iaCache->surface;
// Tr3B - this surface is maybe not in this view but it may still cast a shadow
// into this view
if(surface->viewCount != tr.viewCountNoReset)
{
if(r_shadows->integer < SHADOWING_ESM16 || light->l.noShadows)
continue;
else
iaType = IA_SHADOWONLY;
}
else
{
iaType = iaCache->type;
}
R_AddLightInteraction(light, surface->data, surface->shader, iaCache->cubeSideBits, iaType);
}
}
else
{
interactionCache_t *iaCache;
bspSurface_t *surface;
for(iaCache = light->firstInteractionCache; iaCache; iaCache = iaCache->next)
{
if(iaCache->redundant)
continue;
surface = iaCache->surface;
// Tr3B - this surface is maybe not in this view but it may still cast a shadow
// into this view
if(surface->viewCount != tr.viewCountNoReset)
{
if(r_shadows->integer < SHADOWING_ESM16 || light->l.noShadows)
continue;
else
iaType = IA_SHADOWONLY;
}
else
{
iaType = iaCache->type;
}
R_AddLightInteraction(light, surface->data, surface->shader, iaCache->cubeSideBits, iaType);
}
}
}