streaming: split dense passage-junction grid fills; heavy-first order + far-parent deferral in Unreal (L85)
Tiles with passage-overlap score >= 9 split grid fill over up to 4 helper lanes (disjoint rows, same per-sample order, join before MC) when workers are idle. FI junction tile fill 173 -> 99 ms; estimated serial floor ~0.55-0.60 s. Readiness essentially unchanged (FI/TN LOD0-2 ~2.15 s @W12): the limit is now aggregate work, not single tiles. Unreal: LOD0-2 ordered by passage overlap before travel/distance (L80 modes kept); far parents built after LOD0-1, LOD2 gated on them; helper budget via ParallelFor. Unreal null-RHI FI arrival: LOD0-2 resident at 3.65 s. All tile hashes identical to L84 (four whole views). Fast tier PASS; Linux UE host builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
0a98da6fd0
commit
06f558dc91
17 files changed
+1561
-54
No files matched your search
@@ -15,9 +15,73 @@
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct FVoxelMesherThreadState
|
||||
{
|
||||
int32 OctaveBias=0,SampleStep=1,TileCells=0,TileReachBlocks=0,TileReachBlockIndex=INDEX_NONE;
|
||||
FIntVector TileOrigin=FIntVector::ZeroValue;
|
||||
uint8 TilePostReachFlags=VoxelGenLOD::AllTileReach;
|
||||
bool bTilePostReachDebug=false,bTilePostReachBypass=false;
|
||||
bool bTileReachDiagnosticTile=false,bTileCoreReachProofEnabled=false;
|
||||
bool bTileCoreReachDecisionRecorded=false,bTileBlockReachValid=false;
|
||||
float TileReachScale=1.0f;
|
||||
std::array<uint8,VoxelGenLOD::TileReachMaxBlockCount> TileBlockReachFlags{};
|
||||
|
||||
static FVoxelMesherThreadState Capture()
|
||||
{
|
||||
FVoxelMesherThreadState State;
|
||||
State.OctaveBias=VoxelGenLOD::OctaveBias;
|
||||
State.SampleStep=VoxelGenLOD::SampleStep;
|
||||
State.TileOrigin=VoxelGenLOD::TileOriginVoxels;
|
||||
State.TileCells=VoxelGenLOD::TileCellsPerAxis;
|
||||
State.TilePostReachFlags=VoxelGenLOD::TilePostReachFlags;
|
||||
State.bTilePostReachDebug=VoxelGenLOD::bTilePostReachDebug;
|
||||
State.bTilePostReachBypass=VoxelGenLOD::bTilePostReachBypass;
|
||||
State.bTileReachDiagnosticTile=VoxelGenLOD::bTileReachDiagnosticTile;
|
||||
State.bTileCoreReachProofEnabled=VoxelGenLOD::bTileCoreReachProofEnabled;
|
||||
State.bTileCoreReachDecisionRecorded=VoxelGenLOD::bTileCoreReachDecisionRecorded;
|
||||
State.bTileBlockReachValid=VoxelGenLOD::bTileBlockReachValid;
|
||||
State.TileReachScale=VoxelGenLOD::TileReachScale;
|
||||
State.TileReachBlocks=VoxelGenLOD::TileReachBlocksPerAxis;
|
||||
State.TileReachBlockIndex=VoxelGenLOD::TileReachBlockIndex;
|
||||
std::copy(std::begin(VoxelGenLOD::TileBlockReachFlags),
|
||||
std::end(VoxelGenLOD::TileBlockReachFlags),State.TileBlockReachFlags.begin());
|
||||
return State;
|
||||
}
|
||||
|
||||
void Apply() const
|
||||
{
|
||||
VoxelGenLOD::OctaveBias=OctaveBias;
|
||||
VoxelGenLOD::SampleStep=SampleStep;
|
||||
VoxelGenLOD::TileOriginVoxels=TileOrigin;
|
||||
VoxelGenLOD::TileCellsPerAxis=TileCells;
|
||||
VoxelGenLOD::TilePostReachFlags=TilePostReachFlags;
|
||||
VoxelGenLOD::bTilePostReachDebug=bTilePostReachDebug;
|
||||
VoxelGenLOD::bTilePostReachBypass=bTilePostReachBypass;
|
||||
VoxelGenLOD::bTileReachDiagnosticTile=bTileReachDiagnosticTile;
|
||||
VoxelGenLOD::bTileCoreReachProofEnabled=bTileCoreReachProofEnabled;
|
||||
VoxelGenLOD::bTileCoreReachDecisionRecorded=bTileCoreReachDecisionRecorded;
|
||||
VoxelGenLOD::bTileBlockReachValid=bTileBlockReachValid;
|
||||
VoxelGenLOD::TileReachScale=TileReachScale;
|
||||
VoxelGenLOD::TileReachBlocksPerAxis=TileReachBlocks;
|
||||
VoxelGenLOD::TileReachBlockIndex=TileReachBlockIndex;
|
||||
std::copy(TileBlockReachFlags.begin(),TileBlockReachFlags.end(),
|
||||
std::begin(VoxelGenLOD::TileBlockReachFlags));
|
||||
}
|
||||
};
|
||||
|
||||
struct FVoxelMesherThreadStateScope
|
||||
{
|
||||
explicit FVoxelMesherThreadStateScope(const FVoxelMesherThreadState& InTarget)
|
||||
:Previous(FVoxelMesherThreadState::Capture())
|
||||
{ InTarget.Apply(); }
|
||||
~FVoxelMesherThreadStateScope(){Previous.Apply();}
|
||||
FVoxelMesherThreadState Previous;
|
||||
};
|
||||
|
||||
int32 GVoxelForgeUseOperatorBlock = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeUseOperatorBlock(
|
||||
TEXT("voxel.UseOperatorBlock"),
|
||||
@@ -692,6 +756,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// capacité, donc plus de malloc/free de ~170 Ko (35³ floats) par tuile.
|
||||
static thread_local TArray<float> DensityGrid;
|
||||
DensityGrid.SetNumUninitialized(MDim * MDim * MDim);
|
||||
float* const DensityGridData=DensityGrid.GetData();
|
||||
const FVoxelSharedDensityGrid* ReuseGrid = InSharedDensityGrid != nullptr
|
||||
? InSharedDensityGrid : SharedDensityGrid;
|
||||
const int32 SharedDeltaX = ReuseGrid
|
||||
@@ -739,7 +804,15 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const bool bUseOperatorBlock = !bUseSharedDensityGrid
|
||||
&& !bUseParameterizedBrickEvaluator
|
||||
&& GVoxelForgeUseOperatorBlock != 0;
|
||||
if (bUseOperatorBlock)
|
||||
const int32 GridZSampleCount=GzHi-GzLo+1;
|
||||
const std::size_t GridFillLaneCount=std::min<std::size_t>({
|
||||
GridFillExecution.WorkerLimit,16,
|
||||
static_cast<std::size_t>(FMath::Max(0,GridZSampleCount))});
|
||||
const bool bUseParallelDensityGrid=GridFillExecution.ParallelFor!=nullptr
|
||||
&&GridFillLaneCount>1&&GridFillExecution.BeginWorkerContext!=nullptr
|
||||
&&GridFillExecution.EndWorkerContext!=nullptr&&!bUseSharedDensityGrid
|
||||
&&!bUseParameterizedBrickEvaluator&&OutCaptureGrid==nullptr;
|
||||
if (bUseOperatorBlock&&!bUseParallelDensityGrid)
|
||||
{
|
||||
// The grid includes one sample of halo on every side. The generator groups these exact
|
||||
// lattice points by chunk key and lets the prepared op stack fill each group once.
|
||||
@@ -889,6 +962,80 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if(bUseParallelDensityGrid)
|
||||
{
|
||||
const FVoxelMesherThreadState TileThreadState=FVoxelMesherThreadState::Capture();
|
||||
std::array<int64,16> LaneSampleCounts{};
|
||||
std::atomic<bool> bAborted{false};
|
||||
auto RunLane=[&](std::size_t Lane,std::size_t Worker)
|
||||
{
|
||||
void* WorkerState=GridFillExecution.BeginWorkerContext(
|
||||
GridFillExecution.WorkerContext,Worker);
|
||||
int64 LaneSamples=0;
|
||||
{
|
||||
FVoxelMesherThreadStateScope ThreadStateScope(TileThreadState);
|
||||
const int32 FirstGz=GzLo+static_cast<int32>(
|
||||
(static_cast<std::size_t>(GridZSampleCount)*Lane)/GridFillLaneCount);
|
||||
const int32 EndGz=GzLo+static_cast<int32>(
|
||||
(static_cast<std::size_t>(GridZSampleCount)*(Lane+1))/GridFillLaneCount);
|
||||
if(bUseOperatorBlock)
|
||||
{
|
||||
Generator->BeginDensityBlock(
|
||||
OriginVoxels+FIntVector(-Step,-Step,FirstGz*Step),Step,
|
||||
MDim,MDim,EndGz-FirstGz);
|
||||
}
|
||||
for(int32 gz=FirstGz;gz<EndGz;++gz)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{ bAborted.store(true,std::memory_order_relaxed);break; }
|
||||
for(int32 gy=-1;gy<=GridDim;++gy)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{ bAborted.store(true,std::memory_order_relaxed);break; }
|
||||
for(int32 gx=-1;gx<=GridDim;++gx)
|
||||
{
|
||||
const int32 LocalIndex=((gz+1)*MDim+(gy+1))*MDim+(gx+1);
|
||||
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;
|
||||
DensityGridData[LocalIndex]=Generator->GetDensityAt(
|
||||
OriginVoxels.X+gx*Step,OriginVoxels.Y+gy*Step,
|
||||
OriginVoxels.Z+gz*Step,
|
||||
bCapturePreEditGrid?&PreEditDensity:nullptr);
|
||||
if(bCapturePreEditGrid)
|
||||
OutPreEditDensityGrid->Samples[LocalIndex]=PreEditDensity;
|
||||
++LaneSamples;
|
||||
}
|
||||
}
|
||||
if(bAborted.load(std::memory_order_relaxed))break;
|
||||
}
|
||||
if(bUseOperatorBlock)Generator->EndDensityBlock();
|
||||
}
|
||||
LaneSampleCounts[Lane]=LaneSamples;
|
||||
GridFillExecution.EndWorkerContext(
|
||||
GridFillExecution.WorkerContext,WorkerState,Worker);
|
||||
};
|
||||
struct FDispatchContext{decltype(RunLane)* Run=nullptr;};
|
||||
FDispatchContext DispatchContext{&RunLane};
|
||||
const auto DispatchLane=[](void* RawContext,std::size_t Lane,std::size_t Worker)
|
||||
{
|
||||
auto& Context=*static_cast<FDispatchContext*>(RawContext);
|
||||
(*Context.Run)(Lane,Worker);
|
||||
};
|
||||
GridFillExecution.ParallelFor(GridFillExecution.SchedulerContext,
|
||||
GridFillLaneCount,GridFillLaneCount,&DispatchContext,DispatchLane);
|
||||
if(OutDensitySampleCount)
|
||||
for(std::size_t Lane=0;Lane<GridFillLaneCount;++Lane)
|
||||
*OutDensitySampleCount+=LaneSampleCounts[Lane];
|
||||
if(bAborted.load(std::memory_order_relaxed))return FVoxelMeshData();
|
||||
}
|
||||
else for (int32 gz = GzLo; gz <= GzHi; gz++)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
@@ -959,7 +1106,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
GridSampleCount * sizeof(float));
|
||||
}
|
||||
}
|
||||
if (bUseOperatorBlock)
|
||||
if (bUseOperatorBlock&&!bUseParallelDensityGrid)
|
||||
{
|
||||
Generator->EndDensityBlock();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user