/* * RB_BindVBO */ void RB_BindVBO( int id, int primitive ) { mesh_vbo_t *vbo; rb.primitive = primitive; if( id < RB_VBO_NONE ) { vbo = rb.dynamicStreams[-id - 1].vbo; } else if( id == RB_VBO_NONE ) { vbo = NULL; } else { vbo = R_GetVBOByIndex( id ); } rb.currentVBOId = id; rb.currentVBO = vbo; if( !vbo ) { RB_BindArrayBuffer( 0 ); RB_BindElementArrayBuffer( 0 ); return; } RB_BindArrayBuffer( vbo->vertexId ); RB_BindElementArrayBuffer( vbo->elemId ); }
/* * RB_BindVBO */ void RB_BindVBO( int id, int primitive ) { mesh_vbo_t *vbo; vboSlice_t *batch; if( rb.currentVBOId == id ) { return; } if( id < RB_VBO_NONE ) { vbo = rb.streamVBOs[-id - 1]; batch = &rb.batches[-id - 1]; } else if( id == RB_VBO_NONE ) { vbo = NULL; batch = NULL; } else { vbo = R_GetVBOByIndex( id ); batch = NULL; } rb.primitive = primitive; rb.currentVBOId = id; rb.currentVBO = vbo; rb.currentBatch = batch; if( !vbo ) { RB_BindArrayBuffer( 0 ); RB_BindElementArrayBuffer( 0 ); return; } RB_BindArrayBuffer( vbo->vertexId ); RB_BindElementArrayBuffer( vbo->elemId ); }
/* * R_DiscardVBOVertexData */ void R_DiscardVBOVertexData( mesh_vbo_t *vbo ) { GLenum array_usage = VBO_ARRAY_USAGE_FOR_TAG(vbo->tag); if( vbo->vertexId ) { RB_BindArrayBuffer( vbo->vertexId ); qglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->arrayBufferSize, NULL, array_usage ); } }
/* * R_UploadVBOVertexData * * Uploads required vertex data to the buffer. * * Vertex attributes masked by halfFloatVattribs will use half-precision floats * to save memory, if GL_ARB_half_float_vertex is available. Note that if * VATTRIB_POSITION_BIT is not set, it will also reset bits for other positional * attributes such as autosprite pos and instance pos. */ vattribmask_t R_UploadVBOVertexData( mesh_vbo_t *vbo, int vertsOffset, vattribmask_t vattribs, const mesh_t *mesh, vbo_hint_t hint ) { int i, j; unsigned numVerts; size_t vertSize; vattribmask_t errMask; vattribmask_t hfa; qbyte *data; assert( vbo != NULL ); assert( mesh != NULL ); if( !vbo || !vbo->vertexId ) { return 0; } errMask = 0; numVerts = mesh->numVerts; vertSize = vbo->vertexSize; hfa = vbo->halfFloatAttribs; data = R_VBOVertBuffer( numVerts, vertSize ); RB_BindArrayBuffer( vbo->vertexId ); // upload vertex xyz data if( vattribs & VATTRIB_POSITION_BIT ) { if( !mesh->xyzArray ) { errMask |= VATTRIB_POSITION_BIT; } else { R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_POSITION_BIT, hfa ), mesh->xyzArray[0], 4, vertSize, numVerts, data + 0 ); } } // upload normals data if( vbo->normalsOffset && (vattribs & VATTRIB_NORMAL_BIT) ) { if( !mesh->normalsArray ) { errMask |= VATTRIB_NORMAL_BIT; } else { R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_NORMAL_BIT, hfa ), mesh->normalsArray[0], 4, vertSize, numVerts, data + vbo->normalsOffset ); } } // upload tangent vectors if( vbo->sVectorsOffset && ( ( vattribs & (VATTRIB_SVECTOR_BIT|VATTRIB_AUTOSPRITE2_BIT) ) == VATTRIB_SVECTOR_BIT ) ) { if( !mesh->sVectorsArray ) { errMask |= VATTRIB_SVECTOR_BIT; } else { R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_SVECTOR_BIT, hfa ), mesh->sVectorsArray[0], 4, vertSize, numVerts, data + vbo->sVectorsOffset ); } } // upload texture coordinates if( vbo->stOffset && (vattribs & VATTRIB_TEXCOORDS_BIT) ) { if( !mesh->stArray ) { errMask |= VATTRIB_TEXCOORDS_BIT; } else { R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_TEXCOORDS_BIT, hfa ), mesh->stArray[0], 2, vertSize, numVerts, data + vbo->stOffset ); } } // upload lightmap texture coordinates if( vbo->lmstOffset[0] && ( vattribs & VATTRIB_LMCOORDS0_BIT ) ) { int i; vattribbit_t lmattrbit; lmattrbit = VATTRIB_LMCOORDS0_BIT; for( i = 0; i < MAX_LIGHTMAPS/2; i++ ) { if( !(vattribs & lmattrbit) ) { break; } if( !mesh->lmstArray[i*2+0] ) { errMask |= lmattrbit; break; } R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_LMCOORDS0_BIT, hfa ), mesh->lmstArray[i*2+0][0], 2, vertSize, numVerts, data + vbo->lmstOffset[i] ); if( vattribs & (lmattrbit<<1) ) { if( !mesh->lmstArray[i*2+1] ) { errMask |= lmattrbit<<1; break; } R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_LMCOORDS0_BIT, hfa ), mesh->lmstArray[i*2+1][0], 2, vertSize, numVerts, data + vbo->lmstOffset[i] + 2 * sizeof( float ) ); } lmattrbit <<= 2; } } // upload vertex colors (although indices > 0 are never used) if( vbo->colorsOffset[0] && (vattribs & VATTRIB_COLOR0_BIT) ) { if( !mesh->colorsArray[0] ) { errMask |= VATTRIB_COLOR0_BIT; } else { R_FillVertexBuffer( int, int, (int *)&mesh->colorsArray[0][0], 1, vertSize, numVerts, data + vbo->colorsOffset[0] ); } } // upload centre and radius for autosprites // this code assumes that the mesh has been properly pretransformed if( vbo->spritePointsOffset && ( (vattribs & VATTRIB_AUTOSPRITE2_BIT) == VATTRIB_AUTOSPRITE2_BIT ) ) { // for autosprite2 also upload vertices that form the longest axis // the remaining vertex can be trivially computed in vertex shader vec3_t vd[3]; float d[3]; int longest_edge = -1, longer_edge = -1, short_edge; float longest_dist = 0, longer_dist = 0; const int edges[3][2] = { { 1, 0 }, { 2, 0 }, { 2, 1 } }; vec4_t centre[4]; vec4_t axes[4]; vec4_t *verts = mesh->xyzArray; elem_t *elems, temp_elems[6]; int numQuads; size_t bufferOffset0 = vbo->spritePointsOffset; size_t bufferOffset1 = vbo->sVectorsOffset; if( hint == VBO_HINT_ELEMS_QUAD ) { numQuads = numVerts / 4; } else { assert( mesh->elems != NULL ); if( !mesh->elems ) { numQuads = 0; } else { numQuads = mesh->numElems / 6; } } for( i = 0, elems = mesh->elems; i < numQuads; i++, elems += 6 ) { if( hint == VBO_HINT_ELEMS_QUAD ) { elem_t firstV = i * 4; temp_elems[0] = firstV; temp_elems[1] = firstV + 2 - 1; temp_elems[2] = firstV + 2; temp_elems[3] = firstV; temp_elems[4] = firstV + 3 - 1; temp_elems[5] = firstV + 3; elems = temp_elems; } // find the longest edge, the long edge and the short edge longest_edge = longer_edge = -1; longest_dist = longer_dist = 0; for( j = 0; j < 3; j++ ) { float len; VectorSubtract( verts[elems[edges[j][0]]], verts[elems[edges[j][1]]], vd[j] ); len = VectorLength( vd[j] ); if( !len ) { len = 1; } d[j] = len; if( longest_edge == -1 || longest_dist < len ) { longer_dist = longest_dist; longer_edge = longest_edge; longest_dist = len; longest_edge = j; } else if( longer_dist < len ) { longer_dist = len; longer_edge = j; } } short_edge = 3 - (longest_edge + longer_edge); if( short_edge > 2 ) { continue; } // centre VectorAdd( verts[elems[edges[longest_edge][0]]], verts[elems[edges[longest_edge][1]]], centre[0] ); VectorScale( centre[0], 0.5, centre[0] ); // radius centre[0][3] = d[longest_edge] * 0.5; // unused // right axis, normalized VectorScale( vd[short_edge], 1.0 / d[short_edge], vd[short_edge] ); // up axis, normalized VectorScale( vd[longer_edge], 1.0 / d[longer_edge], vd[longer_edge] ); NormToLatLong( vd[short_edge], &axes[0][0] ); NormToLatLong( vd[longer_edge], &axes[0][2] ); for( j = 1; j < 4; j++ ) { Vector4Copy( centre[0], centre[j] ); Vector4Copy( axes[0], axes[j] ); } R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_AUTOSPRITE_BIT, hfa ), centre[0], 4, vertSize, 4, data + bufferOffset0 ); R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_SVECTOR_BIT, hfa ), axes[0], 4, vertSize, 4, data + bufferOffset1 ); bufferOffset0 += 4 * vertSize; bufferOffset1 += 4 * vertSize; } } else if( vbo->spritePointsOffset && ( (vattribs & VATTRIB_AUTOSPRITE_BIT) == VATTRIB_AUTOSPRITE_BIT ) ) { vec4_t *verts; vec4_t centre[4]; int numQuads = numVerts / 4; size_t bufferOffset = vbo->spritePointsOffset; for( i = 0, verts = mesh->xyzArray; i < numQuads; i++, verts += 4 ) { // centre for( j = 0; j < 3; j++ ) { centre[0][j] = (verts[0][j] + verts[1][j] + verts[2][j] + verts[3][j]) * 0.25; } // radius centre[0][3] = Distance( verts[0], centre[0] ) * 0.707106f; // 1.0f / sqrt(2) for( j = 1; j < 4; j++ ) { Vector4Copy( centre[0], centre[j] ); } R_FillVertexBuffer_float_or_half( FLOAT_VATTRIB_GL_TYPE( VATTRIB_AUTOSPRITE_BIT, hfa ), centre[0], 4, vertSize, 4, data + bufferOffset ); bufferOffset += 4 * vertSize; } } if( vattribs & VATTRIB_BONES_BITS ) { if( vbo->bonesIndicesOffset ) { if( !mesh->blendIndices ) { errMask |= VATTRIB_BONESINDICES_BIT; } else { R_FillVertexBuffer( int, int, (int *)&mesh->blendIndices[0], 1, vertSize, numVerts, data + vbo->bonesIndicesOffset ); } } if( vbo->bonesWeightsOffset ) { if( !mesh->blendWeights ) { errMask |= VATTRIB_BONESWEIGHTS_BIT; } else { R_FillVertexBuffer( int, int, (int *)&mesh->blendWeights[0], 1, vertSize, numVerts, data + vbo->bonesWeightsOffset ); } } } qglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, vertsOffset * vertSize, numVerts * vertSize, data ); return errMask; }
/* * R_CreateMeshVBO * * Create two static buffer objects: vertex buffer and elements buffer, the real * data is uploaded by calling R_UploadVBOVertexData and R_UploadVBOElemData. * * Tag allows vertex buffer objects to be grouped and released simultaneously. */ mesh_vbo_t *R_CreateMeshVBO( void *owner, int numVerts, int numElems, int numInstances, vattribmask_t vattribs, vbo_tag_t tag, vattribmask_t halfFloatVattribs ) { int i; size_t size; GLuint vbo_id; vbohandle_t *vboh = NULL; mesh_vbo_t *vbo = NULL; GLenum array_usage = VBO_ARRAY_USAGE_FOR_TAG(tag); GLenum elem_usage = VBO_ELEM_USAGE_FOR_TAG(tag); size_t vertexSize; vattribbit_t lmattrbit; if( !glConfig.ext.vertex_buffer_object ) return NULL; if( !r_free_vbohandles ) return NULL; if( !glConfig.ext.half_float_vertex ) { halfFloatVattribs = 0; } else { if( !(halfFloatVattribs & VATTRIB_POSITION_BIT) ) { halfFloatVattribs &= ~(VATTRIB_AUTOSPRITE_BIT); } halfFloatVattribs &= ~VATTRIB_COLORS_BITS; halfFloatVattribs &= ~VATTRIB_BONES_BITS; // TODO: convert quaternion component of instance_t to half-float // when uploading instances data halfFloatVattribs &= ~VATTRIB_INSTANCES_BITS; } vboh = r_free_vbohandles; vbo = &r_mesh_vbo[vboh->index]; memset( vbo, 0, sizeof( *vbo ) ); // vertex data vertexSize = 0; vertexSize += FLOAT_VATTRIB_SIZE(VATTRIB_POSITION_BIT, halfFloatVattribs) * 4; // normals data if( vattribs & VATTRIB_NORMAL_BIT ) { assert( !(vertexSize & 3) ); vbo->normalsOffset = vertexSize; vertexSize += FLOAT_VATTRIB_SIZE(VATTRIB_NORMAL_BIT, halfFloatVattribs) * 4; } // s-vectors (tangent vectors) if( vattribs & VATTRIB_SVECTOR_BIT ) { assert( !(vertexSize & 3) ); vbo->sVectorsOffset = vertexSize; vertexSize += FLOAT_VATTRIB_SIZE(VATTRIB_SVECTOR_BIT, halfFloatVattribs) * 4; } // texture coordinates if( vattribs & VATTRIB_TEXCOORDS_BIT ) { assert( !(vertexSize & 3) ); vbo->stOffset = vertexSize; vertexSize += FLOAT_VATTRIB_SIZE(VATTRIB_TEXCOORDS_BIT, halfFloatVattribs) * 2; } // lightmap texture coordinates lmattrbit = VATTRIB_LMCOORDS0_BIT; for( i = 0; i < MAX_LIGHTMAPS/2; i++ ) { if( !(vattribs & lmattrbit) ) { break; } assert( !(vertexSize & 3) ); vbo->lmstOffset[i] = vertexSize; vbo->lmstSize[i] = vattribs & VATTRIB_LMCOORDS0_BIT<<1 ? 4 : 2; vertexSize += FLOAT_VATTRIB_SIZE(VATTRIB_LMCOORDS0_BIT, halfFloatVattribs) * vbo->lmstSize[i]; lmattrbit = ( vattribbit_t )( ( vattribmask_t )lmattrbit << 2 ); } // vertex colors if( vattribs & VATTRIB_COLOR0_BIT ) { assert( !(vertexSize & 3) ); vbo->colorsOffset[0] = vertexSize; vertexSize += sizeof( int ); } // bones data for skeletal animation if( (vattribs & VATTRIB_BONES_BITS) == VATTRIB_BONES_BITS ) { assert( SKM_MAX_WEIGHTS == 4 ); assert( !(vertexSize & 3) ); vbo->bonesIndicesOffset = vertexSize; vertexSize += sizeof( int ); assert( !(vertexSize & 3) ); vbo->bonesWeightsOffset = vertexSize; vertexSize += sizeof( int ); } // autosprites // FIXME: autosprite2 requires waaaay too much data for such a trivial // transformation.. if( (vattribs & VATTRIB_AUTOSPRITE_BIT) == VATTRIB_AUTOSPRITE_BIT ) { assert( !(vertexSize & 3) ); vbo->spritePointsOffset = vertexSize; vertexSize += FLOAT_VATTRIB_SIZE(VATTRIB_AUTOSPRITE_BIT, halfFloatVattribs) * 4; } size = vertexSize * numVerts; // instances data if( ( (vattribs & VATTRIB_INSTANCES_BITS) == VATTRIB_INSTANCES_BITS ) && numInstances && glConfig.ext.instanced_arrays ) { assert( !(vertexSize & 3) ); vbo->instancesOffset = size; size += numInstances * sizeof( GLfloat ) * 8; } // pre-allocate vertex buffer vbo_id = 0; qglGenBuffersARB( 1, &vbo_id ); if( !vbo_id ) goto error; vbo->vertexId = vbo_id; RB_BindArrayBuffer( vbo->vertexId ); qglBufferDataARB( GL_ARRAY_BUFFER_ARB, size, NULL, array_usage ); if( qglGetError () == GL_OUT_OF_MEMORY ) goto error; vbo->arrayBufferSize = size; // pre-allocate elements buffer vbo_id = 0; qglGenBuffersARB( 1, &vbo_id ); if( !vbo_id ) goto error; vbo->elemId = vbo_id; size = numElems * sizeof( elem_t ); RB_BindElementArrayBuffer( vbo->elemId ); qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, size, NULL, elem_usage ); if( qglGetError () == GL_OUT_OF_MEMORY ) goto error; vbo->elemBufferSize = size; r_free_vbohandles = vboh->next; // link to the list of active vbo handles vboh->prev = &r_vbohandles_headnode; vboh->next = r_vbohandles_headnode.next; vboh->next->prev = vboh; vboh->prev->next = vboh; r_num_active_vbos++; vbo->registrationSequence = rsh.registrationSequence; vbo->vertexSize = vertexSize; vbo->numVerts = numVerts; vbo->numElems = numElems; vbo->owner = owner; vbo->index = vboh->index + 1; vbo->tag = tag; vbo->halfFloatAttribs = halfFloatVattribs; return vbo; error: if( vbo ) R_ReleaseMeshVBO( vbo ); RB_BindArrayBuffer( 0 ); RB_BindElementArrayBuffer( 0 ); return NULL; }