diff --git a/src/engine/renderer/GeometryOptimiser.cpp b/src/engine/renderer/GeometryOptimiser.cpp index 1ab9791a01..aa1b9237de 100644 --- a/src/engine/renderer/GeometryOptimiser.cpp +++ b/src/engine/renderer/GeometryOptimiser.cpp @@ -27,6 +27,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "GeometryOptimiser.h" +#include "ShadeCommon.h" +#include "GeometryCache.h" + static int LeafSurfaceCompare( const void* a, const void* b ) { bspSurface_t* aa, * bb; @@ -420,35 +423,131 @@ void MergeDuplicateVertices( bspSurface_t** rendererSurfaces, int numSurfaces, s 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 ); +static void ProcessMaterialSurface( MaterialSurface* surface, SurfaceIndexes* surfaceIdxs, + std::vector& processedSurfaces, + const srfVert_t* vertexes, glIndex_t* idxs, uint32_t* numIndices ) { + vec3_t mins; + vec3_t maxs; + ClearBounds( mins, maxs ); + + for ( uint32_t i = 0; i < surface->count; i++ ) { + AddPointToBounds( vertexes[surfaceIdxs->idxs[i]].xyz, mins, maxs ); } - while ( surface.count > MAX_MATERIAL_SURFACE_TRIS ) { - MaterialSurface srf = surface; + const uint32_t oldFirstIndex = surface->firstIndex; + surface->firstIndex = *numIndices; + + memcpy( idxs + ( *numIndices ), surfaceIdxs->idxs, surface->count * sizeof( glIndex_t ) ); + + *numIndices += surface->count; + + SphereFromBounds( mins, maxs, surface->origin, &surface->radius ); + + processedSurfaces.emplace_back( *surface ); + + surface->firstIndex = oldFirstIndex; +} + +static void OptimiseMaterialSurfaces( std::vector& materialSurfaces, + const uint32_t gridSize[3], const uint32_t cellSize[3], const vec3_t worldMins, + std::function materialSurfaceSort, + const srfVert_t* vertices, const glIndex_t* indices, + glIndex_t* idxs, + std::vector& processedMaterialSurfaces, uint32_t& numIndices ) { + + Grid surfaceGrid( true ); + surfaceGrid.SetSize( gridSize[0], gridSize[1], gridSize[2] ); + surfaceGrid.Clear(); + + Grid surfaceIdxsGrid( true ); + surfaceIdxsGrid.SetSize( gridSize[0], gridSize[1], gridSize[2] ); + + MaterialSurface* surface = &materialSurfaces[0]; + MaterialSurface* surface2; + bool addedSurfaces = false; + + std::function processGrid = [&]() { + if ( !addedSurfaces ) { + surface = surface2; + return; + } + + for ( Grid::Iterator it = surfaceGrid.begin(); it != surfaceGrid.end(); it++ ) { + if ( *it ) { + SurfaceIndexes* srfIdxs = &surfaceIdxsGrid( it - surfaceGrid.begin() ); + surface->count = *it; + ProcessMaterialSurface( surface, srfIdxs, processedMaterialSurfaces, vertices, idxs, &numIndices ); + } + } + + surfaceGrid.Clear(); + + surface = surface2; + addedSurfaces = false; + }; + + for ( uint32_t surfaceIndex = 0; surfaceIndex < materialSurfaces.size(); surfaceIndex++ ) { + surface2 = &materialSurfaces[surfaceIndex]; + if ( surface2->skyBrush ) { + continue; + } + + if ( materialSurfaceSort( *surface, *surface2 ) ) { + processGrid(); + } + + for ( uint32_t i = 0; i < surface2->count; i += 3 ) { + glIndex_t tri[3] = { indices[i + surface2->firstIndex], indices[i + 1 + surface2->firstIndex], + indices[i + 2 + surface2->firstIndex] }; + + srfVert_t triVerts[3] = { vertices[tri[0]], vertices[tri[1]], vertices[tri[2]] }; + + vec3_t origin; + VectorAdd( triVerts[0].xyz, triVerts[1].xyz, origin ); + VectorAdd( origin, triVerts[2].xyz, origin ); + + VectorScale( origin, 1.0f / 3.0f, origin ); + VectorSubtract( origin, worldMins, origin ); + + uint32_t gridOrigin[3]{ uint32_t( origin[0] / cellSize[0] ), uint32_t( origin[1] / cellSize[1] ), + uint32_t( origin[2] / cellSize[2] ) }; + + uint32_t* count = &surfaceGrid( gridOrigin[0], gridOrigin[1], gridOrigin[2] ); + SurfaceIndexes* srfIdxs = &surfaceIdxsGrid( gridOrigin[0], gridOrigin[1], gridOrigin[2] ); + + memcpy( srfIdxs->idxs + ( *count ), tri, 3 * sizeof( glIndex_t ) ); + + *count += 3; + + if ( *count + 3 >= MAX_MATERIAL_SURFACE_INDEXES ) { + const uint32_t oldCount = surface2->count; + surface2->count = *count; - srf.count = MAX_MATERIAL_SURFACE_TRIS; - surface.count -= MAX_MATERIAL_SURFACE_TRIS; - surface.firstIndex += MAX_MATERIAL_SURFACE_TRIS; + ProcessMaterialSurface( surface2, srfIdxs, processedMaterialSurfaces, vertices, idxs, &numIndices ); - processedMaterialSurfaces.push_back( srf ); + surface2->count = oldCount; + *count = 0; + } else { + addedSurfaces = true; + } + } } -} */ -std::vector OptimiseMapGeometryMaterial(bspSurface_t** rendererSurfaces, int numSurfaces ) { + processGrid(); +} + +std::vector OptimiseMapGeometryMaterial( world_t* world, bspSurface_t** rendererSurfaces, int numSurfaces, + const srfVert_t* vertices, const int numVerticesIn, const glIndex_t* indices, const int numIndicesIn ) { std::vector materialSurfaces; + std::vector materialSurfacesExtended; materialSurfaces.reserve( numSurfaces ); + materialSurfacesExtended.reserve( numSurfaces ); - std::vector processedMaterialSurfaces; - processedMaterialSurfaces.reserve( numSurfaces ); - - // std::unordered_map triEdges; - - vec3_t worldBounds[2] = {}; materialSystem.buildOneShader = false; + vec3_t worldBounds[2] = {}; + vec3_t worldBoundsExtended[2] = {}; + ClearBounds( worldBounds[0], worldBounds[1] ); + ClearBounds( worldBoundsExtended[0], worldBoundsExtended[1] ); for ( int i = 0; i < numSurfaces; i++ ) { bspSurface_t* surface = rendererSurfaces[i]; @@ -476,20 +575,157 @@ std::vector OptimiseMapGeometryMaterial(bspSurface_t** renderer VectorCopy( ( ( srfGeneric_t* ) surface->data )->origin, srf.origin ); srf.radius = ( ( srfGeneric_t* ) surface->data )->radius; - BoundsAdd( worldBounds[0], worldBounds[1], - ( ( srfGeneric_t* ) surface->data )->bounds[0], ( ( srfGeneric_t* ) surface->data )->bounds[1] ); - materialSystem.GenerateMaterial( &srf ); - materialSurfaces.emplace_back( srf ); + static const float MAX_NORMAL_SURFACE_DISTANCE = 65536.0f * sqrtf( 2.0f ); + if ( VectorLength( ( ( srfGeneric_t* ) surface->data )->bounds[0] ) > MAX_NORMAL_SURFACE_DISTANCE + || VectorLength( ( ( srfGeneric_t* ) surface->data )->bounds[1] ) > MAX_NORMAL_SURFACE_DISTANCE ) { + BoundsAdd( worldBoundsExtended[0], worldBoundsExtended[1], + ( ( srfGeneric_t* ) surface->data )->bounds[0], ( ( srfGeneric_t* ) surface->data )->bounds[1] ); + + materialSurfacesExtended.emplace_back( srf ); + } else { + BoundsAdd( worldBounds[0], worldBounds[1], + ( ( srfGeneric_t* ) surface->data )->bounds[0], ( ( srfGeneric_t* ) surface->data )->bounds[1] ); + + materialSurfaces.emplace_back( srf ); + } } + vec3_t fullWorldBounds[2]; + VectorCopy( worldBounds[0], fullWorldBounds[0] ); + VectorCopy( worldBounds[1], fullWorldBounds[1] ); + BoundsAdd( fullWorldBounds[0], fullWorldBounds[1], worldBoundsExtended[0], worldBoundsExtended[1] ); + + materialSystem.SetWorldBounds( fullWorldBounds ); + materialSystem.buildOneShader = true; + /* GenerateWorldMaterialsBuffer() must be called before the surface merging loop, because it will compare the UBO offsets, + which are set by this call */ materialSystem.GenerateWorldMaterialsBuffer(); + + std::vector processedMaterialSurfaces; + processedMaterialSurfaces.reserve( numSurfaces ); + + std::function + materialSurfaceSort = []( const MaterialSurface& lhs, const MaterialSurface& rhs ) { + if ( lhs.stages < rhs.stages ) { + return true; + } else if ( lhs.stages > rhs.stages ) { + return false; + } + + for ( uint8_t stage = 0; stage < lhs.stages; stage++ ) { + if ( lhs.materialPackIDs[stage] < rhs.materialPackIDs[stage] ) { + return true; + } else if ( lhs.materialPackIDs[stage] > rhs.materialPackIDs[stage] ) { + return false; + } + + if ( lhs.materialIDs[stage] < rhs.materialIDs[stage] ) { + return true; + } else if ( lhs.materialIDs[stage] > rhs.materialIDs[stage] ) { + return false; + } + + shaderStage_t* pStage = lhs.shaderStages[stage]; + pStage = pStage->materialRemappedStage ? pStage->materialRemappedStage : pStage; + + const uint32_t surfaceMaterialID = + pStage->materialOffset + pStage->variantOffsets[lhs.shaderVariant[stage]]; + + pStage = rhs.shaderStages[stage]; + pStage = pStage->materialRemappedStage ? pStage->materialRemappedStage : pStage; + + const uint32_t surfaceMaterialID2 = + pStage->materialOffset + pStage->variantOffsets[rhs.shaderVariant[stage]]; + + if ( surfaceMaterialID < surfaceMaterialID2 ) { + return true; + } else if ( surfaceMaterialID > surfaceMaterialID2 ) { + return false; + } + + uint32_t texData = lhs.texDataDynamic[stage] + ? ( lhs.texDataIDs[stage] + materialSystem.GetTexDataSize() ) << TEX_BUNDLE_BITS + : lhs.texDataIDs[stage] << TEX_BUNDLE_BITS; + texData |= ( HasLightMap( &lhs ) ? GetLightMapNum( &lhs ) : 255 ) << LIGHTMAP_BITS; + + uint32_t texData2 = rhs.texDataDynamic[stage] + ? ( rhs.texDataIDs[stage] + materialSystem.GetTexDataSize() ) << TEX_BUNDLE_BITS + : rhs.texDataIDs[stage] << TEX_BUNDLE_BITS; + texData2 |= ( HasLightMap( &rhs ) ? GetLightMapNum( &rhs ) : 255 ) << LIGHTMAP_BITS; + + if ( texData < texData2 ) { + return true; + } else if ( texData > texData2 ) { + return false; + } + } + + return lhs.firstIndex < rhs.firstIndex; + }; + + glIndex_t* idxs = ( glIndex_t* ) ri.Hunk_AllocateTempMemory( numIndicesIn * sizeof( glIndex_t ) ); + uint32_t numIndices = 0; + + uint32_t gridSize[3] {}; + uint32_t cellSize[3]; + vec3_t worldSize; + VectorSubtract( worldBounds[1], worldBounds[0], worldSize ); + for ( int i = 0; i < 3; i++ ) { + if ( worldSize[i] < 32768 ) { + cellSize[i] = 1024; + } else { + cellSize[i] = 2048; + } + + gridSize[i] = ( worldSize[i] + cellSize[i] - 1 ) / cellSize[i]; + } + + Log::Debug( "BSP material surface grid cell size: %u %u %u", cellSize[0], cellSize[1], cellSize[2] ); + + std::sort( materialSurfaces.begin(), materialSurfaces.end(), materialSurfaceSort ); + + OptimiseMaterialSurfaces( materialSurfaces, gridSize, cellSize, world->nodes[0].mins, + materialSurfaceSort, + vertices, indices, idxs, processedMaterialSurfaces, numIndices ); + + /* On some maps surfaces go outside the 64k range, used for background and sky brushes. + We process them separately so we can use a larger cell size there, otherwise we'll run out of memory. + The regular surfaces can continue using samller grid sizes this way, + resulting in actually useable bounding spheres */ + if( materialSurfacesExtended.size() ) { + VectorSubtract( worldBoundsExtended[1], worldBoundsExtended[0], worldSize ); + for ( int i = 0; i < 3; i++ ) { + cellSize[i] = std::max( 2048.0f, worldSize[i] / 32.0f ); + + gridSize[i] = ( worldSize[i] + cellSize[i] - 1 ) / cellSize[i]; + } + + Log::Debug( "BSP material surface extended grid cell size: %u %u %u", cellSize[0], cellSize[1], cellSize[2] ); + + std::sort( materialSurfacesExtended.begin(), materialSurfacesExtended.end(), materialSurfaceSort ); + + OptimiseMaterialSurfaces( materialSurfacesExtended, gridSize, cellSize, world->nodes[0].mins, + materialSurfaceSort, + vertices, indices, idxs, processedMaterialSurfaces, numIndices ); + } + materialSystem.GeneratePortalBoundingSpheres(); - materialSystem.SetWorldBounds( worldBounds ); - materialSystem.GenerateWorldCommandBuffer( materialSurfaces ); + materialSystem.GenerateWorldCommandBuffer( processedMaterialSurfaces ); + + vertexAttributeSpec_t attrs[] { + { ATTR_INDEX_POSITION, GL_FLOAT, GL_FLOAT, &vertices[0].xyz, 3, sizeof( *vertices ), 0 }, + { ATTR_INDEX_COLOR, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, &vertices[0].lightColor, 4, sizeof( *vertices ), ATTR_OPTION_NORMALIZE }, + { ATTR_INDEX_QTANGENT, GL_SHORT, GL_SHORT, &vertices[0].qtangent, 4, sizeof( *vertices ), ATTR_OPTION_NORMALIZE }, + { ATTR_INDEX_TEXCOORD, GL_FLOAT, GL_HALF_FLOAT, &vertices[0].st, 4, sizeof( *vertices ), 0 }, + }; + + geometryCache.AddMapGeometry( numVerticesIn, numIndices, std::begin( attrs ), std::end( attrs ), idxs ); + + ri.Hunk_FreeTempMemory( idxs ); return materialSurfaces; } diff --git a/src/engine/renderer/GeometryOptimiser.h b/src/engine/renderer/GeometryOptimiser.h index 263d70af73..3a24a972d9 100644 --- a/src/engine/renderer/GeometryOptimiser.h +++ b/src/engine/renderer/GeometryOptimiser.h @@ -41,30 +41,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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 { @@ -97,6 +73,10 @@ struct MapVertEqual { } }; +struct SurfaceIndexes { + glIndex_t idxs[MAX_MATERIAL_SURFACE_INDEXES]; +}; + void MarkShaderBuildNONE( const shaderStage_t* ); void MarkShaderBuildNOP( const shaderStage_t* ); void MarkShaderBuildGeneric3D( const shaderStage_t* pStage ); @@ -120,6 +100,7 @@ void OptimiseMapGeometryCore( world_t* world, bspSurface_t** rendererSurfaces, i 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( bspSurface_t** rendererSurfaces, int numSurfaces ); +std::vector OptimiseMapGeometryMaterial( world_t* world, bspSurface_t** rendererSurfaces, int numSurfaces, + const srfVert_t* vertices, const int numVerticesIn, const glIndex_t* indices, const int numIndicesIn ); #endif // GEOMETRY_OPTIMISER_H diff --git a/src/engine/renderer/Material.cpp b/src/engine/renderer/Material.cpp index eb8488f4f1..92a29a4565 100644 --- a/src/engine/renderer/Material.cpp +++ b/src/engine/renderer/Material.cpp @@ -498,6 +498,17 @@ void MaterialSystem::GenerateWorldCommandBuffer( std::vector& s totalBatchCount = 0; + for ( MaterialSurface& surface : surfaces ) { + if ( surface.skyBrush ) { + continue; + } + + for ( uint8_t stage = 0; stage < surface.stages; stage++ ) { + Material* material = &materialPacks[surface.materialPackIDs[stage]].materials[surface.materialIDs[stage]]; + material->drawCommandCount++; + } + } + uint32_t batchOffset = 0; uint32_t globalID = 0; for ( MaterialPack& pack : materialPacks ) { @@ -514,6 +525,8 @@ void MaterialSystem::GenerateWorldCommandBuffer( std::vector& s batchOffset += batchCount; material.globalID = globalID; + material.drawCommandCount = 0; + totalBatchCount += batchCount; globalID++; } @@ -521,6 +534,8 @@ void MaterialSystem::GenerateWorldCommandBuffer( std::vector& s Log::Debug( "Total batch count: %u", totalBatchCount ); + totalDrawSurfs = surfaces.size(); + surfaceDescriptorsCount = totalDrawSurfs; descriptorSize = BOUNDING_SPHERE_SIZE + maxStages; surfaceDescriptorsSSBO.BufferData( surfaceDescriptorsCount * descriptorSize, nullptr, GL_STATIC_DRAW ); @@ -650,7 +665,7 @@ void MaterialSystem::GenerateWorldCommandBuffer( std::vector& s for ( uint8_t stage = 0; stage < surface.stages; stage++ ) { Material* material = &materialPacks[surface.materialPackIDs[stage]].materials[surface.materialIDs[stage]]; - uint32_t cmdID = material->surfaceCommandBatchOffset * SURFACE_COMMANDS_PER_BATCH + material->drawCommandCount2; + uint32_t cmdID = material->surfaceCommandBatchOffset * SURFACE_COMMANDS_PER_BATCH + material->drawCommandCount; // Add 1 because cmd 0 == no-command surfaceDescriptor.surfaceCommandIDs[stage] = cmdID + 1; @@ -670,7 +685,7 @@ void MaterialSystem::GenerateWorldCommandBuffer( std::vector& s surfaceCommand.drawCommand.baseInstance |= ( HasLightMap( &surface ) ? GetLightMapNum( &surface ) : 255 ) << LIGHTMAP_BITS; surfaceCommands[cmdID] = surfaceCommand; - material->drawCommandCount2++; + material->drawCommandCount++; } memcpy( surfaceDescriptors, &surfaceDescriptor, descriptorSize * sizeof( uint32_t ) ); @@ -685,7 +700,7 @@ void MaterialSystem::GenerateWorldCommandBuffer( std::vector& s for ( MaterialPack& pack : materialPacks ) { totalCount += pack.materials.size(); } - Log::Notice( "Generated %u BSP materials from %u BSP surfaces", totalCount, surfaces.size() ); + Log::Notice( "Generated %u BSP materials from %u BSP surfaces", totalCount, totalDrawSurfs ); Log::Notice( "Materials UBO: total: %.2f kb, dynamic: %.2f kb, texData: %.2f kb", totalStageSize * 4 / 1024.0f, dynamicStagesSize * 4 / 1024.0f, ( texData.size() + dynamicTexData.size() ) * TEX_BUNDLE_SIZE * 4 / 1024.0f ); @@ -1317,8 +1332,6 @@ void MaterialSystem::ProcessStage( MaterialSurface* surface, shaderStage_t* pSta packIDs[materialPack] = id; - materials[previousMaterialID].drawCommandCount++; - stage++; } @@ -1326,8 +1339,6 @@ void MaterialSystem::ProcessStage( MaterialSurface* surface, shaderStage_t* pSta A material represents a distinct global OpenGL state (e. g. blend function, depth test, depth write etc.) Materials can have a dependency on other materials to make sure that consecutive stages are rendered in the proper order */ void MaterialSystem::GenerateMaterial( MaterialSurface* surface ) { - totalDrawSurfs++; - uint32_t stage = 0; uint32_t previousMaterialID = 0; diff --git a/src/engine/renderer/Material.h b/src/engine/renderer/Material.h index 2fd085ada5..d11b3abb5b 100644 --- a/src/engine/renderer/Material.h +++ b/src/engine/renderer/Material.h @@ -150,7 +150,6 @@ struct Material { int fog = 0; uint32_t drawCommandCount = 0; - uint32_t drawCommandCount2 = 0; bool texturesResident = false; std::vector textures; @@ -382,6 +381,10 @@ class MaterialSystem { void InitGLBuffers(); void FreeGLBuffers(); + uint32_t GetTexDataSize() const { + return texData.size(); + } + void AddStageTextures( MaterialSurface* surface, shader_t* shader, shaderStage_t* pStage, const uint32_t stage, Material* material ); void AddStage( MaterialSurface* surface, shaderStage_t* pStage, uint32_t stage, const bool mayUseVertexOverbright, const bool vertexLit, const bool fullbright ); diff --git a/src/engine/renderer/tr_bsp.cpp b/src/engine/renderer/tr_bsp.cpp index 45ed91ed58..36f98d2c4e 100644 --- a/src/engine/renderer/tr_bsp.cpp +++ b/src/engine/renderer/tr_bsp.cpp @@ -2721,7 +2721,7 @@ static void R_CreateWorldVBO() { MergeDuplicateVertices( rendererSurfaces, numSurfaces, vboVerts, numVertsInitial, vboIdxs, 3 * numTriangles, numVerts, numIndices ); if ( glConfig2.usingMaterialSystem ) { - OptimiseMapGeometryMaterial( rendererSurfaces, numSurfaces ); + OptimiseMapGeometryMaterial( &s_worldData, rendererSurfaces, numSurfaces, vboVerts, numVerts, vboIdxs, numIndices ); } vertexAttributeSpec_t attrs[]{ @@ -2731,10 +2731,6 @@ static void R_CreateWorldVBO() { { ATTR_INDEX_TEXCOORD, GL_FLOAT, GL_HALF_FLOAT, &vboVerts[0].st, 4, sizeof( *vboVerts ), 0 }, }; - if ( glConfig2.usingGeometryCache ) { - geometryCache.AddMapGeometry( numVerts, numIndices, std::begin( attrs ), std::end( attrs ), vboIdxs ); - } - s_worldData.vbo = R_CreateStaticVBO( "staticWorld_VBO", std::begin( attrs ), std::end( attrs ), numVerts ); s_worldData.ibo = R_CreateStaticIBO2( "staticWorld_IBO", numTriangles, vboIdxs ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index 4107c049c1..31a1ce2927 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -2530,6 +2530,10 @@ enum class ssaoMode { grid = ( T* ) ri.Hunk_Alloc( size * sizeof( T ), ha_pref::h_low ); } + void Clear() { + memset( grid, 0, size * sizeof( T ) ); + } + struct Iterator { T* ptr; @@ -2545,6 +2549,10 @@ enum class ssaoMode { return ptr; } + ptrdiff_t operator-( const Iterator& other ) const { + return ptr - other.ptr; + } + Iterator& operator++() { ptr++; return *this;