Dynamically allocate and free lightmap tiles

This commit is contained in:
Magnus Norddahl 2025-03-23 23:41:38 +01:00
commit 737b7ece96
7 changed files with 132 additions and 121 deletions

View file

@ -5,6 +5,8 @@
LevelMesh::LevelMesh()
{
Lightmap.AtlasPacker = std::make_unique<RectPacker>(Lightmap.TextureSize, Lightmap.TextureSize, 0);
LevelMeshLimits limits;
limits.MaxVertices = 12;
limits.MaxIndexes = 3 * 4;
@ -151,39 +153,42 @@ LevelMeshTileStats LevelMesh::GatherTilePixelStats()
return stats;
}
void LevelMesh::PackStaticLightmapAtlas()
void LevelMesh::PackLightmapAtlas()
{
Lightmap.StaticAtlasPacked = true;
Lightmap.DynamicTilesStart = Lightmap.Tiles.Size();
for (auto& tile : Lightmap.Tiles)
{
if (tile.NeedsUpdate) // false for tiles loaded from lump
tile.SetupTileTransform(Lightmap.TextureSize);
}
// Get tiles that needs to be put into the atlas:
std::vector<LightmapTile*> sortedTiles;
sortedTiles.reserve(Lightmap.Tiles.Size());
for (auto& tile : Lightmap.Tiles)
sortedTiles.push_back(&tile);
sortedTiles.reserve(Lightmap.AddedTiles.Size());
for (int index : Lightmap.AddedTiles)
{
LightmapTile& tile = Lightmap.Tiles[index];
tile.AddedThisFrame = false;
if (tile.NeedsUpdate) // false for tiles loaded from lump
tile.SetupTileTransform(Lightmap.TextureSize);
sortedTiles.push_back(&tile);
}
// Sort them by size
std::sort(sortedTiles.begin(), sortedTiles.end(), [](LightmapTile* a, LightmapTile* b) { return a->AtlasLocation.Height != b->AtlasLocation.Height ? a->AtlasLocation.Height > b->AtlasLocation.Height : a->AtlasLocation.Width > b->AtlasLocation.Width; });
// We do not need to add spacing here as this is already built into the tile size itself.
RectPacker packer(Lightmap.TextureSize, Lightmap.TextureSize, 0);
// Find places in the atlas for the tiles
for (LightmapTile* tile : sortedTiles)
{
auto result = packer.Alloc(tile->AtlasLocation.Width, tile->AtlasLocation.Height);
auto result = Lightmap.AtlasPacker->Alloc(tile->AtlasLocation.Width, tile->AtlasLocation.Height);
tile->AtlasLocation.X = result->X;
tile->AtlasLocation.Y = result->Y;
tile->AtlasLocation.ArrayIndex = (int)result->PageIndex;
tile->AtlasLocation.Item = result;
}
Lightmap.TextureCount = (int)packer.GetNumPages();
Lightmap.TextureCount = (int)Lightmap.AtlasPacker->GetNumPages();
// Calculate final texture coordinates
for (int i = 0, count = Mesh.Surfaces.Size(); i < count; i++)
for (int i : Lightmap.AddedSurfaces)
{
auto surface = &Mesh.Surfaces[i];
if (surface->LightmapTileIndex >= 0)
@ -199,58 +204,9 @@ void LevelMesh::PackStaticLightmapAtlas()
}
}
}
}
void LevelMesh::ClearDynamicLightmapAtlas()
{
for (int surfIndex : Lightmap.DynamicSurfaces)
{
Mesh.Surfaces[surfIndex].LightmapTileIndex = -1;
}
Lightmap.DynamicSurfaces.Clear();
Lightmap.Tiles.Resize(Lightmap.DynamicTilesStart);
}
void LevelMesh::PackDynamicLightmapAtlas()
{
std::vector<LightmapTile*> sortedTiles;
sortedTiles.reserve(Lightmap.Tiles.Size() - Lightmap.DynamicTilesStart);
for (unsigned int i = Lightmap.DynamicTilesStart; i < Lightmap.Tiles.Size(); i++)
{
Lightmap.Tiles[i].SetupTileTransform(Lightmap.TextureSize);
sortedTiles.push_back(&Lightmap.Tiles[i]);
}
std::sort(sortedTiles.begin(), sortedTiles.end(), [](LightmapTile* a, LightmapTile* b) { return a->AtlasLocation.Height != b->AtlasLocation.Height ? a->AtlasLocation.Height > b->AtlasLocation.Height : a->AtlasLocation.Width > b->AtlasLocation.Width; });
// We do not need to add spacing here as this is already built into the tile size itself.
RectPacker packer(Lightmap.TextureSize, Lightmap.TextureSize, 0);
for (LightmapTile* tile : sortedTiles)
{
auto result = packer.Alloc(tile->AtlasLocation.Width, tile->AtlasLocation.Height);
tile->AtlasLocation.X = result->X;
tile->AtlasLocation.Y = result->Y;
tile->AtlasLocation.ArrayIndex = Lightmap.TextureCount; // (int)result->PageIndex;
}
for (int surfIndex : Lightmap.DynamicSurfaces)
{
auto surface = &Mesh.Surfaces[surfIndex];
if (surface->LightmapTileIndex >= 0)
{
const LightmapTile& tile = Lightmap.Tiles[surface->LightmapTileIndex];
for (int i = 0; i < surface->MeshLocation.NumVerts; i++)
{
auto& vertex = Mesh.Vertices[surface->MeshLocation.StartVertIndex + i];
FVector2 uv = tile.ToUV(vertex.fPos(), (float)Lightmap.TextureSize);
vertex.lu = uv.X;
vertex.lv = uv.Y;
vertex.lindex = (float)tile.AtlasLocation.ArrayIndex;
}
}
}
Lightmap.AddedTiles.Clear();
Lightmap.AddedSurfaces.Clear();
}
/////////////////////////////////////////////////////////////////////////////

