Added faster Quad Tree for rectangle packing

This commit is contained in:
Robert Beckebans 2022-04-01 17:10:53 +02:00
parent d6a43278db
commit f6fd593c72
7 changed files with 461 additions and 6 deletions

View file

@ -335,7 +335,7 @@ public:
#include "Swap.h"
#include "Callback.h"
#include "ParallelJobList.h"
#include "SoftwareCache.h"
#include "TileMap.h" // RB
#endif /* !__LIB_H__ */

View file

@ -3,6 +3,7 @@
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2022 Robert Beckebans
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
@ -28,7 +29,7 @@ If you have questions concerning this license or the applicable additional terms
#include "precompiled.h"
#pragma hdrstop
#include "TileMap.h"
#include "../libs/binpack2d/binpack2d.h"
@ -196,6 +197,86 @@ void RectAllocator( const idList<idVec2i>& inputSizes, idList<idVec2i>& outputPo
}
}
// Maximum resolution of one tile within tiled shadow map. Resolution must be power of two and
// square, since quad-tree for managing tiles will not work correctly otherwise. Furthermore
// resolution must be at least 16.
//#define MAX_TILE_RES 512
// Specifies how many levels the quad-tree for managing tiles within tiled shadow map should
// have. The higher the value, the smaller the resolution of the smallest used tile will be.
// In the current configuration of 8192 resolution and 8 levels, the smallest tile will have
// a resolution of 64. 16 is the smallest allowed value for the min tile resolution.
//#define NUM_QUAD_TREE_LEVELS 8
void RectAllocatorQuadTree( const idList<idVec2i>& inputSizes, idList<idVec2i>& outputPositions, idVec2i& totalSize, const int TILED_SM_RES, const int MAX_TILE_RES = 512, const int NUM_QUAD_TREE_LEVELS = 8 )
{
outputPositions.SetNum( inputSizes.Num() );
if( inputSizes.Num() == 0 )
{
totalSize.Set( 0, 0 );
return;
}
idList<int> sizeRemap;
sizeRemap.SetNum( inputSizes.Num() );
for( int i = 0; i < inputSizes.Num(); i++ )
{
sizeRemap[i] = i;
}
// Sort the rects from largest to smallest (it makes allocating them in the image better)
idSortrects sortrectsBySize;
sortrectsBySize.inputSizes = &inputSizes;
sizeRemap.SortWithTemplate( sortrectsBySize );
// quad-tree for managing tiles within tiled shadow map
TileMap tileMap;
totalSize.x = 0;
totalSize.y = 0;
// initialize tile-map that will manage tiles within tiled shadow map
if( !tileMap.Init( TILED_SM_RES, MAX_TILE_RES, NUM_QUAD_TREE_LEVELS ) )
{
return;
}
for( int i = 0; i < inputSizes.Num(); i++ )
{
idVec2i size = inputSizes[sizeRemap[i]];
int area = Max( size.x, size.y );
Tile tile;
bool result = tileMap.GetTile( area, tile );
if( !result )
{
outputPositions[sizeRemap[i]].Set( -1, -1 );
}
else
{
// convert from [-1..-1] -> [0..1] and flip y
idVec2 uvPos;
uvPos.x = tile.position.x * 0.5f + 0.5f;
uvPos.y = 1.0f - ( tile.position.y * 0.5f + 0.5f );
outputPositions[sizeRemap[i]].x = uvPos.x * TILED_SM_RES;
outputPositions[sizeRemap[i]].y = uvPos.y * TILED_SM_RES;
if( ( tile.position.x + tile.size ) > totalSize.x )
{
totalSize.x = tile.position.x + tile.size;
}
if( ( tile.position.y + tile.size ) > totalSize.y )
{
totalSize.y = tile.position.y + tile.size;
}
}
}
}
// RB
class MyContent
{

176
neo/idlib/TileMap.cpp Normal file
View file

@ -0,0 +1,176 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 2014 Hawar Doghramachi
Copyright (C) 2022 Robert Beckebans (id Tech 4x integration)
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition 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 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#include "TileMap.h"
static unsigned int GetLog2( float x )
{
return ( unsigned int )( ceil( log( x ) / log( 2.0f ) ) );
}
void TileMap::Release()
{
//tileNodeList.DeleteContents();
}
bool TileMap::Init( unsigned int mapSize, unsigned int maxAbsTileSize, unsigned int numLevels )
{
if( ( !idMath::IsPowerOfTwo( mapSize ) ) || ( numLevels < 1 ) || ( maxAbsTileSize > mapSize ) || ( maxAbsTileSize < 16.0f ) )
{
return false;
}
this->mapSize = ( float )mapSize;
log2MapSize = GetLog2( this->mapSize );
this->maxAbsTileSize = ( float )maxAbsTileSize;
this->numLevels = numLevels;
minAbsTileSize = this->mapSize;
for( unsigned int i = 0; i < ( numLevels - 1 ); i++ )
{
minAbsTileSize *= 0.5f;
}
if( ( minAbsTileSize < 16.0f ) || ( minAbsTileSize > maxAbsTileSize ) )
{
return false;
}
numNodes = 1;
unsigned int multiplier = 1;
for( unsigned int i = 1; i < numLevels; i++ )
{
multiplier *= 4;
numNodes += multiplier;
}
tileNodeList.SetNum( numNodes );
TileNode& rootNode = tileNodeList[nodeIndex];
rootNode.position.x = 0;
rootNode.position.y = 0;
rootNode.level = 0;
rootNode.minLevel = 0;
BuildTree( rootNode, 0 );
return true;
}
void TileMap::BuildTree( TileNode& parentNode, unsigned int level )
{
level++;
if( level == numLevels )
{
return;
}
for( unsigned int i = 0; i < 4; i++ )
{
parentNode.childIndices[i] = ++nodeIndex;
assert( nodeIndex < numNodes );
TileNode& currentNode = tileNodeList[parentNode.childIndices[i]];
unsigned int denominator = 1 << level;
const float size = 1.0f / ( ( float )denominator );
idVec2 offsets[4] = { idVec2( -size, size ), idVec2( -size, -size ), idVec2( size, -size ), idVec2( size, size ) };
//idVec2 offsets[4] = { idVec2( 0, size * 2 ), idVec2( 0, -0 ), idVec2( size * 2, 0 ), idVec2( size * 2, size * 2 ) };
currentNode.position = parentNode.position + offsets[i];
currentNode.level = level;
currentNode.minLevel = 0;
BuildTree( currentNode, level );
}
}
void TileMap::Clear()
{
for( unsigned int i = 0; i < numNodes; i++ )
{
tileNodeList[i].minLevel = 0;
}
}
bool TileMap::GetTile( float size, Tile& tile )
{
size = idMath::ClampInt( minAbsTileSize, maxAbsTileSize, size );
unsigned int requiredLevel = log2MapSize - GetLog2( size );
foundNode = NULL;
TileNode& rootNode = tileNodeList[0];
FindNode( rootNode, requiredLevel );
if( !foundNode )
{
return false;
}
tile.position.x = foundNode->position.x;
tile.position.y = foundNode->position.y;
tile.size = size / mapSize;
return true;
}
void TileMap::FindNode( TileNode& parentNode, unsigned int level )
{
if( foundNode )
{
return;
}
for( unsigned int i = 0; i < 4; i++ )
{
if( foundNode )
{
return;
}
int childIndex = parentNode.childIndices[i];
if( childIndex < 0 )
{
return;
}
TileNode& currentNode = tileNodeList[childIndex];
if( level < currentNode.minLevel )
{
continue;
}
if( level == currentNode.level )
{
parentNode.minLevel = level;
currentNode.minLevel = numLevels;
foundNode = &currentNode;
return;
}
FindNode( currentNode, level );
}
}

122
neo/idlib/TileMap.h Normal file
View file

@ -0,0 +1,122 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 2014 Hawar Doghramachi
Copyright (C) 2022 Robert Beckebans (id Tech 4x integration)
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition 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 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef TILE_MAP_H
#define TILE_MAP_H
// Tile specifies the position and size within a texture atlas.
struct Tile
{
Tile():
size( 0.0f )
{
}
idVec2 position;
float size;
};
// TileNode of a quad-tree that efficiently packs all tiles in a limited area.
struct TileNode
{
TileNode():
level( 0 ),
minLevel( 0 )
{
for( unsigned int i = 0; i < 4; i++ )
{
childIndices[i] = -1;
}
}
idVec2 position;
int childIndices[4];
unsigned int level;
unsigned int minLevel;
};
// TileMap
//
// Quad-tree that manages tiles in a power of two/ squared texture atlas. At initialization the quad-tree is build so that
// all nodes already have the information of the position for the corresponding tile. All nodes are kept in a cache-friendly
// manner in one linear list, which makes clearing the quad-tree very fast. Therefore instead of pointer indirections, indices
// into the underlying list are used.
// At runtime each relevant light will request per frame a tile with a size that corresponds to the screen-space light-area of
// the light. Thereby the size is clamped between a min/ max resolution. To determine the level of the requested tile first the
// next power of two size is determined which is larger than the requested size. However, instead of using the power of two size
// of the determined tile, the actual incoming dynamically changing size is used. In this way unpleasant popping of shadows can
// be avoided, which would occur otherwise when discrete power of two steps would be used.
// Since this operation is working with a O(n) complexity, the quad-tree is held on software-side, which is faster than keeping
// the quad-tree on the GPU.
class TileMap
{
public:
TileMap():
mapSize( 0.0f ),
log2MapSize( 0 ),
minAbsTileSize( 0.0f ),
maxAbsTileSize( 0.0f ),
numLevels( 0 ),
numNodes( 0 ),
nodeIndex( 0 ),
foundNode( NULL )
{
}
~TileMap()
{
Release();
}
void Release();
bool Init( unsigned int mapSize, unsigned int maxAbsTileSize, unsigned int numLevels );
void Clear();
bool GetTile( float size, Tile& tile );
private:
void BuildTree( TileNode& parentNode, unsigned int level );
void FindNode( TileNode& parentNode, unsigned int level );
float mapSize;
unsigned int log2MapSize;
float minAbsTileSize;
float maxAbsTileSize;
unsigned int numLevels;
idList<TileNode> tileNodeList;
unsigned int numNodes;
unsigned int nodeIndex;
TileNode* foundNode;
};
#endif

View file

@ -132,6 +132,19 @@ void idRenderBackend::Init()
hiZGenPass = nullptr;
ssaoPass = nullptr;
// Maximum resolution of one tile within tiled shadow map. Resolution must be power of two and
// square, since quad-tree for managing tiles will not work correctly otherwise. Furthermore
// resolution must be at least 16.
const int MAX_TILE_RES = 512;
// Specifies how many levels the quad-tree for managing tiles within tiled shadow map should
// have. The higher the value, the smaller the resolution of the smallest used tile will be.
// In the current configuration of 8192 resolution and 8 levels, the smallest tile will have
// a resolution of 64. 16 is the smallest allowed value for the min tile resolution.
const int NUM_QUAD_TREE_LEVELS = 8;
tileMap.Init( r_shadowMapAtlasSize.GetInteger(), MAX_TILE_RES, NUM_QUAD_TREE_LEVELS );
tr.SetInitialized();
if( !commandList )

View file

@ -3828,7 +3828,25 @@ void idRenderBackend::ShadowMapPassOld( const drawSurf_t* drawSurfs, viewLight_t
renderLog.CloseBlock();
}
void RectAllocatorBinPack2D( const idList<idVec2i>& inputSizes, const idStrList& inputNames, idList<idVec2i>& outputPositions, idVec2i& totalSize, const int START_MAX );
//void RectAllocatorBinPack2D( const idList<idVec2i>& inputSizes, const idStrList& inputNames, idList<idVec2i>& outputPositions, idVec2i& totalSize, const int START_MAX );
void RectAllocatorQuadTree( const idList<idVec2i>& inputSizes, idList<idVec2i>& outputPositions, idVec2i& totalSize, const int TILED_SM_RES, const int MAX_TILE_RES, const int NUM_QUAD_TREE_LEVELS );
class idSortrects : public idSort_Quick< int, idSortrects >
{
public:
int SizeMetric( idVec2i v ) const
{
// skinny rects will sort earlier than square ones, because
// they are more likely to grow the entire region
return v.x * v.x + v.y * v.y;
}
int Compare( const int& a, const int& b ) const
{
return SizeMetric( ( *inputSizes )[b] ) - SizeMetric( ( *inputSizes )[a] );
}
const idList<idVec2i>* inputSizes;
};
void idRenderBackend::ShadowAtlasPass( const viewDef_t* _viewDef )
{
@ -3919,9 +3937,52 @@ void idRenderBackend::ShadowAtlasPass( const viewDef_t* _viewDef )
}
idList<idVec2i> outputPositions;
idVec2i totalSize;
//idVec2i totalSize;
RectAllocatorBinPack2D( inputSizes, inputNames, outputPositions, totalSize, r_shadowMapAtlasSize.GetInteger() );
//RectAllocatorQuadTree( inputSizes, outputPositions, totalSize, r_shadowMapAtlasSize.GetInteger(), 1024, 8 );
// RB: we don't use RectAllocatorQuadTree here because we don't want to rebuild the quad tree every frame
outputPositions.SetNum( inputSizes.Num() );
idList<int> sizeRemap;
sizeRemap.SetNum( inputSizes.Num() );
for( int i = 0; i < inputSizes.Num(); i++ )
{
sizeRemap[i] = i;
}
// Sort the rects from largest to smallest (it makes allocating them in the image better)
idSortrects sortrectsBySize;
sortrectsBySize.inputSizes = &inputSizes;
sizeRemap.SortWithTemplate( sortrectsBySize );
tileMap.Clear();
for( int i = 0; i < inputSizes.Num(); i++ )
{
idVec2i size = inputSizes[sizeRemap[i]];
int area = Max( size.x, size.y );
Tile tile;
bool result = tileMap.GetTile( area, tile );
if( !result )
{
outputPositions[sizeRemap[i]].Set( -1, -1 );
}
else
{
// convert from [-1..-1] -> [0..1] and flip y
idVec2 uvPos;
uvPos.x = tile.position.x * 0.5f + 0.5f;
uvPos.y = 1.0f - ( tile.position.y * 0.5f + 0.5f );
outputPositions[sizeRemap[i]].x = uvPos.x * r_shadowMapAtlasSize.GetInteger();
outputPositions[sizeRemap[i]].y = uvPos.y * r_shadowMapAtlasSize.GetInteger();
}
}
//
// for each light, perform shadowing to a big atlas Framebuffer

View file

@ -501,7 +501,9 @@ private:
float hdrMaxLuminance;
float hdrTime;
float hdrKey;
// RB end
// quad-tree for managing tiles within tiled shadow map
TileMap tileMap;
private:
#if defined( USE_NVRHI )