Improve light probe lookup speed for large sectors by using a grid instead of subsectors

This commit is contained in:
Magnus Norddahl 2021-10-15 04:21:18 +02:00
parent 6a804cd4c7
commit 81f7b083c0
4 changed files with 98 additions and 33 deletions

View file

@ -457,6 +457,12 @@ public:
int LMTextureSize = 0;
TArray<uint16_t> LMTextureData;
TArray<LightProbe> LightProbes;
int LPMinX = 0;
int LPMinY = 0;
int LPWidth = 0;
int LPHeight = 0;
static const int LPCellSize = 32;
TArray<LightProbeCell> LPCells;
// Portal information.
FDisplacementTable Displacements;

View file

@ -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
//

View file

@ -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<uint32_t> 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;
}
}

View file

@ -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++)