standalone: per-tile speed at plugin level, streaming like the game, softer lighting

Cause found by profiling: room/SDF graph-cache churn (exact XY bounds + sample order rebuilt it
constantly). Region grouping (one LOD3 tile: 1,084 -> 229 builds), taller-tile traversal for LOD6
(36,480 -> 12,160), extent-dependent cache slots, lambdas instead of recursive std::function.
Serial ms/tile, 64 nearest tiles per level (before -> after):
  L0 50.4 -> 50.6, L1 112 -> 110, L2 24 -> 22, L3 223 -> 73, L4 269 -> 134, L5 393 -> 127, L6 283 -> 145.
Hashes of all levels unchanged; parity 3,436/3,436 bit-identical.
Full clipmap, 16 workers BelowNormal: full detail 66.6 s, all 12,054 tiles 175 s. In-stream L0 tail is
high (median 83 ms, mean 198 ms, p95 669 ms) vs 51 ms serial.
Streaming: recentre keeps the intersection, generates only missing tiles nearest/finest first;
one crossing = exactly 441 new tiles in 6.1 s, no stale results.
Lighting: normals already come from the density gradient; the striping was the squared headlamp;
broad linear fill + warmer palette. Daytime CPU rule documented in Standalone/README.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-03 06:49:41 +02:00
1 parent 3f300b912a
commit 81b7d6d0ff
24 files changed
+1096 -196

No files matched your search

