From 81f7b083c074d8e8cd1711ae532104ca4b6d3c95 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Fri, 15 Oct 2021 04:21:18 +0200 Subject: [PATCH] Improve light probe lookup speed for large sectors by using a grid instead of subsectors --- src/g_levellocals.h | 6 ++ src/gamedata/r_defs.h | 8 +- src/maploader/maploader.cpp | 82 ++++++++++++++----- .../hwrenderer/scene/hw_spritelight.cpp | 35 +++++--- 4 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/g_levellocals.h b/src/g_levellocals.h index b3aaea6c1..70c5de51d 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -457,6 +457,12 @@ public: int LMTextureSize = 0; TArray LMTextureData; TArray LightProbes; + int LPMinX = 0; + int LPMinY = 0; + int LPWidth = 0; + int LPHeight = 0; + static const int LPCellSize = 32; + TArray LPCells; // Portal information. FDisplacementTable Displacements; diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index 5a5579fdb..44a0afd83 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -1622,8 +1622,6 @@ struct subsector_t FPortalCoverage portalcoverage[2]; LightmapSurface *lightmap[2]; - LightProbe* firstprobe; - uint32_t numprobes; }; @@ -1696,6 +1694,12 @@ struct LightProbe float Red, Green, Blue; }; +struct LightProbeCell +{ + LightProbe* FirstProbe = nullptr; + int NumProbes = 0; +}; + // // OTHER TYPES // diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index 1daa818e5..8b386220f 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -3326,6 +3326,19 @@ void MapLoader::SetSideLightmap(const LightmapSurface &surface) void MapLoader::LoadLightmap(MapData *map) { + // We have to reset everything as FLevelLocals is recycled between maps + Level->LightProbes.Reset(); + Level->LPCells.Reset(); + Level->LMTexCoords.Reset(); + Level->LMSurfaces.Reset(); + Level->LMTextureData.Reset(); + Level->LMTextureCount = 0; + Level->LMTextureSize = 0; + Level->LPMinX = 0; + Level->LPMinY = 0; + Level->LPWidth = 0; + Level->LPHeight = 0; + if (!map->Size(ML_LIGHTMAP)) return; @@ -3351,36 +3364,67 @@ void MapLoader::LoadLightmap(MapData *map) if (numSurfaces == 0 || numTexCoords == 0 || numTexBytes == 0) return; - if (numSubsectors != Level->subsectors.Size()) + /*if (numSubsectors != Level->subsectors.Size()) { - Printf(PRINT_HIGH, "LoadLightmap: subsector count for level doesn't match\n"); - return; - } + Printf(PRINT_HIGH, "LoadLightmap: subsector count for level doesn't match (%d in wad vs %d in engine)\n", (int)numSubsectors, (int)Level->subsectors.Size()); + }*/ if (numLightProbes > 0) { Level->LightProbes.Resize(numLightProbes); fr.Read(&Level->LightProbes[0], sizeof(LightProbe) * numLightProbes); - } - if (Level->subsectors.Size() > 0) - { - TArray counts; - counts.Resize(Level->subsectors.Size()); - fr.Read(&counts[0], sizeof(uint32_t) * Level->subsectors.Size()); + // Sort the light probes so that they are ordered by cell. + // This lets us point at the first probe knowing all other probes in the cell will follow. + // Also improves locality. - unsigned int startIndex = 0; - for (unsigned int i = 0; i < Level->subsectors.Size(); i++) + double rcpCellSize = 1.0 / Level->LPCellSize; + auto cellCompareLess = [=](const LightProbe& a, const LightProbe& b) { - unsigned int count = counts[i]; - if (startIndex + count > Level->LightProbes.Size()) + double cellY_A = std::floor(a.Y * rcpCellSize); + double cellY_B = std::floor(b.Y * rcpCellSize); + if (cellY_A != cellY_B) + return cellY_A < cellY_B; + double cellX_A = std::floor(a.X * rcpCellSize); + double cellX_B = std::floor(b.X * rcpCellSize); + return cellX_A < cellX_B; + }; + std::sort(Level->LightProbes.begin(), Level->LightProbes.end(), cellCompareLess); + + // Find probe bounds and the grid that covers it + float probesMinX = Level->LightProbes[0].X; + float probesMaxX = Level->LightProbes[0].X; + float probesMinY = Level->LightProbes[0].Y; + float probesMaxY = Level->LightProbes[0].Y; + for (const LightProbe& p : Level->LightProbes) + { + probesMinX = std::min(probesMinX, p.X); + probesMaxX = std::max(probesMaxX, p.X); + probesMinY = std::min(probesMinY, p.Y); + probesMaxY = std::max(probesMaxY, p.Y); + } + Level->LPMinX = (int)std::floor(probesMinX * rcpCellSize); + Level->LPMinY = (int)std::floor(probesMinY * rcpCellSize); + Level->LPWidth = (int)std::floor(probesMaxX * rcpCellSize) + 1 - Level->LPMinX; + Level->LPHeight = (int)std::floor(probesMaxY * rcpCellSize) + 1 - Level->LPMinY; + + // Place probes in a grid for faster search + Level->LPCells.Resize(Level->LPWidth * Level->LPHeight); + int minX = Level->LPMinX; + int minY = Level->LPMinY; + int width = Level->LPWidth; + int height = Level->LPHeight; + for (LightProbe& p : Level->LightProbes) + { + int gridX = (int)std::floor(p.X * rcpCellSize) - minX; + int gridY = (int)std::floor(p.Y * rcpCellSize) - minY; + if (gridX >= 0 && gridY >= 0 && gridX < width && gridY < height) { - Printf(PRINT_HIGH, "LoadLightmap: invalid light probe data\n"); - break; + LightProbeCell& cell = Level->LPCells[gridX + (size_t)gridY * width]; + if (!cell.FirstProbe) + cell.FirstProbe = &p; + cell.NumProbes++; } - Level->subsectors[i].firstprobe = &Level->LightProbes[startIndex]; - Level->subsectors[i].numprobes = count; - startIndex += count; } } diff --git a/src/rendering/hwrenderer/scene/hw_spritelight.cpp b/src/rendering/hwrenderer/scene/hw_spritelight.cpp index 206ff07d2..56ef280da 100644 --- a/src/rendering/hwrenderer/scene/hw_spritelight.cpp +++ b/src/rendering/hwrenderer/scene/hw_spritelight.cpp @@ -51,24 +51,35 @@ LightProbe* FindLightProbe(FLevelLocals* level, float x, float y, float z) if (level->LightProbes.Size() > 0) { #if 1 - float radiusSquared = 32.0f * 32.0f; + double rcpCellSize = 1.0 / level->LPCellSize; + int gridCenterX = (int)std::floor(x * rcpCellSize) - level->LPMinX; + int gridCenterY = (int)std::floor(y * rcpCellSize) - level->LPMinY; + int gridWidth = level->LPWidth; + int gridHeight = level->LPHeight; float lastdist = 0.0f; - BSPWalkCircle(level, x, y, radiusSquared, [&](subsector_t* subsector) // Iterate through all subsectors potentially touched by actor + for (int gridY = gridCenterY - 1; gridY <= gridCenterY + 1; gridY++) { - for (uint32_t i = 0; i < subsector->numprobes; i++) + for (int gridX = gridCenterX - 1; gridX <= gridCenterX + 1; gridX++) { - LightProbe* probe = subsector->firstprobe + i; - float dx = probe->X - x; - float dy = probe->Y - y; - float dz = probe->Z - z; - float dist = dx * dx + dy * dy + dz * dz; - if (!foundprobe || dist < lastdist) + if (gridX >= 0 && gridY >= 0 && gridX < gridWidth && gridY < gridHeight) { - foundprobe = probe; - lastdist = dist; + const LightProbeCell& cell = level->LPCells[gridX + (size_t)gridY * gridWidth]; + for (int i = 0; i < cell.NumProbes; i++) + { + LightProbe* probe = cell.FirstProbe + i; + float dx = probe->X - x; + float dy = probe->Y - y; + float dz = probe->Z - z; + float dist = dx * dx + dy * dy + dz * dz; + if (!foundprobe || dist < lastdist) + { + foundprobe = probe; + lastdist = dist; + } + } } } - }); + } #else float lastdist = 0.0f; for (unsigned int i = 0; i < level->LightProbes.Size(); i++)