View file

@ -144,11 +144,13 @@ public:
SurfaceAllocInfo AllocSurface();
LightAllocInfo AllocLight();
LightListAllocInfo AllocLightList(int count);
int AllocTile(const LightmapTile& tile);
void FreeGeometry(int vertexStart, int vertexCount, int indexStart, int indexCount);
void FreeUniforms(int start, int count);
void FreeSurface(unsigned int surfaceIndex);
void FreeLightList(int start, int count);
void FreeTile(int index);
// Sets the sizes of all the GPU buffers and empties the mesh
virtual void Reset(const LevelMeshLimits& limits);
@ -230,15 +232,15 @@ public:
TArray<uint16_t> TextureData;
uint16_t SampleDistance = 8;
TArray<LightmapTile> Tiles;
bool StaticAtlasPacked = false;
int DynamicTilesStart = 0;
TArray<int> DynamicSurfaces;
int UsedTiles = 0;
TArray<int> FreeTiles;
TArray<int> AddedTiles;
TArray<int> AddedSurfaces;
std::unique_ptr<RectPacker> AtlasPacker;
} Lightmap;
uint32_t AtlasPixelCount() const { return uint32_t(Lightmap.TextureCount * Lightmap.TextureSize * Lightmap.TextureSize); }
void PackStaticLightmapAtlas();
void ClearDynamicLightmapAtlas();
void PackDynamicLightmapAtlas();
void PackLightmapAtlas();
void AddEmptyMesh();
@ -320,6 +322,21 @@ inline SurfaceAllocInfo LevelMesh::AllocSurface()
return info;
}
inline int LevelMesh::AllocTile(const LightmapTile& tile)
{
Lightmap.UsedTiles++;
if (Lightmap.FreeTiles.Size() != 0)
{
int index = Lightmap.FreeTiles.Last();
Lightmap.FreeTiles.Pop();
Lightmap.Tiles[index] = tile;
return index;
}
int index = Lightmap.Tiles.Size();
Lightmap.Tiles.Push(tile);
return index;
}
inline void LevelMesh::FreeGeometry(int vertexStart, int vertexCount, int indexStart, int indexCount)
{
// Convert triangles to degenerates
@ -346,6 +363,12 @@ inline void LevelMesh::FreeSurface(unsigned int surfaceIndex)
FreeLists.Surface.Free(surfaceIndex, 1);
}
inline void LevelMesh::FreeTile(int index)
{
Lightmap.UsedTiles--;
Lightmap.FreeTiles.Push(index);
}
inline void LevelMesh::UploadPortals()
{
UploadRanges.Portals.Clear();

View file

@ -8,6 +8,8 @@
#define LIGHTMAP_GLOBAL_SAMPLE_DISTANCE_MIN (int)4
#define LIGHTMAP_GLOBAL_SAMPLE_DISTANCE_MAX (int)64
class RectPackerItem;
struct LevelMeshSurface;
struct LightmapTileBinding
@ -34,6 +36,7 @@ struct LightmapTile
int Width = 0;
int Height = 0;
int ArrayIndex = 0;
RectPackerItem* Item = nullptr;
uint32_t Area() const { return Width * Height; }
} AtlasLocation;
@ -45,6 +48,9 @@ struct LightmapTile
FVector3 ProjLocalToV = { 0.0f, 0.0f, 0.0f };
} Transform;
int UseCount = 0;
bool AddedThisFrame = false;
LightmapTileBinding Binding;
BBox Bounds;

