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:
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)
|
||||
|
||||
Reference in new issue
Block a user