diff --git a/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp b/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp index 0a5219cff..38fad93f9 100644 --- a/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp +++ b/src/common/rendering/hwrenderer/data/hw_levelmesh.cpp @@ -5,6 +5,8 @@ LevelMesh::LevelMesh() { + Lightmap.AtlasPacker = std::make_unique(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 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 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(); } ///////////////////////////////////////////////////////////////////////////// diff --git a/src/common/rendering/hwrenderer/data/hw_levelmesh.h b/src/common/rendering/hwrenderer/data/hw_levelmesh.h index 33a903ecc..bbb213695 100644 --- a/src/common/rendering/hwrenderer/data/hw_levelmesh.h +++ b/src/common/rendering/hwrenderer/data/hw_levelmesh.h @@ -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 TextureData; uint16_t SampleDistance = 8; TArray Tiles; - bool StaticAtlasPacked = false; - int DynamicTilesStart = 0; - TArray DynamicSurfaces; + int UsedTiles = 0; + TArray FreeTiles; + TArray AddedTiles; + TArray AddedSurfaces; + std::unique_ptr 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(); diff --git a/src/common/rendering/hwrenderer/data/hw_lightmaptile.h b/src/common/rendering/hwrenderer/data/hw_lightmaptile.h index afeec2e26..330bb5686 100644 --- a/src/common/rendering/hwrenderer/data/hw_lightmaptile.h +++ b/src/common/rendering/hwrenderer/data/hw_lightmaptile.h @@ -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; diff --git a/src/common/rendering/vulkan/textures/vk_texture.cpp b/src/common/rendering/vulkan/textures/vk_texture.cpp index d06770855..5fe88603e 100644 --- a/src/common/rendering/vulkan/textures/vk_texture.cpp +++ b/src/common/rendering/vulkan/textures/vk_texture.cpp @@ -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; diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index 52d734dd5..c33aa21a2 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -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); diff --git a/src/rendering/hwrenderer/doom_levelmesh.cpp b/src/rendering/hwrenderer/doom_levelmesh.cpp index 1323edc31..f8490f2d2 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.cpp +++ b/src/rendering/hwrenderer/doom_levelmesh.cpp @@ -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(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(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; diff --git a/src/rendering/hwrenderer/doom_levelmesh.h b/src/rendering/hwrenderer/doom_levelmesh.h index c78f64757..cb1c37cb6 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.h +++ b/src/rendering/hwrenderer/doom_levelmesh.h @@ -186,6 +186,8 @@ private: void UploadDynLights(FLevelLocals& doomMap); + void ReleaseTiles(int surfaceIndex); + TArray DoomSurfaceInfos; TArray Sides; @@ -195,6 +197,6 @@ private: TArray SideUpdateList; TArray FlatUpdateList; - std::map bindings; + std::map TileBindings; MeshBuilder state; };