Skip to content

Commit

Permalink
Introduce runtime VBO layout; do IQM, MD5
Browse files Browse the repository at this point in the history
Generate the layout for interleaved vertex attribute data at runtime. The
motivation for this is to support OpenGL implementations that don't
provide half float support
(#1179). The vertex "struct"
may contain a 16-bit or 32-bit float, depending on the graphics card.

Now, instead of defining a struct for the data to be uploaded into a
VBO, one must separately specify inputs for each attribute. The input is
defined by a type, base address, stride, etc.; very similarly to the
arguments of glVertexAttribPointer itself. The new version of
R_CreateStaticVBO takes these inputs and writes them to an interleaved
format, performing any neede type conversions along the way.

In this commit just skeletal models (IQM and MD5) are migrated to the
new method.
  • Loading branch information
slipher committed Oct 10, 2024
1 parent e2de6c3 commit ff48a2b
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 160 deletions.
25 changes: 21 additions & 4 deletions src/engine/renderer/tr_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,8 @@ enum class realtimeLightingRenderer_t { LEGACY, TILED };

enum class vboLayout_t
{
VBO_LAYOUT_CUSTOM,
VBO_LAYOUT_VERTEX_ANIMATION,
VBO_LAYOUT_SKELETAL,
VBO_LAYOUT_STATIC,
VBO_LAYOUT_XYST
};
Expand All @@ -672,13 +672,27 @@ enum class realtimeLightingRenderer_t { LEGACY, TILED };
i16vec4_t *qtangent;
u8vec4_t *color;
union { f16vec2_t *st; vec2_t *stf; };
int (*boneIndexes)[ 4 ];
vec4_t *boneWeights;

int numFrames;
int numVerts;
};

enum
{
ATTR_OPTION_NORMALIZE = BIT( 0 ),
};

struct vertexAttributeSpec_t
{
int attrIndex;
GLenum componentInputType;
GLenum componentStorageType;
const void *begin;
uint32_t numComponents;
uint32_t stride;
int attrOptions;
};

