rallyunlimited-engine/code/renderer/tr_vbo.c
2024-02-02 19:46:17 +03:00

1540 lines
38 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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.
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "tr_local.h"
/*
General concept of this VBO implementation is to store all possible static data
(vertexes,colors,tex.coords[0..1],normals) in VBO and accessing it via indexes ONLY.
Static data in current meaning is a world surfaces whose shader data
can be evaluated at map load time.
Every static surface gets unique item index which will be added to queue
instead of tesselation like for regular surfaces. Using items queue also
eleminates run-time tesselation limits.
When it is time to render - we sort queued items to get longest possible
index sequence run to check if it is long enough i.e. worth switching to GPU-side IBO.
So long index runs are rendered via multiple glDrawElements() calls,
all remaining short index sequences are grouped together into single software index
which is finally rendered via single legacy index array transfer.
For VBO storage 'Structure of Arrays' approach were selected as it is much easier to
maintain, also it can be used for re-tesselation in future.
No performance differences from 'Array of Structures' were observed.
*/
#define MAX_VBO_STAGES MAX_SHADER_STAGES
#define MIN_IBO_RUN 320
//[ibo]: [index0][index1]...
//[vbo]: [vertex0][color0][tx0][vertex1][color1][tx1]...
typedef struct vbo_item_s {
int index_offset; // int glIndex_t units, device-local, relative to current shader
int soft_offset; // host-visible, absolute
int num_indexes;
int num_vertexes;
} vbo_item_t;
typedef struct ibo_item_s {
int offset;
int length;
} ibo_item_t;
typedef struct vbo_s {
byte *vbo_buffer;
int vbo_offset;
int vbo_size;
byte *ibo_buffer;
int ibo_offset;
int ibo_size;
glIndex_t *soft_buffer;
uint32_t soft_buffer_indexes;
ibo_item_t *ibo_items;
int ibo_items_count;
vbo_item_t *items;
int items_count;
int *items_queue;
int items_queue_count;
int items_queue_vertexes;
int items_queue_indexes;
short fogFPindex; // fog-only
short fogVPindex[2];// eye-in/eye-out
} vbo_t;
static vbo_t world_vbo;
GLuint VBO_world_data;
GLuint VBO_world_indexes;
void VBO_Cleanup( void );
static const char *genATestFP( int function )
{
switch ( function )
{
case GLS_ATEST_GT_0:
return
"MOV t.x, -base.a; \n" // '>0' -> '<0'
"SLT t.x, t.x, 0.0; \n" // if ( t.x < 0 ) t.x = 1; else t.x = 0;
"SUB t.x, t.x, 0.5; \n" // if (( t.x - 0.5 ) < 0) kill_fragment;
"KIL t.x;\n ";
case GLS_ATEST_LT_80:
return
"SGE t.x, base.a, 0.5; \n"
"MOV t.x, -t.x; \n" // "MUL t.x, t.x, {-1.0}; \n"
"KIL t.x;\n ";
case GLS_ATEST_GE_80:
return
"SGE t.x, base.a, 0.5; \n"
"SUB t.x, t.x, {0.5}; \n"
"KIL t.x;\n";
default:
return "";
}
}
enum {
VP_FOG_NONE,
VP_FOG_EYE_IN,
VP_FOG_EYE_OUT,
};
enum {
FP_FOG_NONE,
FP_FOG_BLEND,
FP_FOG_ONLY
};
static const char *BuildVP( int multitexture, int fogmode, int texgen )
{
static char buf[2048], b[256];
const char *tex0;
const char *tex1;
strcpy( buf,
"!!ARBvp1.0 \n"
"OPTION ARB_position_invariant; \n" );
switch ( fogmode ) {
default:
case VP_FOG_NONE:
break;
case VP_FOG_EYE_IN:
strcat( buf, fogInVPCode ); break;
case VP_FOG_EYE_OUT:
strcat( buf, fogOutVPCode ); break;
}
if ( texgen ) {
// environment mapping
strcat( buf,
"TEMP viewer, d; \n"
// VectorSubtract( backEnd.or.viewOrigin, v, viewer );
"SUB viewer, program.local[0], vertex.position;\n"
// VectorNormalize( viewer )
"DP3 viewer.w, viewer, viewer; \n"
"RSQ viewer.w, viewer.w; \n"
"MUL viewer.xyz, viewer.w, viewer; \n"
// d = DotProduct( normal, viewer );
"DP3 d, vertex.normal, viewer; \n"
//reflected[] = normal[]*2*d - viewer[];
"MUL d, d, 2.0; \n"
"MAD d, vertex.normal, d, -viewer; \n"
//st[0] = 0.5 + reflected[1] * 0.5;
//st[1] = 0.5 - reflected[2] * 0.5;
//"MAD st.x, d.y, 0.5, 0.5; \n"
//"MAD st.y, d.z, -0.5, 0.5; \n"
"PARAM m = { 0.0, 0.5, -0.5, 0.0 }; \n"
"MAD d, d, m, 0.5; \n" );
if ( texgen & 1 )
tex0 = "d.yzwx";
else
tex0 = "vertex.texcoord[0]";
if ( texgen & 2 )
tex1 = "d.yzwx";
else
tex1 = "vertex.texcoord[1]";
} else {
tex0 = "vertex.texcoord[0]";
tex1 = "vertex.texcoord[1]";
}
switch ( multitexture ) {
case GL_ADD:
case GL_MODULATE:
sprintf( b,
"MOV result.texcoord[0], %s; \n"
"MOV result.texcoord[1], %s; \n",
tex0, tex1 );
break;
case GL_REPLACE:
sprintf( b, "MOV result.texcoord[1], %s; \n", tex1 );
break;
default:
sprintf( b, "MOV result.texcoord[0], %s; \n", tex0 );
break;
}
strcat( buf, b );
strcat( buf,
"MOV result.color, vertex.color; \n"
"END \n" );
return buf;
}
const char *BuildFP( int multitexture, int alphatest, int fogMode )
{
static char buf[1024];
strcpy( buf, "!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"TEMP base; \n" );
if ( fogMode == FP_FOG_ONLY ) {
strcat( buf, "TEX base, fragment.texcoord[4], texture[2], 2D; \n" );
strcat( buf, "MUL result.color, base, program.local[0]; \n" );
strcat( buf, "END \n" );
return buf;
}
switch ( multitexture ) {
case 0:
strcat( buf, "TEMP t; \n" );
strcat( buf, "TEX base, fragment.texcoord[0], texture[0], 2D; \n" );
strcat( buf, genATestFP( alphatest ) );
break;
case GL_ADD:
strcat( buf, "TEMP t; \n" );
strcat( buf, "TEX base, fragment.texcoord[0], texture[0], 2D; \n" );
strcat( buf, genATestFP( alphatest ) );
strcat( buf, "TEX t, fragment.texcoord[1], texture[1], 2D; \n"
"ADD base, base, t; \n" );
break;
case GL_MODULATE:
strcat( buf, "TEMP t; \n" );
strcat( buf, "TEX base, fragment.texcoord[0], texture[0], 2D; \n" );
strcat( buf, genATestFP( alphatest ) );
strcat( buf, "TEX t, fragment.texcoord[1], texture[1], 2D; \n" );
strcat( buf, "MUL base, base, t; \n" );
break;
case GL_REPLACE:
strcat( buf, "TEX base, fragment.texcoord[1], texture[1], 2D; \n" );
//strcat( buf, genATestFP( alphatest ) );
break;
default:
ri.Error( ERR_DROP, "Invalid multitexture mode %04x", multitexture );
break;
}
if ( fogMode == FP_FOG_BLEND ) {
strcat( buf, "MUL base, base, fragment.color; \n" );
strcat( buf, "TEMP fog; \n"
"TEX fog, fragment.texcoord[4], texture[2], 2D; \n"
"MUL fog, fog, program.local[0]; \n"
"LRP_SAT result.color, fog.a, fog, base; \n"
"END \n" );
} else {
strcat( buf,
"MUL result.color, base, fragment.color; \n"
"END \n" );
}
return buf;
}
// multitexture modes: single, mt-add, mt-modulate, mt-replace
// environment mapping: none, tx0, tx1, tx0 + tx1
// fog modes: disabled, eye-in, eye-out, fog-only
static GLuint vbo_vp[4*4*4+1];
// multitexture modes: single, mt-add, mt-modulate, mt-replace
// alpha test modes: disabled, GT0, LT80, GE80
// fog modes: disabled, enabled, fog-only, unused
static GLuint vbo_fp[4*4*4+1];
static int getVPindex( int multitexture, int fogmode, int texgen )
{
int index;
switch ( multitexture )
{
default: index = 0; break;
case GL_ADD: index = 1; break;
case GL_MODULATE: index = 2; break;
case GL_REPLACE: index = 3; break;
}
index <<= 2; // reserve bits for texgen
index |= texgen & 3; // environment mapping: none, tx0, tx1, tx0 + tx1
index <<= 2; // reserve bits for fogmode
index |= fogmode & 3; // disabled, eye-in, eye-out, fog-only
return index + 1;
}
static int getFPindex( int multitexture, int atest, int fogmode )
{
int index;
switch( multitexture )
{
default: index = 0; break;
case GL_ADD: index = 1; break;
case GL_MODULATE: index = 2; break;
case GL_REPLACE: index = 3; break;
}
index <<= 2; // reserve bits for atest
switch ( atest )
{
case GLS_ATEST_GT_0: index |= 1; break;
case GLS_ATEST_LT_80: index |= 2; break;
case GLS_ATEST_GE_80: index |= 3; break;
default: break;
}
index <<= 2; // reserve bits for fog mode
index |= fogmode & 3; // disabled, blend, fog-only
return index + 1;
}
static qboolean isStaticRGBgen( colorGen_t cgen )
{
switch ( cgen )
{
case CGEN_BAD:
case CGEN_IDENTITY_LIGHTING: // tr.identityLight
case CGEN_IDENTITY: // always (1,1,1,1)
case CGEN_ENTITY: // grabbed from entity's modulate field
case CGEN_ONE_MINUS_ENTITY: // grabbed from 1 - entity.modulate
case CGEN_EXACT_VERTEX: // tess.vertexColors
case CGEN_VERTEX: // tess.vertexColors * tr.identityLight
case CGEN_ONE_MINUS_VERTEX:
// case CGEN_WAVEFORM, // programmatically generated
case CGEN_LIGHTING_DIFFUSE:
//case CGEN_FOG, // standard fog
case CGEN_CONST: // fixed color
return qtrue;
default:
return qfalse;
}
}
static qboolean isStaticTCgen( shaderStage_t *stage, int bundle )
{
switch ( stage->bundle[bundle].tcGen )
{
case TCGEN_BAD:
case TCGEN_IDENTITY: // clear to 0,0
case TCGEN_LIGHTMAP:
case TCGEN_TEXTURE:
return qtrue;
case TCGEN_ENVIRONMENT_MAPPED:
if ( stage->bundle[bundle].numTexMods == 0 && ( stage->bundle[bundle].lightmap == LIGHTMAP_INDEX_NONE || r_mergeLightmaps->integer == 0 ) ) {
stage->tessFlags |= TESS_ENV0 << bundle;
stage->tessFlags &= ~( TESS_ST0 << bundle );
return qtrue;
} else {
stage->tessFlags |= TESS_ST0 << bundle;
stage->tessFlags &= ~( TESS_ENV0 << bundle );
return qfalse;
}
//case TCGEN_ENVIRONMENT_MAPPED_FP:
//case TCGEN_FOG:
case TCGEN_VECTOR: // S and T from world coordinates
return qtrue;
default:
return qfalse;
}
}
static qboolean isStaticTCmod( const textureBundle_t *bundle )
{
int i;
for ( i = 0; i < bundle->numTexMods; i++ ) {
switch ( bundle->texMods[i].type ) {
case TMOD_NONE:
case TMOD_SCALE:
case TMOD_TRANSFORM:
case TMOD_OFFSET:
case TMOD_SCALE_OFFSET:
break;
default:
return qfalse;
}
}
return qtrue;
}
static qboolean isStaticAgen( alphaGen_t agen )
{
switch ( agen )
{
case AGEN_IDENTITY:
case AGEN_SKIP:
case AGEN_ENTITY:
case AGEN_ONE_MINUS_ENTITY:
case AGEN_VERTEX:
case AGEN_ONE_MINUS_VERTEX:
//case AGEN_LIGHTING_SPECULAR:
//case AGEN_WAVEFORM:
//case AGEN_PORTAL:
case AGEN_CONST:
return qtrue;
default:
return qfalse;
}
}
static void CompileVertexProgram( int VPindex, int mtx, int fogMode, int texgen )
{
if ( vbo_vp[ VPindex ] == 0 )
{
// generate vertex program
qglGenProgramsARB( 1, &vbo_vp[ VPindex ] );
ARB_CompileProgram( Vertex, BuildVP( mtx, fogMode, texgen ), vbo_vp[ VPindex ] );
}
}
static void CompileFragmentProgram( int FPindex, int mtx, int atestBits, int fogMode )
{
if ( vbo_fp[ FPindex ] == 0 )
{
// generate fragment program
qglGenProgramsARB( 1, &vbo_fp[ FPindex ] );
ARB_CompileProgram( Fragment, BuildFP( mtx, atestBits, fogMode ), vbo_fp[ FPindex ] );
}
}
/*
=============
isStaticShader
Decide if we can put surface in static vbo
=============
*/
static qboolean isStaticShader( shader_t *shader )
{
shaderStage_t* stage;
int i, svarsSize, mtx;
GLbitfield atestBits;
if ( shader->isStaticShader )
return qtrue;
if ( shader->isSky || shader->remappedShader )
return qfalse;
if ( shader->numDeforms || shader->numUnfoggedPasses > MAX_VBO_STAGES )
return qfalse;
svarsSize = 0;
for ( i = 0; i < shader->numUnfoggedPasses; i++ )
{
stage = shader->stages[ i ];
if ( !stage || !stage->active )
break;
if ( stage->depthFragment )
return qfalse;
if ( stage->adjustColorsForFog != ACFF_NONE )
return qfalse;
if ( !isStaticTCmod( &stage->bundle[0] ) || !isStaticTCmod( &stage->bundle[1] ) )
return qfalse;
if ( !isStaticRGBgen( stage->rgbGen ) )
return qfalse;
if ( !isStaticTCgen( stage, 0 ) )
return qfalse;
if ( !isStaticTCgen( stage, 1 ) )
return qfalse;
if ( !isStaticAgen( stage->alphaGen ) )
return qfalse;
svarsSize += sizeof( tess.svars.colors[0] );
if ( stage->tessFlags & TESS_ST0 )
svarsSize += sizeof( tess.svars.texcoords[0][0] );
if ( stage->tessFlags & TESS_ST1 )
svarsSize += sizeof( tess.svars.texcoords[1][0] );
}
if ( i == 0 )
return qfalse;
shader->isStaticShader = qtrue;
// TODO: alloc separate structure?
shader->svarsSize = svarsSize;
shader->iboOffset = -1;
shader->vboOffset = -1;
shader->curIndexes = 0;
shader->curVertexes = 0;
shader->numIndexes = 0;
shader->numVertexes = 0;
for ( i = 0; i < shader->numUnfoggedPasses; i++ )
{
int texgen;
stage = shader->stages[ i ];
if ( !stage || !stage->active )
break;
mtx = stage->mtEnv;
atestBits = stage->stateBits & GLS_ATEST_BITS;
texgen = 0;
if ( stage->tessFlags & TESS_ENV0 ) {
texgen |= 1;
}
if ( stage->tessFlags & TESS_ENV1 ) {
texgen |= 2;
}
if ( texgen || shader->numUnfoggedPasses == 1 ) {
stage->vboVPindex[1] = getVPindex( mtx, VP_FOG_EYE_IN, texgen );
stage->vboVPindex[2] = getVPindex( mtx, VP_FOG_EYE_OUT, texgen );
stage->vboFPindex[1] = getFPindex( mtx, atestBits, FP_FOG_BLEND );
CompileVertexProgram( stage->vboVPindex[1], mtx, VP_FOG_EYE_IN, texgen );
CompileVertexProgram( stage->vboVPindex[2], mtx, VP_FOG_EYE_OUT, texgen );
CompileFragmentProgram( stage->vboFPindex[1], mtx, atestBits, FP_FOG_BLEND );
if ( texgen ) {
stage->vboVPindex[0] = getVPindex( mtx, VP_FOG_NONE, texgen );
stage->vboFPindex[0] = getFPindex( mtx, atestBits, FP_FOG_NONE );
CompileVertexProgram( stage->vboVPindex[0], mtx, VP_FOG_NONE, texgen );
CompileFragmentProgram( stage->vboFPindex[0], mtx, atestBits, FP_FOG_NONE );
}
}
}
world_vbo.fogVPindex[0] = getVPindex( 0, VP_FOG_EYE_IN, 0 );
world_vbo.fogVPindex[1] = getVPindex( 0, VP_FOG_EYE_OUT, 0 );
world_vbo.fogFPindex = getFPindex( 0, 0, FP_FOG_ONLY );
CompileVertexProgram( world_vbo.fogVPindex[0], 0, VP_FOG_EYE_IN, 0 );
CompileVertexProgram( world_vbo.fogVPindex[0], 0, VP_FOG_EYE_OUT, 0 );
CompileFragmentProgram( world_vbo.fogFPindex, 0 /*mtx*/, 0 /*atest*/, FP_FOG_ONLY );
return qtrue;
}
static void VBO_AddGeometry( vbo_t *vbo, vbo_item_t *vi, shaderCommands_t *input )
{
uint32_t size, offs;
int i;
if ( input->shader->iboOffset == -1 || input->shader->vboOffset == -1 ) {
// allocate indexes
input->shader->iboOffset = vbo->ibo_offset;
vbo->ibo_offset += input->shader->numIndexes * sizeof( input->indexes[0] );
// allocate xyz + normals + svars
input->shader->vboOffset = vbo->vbo_offset;
vbo->vbo_offset += input->shader->numVertexes * ( sizeof( input->xyz[0] ) + sizeof( input->normal[0] ) + input->shader->svarsSize );
// go to normals offset
input->shader->normalOffset = input->shader->vboOffset + input->shader->numVertexes * sizeof( input->xyz[0] );
// go to first color offset
offs = input->shader->normalOffset + input->shader->numVertexes * sizeof( input->normal[0] );
for ( i = 0; i < MAX_VBO_STAGES; i++ )
{
shaderStage_t *pStage = input->xstages[ i ];
if ( !pStage )
break;
pStage->color_offset = offs; offs += input->shader->numVertexes * sizeof( tess.svars.colors[0] );
if ( pStage->tessFlags & TESS_ST0 )
{
pStage->tex_offset[0] = offs; offs += input->shader->numVertexes * sizeof( tess.svars.texcoords[0][0] );
}
if ( pStage->tessFlags & TESS_ST1 )
{
pStage->tex_offset[1] = offs; offs += input->shader->numVertexes * sizeof( tess.svars.texcoords[1][0] );
}
}
input->shader->curVertexes = 0;
input->shader->curIndexes = 0;
}
// shift indexes relative to current shader
for ( i = 0; i < input->numIndexes; i++ )
input->indexes[ i ] += input->shader->curVertexes;
if ( vi->index_offset == -1 ) // one-time initialization
{
// initialize geometry offsets relative to current shader
vi->index_offset = input->shader->curIndexes;
//vi->soft_offset = input->shader->iboOffset;
vi->soft_offset = input->shader->iboOffset + input->shader->curIndexes * sizeof( input->indexes[0] );
}
size = input->numIndexes * sizeof( input->indexes[ 0 ] );
offs = input->shader->iboOffset + input->shader->curIndexes * sizeof( input->indexes[0] );
if ( offs + size > vbo->ibo_size ) {
ri.Error( ERR_DROP, "Index0 overflow" );
}
memcpy( vbo->ibo_buffer + offs, input->indexes, size );
//Com_Printf( "i offs=%i size=%i\n", offs, size );
// vertexes
offs = input->shader->vboOffset + input->shader->curVertexes * sizeof( input->xyz[0] );
size = input->numVertexes * sizeof( input->xyz[ 0 ] );
if ( offs + size > vbo->vbo_size ) {
ri.Error( ERR_DROP, "Vertex overflow" );
}
//Com_Printf( "v offs=%i size=%i\n", offs, size );
memcpy( vbo->vbo_buffer + offs, input->xyz, size );
// normals
offs = input->shader->normalOffset + input->shader->curVertexes * sizeof( input->normal[0] );
size = input->numVertexes * sizeof( input->normal[ 0 ] );
if ( offs + size > vbo->vbo_size ) {
ri.Error( ERR_DROP, "Normals overflow" );
}
//Com_Printf( "v offs=%i size=%i\n", offs, size );
memcpy( vbo->vbo_buffer + offs, input->normal, size );
vi->num_indexes += input->numIndexes;
vi->num_vertexes += input->numVertexes;
}
static void VBO_AddStageColors( vbo_t *vbo, int stage, const shaderCommands_t *input )
{
const int offs = input->xstages[ stage ]->color_offset + input->shader->curVertexes * sizeof( input->svars.colors[0] );
const int size = input->numVertexes * sizeof( input->svars.colors[ 0 ] );
memcpy( vbo->vbo_buffer + offs, input->svars.colors, size );
}
static void VBO_AddStageTxCoords( vbo_t *vbo, int stage, const shaderCommands_t *input, int unit )
{
const int offs = input->xstages[ stage ]->tex_offset[ unit ] + input->shader->curVertexes * sizeof( input->svars.texcoords[unit][0] );
const int size = input->numVertexes * sizeof( input->svars.texcoords[unit][0] );
memcpy( vbo->vbo_buffer + offs, input->svars.texcoordPtr[ unit ], size );
}
void VBO_PushData( int itemIndex, shaderCommands_t *input )
{
const shaderStage_t *pStage;
vbo_t *vbo = &world_vbo;
vbo_item_t *vi = vbo->items + itemIndex;
int i;
VBO_AddGeometry( vbo, vi, input );
for ( i = 0; i < MAX_VBO_STAGES; i++ )
{
pStage = input->xstages[ i ];
if ( !pStage )
break;
R_ComputeColors( pStage );
VBO_AddStageColors( vbo, i, input );
if ( pStage->tessFlags & TESS_ST0 )
{
R_ComputeTexCoords( 0, &pStage->bundle[0] );
VBO_AddStageTxCoords( vbo, i, input, 0 );
}
if ( pStage->tessFlags & TESS_ST1 )
{
R_ComputeTexCoords( 1, &pStage->bundle[1] );
VBO_AddStageTxCoords( vbo, i, input, 1 );
}
}
input->shader->curVertexes += input->numVertexes;
input->shader->curIndexes += input->numIndexes;
//Com_Printf( "%s: vert %i (of %i), ind %i (of %i)\n", input->shader->name,
// input->shader->curVertexes, input->shader->numVertexes,
// input->shader->curIndexes, input->shader->numIndexes );
}
static GLuint curr_index_bind = 0;
static GLuint curr_vertex_bind = 0;
int VBO_Active( void )
{
return curr_vertex_bind;
}
static qboolean VBO_BindData( void )
{
if ( curr_vertex_bind )
return qfalse;
qglBindBufferARB( GL_ARRAY_BUFFER_ARB, VBO_world_data );
curr_vertex_bind = VBO_world_data;
return qtrue;
}
static void VBO_BindIndex( qboolean enable )
{
if ( !enable )
{
if ( curr_index_bind )
{
qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
curr_index_bind = 0;
}
}
else
{
if ( !curr_index_bind )
{
qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, VBO_world_indexes );
curr_index_bind = VBO_world_indexes;
}
}
}
void VBO_UnBind( void )
{
if ( curr_index_bind )
{
qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
curr_index_bind = 0;
}
if ( curr_vertex_bind )
{
qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
curr_vertex_bind = 0;
}
tess.vboIndex = 0;
}
static int surfSortFunc( const void *a, const void *b )
{
const msurface_t **sa = (const msurface_t **)a;
const msurface_t **sb = (const msurface_t **)b;
return (*sa)->shader - (*sb)->shader;
}
static void initItem( vbo_item_t *item )
{
item->num_vertexes = 0;
item->num_indexes = 0;
item->index_offset = -1;
item->soft_offset = -1;
}
void R_BuildWorldVBO( msurface_t *surf, int surfCount )
{
vbo_t *vbo = &world_vbo;
msurface_t **surfList;
srfSurfaceFace_t *face;
srfTriangles_t *tris;
srfGridMesh_t *grid;
msurface_t *sf;
int ibo_size;
int vbo_size;
int i, n;
GLenum err;
int numStaticSurfaces = 0;
int numStaticIndexes = 0;
int numStaticVertexes = 0;
if ( !qglBindBufferARB || !r_vbo->integer )
return;
if ( glConfig.numTextureUnits < 3 ) {
ri.Printf( PRINT_WARNING, "... not enough texture units for VBO\n" );
return;
}
VBO_Cleanup();
vbo_size = 0;
// initial scan to count surfaces/indexes/vertexes for memory allocation
for ( i = 0, sf = surf; i < surfCount; i++, sf++ ) {
face = (srfSurfaceFace_t *) sf->data;
if ( face->surfaceType == SF_FACE && isStaticShader( sf->shader ) ) {
face->vboItemIndex = ++numStaticSurfaces;
numStaticVertexes += face->numPoints;
numStaticIndexes += face->numIndices;
vbo_size += face->numPoints * (sf->shader->svarsSize + sizeof( tess.xyz[0] ) + sizeof( tess.normal[0] ) );
sf->shader->numVertexes += face->numPoints;
sf->shader->numIndexes += face->numIndices;
continue;
}
tris = (srfTriangles_t *) sf->data;
if ( tris->surfaceType == SF_TRIANGLES && isStaticShader( sf->shader ) ) {
tris->vboItemIndex = ++numStaticSurfaces;
numStaticVertexes += tris->numVerts;
numStaticIndexes += tris->numIndexes;
vbo_size += tris->numVerts * (sf->shader->svarsSize + sizeof( tess.xyz[0] ) + sizeof( tess.normal[0] ) );
sf->shader->numVertexes += tris->numVerts;
sf->shader->numIndexes += tris->numIndexes;
continue;
}
grid = (srfGridMesh_t *) sf->data;
if ( grid->surfaceType == SF_GRID && isStaticShader( sf->shader ) ) {
grid->vboItemIndex = ++numStaticSurfaces;
RB_SurfaceGridEstimate( grid, &grid->vboExpectVertices, &grid->vboExpectIndices );
numStaticVertexes += grid->vboExpectVertices;
numStaticIndexes += grid->vboExpectIndices;
vbo_size += grid->vboExpectVertices * (sf->shader->svarsSize + sizeof( tess.xyz[0] ) + sizeof( tess.normal[0] ) );
sf->shader->numVertexes += grid->vboExpectVertices;
sf->shader->numIndexes += grid->vboExpectIndices;
continue;
}
}
if ( numStaticSurfaces == 0 ) {
ri.Printf( PRINT_ALL, "...no static surfaces for VBO\n" );
return;
}
vbo_size = PAD( vbo_size, 32 );
ibo_size = numStaticIndexes * sizeof( tess.indexes[0] );
ibo_size = PAD( ibo_size, 32 );
// 0 item is unused
vbo->items = ri.Hunk_Alloc( ( numStaticSurfaces + 1 ) * sizeof( vbo_item_t ), h_low );
vbo->items_count = numStaticSurfaces;
// last item will be used for run length termination
vbo->items_queue = ri.Hunk_Alloc( ( numStaticSurfaces + 1 ) * sizeof( int ), h_low );
vbo->items_queue_count = 0;
ri.Printf( PRINT_ALL, "...found %i VBO surfaces (%i vertexes, %i indexes)\n",
numStaticSurfaces, numStaticVertexes, numStaticIndexes );
//Com_Printf( S_COLOR_CYAN "VBO size: %i\n", vbo_size );
//Com_Printf( S_COLOR_CYAN "IBO size: %i\n", ibo_size );
// vertex buffer
vbo->vbo_buffer = ri.Hunk_AllocateTempMemory( vbo_size );
vbo->vbo_offset = 0;
vbo->vbo_size = vbo_size;
// index buffer
vbo->ibo_buffer = ri.Hunk_Alloc( ibo_size, h_low );
vbo->ibo_offset = 0;
vbo->ibo_size = ibo_size;
// soft index buffer
vbo->soft_buffer = ri.Hunk_Alloc( ibo_size, h_low );
vbo->soft_buffer_indexes = 0;
// ibo runs buffer
vbo->ibo_items = ri.Hunk_Alloc( ( (numStaticIndexes / MIN_IBO_RUN) + 1 ) * sizeof( ibo_item_t ), h_low );
vbo->ibo_items_count = 0;
surfList = ri.Hunk_AllocateTempMemory( numStaticSurfaces * sizeof( msurface_t* ) );
for ( i = 0, n = 0, sf = surf; i < surfCount; i++, sf++ ) {
face = (srfSurfaceFace_t *) sf->data;
if ( face->surfaceType == SF_FACE && face->vboItemIndex ) {
surfList[ n++ ] = sf;
continue;
}
tris = (srfTriangles_t *) sf->data;
if ( tris->surfaceType == SF_TRIANGLES && tris->vboItemIndex ) {
surfList[ n++ ] = sf;
continue;
}
grid = (srfGridMesh_t *) sf->data;
if ( grid->surfaceType == SF_GRID && grid->vboItemIndex ) {
surfList[ n++ ] = sf;
continue;
}
}
if ( n != numStaticSurfaces ) {
ri.Error( ERR_DROP, "Invalid VBO surface count" );
}
// sort surfaces by shader
qsort( surfList, numStaticSurfaces, sizeof( surfList[0] ), surfSortFunc );
tess.numIndexes = 0;
tess.numVertexes = 0;
Com_Memset( &backEnd.viewParms, 0, sizeof( backEnd.viewParms ) );
backEnd.currentEntity = &tr.worldEntity;
for ( i = 0; i < numStaticSurfaces; i++ )
{
sf = surfList[ i ];
face = (srfSurfaceFace_t *) sf->data;
tris = (srfTriangles_t *) sf->data;
grid = (srfGridMesh_t *) sf->data;
if ( face->surfaceType == SF_FACE )
face->vboItemIndex = i + 1;
else if ( tris->surfaceType == SF_TRIANGLES ) {
tris->vboItemIndex = i + 1;
} else if ( grid->surfaceType == SF_GRID ){
grid->vboItemIndex = i + 1;
} else {
ri.Error( ERR_DROP, "Unexpected surface type" );
}
initItem( vbo->items + i + 1 );
RB_BeginSurface( sf->shader, 0 );
tess.allowVBO = qfalse; // block execution of VBO path as we need to tesselate geometry
#ifdef USE_TESS_NEEDS_NORMAL
tess.needsNormal = qtrue;
#endif
#ifdef USE_TESS_NEEDS_ST2
tess.needsST2 = qtrue;
#endif
// tesselate
rb_surfaceTable[ *sf->data ]( sf->data ); // VBO_PushData() may be called multiple times there
// setup colors and texture coordinates
VBO_PushData( i + 1, &tess );
if ( grid->surfaceType == SF_GRID ) {
vbo_item_t *vi = vbo->items + i + 1;
if ( vi->num_vertexes != grid->vboExpectVertices || vi->num_indexes != grid->vboExpectIndices ) {
ri.Error( ERR_DROP, "Unexpected grid vertexes/indexes count" );
}
}
tess.numIndexes = 0;
tess.numVertexes = 0;
}
ri.Hunk_FreeTempMemory( surfList );
// reset error state
qglGetError();
if ( !VBO_world_data ) {
qglGenBuffersARB( 1, &VBO_world_data );
if ( (err = qglGetError()) != GL_NO_ERROR )
goto __fail;
}
// upload vertex array & colors & textures
if ( VBO_world_data ) {
VBO_BindData();
qglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->vbo_size, vbo->vbo_buffer, GL_STATIC_DRAW_ARB );
if ( (err = qglGetError()) != GL_NO_ERROR )
goto __fail;
}
if ( !VBO_world_indexes ) {
qglGenBuffersARB( 1, &VBO_world_indexes );
if ( (err = qglGetError()) != GL_NO_ERROR )
goto __fail;
}
// upload index array
if ( VBO_world_indexes ) {
VBO_BindIndex( qtrue );
qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, vbo->ibo_size, vbo->ibo_buffer, GL_STATIC_DRAW_ARB );
if ( (err = qglGetError()) != GL_NO_ERROR )
goto __fail;
}
VBO_UnBind();
ri.Hunk_FreeTempMemory( vbo->vbo_buffer );
vbo->vbo_buffer = NULL;
return;
__fail:
if ( err == GL_OUT_OF_MEMORY )
ri.Printf( PRINT_WARNING, "%s: out of memory\n", __func__ );
else
ri.Printf( PRINT_ERROR, "%s: error %i\n", __func__, err );
// reset vbo markers
for ( i = 0, sf = surf; i < surfCount; i++, sf++ ) {
face = (srfSurfaceFace_t *) sf->data;
if ( face->surfaceType == SF_FACE ) {
face->vboItemIndex = 0;
continue;
}
tris = (srfTriangles_t *) sf->data;
if ( tris->surfaceType == SF_TRIANGLES ) {
tris->vboItemIndex = 0;
continue;
}
grid = (srfGridMesh_t *) sf->data;
if ( grid->surfaceType == SF_GRID ) {
grid->vboItemIndex = 0;
continue;
}
}
VBO_UnBind();
// release host memory
ri.Hunk_FreeTempMemory( vbo->vbo_buffer );
vbo->vbo_buffer = NULL;
// release GPU resources
VBO_Cleanup();
}
void VBO_Cleanup( void )
{
int i;
if ( qglGenBuffersARB )
{
VBO_UnBind();
if ( VBO_world_data )
{
qglDeleteBuffersARB( 1, &VBO_world_data );
VBO_world_data = 0;
}
if ( VBO_world_indexes )
{
qglDeleteBuffersARB( 1, &VBO_world_indexes );
VBO_world_indexes = 0;
}
}
for ( i = 0; i < ARRAY_LEN( vbo_vp ); i++ )
{
if ( vbo_vp[i] )
{
qglDeleteProgramsARB( 1, vbo_vp + i );
}
}
memset( vbo_vp, 0, sizeof( vbo_vp ) );
for ( i = 0; i < ARRAY_LEN( vbo_fp ); i++ )
{
if ( vbo_fp[i] )
{
qglDeleteProgramsARB( 1, vbo_fp + i );
}
}
memset( vbo_fp, 0, sizeof( vbo_fp ) );
memset( &world_vbo, 0, sizeof( world_vbo ) );
for ( i = 0; i < tr.numShaders; i++ )
{
tr.shaders[ i ]->isStaticShader = qfalse;
tr.shaders[ i ]->iboOffset = -1;
tr.shaders[ i ]->vboOffset = -1;
}
}
/*
=============
qsort_int
=============
*/
static void qsort_int( int *a, const int n ) {
int temp, m;
int i, j;
if ( n < 32 ) { // CUTOFF
for ( i = 1 ; i < n + 1 ; i++ ) {
j = i;
while ( j > 0 && a[j] < a[j-1] ) {
temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
j--;
}
}
return;
}
i = 0;
j = n;
m = a[ n>>1 ];
do {
while ( a[i] < m ) i++;
while ( a[j] > m ) j--;
if ( i <= j ) {
temp = a[i];
a[i] = a[j];
a[j] = temp;
i++;
j--;
}
} while ( i <= j );
if ( j > 0 ) qsort_int( a, j );
if ( n > i ) qsort_int( a+i, n-i );
}
static int run_length( const int *a, int from, int to, int *count )
{
vbo_t *vbo = &world_vbo;
int i, n, cnt;
for ( cnt = 0, n = 1, i = from; i < to; i++, n++ )
{
cnt += vbo->items[a[i]].num_indexes;
if ( a[i]+1 != a[i+1] )
break;
}
*count = cnt;
return n;
}
void VBO_QueueItem( int itemIndex )
{
vbo_t *vbo = &world_vbo;
if ( vbo->items_queue_count < vbo->items_count )
{
vbo->items_queue[vbo->items_queue_count++] = itemIndex;
}
else
{
ri.Error( ERR_DROP, "VBO queue overflow" );
}
}
void VBO_ClearQueue( void )
{
vbo_t *vbo = &world_vbo;
vbo->items_queue_count = 0;
}
void VBO_Flush( void )
{
if ( tess.vboIndex )
{
RB_EndSurface();
tess.vboIndex = 0;
RB_BeginSurface( tess.shader, tess.fogNum );
}
VBO_UnBind();
}
static void VBO_AddItemDataToSoftBuffer( int itemIndex )
{
vbo_t *vbo = &world_vbo;
const vbo_item_t *vi = vbo->items + itemIndex;
memcpy( &vbo->soft_buffer[ vbo->soft_buffer_indexes ], vbo->ibo_buffer + vi->soft_offset, vi->num_indexes * sizeof( glIndex_t ) );
vbo->soft_buffer_indexes += vi->num_indexes;
}
static void VBO_AddItemRangeToIBOBuffer( int offset, int length )
{
vbo_t *vbo = &world_vbo;
ibo_item_t *it;
it = vbo->ibo_items + vbo->ibo_items_count++;
it->offset = offset * sizeof( glIndex_t ) + tess.shader->iboOffset;
it->length = length;
}
static void VBO_RenderIBOItems( void )
{
const vbo_t *vbo = &world_vbo;
int i;
// from device-local memory
if ( vbo->ibo_items_count )
{
VBO_BindIndex( qtrue );
for ( i = 0; i < vbo->ibo_items_count; i++ )
{
qglDrawElements( GL_TRIANGLES, vbo->ibo_items[ i ].length, GL_INDEX_TYPE, (const GLvoid *)(intptr_t) vbo->ibo_items[ i ].offset );
}
}
}
static void VBO_RenderSoftItems( void )
{
const vbo_t *vbo = &world_vbo;
if ( vbo->soft_buffer_indexes )
{
VBO_BindIndex( qfalse );
qglDrawElements( GL_TRIANGLES, vbo->soft_buffer_indexes, GL_INDEX_TYPE, vbo->soft_buffer );
}
}
static void VBO_RenderIndexes( void )
{
if ( curr_index_bind )
{
VBO_RenderIBOItems();
VBO_RenderSoftItems();
}
else
{
VBO_RenderSoftItems();
VBO_RenderIBOItems();
}
}
static void VBO_PrepareQueues( void )
{
vbo_t *vbo = &world_vbo;
int i, item_run, index_run, n;
const vbo_item_t *vi;
const int *a;
vbo->items_queue[ vbo->items_queue_count ] = 0; // terminate run
// sort items so we can scan for longest runs
if ( vbo->items_queue_count > 1 )
qsort_int( vbo->items_queue, vbo->items_queue_count-1 );
vbo->soft_buffer_indexes = 0;
vbo->ibo_items_count = 0;
vbo->items_queue_vertexes = 0;
vbo->items_queue_indexes = 0;
for ( i = 0; i < vbo->items_queue_count; i++ ) {
vi = &vbo->items[ vbo->items_queue[ i ] ];
vbo->items_queue_vertexes += vi->num_vertexes;
vbo->items_queue_indexes += vi->num_indexes;
}
a = vbo->items_queue;
i = 0;
while ( i < vbo->items_queue_count )
{
item_run = run_length( a, i, vbo->items_queue_count, &index_run );
if ( index_run < MIN_IBO_RUN )
{
for ( n = 0; n < item_run; n++ )
VBO_AddItemDataToSoftBuffer( a[ i + n ] );
}
else
{
vbo_item_t *start = vbo->items + a[ i ];
vbo_item_t *end = vbo->items + a[ i + item_run - 1 ];
n = (end->index_offset - start->index_offset) + end->num_indexes;
VBO_AddItemRangeToIBOBuffer( start->index_offset, n );
}
i += item_run;
}
}
static const fogProgramParms_t *VBO_SetupFog( int VPindex, int FPindex, GLuint *pvp, GLuint *pfp )
{
const fogProgramParms_t *fparm;
GLuint vp, fp;
GL_BindTexture( 2, tr.fogImage->texnum );
GL_SelectTexture( 0 );
fparm = RB_CalcFogProgramParms();
if ( fparm->eyeOutside )
vp = vbo_vp[ VPindex + 1 ];
else
vp = vbo_vp[ VPindex + 0 ];
fp = vbo_fp[ FPindex ];
ARB_ProgramEnableExt( vp, fp );
qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 2, fparm->fogDistanceVector );
qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 3, fparm->fogDepthVector );
qglProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 4, fparm->eyeT, 0.0f, 0.0f, 0.0f );
qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, fparm->fogColor );
*pvp = vp;
*pfp = fp;
return fparm;
}
/*
** RB_IterateStagesVBO
*/
static void RB_IterateStagesVBO( const shaderCommands_t *input )
{
const shaderStage_t *pStage;
const fogProgramParms_t *fparm;
int i;
GLbitfield stateBits, normalMask;
qboolean fogPass;
GLuint vp, fp;
fogPass = ( tess.fogNum && tess.shader->fogPass );
if ( fogPass && tess.shader->numUnfoggedPasses == 1 ) {
// combined fog + single stage program
pStage = input->xstages[ 0 ];
fparm = VBO_SetupFog( pStage->vboVPindex[1], pStage->vboFPindex[1], &vp, &fp );
} else {
fparm = NULL;
vp = fp = 0;
}
VBO_PrepareQueues();
VBO_BindData();
qglVertexPointer( 3, GL_FLOAT, 16, (const GLvoid *)(intptr_t)tess.shader->vboOffset );
if ( qglLockArraysEXT ) {
// FIXME: not needed for VBOs?
qglLockArraysEXT( 0, world_vbo.items_queue_vertexes );
}
for ( i = 0; i < MAX_VBO_STAGES; i++ ) {
if ( !input->xstages[i] )
break;
pStage = input->xstages[i];
stateBits = pStage->stateBits;
if ( fparm == NULL ) {
vp = vbo_vp[ pStage->vboVPindex[0] ];
fp = vbo_fp[ pStage->vboFPindex[0] ];
ARB_ProgramEnableExt( vp, fp );
}
if ( fp ) {
stateBits &= ~GLS_ATEST_BITS; // done in shaders
}
GL_State( stateBits );
if ( pStage->tessFlags & ( TESS_ENV0 | TESS_ENV1 ) ) {
// setup viewpos needed for environment mapping program
qglProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 0,
backEnd.or.viewOrigin[0],
backEnd.or.viewOrigin[1],
backEnd.or.viewOrigin[2],
0.0 );
normalMask = CLS_NORMAL_ARRAY;
} else {
normalMask = 0;
}
if ( pStage->tessFlags & TESS_ENV0 ) {
GL_ClientState( 0, normalMask | CLS_COLOR_ARRAY );
} else {
GL_ClientState( 0, normalMask | CLS_TEXCOORD_ARRAY | CLS_COLOR_ARRAY );
// bind colors and first texture array
qglTexCoordPointer( 2, GL_FLOAT, 0, (const GLvoid *)(intptr_t)pStage->tex_offset[0] );
}
if ( normalMask ) {
// bind normals
qglNormalPointer( GL_FLOAT, 16, (const GLvoid *)(intptr_t)tess.shader->normalOffset );
}
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, (const GLvoid *)(intptr_t)pStage->color_offset );
GL_SelectTexture( 0 );
R_BindAnimatedImage( &pStage->bundle[0] );
if ( pStage->mtEnv ) { // multitexture
if ( fp == 0 ) {
// bind second texture array
GL_ClientState( 1, CLS_TEXCOORD_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 0, (const GLvoid *)(intptr_t)pStage->tex_offset[1] );
GL_SelectTexture( 1 );
qglEnable( GL_TEXTURE_2D );
R_BindAnimatedImage( &pStage->bundle[1] );
if ( fparm == NULL ) {
if ( r_lightmap->integer )
GL_TexEnv( GL_REPLACE );
else
GL_TexEnv( pStage->mtEnv );
}
VBO_RenderIndexes();
qglDisable( GL_TEXTURE_2D );
GL_SelectTexture( 0 );
} else {
if ( pStage->tessFlags & TESS_ENV1 ) {
GL_ClientState( 1, CLS_NONE );
} else {
GL_ClientState( 1, CLS_TEXCOORD_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 0, (const GLvoid *)(intptr_t)pStage->tex_offset[1] );
}
GL_SelectTexture( 1 );
// fragment programs have explicit control over texturing
// so no need to enable/disable GL_TEXTURE_2D
R_BindAnimatedImage( &pStage->bundle[1] );
VBO_RenderIndexes();
GL_SelectTexture( 0 );
}
} else {
GL_ClientState( 1, CLS_NONE );
VBO_RenderIndexes();
}
}
GL_ClientState( 1, CLS_NONE );
//fog-only pass
if ( fogPass && fparm == NULL ) {
GL_ClientState( 0, CLS_NONE );
VBO_SetupFog( world_vbo.fogVPindex[0], world_vbo.fogFPindex, &vp, &fp );
if ( tess.shader->fogPass == FP_EQUAL ) {
GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL );
} else {
GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
}
VBO_RenderIndexes();
}
ARB_ProgramEnableExt( 0, 0 );
if ( r_showtris->integer ) {
if ( r_showtris->integer == 1 && backEnd.drawConsole )
return;
ARB_ProgramEnableExt( 0, 0 );
GL_ClientState( 0, CLS_NONE );
GL_SelectTexture( 0 );
qglDisable( GL_TEXTURE_2D );
GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
qglDepthRange( 0, 0 );
// green for IBO items
qglColor4f( 0.25f, 1.0f, 0.25f, 1.0f );
VBO_RenderIBOItems();
// cyan for soft-index items
qglColor4f( 0.25f, 1.0f, 0.55f, 1.0f );
VBO_RenderSoftItems();
qglDepthRange( 0, 1 );
qglEnable( GL_TEXTURE_2D );
}
if ( qglUnlockArraysEXT ) {
qglUnlockArraysEXT();
}
if ( r_speeds->integer == 1 ) {
// update performance stats
backEnd.pc.c_totalIndexes += world_vbo.items_queue_indexes;
backEnd.pc.c_indexes += world_vbo.items_queue_indexes;
backEnd.pc.c_vertexes += world_vbo.items_queue_vertexes;
backEnd.pc.c_shaders++;
}
//VBO_UnBind();
}
void RB_StageIteratorVBO( void )
{
const shaderCommands_t *input;
shader_t *shader;
input = &tess;
shader = input->shader;
// set face culling appropriately
GL_Cull( shader->cullType );
// set polygon offset if necessary
if ( shader->polygonOffset )
{
qglEnable( GL_POLYGON_OFFSET_FILL );
qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
}
RB_IterateStagesVBO( input );
// reset polygon offset
if ( shader->polygonOffset )
{
qglDisable( GL_POLYGON_OFFSET_FILL );
}
tess.vboIndex = 0;
VBO_ClearQueue();
}