mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-04-24 10:38:53 +00:00
Added faster Quad Tree for rectangle packing
This commit is contained in:
parent
d6a43278db
commit
f6fd593c72
7 changed files with 461 additions and 6 deletions
|
@ -335,7 +335,7 @@ public:
|
|||
#include "Swap.h"
|
||||
#include "Callback.h"
|
||||
#include "ParallelJobList.h"
|
||||
|
||||
#include "SoftwareCache.h"
|
||||
#include "TileMap.h" // RB
|
||||
|
||||
#endif /* !__LIB_H__ */
|
||||
|
|
|
@ -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
176
neo/idlib/TileMap.cpp
Normal 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 = ¤tNode;
|
||||
return;
|
||||
}
|
||||
|
||||
FindNode( currentNode, level );
|
||||
}
|
||||
}
|
||||
|
122
neo/idlib/TileMap.h
Normal file
122
neo/idlib/TileMap.h
Normal 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
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
|
Loading…
Reference in a new issue