@@ -103,13 +103,15 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
int32 BandZMinVox, int32 BandZMaxVox,
int64* OutDensitySampleCount,
FVoxelSharedDensityGrid* OutPreEditDensityGrid,
const FVoxelSharedDensityGrid* InSharedDensityGrid)
const FVoxelSharedDensityGrid* InSharedDensityGrid,
std::vector<float>* OutDebugDensitySamples)
{
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
FVoxelMeshData MeshData;
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
if (OutPreEditDensityGrid) { OutPreEditDensityGrid->Reset(); }
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); }
if (!Generator) return MeshData;
VoxelDensityProfile::FScopedTimer MesherProfileTimer(
VoxelDensityProfile::EBucket::MesherGenerateMesh);
@@ -284,7 +286,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
//
// CellsPerAxis = nombre de cellules par axe ; GridDim = points de grille (+1).
// Le point de grille (gx,gy,gz) correspond au voxel monde (gx,gy,gz)*Step.
// Ordre de remplissage z→y→x : garde le cache SDF (search-box) bien chaud.
// The grid fill order is selected below by tile extent to preserve room-cache locality.
// On échantillonne avec un anneau de marge de 1 point de chaque côté (indices -1..GridDim)
// pour pouvoir calculer les normales par GRADIENT DE GRILLE (T1.b) — différences centrales
// sur la grille au lieu de 6 appels densité frais par vertex. La marge utilise les mêmes
@@ -869,56 +871,114 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
}
#endif
}
else for (int32 gz = GzLo; gz <= GzHi; gz++)
else
{
if (ShouldAbortWork())
auto BuildAxisGroups = [&](int32 AxisOrigin, int32 RegionSize)
{
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
return FVoxelMeshData();
}
for (int32 gy = -1; gy <= GridDim; gy++)
{
if (ShouldAbortWork())
std::vector<std::vector<int32>> Groups;
int32 PreviousRegion = INT32_MIN;
for (int32 Index = -1; Index <= GridDim; ++Index)
{
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
return FVoxelMeshData();
}
for (int32 gx = -1; gx <= GridDim; gx++)
{
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
if (bUseSharedDensityGrid)
const int64 WorldCoordinate = static_cast<int64>(AxisOrigin)
+ static_cast<int64>(Index) * Step;
const int32 Region = FMath::FloorToInt(
static_cast<float>(WorldCoordinate) / static_cast<float>(RegionSize));
if (Groups.empty() || Region != PreviousRegion)
{
const int32 SharedX = SharedDeltaX + gx;
const int32 SharedY = SharedDeltaY + gy;
const int32 SharedZ = SharedDeltaZ + gz;
DensityGrid[LocalIndex] = ReuseGrid->Samples[
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
Groups.emplace_back();
PreviousRegion = Region;
}
else
Groups.back().push_back(Index);
}
return Groups;
};
const int32 RoomCacheRegionSize = CHUNK_SIZE
* (VoxelGenLOD::IsUseFusedEvaluatorEnabled() ? 4 : 1);
const auto XGroups = BuildAxisGroups(OriginVoxels.X,RoomCacheRegionSize);
const auto YGroups = BuildAxisGroups(OriginVoxels.Y,RoomCacheRegionSize);
auto SampleGridPoint = [&](int32 gx, int32 gy, int32 gz)
{
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
if (bUseSharedDensityGrid)
{
const int32 SharedX = SharedDeltaX + gx;
const int32 SharedY = SharedDeltaY + gy;
const int32 SharedZ = SharedDeltaZ + gz;
DensityGrid[LocalIndex] = ReuseGrid->Samples[
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
}
else
{
const int32 ReachBlockX = FMath::Clamp(gx, 0, CellsPerAxis - 1)
/ VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockY = FMath::Clamp(gy, 0, CellsPerAxis - 1)
/ VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockZ = FMath::Clamp(gz, 0, CellsPerAxis - 1)
/ VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex =
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
* ReachBlocksPerAxis + ReachBlockX;
float PreEditDensity = 0.0f;
DensityGrid[LocalIndex] = Generator->GetDensityAt(
OriginVoxels.X + gx * Step,
OriginVoxels.Y + gy * Step,
OriginVoxels.Z + gz * Step,
bCapturePreEditGrid ? &PreEditDensity : nullptr);
if (bCapturePreEditGrid)
{
// World voxel = tile origin + grid offset scaled by the cell size (Step).
const int32 ReachBlockX = FMath::Clamp(gx, 0, CellsPerAxis - 1)
/ VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockY = FMath::Clamp(gy, 0, CellsPerAxis - 1)
/ VoxelGenLOD::TileReachBlockCells;
const int32 ReachBlockZ = FMath::Clamp(gz, 0, CellsPerAxis - 1)
/ VoxelGenLOD::TileReachBlockCells;
VoxelGenLOD::TileReachBlockIndex =
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
* ReachBlocksPerAxis + ReachBlockX;
float PreEditDensity = 0.0f;
DensityGrid[LocalIndex] = Generator->GetDensityAt(
OriginVoxels.X + gx * Step,
OriginVoxels.Y + gy * Step,
OriginVoxels.Z + gz * Step,
bCapturePreEditGrid ? &PreEditDensity : nullptr);
if (bCapturePreEditGrid)
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
}
if (OutDensitySampleCount)
{
++(*OutDensitySampleCount);
}
}
};
// Very coarse tiles revisit the same XY cache window across a tall Z run. Keep that
// vertical run contiguous; SampleGridPoint writes by lattice index, so output bits do
// not depend on this traversal. Finer tiles retain plane-first traversal, which has
// lower overhead when a tile spans only one room-graph region.
const int64 TileExtentVoxels = static_cast<int64>(Step) * CellsPerAxis;
const bool bUseVerticalCacheTraversal = TileExtentVoxels > CHUNK_SIZE * 16;
if (bUseVerticalCacheTraversal)
{
for (const std::vector<int32>& YGroup : YGroups)
for (const std::vector<int32>& XGroup : XGroups)
for (const int32 gy : YGroup)
for (const int32 gx : XGroup)
{
for (int32 gz = GzLo; gz <= GzHi; ++gz)
{
if (ShouldAbortWork())
{
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
return FVoxelMeshData();
}
if (OutDensitySampleCount)
SampleGridPoint(gx,gy,gz);
}
}
}
else
{
for (int32 gz = GzLo; gz <= GzHi; ++gz)
{
if (ShouldAbortWork())
{
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
return FVoxelMeshData();
}
for (const std::vector<int32>& YGroup : YGroups)
for (const std::vector<int32>& XGroup : XGroups)
for (const int32 gy : YGroup)
{
if (ShouldAbortWork())
{
++(*OutDensitySampleCount);
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
return FVoxelMeshData();
}
for (const int32 gx : XGroup)
{
SampleGridPoint(gx,gy,gz);
}
}
}
@@ -943,6 +1003,19 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
{
Generator->EndDensityBlock();
}
if (OutDebugDensitySamples)
{
const size_t SampleCount = static_cast<size_t>(GzHi - GzLo + 1)
* static_cast<size_t>(MDim) * static_cast<size_t>(MDim);
OutDebugDensitySamples->reserve(SampleCount);
for (int32 gz = GzLo; gz <= GzHi; ++gz)
for (int32 gy = -1; gy <= GridDim; ++gy)
for (int32 gx = -1; gx <= GridDim; ++gx)
{
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
OutDebugDensitySamples->push_back(DensityGrid[LocalIndex]);
}
}
MesherDensityGridTimer.End();
if (bUseProofBlocks)