struct VBO_t
{
char name[ 96 ]; // only for debugging with /listVBOs
Expand All @@ -690,7 +704,7 @@ enum class realtimeLightingRenderer_t { LEGACY, TILED };
uint32_t vertexesNum;
uint32_t framesNum; // number of frames for vertex animation

vboAttributeLayout_t attribs[ ATTR_INDEX_MAX ]; // info for buffer manipulation
std::array<vboAttributeLayout_t, ATTR_INDEX_MAX> attribs; // info for buffer manipulation

vboLayout_t layout;
uint32_t attribBits; // Which attributes it has. Mostly for detecting errors
Expand Down Expand Up @@ -3477,6 +3491,9 @@ inline bool checkGLErrors()
============================================================
*/
VBO_t *R_CreateStaticVBO(
Str::StringRef name, const vertexAttributeSpec_t *attrBegin, const vertexAttributeSpec_t *attrEnd,
uint32_t numVerts );
VBO_t *R_CreateStaticVBO( const char *name, vboData_t data, vboLayout_t layout );
VBO_t *R_CreateStaticVBO2( const char *name, int numVertexes, shaderVertex_t *verts, uint32_t stateBits );

Expand Down
44 changes: 16 additions & 28 deletions src/engine/renderer/tr_model_iqm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,21 +770,13 @@ bool R_LoadIQModel( model_t *mod, const void *buffer, int filesize,
if( r_vboModels->integer && glConfig2.vboVertexSkinningAvailable
&& IQModel->num_joints <= glConfig2.maxVertexSkinningBones ) {

int *indexbuf = (int *)ri.Hunk_AllocateTempMemory( sizeof(int[4]) * IQModel->num_vertexes );
for(int i = 0; i < IQModel->num_vertexes; i++ ) {
indexbuf[ 4 * i + 0 ] = IQModel->blendIndexes[ 4 * i + 0 ];
indexbuf[ 4 * i + 1 ] = IQModel->blendIndexes[ 4 * i + 1 ];
indexbuf[ 4 * i + 2 ] = IQModel->blendIndexes[ 4 * i + 2 ];
indexbuf[ 4 * i + 3 ] = IQModel->blendIndexes[ 4 * i + 3 ];
}
uint16_t *boneFactorBuf = (uint16_t*)ri.Hunk_AllocateTempMemory( IQModel->num_vertexes * ( 4 * sizeof(uint16_t) ) );

const float weightscale = 1.0f / 255.0f;
float *weightbuf = (float *)ri.Hunk_AllocateTempMemory( sizeof(vec4_t) * IQModel->num_vertexes );
for(int i = 0; i < IQModel->num_vertexes; i++ ) {
weightbuf[ 4 * i + 0 ] = weightscale * IQModel->blendWeights[ 4 * i + 0 ];
weightbuf[ 4 * i + 1 ] = weightscale * IQModel->blendWeights[ 4 * i + 1 ];
weightbuf[ 4 * i + 2 ] = weightscale * IQModel->blendWeights[ 4 * i + 2 ];
weightbuf[ 4 * i + 3 ] = weightscale * IQModel->blendWeights[ 4 * i + 3 ];
for (int i = 0; i < IQModel->num_vertexes; i++ ) {
boneFactorBuf[ 4 * i + 0 ] = uint16_t(IQModel->blendWeights[ 4 * i + 0 ]) << 8 | IQModel->blendIndexes[ 4 * i + 0 ];
boneFactorBuf[ 4 * i + 1 ] = uint16_t(IQModel->blendWeights[ 4 * i + 1 ]) << 8 | IQModel->blendIndexes[ 4 * i + 1 ];
boneFactorBuf[ 4 * i + 2 ] = uint16_t(IQModel->blendWeights[ 4 * i + 2 ]) << 8 | IQModel->blendIndexes[ 4 * i + 2 ];
boneFactorBuf[ 4 * i + 3 ] = uint16_t(IQModel->blendWeights[ 4 * i + 3 ]) << 8 | IQModel->blendIndexes[ 4 * i + 3 ];
}

i16vec4_t *qtangentbuf = static_cast<i16vec4_t *>(
Expand All @@ -797,24 +789,20 @@ bool R_LoadIQModel( model_t *mod, const void *buffer, int filesize,
qtangentbuf[ i ] );
}

vboData_t vboData{};

vboData.xyz = (vec3_t *)IQModel->positions;
vboData.qtangent = qtangentbuf;
vboData.numFrames = 0;
vboData.color = (u8vec4_t *)IQModel->colors;
vboData.st = (f16vec2_t *)IQModel->texcoords;
vboData.boneIndexes = (int (*)[4])indexbuf;
vboData.boneWeights = (vec4_t *)weightbuf;
vboData.numVerts = IQModel->num_vertexes;
const vertexAttributeSpec_t attrs[] {
{ ATTR_INDEX_BONE_FACTORS, GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT, boneFactorBuf, 4, sizeof( u16vec4_t ), 0 },
{ ATTR_INDEX_POSITION, GL_FLOAT, GL_SHORT, IQModel->positions, 3, sizeof( float[ 3 ] ), ATTR_OPTION_NORMALIZE },
{ ATTR_INDEX_QTANGENT, GL_SHORT, GL_SHORT, qtangentbuf, 4, sizeof( i16vec4_t ), ATTR_OPTION_NORMALIZE, },
{ ATTR_INDEX_TEXCOORD, GL_HALF_FLOAT, GL_HALF_FLOAT, IQModel->texcoords, 2, sizeof( f16_t[ 2 ] ), 0 },
{ ATTR_INDEX_COLOR, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, IQModel->colors, 4, sizeof( u8vec4_t ), ATTR_OPTION_NORMALIZE },
};

std::string name = mod->name;
vbo = R_CreateStaticVBO( ( "IQM surface VBO " + name ).c_str(), vboData,
vboLayout_t::VBO_LAYOUT_SKELETAL );
vbo = R_CreateStaticVBO( "IQM surface VBO " + name,
std::begin( attrs ), std::end( attrs ), IQModel->num_vertexes );

ri.Hunk_FreeTempMemory( qtangentbuf );
ri.Hunk_FreeTempMemory( weightbuf );
ri.Hunk_FreeTempMemory( indexbuf );
ri.Hunk_FreeTempMemory( boneFactorBuf );

// create IBO
ibo = R_CreateStaticIBO( ( "IQM surface IBO " + name ).c_str(),
Expand Down
57 changes: 30 additions & 27 deletions src/engine/renderer/tr_model_skel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ bool R_AddTriangleToVBOTriangleList(
return hasWeights;
}

// index has to be in range 0-255, weight has to be >= 0 and <= 1
static unsigned short boneFactor( int index, float weight ) {
int scaledWeight = lrintf( weight * 255.0F );
return (unsigned short)( ( scaledWeight << 8 ) | index );
}

srfVBOMD5Mesh_t *R_GenerateMD5VBOSurface(
Str::StringRef surfName, const std::vector<skelTriangle_t> &vboTriangles,
md5Model_t *md5, md5Surface_t *surf, int skinIndex, int boneReferences[ MAX_BONES ] )
Expand All @@ -118,15 +124,8 @@ srfVBOMD5Mesh_t *R_GenerateMD5VBOSurface(
vboSurf->numIndexes = indexesNum;
vboSurf->numVerts = vertexesNum;

vboData_t data{};

data.xyz = ( vec3_t * ) ri.Hunk_AllocateTempMemory( sizeof( *data.xyz ) * vertexesNum );
data.qtangent = ( i16vec4_t * ) ri.Hunk_AllocateTempMemory( sizeof( i16vec4_t ) * vertexesNum );
data.boneIndexes = ( int (*)[ 4 ] ) ri.Hunk_AllocateTempMemory( sizeof( *data.boneIndexes ) * vertexesNum );
data.boneWeights = ( vec4_t * ) ri.Hunk_AllocateTempMemory( sizeof( *data.boneWeights ) * vertexesNum );
data.st = ( f16vec2_t * ) ri.Hunk_AllocateTempMemory( sizeof( f16vec2_t ) * vertexesNum );
data.numVerts = vertexesNum;

i16vec4_t *qtangents = ( i16vec4_t * ) ri.Hunk_AllocateTempMemory( sizeof( i16vec4_t ) * vertexesNum );
u16vec4_t *boneFactors = (u16vec4_t*)ri.Hunk_AllocateTempMemory( sizeof( u16vec4_t ) * vertexesNum );
indexes = ( glIndex_t * ) ri.Hunk_AllocateTempMemory( indexesNum * sizeof( glIndex_t ) );

vboSurf->numBoneRemap = 0;
Expand Down Expand Up @@ -156,41 +155,45 @@ srfVBOMD5Mesh_t *R_GenerateMD5VBOSurface(

for ( j = 0; j < vertexesNum; j++ )
{
VectorCopy( surf->verts[ j ].position, data.xyz[ j ] );
R_TBNtoQtangents( surf->verts[ j ].tangent, surf->verts[ j ].binormal,
surf->verts[ j ].normal, data.qtangent[ j ] );

Vector2Copy( surf->verts[ j ].texCoords, data.st[ j ] );
surf->verts[ j ].normal, qtangents[ j ] );

for (unsigned k = 0; k < MAX_WEIGHTS; k++ )
{
if ( k < surf->verts[ j ].numWeights )
{
data.boneIndexes[ j ][ k ] = vboSurf->boneRemap[ surf->verts[ j ].boneIndexes[ k ] ];
data.boneWeights[ j ][ k ] = surf->verts[ j ].boneWeights[ k ];
uint16_t boneIndex = vboSurf->boneRemap[ surf->verts[ j ].boneIndexes[ k ] ];
boneFactors[ j ][ k ] = boneFactor( boneIndex, surf->verts[ j ].boneWeights[ k ] );
}
else
{
data.boneWeights[ j ][ k ] = 0;
data.boneIndexes[ j ][ k ] = 0;
boneFactors[ j ][ k ] = 0;
}
}
}

vboSurf->vbo = R_CreateStaticVBO( ( "MD5 surface VBO " + surfName ).c_str(), data, vboLayout_t::VBO_LAYOUT_SKELETAL );
// MD5 does not have color, but shaders always require the color vertex attribute, so we have
// to provide this 0 color.
// TODO: optimize a vertexAttributeSpec_t with 0 stride to use a non-array vertex attribute?
// (although that would mess up the nice 32-bit size)
const byte dummyColor[ 4 ]{};

vboSurf->ibo = R_CreateStaticIBO( ( "MD5 surface IBO " + surfName ).c_str(), indexes, indexesNum );
vertexAttributeSpec_t attributes[] {
{ ATTR_INDEX_BONE_FACTORS, GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT, boneFactors, 4, sizeof(u16vec4_t), 0 },
{ ATTR_INDEX_POSITION, GL_FLOAT, GL_SHORT, &surf->verts[ 0 ].position, 3, sizeof(md5Vertex_t), ATTR_OPTION_NORMALIZE },
{ ATTR_INDEX_QTANGENT, GL_SHORT, GL_SHORT, qtangents, 4, sizeof(i16vec4_t), ATTR_OPTION_NORMALIZE },
{ ATTR_INDEX_TEXCOORD, GL_HALF_FLOAT, GL_HALF_FLOAT, &surf->verts[ 0 ].texCoords, 2, sizeof(md5Vertex_t), 0 },
{ ATTR_INDEX_COLOR, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, dummyColor, 4, 0, ATTR_OPTION_NORMALIZE },
};

vboSurf->vbo = R_CreateStaticVBO( "MD5 surface VBO " + surfName,
std::begin( attributes ), std::end( attributes ), vertexesNum );

// MD5 does not have color, but shaders always request it and the skeletal animation
// vertex layout includes a color field, which is zeroed by default.
vboSurf->vbo->attribBits |= ATTR_COLOR;
vboSurf->ibo = R_CreateStaticIBO( ( "MD5 surface IBO " + surfName ).c_str(), indexes, indexesNum );

ri.Hunk_FreeTempMemory( indexes );
ri.Hunk_FreeTempMemory( data.st );
ri.Hunk_FreeTempMemory( data.boneWeights );
ri.Hunk_FreeTempMemory( data.boneIndexes );
ri.Hunk_FreeTempMemory( data.qtangent );
ri.Hunk_FreeTempMemory( data.xyz );
ri.Hunk_FreeTempMemory( boneFactors );
ri.Hunk_FreeTempMemory( qtangents );

return vboSurf;
}
1 change: 1 addition & 0 deletions src/engine/renderer/tr_public.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ struct glconfig2_t
bool mapBufferRangeAvailable;
bool syncAvailable;
bool depthClampAvailable;
bool halfFloatVertexAvailable;

bool realtimeLighting;
bool shadowMapping;
Expand Down
Loading

0 comments on commit ff48a2b

Please sign in to comment.