diff --git a/src.cmake b/src.cmake index 8a382b5405..1af07da643 100644 --- a/src.cmake +++ b/src.cmake @@ -94,6 +94,8 @@ set(RENDERERLIST ${ENGINE_DIR}/renderer/tr_font.cpp ${ENGINE_DIR}/renderer/GeometryCache.cpp ${ENGINE_DIR}/renderer/GeometryCache.h + ${ENGINE_DIR}/renderer/GeometryOptimiser.cpp + ${ENGINE_DIR}/renderer/GeometryOptimiser.h ${ENGINE_DIR}/renderer/InternalImage.cpp ${ENGINE_DIR}/renderer/InternalImage.h ${ENGINE_DIR}/renderer/Material.cpp diff --git a/src/engine/renderer/GeometryOptimiser.cpp b/src/engine/renderer/GeometryOptimiser.cpp new file mode 100644 index 0000000000..0f79ee8cb8 --- /dev/null +++ b/src/engine/renderer/GeometryOptimiser.cpp @@ -0,0 +1,388 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2006-2011 Robert Beckebans +Copyright (C) 2009 Peter McNeill + +This file is part of Daemon source code. + +Daemon 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. + +Daemon 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 Daemon source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// GeometryOptimiser.cpp + +#include "common/Common.h" + +#include "GeometryOptimiser.h" + +static int LeafSurfaceCompare( 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; + } + + if ( aa->fogIndex < bb->fogIndex ) { + return -1; + } else if ( aa->fogIndex > bb->fogIndex ) { + return 1; + } + + // sort by leaf + if ( aa->interactionBits < bb->interactionBits ) { + return -1; + } else if ( aa->interactionBits > bb->interactionBits ) { + return 1; + } + + // sort by leaf marksurfaces index to increase the likelihood of multidraw merging in the backend + if ( aa->lightCount < bb->lightCount ) { + return -1; + } else if ( aa->lightCount > bb->lightCount ) { + return 1; + } + return 0; +} + +static void CoreResetSurfaceViewCounts( bspSurface_t** rendererSurfaces, int numSurfaces ) { + for ( int i = 0; i < numSurfaces; i++ ) { + bspSurface_t* surface = rendererSurfaces[i]; + + surface->viewCount = -1; + surface->lightCount = -1; + surface->interactionBits = 0; + } +} + +void OptimiseMapGeometryCore( world_t* world, bspSurface_t** rendererSurfaces, int numSurfaces ) { + CoreResetSurfaceViewCounts( rendererSurfaces, numSurfaces ); + + // mark matching surfaces + for ( int i = 0; i < world->numnodes - world->numDecisionNodes; i++ ) { + bspNode_t *leaf = world->nodes + world->numDecisionNodes + i; + + for ( int j = 0; j < leaf->numMarkSurfaces; j++ ) { + bspSurface_t* surf1 = world->markSurfaces[leaf->firstMarkSurface + j]; + + if ( surf1->viewCount != -1 ) { + continue; + } + + if ( !surf1->renderable ) { + continue; + } + + shader_t* shader1 = surf1->shader; + + int fogIndex1 = surf1->fogIndex; + int lightMapNum1 = surf1->lightmapNum; + surf1->viewCount = surf1 - world->surfaces; + surf1->lightCount = j; + surf1->interactionBits = i; + + bool merged = false; + for ( int k = j + 1; k < leaf->numMarkSurfaces; k++ ) { + bspSurface_t* surf2 = world->markSurfaces[leaf->firstMarkSurface + k]; + + if ( surf2->viewCount != -1 ) { + continue; + } + + if ( !surf2->renderable ) { + continue; + } + + shader_t* shader2 = surf2->shader; + int fogIndex2 = surf2->fogIndex; + int lightMapNum2 = surf2->lightmapNum; + if ( shader1 != shader2 || fogIndex1 != fogIndex2 || lightMapNum1 != lightMapNum2 ) { + continue; + } + + surf2->viewCount = surf1->viewCount; + surf2->lightCount = k; + surf2->interactionBits = i; + merged = true; + } + + if ( !merged ) { + surf1->viewCount = -1; + surf1->lightCount = -1; + // don't clear the leaf number so + // surfaces that arn't merged are placed + // closer to other leafs in the vbo + } + } + } + + qsort( rendererSurfaces, numSurfaces, sizeof( bspSurface_t* ), LeafSurfaceCompare ); +} + +static void SphereFromBounds( vec3_t mins, vec3_t maxs, vec3_t origin, float* radius ) { + vec3_t temp; + + VectorAdd( mins, maxs, origin ); + VectorScale( origin, 0.5, origin ); + VectorSubtract( maxs, origin, temp ); + *radius = VectorLength( temp ); +} + +void MergeLeafSurfacesCore( world_t* world, bspSurface_t** rendererSurfaces, int numSurfaces ) { + if ( !r_mergeLeafSurfaces->integer ) { + return; + } + + // count merged/unmerged surfaces + int numUnmergedSurfaces = 0; + int numMergedSurfaces = 0; + int oldViewCount = -2; + + for ( int i = 0; i < numSurfaces; i++ ) { + bspSurface_t* surface = rendererSurfaces[i]; + + if ( surface->viewCount == -1 ) { + numUnmergedSurfaces++; + } else if ( surface->viewCount != oldViewCount ) { + oldViewCount = surface->viewCount; + numMergedSurfaces++; + } + } + + // Allocate merged surfaces + world->mergedSurfaces = ( bspSurface_t* ) ri.Hunk_Alloc( sizeof( bspSurface_t ) * numMergedSurfaces, ha_pref::h_low ); + + // actually merge surfaces + bspSurface_t* mergedSurf = world->mergedSurfaces; + oldViewCount = -2; + for ( int i = 0; i < numSurfaces; i++ ) { + vec3_t bounds[2]; + int surfVerts = 0; + int surfIndexes = 0; + srfVBOMesh_t* vboSurf; + bspSurface_t* surf1 = rendererSurfaces[i]; + + // skip unmergable surfaces + if ( surf1->viewCount == -1 ) { + continue; + } + + // skip surfaces that have already been merged + if ( surf1->viewCount == oldViewCount ) { + continue; + } + + oldViewCount = surf1->viewCount; + + srfGeneric_t* srf1 = ( srfGeneric_t* ) surf1->data; + int firstIndex = srf1->firstIndex; + + // count verts and indexes and add bounds for the merged surface + ClearBounds( bounds[0], bounds[1] ); + for ( int j = i; j < numSurfaces; j++ ) { + bspSurface_t* surf2 = rendererSurfaces[j]; + + // stop merging when we hit a surface that can't be merged + if ( surf2->viewCount != surf1->viewCount ) { + break; + } + + srfGeneric_t* srf2 = ( srfGeneric_t* ) surf2->data; + surfIndexes += srf2->numTriangles * 3; + surfVerts += srf2->numVerts; + BoundsAdd( bounds[0], bounds[1], srf2->bounds[0], srf2->bounds[1] ); + } + + if ( !surfIndexes || !surfVerts ) { + continue; + } + + vboSurf = ( srfVBOMesh_t* ) ri.Hunk_Alloc( sizeof( *vboSurf ), ha_pref::h_low ); + *vboSurf = {}; + vboSurf->surfaceType = surfaceType_t::SF_VBO_MESH; + + vboSurf->numTriangles = surfIndexes / 3; + vboSurf->numVerts = surfVerts; + vboSurf->firstIndex = firstIndex; + + vboSurf->lightmapNum = surf1->lightmapNum; + vboSurf->vbo = world->vbo; + vboSurf->ibo = world->ibo; + + VectorCopy( bounds[0], vboSurf->bounds[0] ); + VectorCopy( bounds[1], vboSurf->bounds[1] ); + SphereFromBounds( vboSurf->bounds[0], vboSurf->bounds[1], vboSurf->origin, &vboSurf->radius ); + + mergedSurf->data = ( surfaceType_t* ) vboSurf; + mergedSurf->fogIndex = surf1->fogIndex; + mergedSurf->shader = surf1->shader; + mergedSurf->lightmapNum = surf1->lightmapNum; + mergedSurf->viewCount = -1; + + // redirect view surfaces to this surf + for ( int k = 0; k < world->numMarkSurfaces; k++ ) { + bspSurface_t** view = world->viewSurfaces + k; + + if ( ( *view )->viewCount == surf1->viewCount ) { + *view = mergedSurf; + } + } + + mergedSurf++; + } + + Log::Debug( "Processed %d surfaces into %d merged, %d unmerged", numSurfaces, numMergedSurfaces, numUnmergedSurfaces ); +} + +/* +=========================================================================== + +Daemon BSD Source Code +Copyright (c) 2025 Daemon Developers +All rights reserved. + +This file is part of the Daemon BSD Source Code (Daemon Source Code). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Daemon developers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== +*/ +void MergeDuplicateVertices( bspSurface_t** rendererSurfaces, int numSurfaces, srfVert_t* vertices, int numVerticesIn, + glIndex_t* indices, int numIndicesIn, int& numVerticesOut, int& numIndicesOut ) { + int start = Sys::Milliseconds(); + + std::unordered_map verts; + uint32_t idx = 0; + uint32_t vertIdx = 0; + + // To shut CI up since this *is* used for an assert + Q_UNUSED( numIndicesIn ); + for ( int i = 0; i < numSurfaces; i++ ) { + bspSurface_t* surface = rendererSurfaces[i]; + + srfGeneric_t* face = ( srfGeneric_t* ) surface->data; + face->firstIndex = idx; + for ( srfTriangle_t* triangle = face->triangles; triangle < face->triangles + face->numTriangles; triangle++ ) { + for ( int j = 0; j < 3; j++ ) { + srfVert_t& vert = face->verts[triangle->indexes[j]]; + uint32_t index = verts[vert]; + + ASSERT_LT( idx, numIndicesIn ); + if ( !index ) { + verts[vert] = vertIdx + 1; + vertices[vertIdx] = vert; + indices[idx] = vertIdx; + + ASSERT_LT( vertIdx, numVerticesIn ); + + vertIdx++; + } else { + indices[idx] = index - 1; + } + idx++; + } + } + } + + numVerticesOut = vertIdx; + numIndicesOut = idx; + + Log::Notice( "Merged %i vertices into %i in %i ms", numVerticesIn, numVerticesOut, Sys::Milliseconds() - start ); +} + +/* static void ProcessMaterialSurface( MaterialSurface& surface, std::vector& materialSurfaces, + std::vector& processedMaterialSurfaces, + srfVert_t* verts, glIndex_t* indices ) { + if ( surface.count == MAX_MATERIAL_SURFACE_TRIS ) { + materialSurfaces.emplace_back( surface ); + } + + while ( surface.count > MAX_MATERIAL_SURFACE_TRIS ) { + MaterialSurface srf = surface; + + srf.count = MAX_MATERIAL_SURFACE_TRIS; + surface.count -= MAX_MATERIAL_SURFACE_TRIS; + surface.firstIndex += MAX_MATERIAL_SURFACE_TRIS; + + processedMaterialSurfaces.push_back( srf ); + } +} */ + +std::vector OptimiseMapGeometryMaterial( world_t* world, int numSurfaces ) { + std::vector materialSurfaces; + materialSurfaces.reserve( numSurfaces ); + + std::vector processedMaterialSurfaces; + processedMaterialSurfaces.reserve( numSurfaces ); + + // std::unordered_map triEdges; + + int surfaceIndex = 0; + for ( int k = 0; k < world->numSurfaces; k++ ) { + bspSurface_t* surface = &world->surfaces[k]; + + MaterialSurface srf {}; + + srf.shader = surface->shader; + srf.bspSurface = true; + srf.fog = surface->fogIndex; + + srf.firstIndex = ( ( srfGeneric_t* ) surface->data )->firstIndex; + srf.count = ( ( srfGeneric_t* ) surface->data )->numTriangles; + srf.verts = ( ( srfGeneric_t* ) surface->data )->verts; + srf.tris = ( ( srfGeneric_t* ) surface->data )->triangles; + + materialSurfaces.emplace_back( srf ); + surfaceIndex++; + } + + return materialSurfaces; +} diff --git a/src/engine/renderer/GeometryOptimiser.h b/src/engine/renderer/GeometryOptimiser.h new file mode 100644 index 0000000000..b2f2b43796 --- /dev/null +++ b/src/engine/renderer/GeometryOptimiser.h @@ -0,0 +1,111 @@ +/* +=========================================================================== + +Daemon BSD Source Code +Copyright (c) 2025 Daemon Developers +All rights reserved. + +This file is part of the Daemon BSD Source Code (Daemon Source Code). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Daemon developers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== +*/ +// GeometryOptimiser.h + +#include "tr_local.h" +#include "Material.h" + +#ifndef GEOMETRY_OPTIMISER_H +#define GEOMETRY_OPTIMISER_H + +static const uint32_t MAX_MATERIAL_SURFACE_TRIS = 64; +static const uint32_t MAX_MATERIAL_SURFACE_INDEXES = 3 * MAX_MATERIAL_SURFACE_TRIS; +static const uint32_t MAX_MATERIAL_SURFACE_DISTANCE = 256; + +struct TriEdge { + enum State : uint32_t { + MERGEABLE_TRUE = BIT( 27 ), + MERGEABLE_FALSE = BIT( 28 ), + MERGEABLE_NOT_PROCESSED = BIT( 29 ), + NONE = BIT( 30 ) + }; + + uint32_t index1; + uint32_t index2; +}; + +struct TriEdgeHasher { + size_t operator()( const TriEdge& triEdge ) { + return std::hash{} ( triEdge.index1 ) ^ ( std::hash{} ( triEdge.index2 ) << 16 ); + } +}; + +struct TriIndex { + int tri1 = -1; + int tri2 = -1; +}; + +struct MapVertHasher { + size_t operator()( const srfVert_t& vert ) const { + size_t hash = ~( *( ( size_t* ) &vert.xyz[0] ) << 15 ); + hash ^= ( *( ( size_t* ) &vert.xyz[0] ) >> 10 ); + hash += ( *( ( size_t* ) &vert.xyz[1] ) << 3 ); + hash ^= ( *( ( size_t* ) &vert.xyz[1] ) >> 6 ); + hash += ~( *( ( size_t* ) &vert.xyz[2] ) << 11 ); + hash ^= ( *( ( size_t* ) &vert.xyz[2] ) >> 16 ); + + hash ^= ( *( ( size_t* ) &vert.st[0] ) << 7 ); + hash += ( *( ( size_t* ) &vert.st[0] ) >> 12 ); + + hash ^= ( *( ( size_t* ) &vert.st[1] ) << 13 ); + hash += ( *( ( size_t* ) &vert.st[1] ) >> 8 ); + + return hash; + } +}; + +static bool CompareEpsilon( float lhs, float rhs ) { + float diff = fabsf( lhs - rhs ); + return diff <= 0.000001; +} + +struct MapVertEqual { + bool operator()( const srfVert_t& lhs, const srfVert_t& rhs ) const { + return VectorCompare( lhs.xyz, rhs.xyz ) + && CompareEpsilon( lhs.st[0], rhs.st[0] ) && CompareEpsilon( lhs.st[1], rhs.st[1] ) + && CompareEpsilon( lhs.lightmap[0], rhs.lightmap[0] ) && CompareEpsilon( lhs.lightmap[1], rhs.lightmap[1] ) + && VectorCompareEpsilon( lhs.normal, rhs.normal, 0.0001f ) + && CompareEpsilon( lhs.qtangent[0], rhs.qtangent[0] ) && CompareEpsilon( lhs.qtangent[1], rhs.qtangent[1] ) + && CompareEpsilon( lhs.qtangent[2], rhs.qtangent[2] ) && CompareEpsilon( lhs.qtangent[3], rhs.qtangent[3] ) + && lhs.lightColor.ArrayBytes() == rhs.lightColor.ArrayBytes(); + } +}; + +void OptimiseMapGeometryCore( world_t* world, bspSurface_t** rendererSurfaces, int numSurfaces ); +void MergeLeafSurfacesCore( world_t* world, bspSurface_t** rendererSurfaces, int numSurfaces ); +void MergeDuplicateVertices( bspSurface_t** rendererSurfaces, int numSurfaces, srfVert_t* vertices, int numVerticesIn, + glIndex_t* indices, int numIndicesIn, int& numVerticesOut, int& numIndicesOut ); +std::vector OptimiseMapGeometryMaterial( world_t* world, int numSurfaces ); + +#endif // GEOMETRY_OPTIMISER_H diff --git a/src/engine/renderer/Material.h b/src/engine/renderer/Material.h index 0d7f0c504b..0a32e9fa4b 100644 --- a/src/engine/renderer/Material.h +++ b/src/engine/renderer/Material.h @@ -76,6 +76,27 @@ struct DrawCommand { } }; +struct MaterialSurface { + shader_t* shader; + bool bspSurface; + int fog; + int portalNum = -1; + + GLuint firstIndex; + GLuint count; + + srfVert_t* verts; + srfTriangle_t* tris; + + uint32_t materialPackIDs[MAX_SHADER_STAGES]; + uint32_t materialIDs[MAX_SHADER_STAGES]; + + uint32_t drawCommandIDs[MAX_SHADER_STAGES]; + uint32_t texDataIDs[MAX_SHADER_STAGES]; + bool texDataDynamic[MAX_SHADER_STAGES]; + uint32_t shaderVariant[MAX_SHADER_STAGES]; +}; + struct Material { uint32_t materialsSSBOOffset = 0; diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 4a34ab8a75..ec0f0aeefb 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "tr_local.h" #include "framework/CommandSystem.h" #include "GeometryCache.h" +#include "GeometryOptimiser.h" /* ======================================================== @@ -2519,94 +2520,24 @@ static void R_CreateClusters() } } -/* -SmoothNormals() -smooths together coincident vertex normals across the bsp -*/ -static int LeafSurfaceCompare( 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; - } - - // sort by leaf - if ( aa->interactionBits < bb->interactionBits ) - { - return -1; - } - else if ( aa->interactionBits > bb->interactionBits ) - { - return 1; - } - - // sort by leaf marksurfaces index to increase the likelihood of multidraw merging in the backend - if ( aa->lightCount < bb->lightCount ) - { - return -1; - } - else if ( aa->lightCount > bb->lightCount ) - { - return 1; - } - return 0; -} - /* =============== R_CreateWorldVBO =============== */ -static void R_CreateWorldVBO() -{ - int i, j, k; +static void R_CreateWorldVBO() { + int startTime = ri.Milliseconds(); - int numVerts; - glIndex_t *vboIdxs; - - int numTriangles; - - int numSurfaces; - bspSurface_t *surface; - bspSurface_t **surfaces; - bspSurface_t *mergedSurf; - int startTime, endTime; - - startTime = ri.Milliseconds(); - - numVerts = 0; - numTriangles = 0; - numSurfaces = 0; + int numVertsInitial = 0; + int numTriangles = 0; + int numSurfaces = 0; int numPortals = 0; + for ( int i = 0; i < s_worldData.numSurfaces; i++ ) { + bspSurface_t* surface = &s_worldData.surfaces[i]; - for ( k = 0; k < s_worldData.numSurfaces; k++ ) - { - surface = &s_worldData.surfaces[ k ]; - - if ( surface->shader->isPortal || surface->shader->autoSpriteMode != 0 ) - { + // HACK: portals: don't use VBO because when adding a portal we have to read back the verts CPU-side + // Autosprite: don't use VBO because verts are rewritten each time based on view origin + if ( surface->shader->isPortal || surface->shader->autoSpriteMode != 0 ) { if ( surface->shader->isPortal ) { numPortals++; } @@ -2614,195 +2545,55 @@ static void R_CreateWorldVBO() } if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID - || *surface->data == surfaceType_t::SF_TRIANGLES ) - { + || *surface->data == surfaceType_t::SF_TRIANGLES ) { srfGeneric_t* srf = ( srfGeneric_t* ) surface->data; - numVerts += srf->numVerts; + numVertsInitial += srf->numVerts; numTriangles += srf->numTriangles; - } - else - { + } else { continue; } + surface->renderable = true; numSurfaces++; } - if ( !numVerts || !numTriangles || !numSurfaces ) - { + if ( !numVertsInitial || !numTriangles || !numSurfaces ) { return; } - // reset surface view counts - for ( i = 0; i < s_worldData.numSurfaces; i++ ) - { - surface = &s_worldData.surfaces[ i ]; - - surface->viewCount = -1; - surface->lightCount = -1; - surface->interactionBits = 0; - } - - // mark matching surfaces - for ( i = 0; i < s_worldData.numnodes - s_worldData.numDecisionNodes; i++ ) - { - bspNode_t *leaf = s_worldData.nodes + s_worldData.numDecisionNodes + i; - - for ( j = 0; j < leaf->numMarkSurfaces; j++ ) - { - bspSurface_t *surf1; - shader_t *shader1; - int fogIndex1; - int lightMapNum1; - bool merged = false; - surf1 = s_worldData.markSurfaces[ leaf->firstMarkSurface + j ]; - - if ( surf1->viewCount != -1 ) - { - continue; - } - - if ( *surf1->data != surfaceType_t::SF_GRID && *surf1->data != surfaceType_t::SF_TRIANGLES && *surf1->data != surfaceType_t::SF_FACE ) - { - continue; - } - - shader1 = surf1->shader; - - if ( shader1->isPortal || shader1->autoSpriteMode != 0 ) - { - continue; - } - - fogIndex1 = surf1->fogIndex; - lightMapNum1 = surf1->lightmapNum; - surf1->viewCount = surf1 - s_worldData.surfaces; - surf1->lightCount = j; - surf1->interactionBits = i; - - for ( k = j + 1; k < leaf->numMarkSurfaces; k++ ) - { - bspSurface_t *surf2; - shader_t *shader2; - int fogIndex2; - int lightMapNum2; - - surf2 = s_worldData.markSurfaces[ leaf->firstMarkSurface + k ]; - - if ( surf2->viewCount != -1 ) - { - continue; - } - - if ( *surf2->data != surfaceType_t::SF_GRID && *surf2->data != surfaceType_t::SF_TRIANGLES && *surf2->data != surfaceType_t::SF_FACE ) - { - continue; - } - - shader2 = surf2->shader; - fogIndex2 = surf2->fogIndex; - lightMapNum2 = surf2->lightmapNum; - if ( shader1 != shader2 || fogIndex1 != fogIndex2 || lightMapNum1 != lightMapNum2 ) - { - continue; - } - - surf2->viewCount = surf1->viewCount; - surf2->lightCount = k; - surf2->interactionBits = i; - merged = true; - } - - if ( !merged ) - { - surf1->viewCount = -1; - surf1->lightCount = -1; - // don't clear the leaf number so - // surfaces that arn't merged are placed - // closer to other leafs in the vbo - } - } - } - - surfaces = ( bspSurface_t ** ) ri.Hunk_AllocateTempMemory( sizeof( *surfaces ) * numSurfaces ); - + bspSurface_t** rendererSurfaces = ( bspSurface_t** ) ri.Hunk_AllocateTempMemory( sizeof( bspSurface_t* ) * numSurfaces ); numSurfaces = 0; - for ( k = 0; k < s_worldData.numSurfaces; k++ ) - { - surface = &s_worldData.surfaces[ k ]; + for ( int i = 0; i < s_worldData.numSurfaces; i++ ) { + bspSurface_t* surface = &s_worldData.surfaces[i]; - if ( surface->shader->isPortal ) - { - // HACK: don't use VBO because when adding a portal we have to read back the verts CPU-side - continue; - } - - if ( surface->shader->autoSpriteMode != 0 ) - { - // don't use VBO because verts are rewritten each time based on view origin - continue; - } - - if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID || *surface->data == surfaceType_t::SF_TRIANGLES ) - { - surfaces[ numSurfaces++ ] = surface; + if ( surface->renderable ) { + rendererSurfaces[numSurfaces++] = surface; } } - qsort( surfaces, numSurfaces, sizeof( *surfaces ), LeafSurfaceCompare ); + OptimiseMapGeometryCore( &s_worldData, rendererSurfaces, numSurfaces ); - Log::Debug("...calculating world VBO ( %i verts %i tris )", numVerts, numTriangles ); + Log::Debug( "...calculating world VBO ( %i verts %i tris )", numVertsInitial, numTriangles ); // Use srfVert_t for the temporary array used to feed R_CreateStaticVBO, despite containing // extraneous data, so that verts can be conveniently be bulk copied from the surface. - auto *vboVerts = (srfVert_t *)ri.Hunk_AllocateTempMemory( numVerts * sizeof( srfVert_t ) ); - vboIdxs = (glIndex_t *)ri.Hunk_AllocateTempMemory( 3 * numTriangles * sizeof( glIndex_t ) ); - - // set up triangle and vertex arrays - int vboNumVerts = 0; - int vboNumIndexes = 0; + srfVert_t* vboVerts = ( srfVert_t* ) ri.Hunk_AllocateTempMemory( numVertsInitial * sizeof( srfVert_t ) ); + glIndex_t* vboIdxs = ( glIndex_t* ) ri.Hunk_AllocateTempMemory( 3 * numTriangles * sizeof( glIndex_t ) ); - for ( k = 0; k < numSurfaces; k++ ) - { - surface = surfaces[ k ]; - - const srfVert_t *surfVerts; - int numSurfVerts; - const srfTriangle_t *surfTriangle, *surfTriangleEnd; - - if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID - || *surface->data == surfaceType_t::SF_TRIANGLES ) - { - srfGeneric_t* srf = ( srfGeneric_t* ) surface->data; + int numVerts; + int numIndices; + MergeDuplicateVertices( rendererSurfaces, numSurfaces, vboVerts, numVertsInitial, vboIdxs, 3 * numTriangles, numVerts, numIndices ); - srf->firstIndex = vboNumIndexes; - surfVerts = srf->verts; - numSurfVerts = srf->numVerts; - surfTriangle = srf->triangles; - surfTriangleEnd = surfTriangle + srf->numTriangles; - } - else - { - continue; - } - - for ( ; surfTriangle < surfTriangleEnd; surfTriangle++ ) - { - vboIdxs[ vboNumIndexes++ ] = vboNumVerts + surfTriangle->indexes[ 0 ]; - vboIdxs[ vboNumIndexes++ ] = vboNumVerts + surfTriangle->indexes[ 1 ]; - vboIdxs[ vboNumIndexes++ ] = vboNumVerts + surfTriangle->indexes[ 2 ]; - } - - std::copy_n( surfVerts, numSurfVerts, vboVerts + vboNumVerts ); - vboNumVerts += numSurfVerts; + if ( glConfig2.usingMaterialSystem ) { + OptimiseMapGeometryMaterial( &s_worldData, numSurfaces ); } s_worldData.numPortals = numPortals; s_worldData.portals = ( AABB* ) ri.Hunk_Alloc( numPortals * sizeof( AABB ), ha_pref::h_low ); int portal = 0; - for ( i = 0; i < s_worldData.numSurfaces; i++ ) { - surface = &s_worldData.surfaces[i]; + for ( int i = 0; i < s_worldData.numSurfaces; i++ ) { + bspSurface_t* surface = &s_worldData.surfaces[i]; if ( surface->shader->isPortal ) { surface->portalNum = portal; @@ -2836,166 +2627,36 @@ static void R_CreateWorldVBO() } } - ASSERT_EQ( vboNumVerts, numVerts ); - ASSERT_EQ( vboNumIndexes, numTriangles * 3 ); - - vertexAttributeSpec_t attrs[] { - { ATTR_INDEX_POSITION, GL_FLOAT, GL_FLOAT, &vboVerts[ 0 ].xyz, 3, sizeof( *vboVerts ), 0 }, - { ATTR_INDEX_COLOR, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, &vboVerts[ 0 ].lightColor, 4, sizeof( *vboVerts ), ATTR_OPTION_NORMALIZE }, - { ATTR_INDEX_QTANGENT, GL_SHORT, GL_SHORT, &vboVerts[ 0 ].qtangent, 4, sizeof( *vboVerts ), ATTR_OPTION_NORMALIZE }, - { ATTR_INDEX_TEXCOORD, GL_FLOAT, GL_HALF_FLOAT, &vboVerts[ 0 ].st, 4, sizeof( *vboVerts ), 0 }, + vertexAttributeSpec_t attrs[]{ + { ATTR_INDEX_POSITION, GL_FLOAT, GL_FLOAT, &vboVerts[0].xyz, 3, sizeof( *vboVerts ), 0 }, + { ATTR_INDEX_COLOR, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, &vboVerts[0].lightColor, 4, sizeof( *vboVerts ), ATTR_OPTION_NORMALIZE }, + { ATTR_INDEX_QTANGENT, GL_SHORT, GL_SHORT, &vboVerts[0].qtangent, 4, sizeof( *vboVerts ), ATTR_OPTION_NORMALIZE }, + { ATTR_INDEX_TEXCOORD, GL_FLOAT, GL_HALF_FLOAT, &vboVerts[0].st, 4, sizeof( *vboVerts ), 0 }, }; if ( glConfig2.usingGeometryCache ) { - geometryCache.AddMapGeometry( vboNumVerts, vboNumIndexes, std::begin( attrs ), std::end( attrs ), vboIdxs ); + geometryCache.AddMapGeometry( numVerts, numIndices, std::begin( attrs ), std::end( attrs ), vboIdxs ); } s_worldData.vbo = R_CreateStaticVBO( - "staticWorld_VBO", std::begin( attrs ), std::end( attrs ), vboNumVerts ); + "staticWorld_VBO", std::begin( attrs ), std::end( attrs ), numVerts ); s_worldData.ibo = R_CreateStaticIBO2( "staticWorld_IBO", numTriangles, vboIdxs ); ri.Hunk_FreeTempMemory( vboIdxs ); ri.Hunk_FreeTempMemory( vboVerts ); - if ( r_mergeLeafSurfaces->integer ) - { - // count merged/unmerged surfaces - int numUnmergedSurfaces = 0; - int numMergedSurfaces = 0; - int oldViewCount = -2; - - for ( i = 0; i < numSurfaces; i++ ) - { - surface = surfaces[ i ]; - - if ( surface->viewCount == -1 ) - { - numUnmergedSurfaces++; - } - else if ( surface->viewCount != oldViewCount ) - { - oldViewCount = surface->viewCount; - numMergedSurfaces++; - } - } - - // Allocate merged surfaces - s_worldData.mergedSurfaces = ( bspSurface_t * ) ri.Hunk_Alloc( sizeof( *s_worldData.mergedSurfaces ) * numMergedSurfaces, ha_pref::h_low ); - - // actually merge surfaces - mergedSurf = s_worldData.mergedSurfaces; - oldViewCount = -2; - for ( i = 0; i < numSurfaces; i++ ) - { - vec3_t bounds[ 2 ]; - int surfVerts = 0; - int surfIndexes = 0; - int firstIndex = numTriangles * 3; - srfVBOMesh_t *vboSurf; - bspSurface_t *surf1 = surfaces[ i ]; - - // skip unmergable surfaces - if ( surf1->viewCount == -1 ) - { - continue; - } - - // skip surfaces that have already been merged - if ( surf1->viewCount == oldViewCount ) - { - continue; - } - - oldViewCount = surf1->viewCount; - - if ( *surf1->data == surfaceType_t::SF_FACE || *surf1->data == surfaceType_t::SF_TRIANGLES - || *surf1->data == surfaceType_t::SF_GRID ) - { - srfGeneric_t* srf = ( srfGeneric_t* ) surf1->data; - firstIndex = srf->firstIndex; - } + MergeLeafSurfacesCore( &s_worldData, rendererSurfaces, numSurfaces ); - // count verts and indexes and add bounds for the merged surface - ClearBounds( bounds[ 0 ], bounds[ 1 ] ); - for ( j = i; j < numSurfaces; j++ ) - { - bspSurface_t *surf2 = surfaces[ j ]; - - // stop merging when we hit a surface that can't be merged - if ( surf2->viewCount != surf1->viewCount ) - { - break; - } - - if ( *surf2->data == surfaceType_t::SF_FACE || *surf2->data == surfaceType_t::SF_TRIANGLES - || *surf2->data == surfaceType_t::SF_GRID ) - { - srfGeneric_t* srf = ( srfGeneric_t* ) surf2->data; - surfIndexes += srf->numTriangles * 3; - surfVerts += srf->numVerts; - BoundsAdd( bounds[ 0 ], bounds[ 1 ], srf->bounds[ 0 ], srf->bounds[ 1 ] ); - } - } - - if ( !surfIndexes || !surfVerts ) - { - continue; - } - - vboSurf = ( srfVBOMesh_t * ) ri.Hunk_Alloc( sizeof( *vboSurf ), ha_pref::h_low ); - *vboSurf = {}; - vboSurf->surfaceType = surfaceType_t::SF_VBO_MESH; - - vboSurf->numTriangles = surfIndexes / 3; - vboSurf->numVerts = surfVerts; - vboSurf->firstIndex = firstIndex; - - vboSurf->lightmapNum = surf1->lightmapNum; - vboSurf->vbo = s_worldData.vbo; - vboSurf->ibo = s_worldData.ibo; - - VectorCopy( bounds[ 0 ], vboSurf->bounds[ 0 ] ); - VectorCopy( bounds[ 1 ], vboSurf->bounds[ 1 ] ); - SphereFromBounds( vboSurf->bounds[ 0 ], vboSurf->bounds[ 1 ], vboSurf->origin, &vboSurf->radius ); - - mergedSurf->data = ( surfaceType_t * ) vboSurf; - mergedSurf->fogIndex = surf1->fogIndex; - mergedSurf->shader = surf1->shader; - mergedSurf->lightmapNum = surf1->lightmapNum; - mergedSurf->viewCount = -1; - - // redirect view surfaces to this surf - for ( k = 0; k < s_worldData.numMarkSurfaces; k++ ) - { - bspSurface_t **view = s_worldData.viewSurfaces + k; - - if ( ( *view )->viewCount == surf1->viewCount ) - { - *view = mergedSurf; - } - } - - mergedSurf++; - } - - Log::Debug("Processed %d surfaces into %d merged, %d unmerged", numSurfaces, numMergedSurfaces, numUnmergedSurfaces ); - } - - endTime = ri.Milliseconds(); - Log::Debug("world VBO calculation time = %5.2f seconds", ( endTime - startTime ) / 1000.0 ); + int endTime = ri.Milliseconds(); + Log::Debug( "world VBO calculation time = %5.2f seconds", ( endTime - startTime ) / 1000.0 ); // point triangle surfaces to world VBO - for ( k = 0; k < numSurfaces; k++ ) - { - surface = surfaces[ k ]; + for ( int i = 0; i < numSurfaces; i++ ) { + bspSurface_t* surface = rendererSurfaces[i]; - if ( *surface->data == surfaceType_t::SF_FACE || *surface->data == surfaceType_t::SF_GRID - || *surface->data == surfaceType_t::SF_TRIANGLES ) - { - srfGeneric_t* srf = ( srfGeneric_t* ) surface->data; - srf->vbo = s_worldData.vbo; - srf->ibo = s_worldData.ibo; - } + srfGeneric_t* srf = ( srfGeneric_t* ) surface->data; + srf->vbo = s_worldData.vbo; + srf->ibo = s_worldData.ibo; // clear data used for sorting surface->viewCount = -1; @@ -3003,7 +2664,7 @@ static void R_CreateWorldVBO() surface->interactionBits = 0; } - ri.Hunk_FreeTempMemory( surfaces ); + ri.Hunk_FreeTempMemory( rendererSurfaces ); } /* diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 4d6aa33ef0..d74eca1cc1 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -1840,6 +1840,8 @@ enum class ssaoMode { int16_t fogIndex; int portalNum; + bool renderable = false; + surfaceType_t *data; // any of srf*_t };