View file

@ -473,7 +473,10 @@ void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCoun
return;
Lightmap.Size = newLMTextureSize;
Lightmap.Count = newLMTextureCount + 1; // the extra texture is for the dynamic lightmap
// Always create minimum 16 textures with 4 additional than the initial watermark
// To do: add support for expanding the texture dynamically instead
Lightmap.Count = std::max(newLMTextureCount, 12) + 4;
int w = newLMTextureSize;
int h = newLMTextureSize;

View file

@ -3026,7 +3026,7 @@ void MapLoader::InitLevelMesh(MapData* map)
{
if (Level->lightmaps)
{
Level->levelMesh->PackStaticLightmapAtlas();
Level->levelMesh->PackLightmapAtlas();
}
}
}
@ -3184,7 +3184,7 @@ bool MapLoader::LoadLightmap(MapData* map)
}
// Place all tiles in atlas textures
Level->levelMesh->PackStaticLightmapAtlas();
Level->levelMesh->PackLightmapAtlas();
// Start with empty lightmap textures
Level->levelMesh->Lightmap.TextureData.Resize(Level->levelMesh->Lightmap.TextureCount * textureSize * textureSize * 3);

View file

@ -83,7 +83,9 @@ ADD_STAT(lightmap)
"Atlas efficiency: %.4f%%\n"
"Dynamic BLAS time: %2.3f ms\n"
"Level mesh process time: %2.3f ms\n"
"Level mesh index buffer: %d K used (%d%%)",
"Level mesh index buffer: %d K used (%d%%)\n"
"Lightmap tiles in use: %d\n"
"Lightmap texture count: %d",
stats.tiles.total, stats.tiles.dirty, stats.tiles.dirtyDynamic,
stats.pixels.dirty, stats.pixels.dirtyDynamic,
stats.pixels.total,
@ -92,7 +94,9 @@ ADD_STAT(lightmap)
DynamicBLASTime.TimeMS(),
ProcessLevelMesh.TimeMS(),
indexBufferUsed / 1000,
indexBufferUsed * 100 / indexBufferTotal);
indexBufferUsed * 100 / indexBufferTotal,
levelMesh->Lightmap.UsedTiles,
levelMesh->Lightmap.TextureCount);
return out;
}
@ -293,7 +297,8 @@ void DoomLevelMesh::BeginFrame(FLevelLocals& doomMap)
r_viewpoint.extralight = 0;
r_viewpoint.camera = nullptr;
ClearDynamicLightmapAtlas();
// If something changes we put it into new tiles.
TileBindings.clear();
for (side_t* side : PolySides)
{
@ -328,7 +333,7 @@ void DoomLevelMesh::BeginFrame(FLevelLocals& doomMap)
}
FlatUpdateList.Clear();
PackDynamicLightmapAtlas();
PackLightmapAtlas();
UpdateWallPortals();
UploadDynLights(doomMap);
@ -495,7 +500,6 @@ bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float di
void DoomLevelMesh::CreateSurfaces(FLevelLocals& doomMap)
{
bindings.clear();
Sides.clear();
Flats.clear();
Sides.resize(doomMap.sides.size());
@ -525,11 +529,37 @@ void DoomLevelMesh::CreateSurfaces(FLevelLocals& doomMap)
}
}
void DoomLevelMesh::ReleaseTiles(int surf)
{
while (surf != -1)
{
int& tileIndex = Mesh.Surfaces[surf].LightmapTileIndex;
if (tileIndex != -1)
{
LightmapTile& tile = Lightmap.Tiles[tileIndex];
tile.UseCount--;
if (tile.UseCount == 0) // Nothing is referencing this tile anymore. Release it back to the atlas packer.
{
if (tile.AtlasLocation.Item)
{
Lightmap.AtlasPacker->Free(tile.AtlasLocation.Item);
tile.AtlasLocation.Item = nullptr;
}
FreeTile(tileIndex);
}
tileIndex = -1;
}
surf = DoomSurfaceInfos[surf].NextSurface;
}
}
void DoomLevelMesh::FreeSide(FLevelLocals& doomMap, unsigned int sideIndex)
{
if (sideIndex < 0 || sideIndex >= Sides.Size())
return;
ReleaseTiles(Sides[sideIndex].FirstSurface);
int surf = Sides[sideIndex].FirstSurface;
while (surf != -1)
{
@ -562,6 +592,8 @@ void DoomLevelMesh::FreeFlat(FLevelLocals& doomMap, unsigned int sectorIndex)
if (sectorIndex < 0 || sectorIndex >= Flats.Size())
return;
ReleaseTiles(Flats[sectorIndex].FirstSurface);
int surf = Flats[sectorIndex].FirstSurface;
while (surf != -1)
{
@ -1059,27 +1091,14 @@ void DoomLevelMesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, Mesh
sinfo.Surface->LightList.Pos = lightlist.Start;
sinfo.Surface->LightList.Count = lightlist.Count;
if (side->Flags & WALLF_POLYOBJ) // Polyobjects gets a new tile every frame
if (disp.Level->lightmaps && !sinfo.Surface->IsSky)
{
LightmapTileBinding binding;
binding.Type = info.Type;
binding.TypeIndex = info.TypeIndex;
binding.ControlSector = info.ControlSector ? info.ControlSector->Index() : (int)0xffffffffUL;
LightmapTile tile;
tile.Binding = binding;
tile.Bounds = sinfo.Surface->Bounds;
tile.Plane = sinfo.Surface->Plane;
tile.SampleDimension = GetSampleDimension(sampleDimension);
tile.AlwaysUpdate = 2; // Ignore lm_dynamic
sinfo.Surface->LightmapTileIndex = Lightmap.Tiles.Size();
Lightmap.Tiles.Push(tile);
Lightmap.DynamicSurfaces.Push(sinfo.Index);
sinfo.Surface->LightmapTileIndex = AddSurfaceToTile(info, *sinfo.Surface, sampleDimension, !!(side->sector->Flags & SECF_LM_DYNAMIC));
Lightmap.AddedSurfaces.Push(sinfo.Index);
}
else
{
sinfo.Surface->LightmapTileIndex = disp.Level->lightmaps ? AddSurfaceToTile(info, *sinfo.Surface, sampleDimension, !!(side->sector->Flags & SECF_LM_DYNAMIC)) : -1;
sinfo.Surface->LightmapTileIndex = -1;
}
SetSideLightmap(sinfo.Index);
@ -1171,49 +1190,41 @@ void DoomLevelMesh::SortDrawLists()
int DoomLevelMesh::AddSurfaceToTile(const DoomSurfaceInfo& info, const LevelMeshSurface& surf, uint16_t sampleDimension, uint8_t alwaysUpdate)
{
if (surf.IsSky)
return -1;
LightmapTileBinding binding;
binding.Type = info.Type;
binding.TypeIndex = info.TypeIndex;
binding.ControlSector = info.ControlSector ? info.ControlSector->Index() : (int)0xffffffffUL;
auto it = bindings.find(binding);
if (it != bindings.end())
auto it = TileBindings.find(binding);
if (it != TileBindings.end())
{
int index = it->second;
if (!Lightmap.StaticAtlasPacked)
{
LightmapTile& tile = Lightmap.Tiles[index];
tile.Bounds.min.X = std::min(tile.Bounds.min.X, surf.Bounds.min.X);
tile.Bounds.min.Y = std::min(tile.Bounds.min.Y, surf.Bounds.min.Y);
tile.Bounds.min.Z = std::min(tile.Bounds.min.Z, surf.Bounds.min.Z);
tile.Bounds.max.X = std::max(tile.Bounds.max.X, surf.Bounds.max.X);
tile.Bounds.max.Y = std::max(tile.Bounds.max.Y, surf.Bounds.max.Y);
tile.Bounds.max.Z = std::max(tile.Bounds.max.Z, surf.Bounds.max.Z);
tile.AlwaysUpdate = max<uint8_t>(tile.AlwaysUpdate, alwaysUpdate);
}
LightmapTile& tile = Lightmap.Tiles[index];
tile.Bounds.min.X = std::min(tile.Bounds.min.X, surf.Bounds.min.X);
tile.Bounds.min.Y = std::min(tile.Bounds.min.Y, surf.Bounds.min.Y);
tile.Bounds.min.Z = std::min(tile.Bounds.min.Z, surf.Bounds.min.Z);
tile.Bounds.max.X = std::max(tile.Bounds.max.X, surf.Bounds.max.X);
tile.Bounds.max.Y = std::max(tile.Bounds.max.Y, surf.Bounds.max.Y);
tile.Bounds.max.Z = std::max(tile.Bounds.max.Z, surf.Bounds.max.Z);
tile.AlwaysUpdate = max<uint8_t>(tile.AlwaysUpdate, alwaysUpdate);
tile.UseCount++;
return index;
}
else
{
if (Lightmap.StaticAtlasPacked)
return -1;
int index = Lightmap.Tiles.Size();
LightmapTile tile;
tile.Binding = binding;
tile.Bounds = surf.Bounds;
tile.Plane = surf.Plane;
tile.SampleDimension = GetSampleDimension(sampleDimension);
tile.AlwaysUpdate = alwaysUpdate;
tile.UseCount = 1;
Lightmap.Tiles.Push(tile);
bindings[binding] = index;
int index = AllocTile(tile);
Lightmap.AddedTiles.Push(index);
TileBindings[binding] = index;
return index;
}
}
@ -1414,7 +1425,17 @@ void DoomLevelMesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state
surf.MeshLocation.NumVerts = sub->numlines;
surf.MeshLocation.NumElements = (sub->numlines - 2) * 3;
surf.Bounds = GetBoundsFromSurface(surf);
surf.LightmapTileIndex = disp.Level->lightmaps ? AddSurfaceToTile(info, surf, sampleDimension, !!(flatpart.sector->Flags & SECF_LM_DYNAMIC)) : -1;
if (disp.Level->lightmaps && !surf.IsSky)
{
surf.LightmapTileIndex = AddSurfaceToTile(info, surf, sampleDimension, !!(flatpart.sector->Flags & SECF_LM_DYNAMIC));
Lightmap.AddedSurfaces.Push(sinfo.Index);
}
else
{
surf.LightmapTileIndex = -1;
}
surf.LightList.Pos = lightlist.Start;
surf.LightList.Count = lightlist.Count;

View file

@ -186,6 +186,8 @@ private:
void UploadDynLights(FLevelLocals& doomMap);
void ReleaseTiles(int surfaceIndex);
TArray<DoomSurfaceInfo> DoomSurfaceInfos;
TArray<SideSurfaceBlock> Sides;
@ -195,6 +197,6 @@ private:
TArray<int> SideUpdateList;
TArray<int> FlatUpdateList;
std::map<LightmapTileBinding, int> bindings;
std::map<LightmapTileBinding, int> TileBindings;
MeshBuilder state;
};