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
@@ -2055,7 +2055,7 @@ namespace StandaloneStreamBenchmark
|
||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,
|
||||
bAllowUniformSkipsWhenHashing);
|
||||
bAllowUniformSkipsWhenHashing,&Scheduler);
|
||||
Streamer.SetPassageHeavyPriority(ResolvedLayout);
|
||||
Streamer.SetSchedulerPolicy(SchedulerPolicy);
|
||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||
|
||||
@@ -435,6 +435,29 @@ namespace StandaloneWorldMesher
|
||||
}
|
||||
}
|
||||
|
||||
// Grid fills can be nested inside the tile worker pool. Keep at most one such fill active so a
|
||||
// burst of passage tiles adds only a bounded helper group instead of multiplying the worker count.
|
||||
class FStandaloneGridFillScheduler final : public VoxelForgeCore::JobScheduler
|
||||
{
|
||||
public:
|
||||
explicit FStandaloneGridFillScheduler(const VoxelForgeCore::JobScheduler* InInner)
|
||||
:Inner(InInner){}
|
||||
|
||||
double parallel_for(std::size_t Count,std::size_t BatchSize,std::size_t WorkerLimit,
|
||||
void* Context,Task Callback,double* WorkerActiveSeconds,
|
||||
std::size_t WorkerActiveCapacity) const override
|
||||
{
|
||||
if(!Inner)return 0.0;
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
return Inner->parallel_for(Count,BatchSize,WorkerLimit,Context,Callback,
|
||||
WorkerActiveSeconds,WorkerActiveCapacity);
|
||||
}
|
||||
|
||||
private:
|
||||
const VoxelForgeCore::JobScheduler* Inner=nullptr;
|
||||
mutable std::mutex Mutex;
|
||||
};
|
||||
|
||||
class FStandaloneStreamingGenerator
|
||||
{
|
||||
private:
|
||||
@@ -462,10 +485,14 @@ public:
|
||||
bool bInCaptureColumnCoordinates=false,
|
||||
int32 InLod8HorizonInteriorStepVoxels=0,
|
||||
bool bInSharedColumnCache=true,
|
||||
bool bInAllowUniformSkipsWhenHashing=false)
|
||||
bool bInAllowUniformSkipsWhenHashing=false,
|
||||
const VoxelForgeCore::JobScheduler* InGridFillScheduler=nullptr)
|
||||
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
|
||||
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
|
||||
CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
|
||||
CoreWorld(InCoreWorld),
|
||||
GridFillScheduler(InGridFillScheduler
|
||||
?std::make_unique<FStandaloneGridFillScheduler>(InGridFillScheduler):nullptr),
|
||||
bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
|
||||
bSkipUV(bInSkipUV),bSkipColors(bInSkipColors),
|
||||
bCaptureColumnCoordinates(bInCaptureColumnCoordinates),
|
||||
bSharedColumnCache(bInSharedColumnCache),
|
||||
@@ -1116,6 +1143,25 @@ private:
|
||||
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
||||
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||
Options.useSharedColumnCache=bSharedColumnCache;
|
||||
// Split only dense passage/mouth overlaps, and spend only worker lanes left idle by
|
||||
// the tile pool. The grid fill runs on a second pool while this tile worker waits.
|
||||
const uint32 PassageScore=PassageOverlapScore(Job.Key);
|
||||
if(GridFillScheduler&&Job.Key.Level==0&&WorkerCount>1&&PassageScore>=9)
|
||||
{
|
||||
size_t ActiveTileCount=0;
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
ActiveTileCount=Running.size();
|
||||
}
|
||||
const int32 AvailableHelperLanes=std::max(0,
|
||||
12-std::max(0,static_cast<int32>(ActiveTileCount)-1));
|
||||
const int32 GridFillWorkers=std::min({4,WorkerCount,AvailableHelperLanes});
|
||||
if(GridFillWorkers>1)
|
||||
{
|
||||
Options.gridFillScheduler=GridFillScheduler.get();
|
||||
Options.gridFillWorkerLimit=static_cast<std::uint32_t>(GridFillWorkers);
|
||||
}
|
||||
}
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
|
||||
Options.profilePassageComposer=bProfilePassageComposer;
|
||||
@@ -1330,7 +1376,7 @@ private:
|
||||
const char* TileClassName=D.tileClass==VoxelForgeCore::TileClass::allAir?"air":
|
||||
D.tileClass==VoxelForgeCore::TileClass::allSolid?"solid":
|
||||
D.tileClass==VoxelForgeCore::TileClass::mixed?"mixed":"unknown";
|
||||
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f payload_ms=%.3f tile_classified=%d tile_class=%s tile_skip=%d density_point_calls=%llu density_float_points=%llu density_lattice_points=%llu density_unclamped_points=%llu density_x8_points=%llu field_eval_sample_ns=%llu field_eval_sample_points=%llu samples=%llu classify_ms=%.3f column_ms=%.3f column_exclusive_ms=%.3f mc_ms=%.3f mc_grid_ms=%.3f mc_cell_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_skirt_ms=%.3f mc_other_ms=%.3f connector_ms=%.3f ground_ms=%.3f cap_ms=%.3f horizon_ms=%.3f mesh_assembly_ms=%.3f candidate_cells=%llu classified_cells=%llu surface_cells=%llu proof_solid_cells=%llu proof_air_cells=%llu proof_density_samples=%llu zband_reject_cells=%llu x8_calls=%llu x8_active_lanes=%llu grad_calls=%llu grad_memo_hits=%llu grad_fallback_calls=%llu midpoint_attempts=%llu midpoint_memo_hits=%llu skirt_edges=%llu skirt_boundary_edges=%llu skirt_density_probes=%llu skirt_triangles=%u fi_local_hit=%llu fi_local_miss=%llu fi_local_conflicts=%llu fi_shared_hit=%llu fi_shared_miss=%llu fi_shared_computes=%llu fi_shared_conflicts=%llu fi_lock_wait_us=%.3f\n",
|
||||
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f payload_ms=%.3f tile_classified=%d tile_class=%s tile_skip=%d density_point_calls=%llu density_float_points=%llu density_lattice_points=%llu density_unclamped_points=%llu density_x8_points=%llu field_eval_sample_ns=%llu field_eval_sample_points=%llu samples=%llu classify_ms=%.3f column_ms=%.3f column_exclusive_ms=%.3f mc_ms=%.3f mc_grid_ms=%.3f mc_grid_workers=%u mc_cell_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_skirt_ms=%.3f mc_other_ms=%.3f connector_ms=%.3f ground_ms=%.3f cap_ms=%.3f horizon_ms=%.3f mesh_assembly_ms=%.3f candidate_cells=%llu classified_cells=%llu surface_cells=%llu proof_solid_cells=%llu proof_air_cells=%llu proof_density_samples=%llu zband_reject_cells=%llu x8_calls=%llu x8_active_lanes=%llu grad_calls=%llu grad_memo_hits=%llu grad_fallback_calls=%llu midpoint_attempts=%llu midpoint_memo_hits=%llu skirt_edges=%llu skirt_boundary_edges=%llu skirt_density_probes=%llu skirt_triangles=%u fi_local_hit=%llu fi_local_miss=%llu fi_local_conflicts=%llu fi_shared_hit=%llu fi_shared_miss=%llu fi_shared_computes=%llu fi_shared_conflicts=%llu fi_lock_wait_us=%.3f\n",
|
||||
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
||||
Event.WorkerId,Event.WorkerMilliseconds,Event.PayloadBuildMilliseconds,
|
||||
D.tileWasClassified?1:0,TileClassName,D.meshingSkipped?1:0,
|
||||
@@ -1344,7 +1390,8 @@ private:
|
||||
static_cast<unsigned long long>(Event.DensitySamples),D.classificationMilliseconds,
|
||||
D.columnComputeMilliseconds,D.columnEvaluationExclusiveMilliseconds,
|
||||
D.marchingCubesExclusiveMilliseconds+D.marchingCubesSkirtMilliseconds,
|
||||
D.marchingCubesDensityGridMilliseconds,D.marchingCubesCellClassificationMilliseconds,
|
||||
D.marchingCubesDensityGridMilliseconds,D.marchingCubesGridFillWorkers,
|
||||
D.marchingCubesCellClassificationMilliseconds,
|
||||
D.marchingCubesGradientNormalsMilliseconds,D.marchingCubesEdgeInterpolationMilliseconds,
|
||||
D.marchingCubesVertexDedupStreamMilliseconds,D.marchingCubesSkirtMilliseconds,
|
||||
D.marchingCubesOtherMilliseconds,D.farConnectorShellMilliseconds,
|
||||
@@ -1441,6 +1488,7 @@ private:
|
||||
const FStandaloneMeshCache* MeshCache=nullptr;
|
||||
int32 MaxLevelFilter=-1;
|
||||
VoxelForgeCore::World* CoreWorld=nullptr;
|
||||
std::unique_ptr<FStandaloneGridFillScheduler> GridFillScheduler;
|
||||
bool bSkipUvAndColors=false;
|
||||
bool bSkipMaterialGroups=false;
|
||||
bool bSkipUV=false;
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include "HAL/IConsoleManager.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
#include <immintrin.h>
|
||||
@@ -24,6 +27,69 @@
|
||||
|
||||
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"),
|
||||
@@ -957,6 +1023,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
|
||||
@@ -1063,14 +1130,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const bool bUseOperatorBlock = !bUseSharedDensityGrid
|
||||
&& !bUseParameterizedBrickEvaluator
|
||||
&& GVoxelForgeUseOperatorBlock != 0;
|
||||
if (bUseOperatorBlock)
|
||||
{
|
||||
// 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.
|
||||
Generator->BeginDensityBlock(
|
||||
OriginVoxels + FIntVector(-Step, -Step, GzLo * Step),
|
||||
Step, MDim, MDim, GzHi - GzLo + 1);
|
||||
}
|
||||
bool bGridFillParallel=false;
|
||||
if (bUseParameterizedBrickEvaluator)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
@@ -1238,7 +1298,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
* (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)
|
||||
auto SampleGridPoint = [&](int32 gx, int32 gy, int32 gz,
|
||||
FVoxelMarchingCubesStageTimings* StageTimings,
|
||||
int64* DensitySampleCount,float* GridData)
|
||||
{
|
||||
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
|
||||
if(bSkipProofBlockGrid&&RequiredGridSamples[LocalIndex]==0)return;
|
||||
@@ -1247,7 +1309,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 SharedX = SharedDeltaX + gx;
|
||||
const int32 SharedY = SharedDeltaY + gy;
|
||||
const int32 SharedZ = SharedDeltaZ + gz;
|
||||
DensityGrid[LocalIndex] = ReuseGrid->Samples[
|
||||
GridData[LocalIndex] = ReuseGrid->Samples[
|
||||
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
|
||||
}
|
||||
else
|
||||
@@ -1272,22 +1334,22 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
bBoundarySample
|
||||
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
|
||||
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
|
||||
DensityGrid[LocalIndex] = ShapeWorldContext
|
||||
GridData[LocalIndex] = ShapeWorldContext
|
||||
?ShapeWorldContext->DensityAtLattice(SampleX,SampleY,SampleZ)
|
||||
:Generator->GetDensityAtLattice(SampleX,SampleY,SampleZ,
|
||||
bCapturePreEditGrid ? &PreEditDensity : nullptr);
|
||||
if(OutStageTimings)
|
||||
if(StageTimings)
|
||||
{
|
||||
if(bBoundarySample)++OutStageTimings->DensityGridHaloSamples;
|
||||
else ++OutStageTimings->DensityGridInteriorSamples;
|
||||
if(bBoundarySample)++StageTimings->DensityGridHaloSamples;
|
||||
else ++StageTimings->DensityGridInteriorSamples;
|
||||
}
|
||||
if (bCapturePreEditGrid)
|
||||
{
|
||||
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
|
||||
}
|
||||
if (OutDensitySampleCount)
|
||||
if (DensitySampleCount)
|
||||
{
|
||||
++(*OutDensitySampleCount);
|
||||
++(*DensitySampleCount);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1308,7 +1370,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const bool bUseShapeWorldXBatch=ActiveShapeWorld!=nullptr
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid
|
||||
&&(!bSkipProofBlockGrid||bStandaloneEnableActiveLaneBatching);
|
||||
const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz)
|
||||
const auto SampleShapeWorldXBatch=[&](int32 GX0,int32 BatchCount,int32 gy,int32 gz,
|
||||
FVoxelMarchingCubesStageTimings* StageTimings,int64* DensitySampleCount,
|
||||
float* GridData)
|
||||
{
|
||||
float Densities[8]{};
|
||||
const bool bBoundarySample=GX0<0||GX0+BatchCount-1>GridDim
|
||||
@@ -1320,24 +1384,24 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
|
||||
ActiveShapeWorld->DensityAtLatticeX8(OriginVoxels.X+GX0*Step,Step,
|
||||
OriginVoxels.Y+gy*Step,OriginVoxels.Z+gz*Step,BatchCount,Densities);
|
||||
if(OutStageTimings)
|
||||
if(StageTimings)
|
||||
{
|
||||
++OutStageTimings->X8DensityCalls;
|
||||
OutStageTimings->X8ActiveLanes+=static_cast<uint64>(BatchCount);
|
||||
++StageTimings->X8DensityCalls;
|
||||
StageTimings->X8ActiveLanes+=static_cast<uint64>(BatchCount);
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
{
|
||||
const int32 SampleGX=GX0+Lane;
|
||||
if(SampleGX<0||SampleGX>GridDim||gy<0||gy>GridDim
|
||||
||gz<0||gz>GridDim)
|
||||
++OutStageTimings->DensityGridHaloSamples;
|
||||
else ++OutStageTimings->DensityGridInteriorSamples;
|
||||
++StageTimings->DensityGridHaloSamples;
|
||||
else ++StageTimings->DensityGridInteriorSamples;
|
||||
}
|
||||
}
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
{
|
||||
const int32 GX=GX0+Lane;
|
||||
const int32 LocalIndex=((gz+1)*MDim+(gy+1))*MDim+(GX+1);
|
||||
DensityGrid[LocalIndex]=Densities[Lane];
|
||||
GridData[LocalIndex]=Densities[Lane];
|
||||
}
|
||||
const int32 LastGX=GX0+BatchCount-1;
|
||||
const int32 ReachBlockX=FMath::Clamp(LastGX,0,CellsPerAxis-1)
|
||||
@@ -1348,9 +1412,202 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex=
|
||||
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)*ReachBlocksPerAxis+ReachBlockX;
|
||||
if(OutDensitySampleCount)*OutDensitySampleCount+=BatchCount;
|
||||
if(DensitySampleCount)*DensitySampleCount+=BatchCount;
|
||||
};
|
||||
if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
|
||||
const int32 GridZSampleCount=GzHi-GzLo+1;
|
||||
const int32 GridYSampleCount=GridDim+2;
|
||||
const bool bParallelizeGridRows=bUseShapeWorldZBatch
|
||||
||bUseVerticalCacheTraversal||!bUseShapeWorldXBatch;
|
||||
const std::size_t GridFillLaneCount=std::min<std::size_t>({
|
||||
GridFillExecution.WorkerLimit,16,
|
||||
static_cast<std::size_t>(FMath::Max(0,
|
||||
bParallelizeGridRows?GridYSampleCount:GridZSampleCount))});
|
||||
const bool bUseParallelShapeWorldGrid=GridFillExecution.ParallelFor!=nullptr
|
||||
&&GridFillLaneCount>1&&GridFillExecution.BeginWorkerContext!=nullptr
|
||||
&&GridFillExecution.EndWorkerContext!=nullptr
|
||||
&&!bUseSharedDensityGrid&&!bCapturePreEditGrid&&!bSkipProofBlockGrid
|
||||
&&OutCaptureGrid==nullptr;
|
||||
if(bUseOperatorBlock&&!bUseParallelShapeWorldGrid)
|
||||
{
|
||||
// The grid includes one sample of halo on every side. The prepared operator block
|
||||
// is thread-local, so a parallel fill below opens one block per worker slab.
|
||||
Generator->BeginDensityBlock(
|
||||
OriginVoxels+FIntVector(-Step,-Step,GzLo*Step),
|
||||
Step,MDim,MDim,GzHi-GzLo+1);
|
||||
}
|
||||
if(bUseParallelShapeWorldGrid)
|
||||
{
|
||||
bGridFillParallel=true;
|
||||
const FVoxelMesherThreadState TileThreadState=FVoxelMesherThreadState::Capture();
|
||||
std::array<int64,16> LaneSampleCounts{};
|
||||
std::array<FVoxelMarchingCubesStageTimings,16> LaneTimings{};
|
||||
std::atomic<bool> bAborted{false};
|
||||
auto RunLane=[&](std::size_t Lane,std::size_t Worker)
|
||||
{
|
||||
void* WorkerState=GridFillExecution.BeginWorkerContext(
|
||||
GridFillExecution.WorkerContext,Worker);
|
||||
int64& LaneSamples=LaneSampleCounts[Lane];
|
||||
FVoxelMarchingCubesStageTimings& StageTimings=LaneTimings[Lane];
|
||||
{
|
||||
FVoxelMesherThreadStateScope ThreadStateScope(TileThreadState);
|
||||
if(bUseOperatorBlock)
|
||||
Generator->BeginDensityBlock(
|
||||
OriginVoxels+FIntVector(-Step,-Step,GzLo*Step),Step,
|
||||
MDim,MDim,GridZSampleCount);
|
||||
if(bUseShapeWorldZBatch)
|
||||
{
|
||||
const int32 FirstGy=-1+static_cast<int32>(
|
||||
(static_cast<std::size_t>(GridYSampleCount)*Lane)/GridFillLaneCount);
|
||||
const int32 EndGy=-1+static_cast<int32>(
|
||||
(static_cast<std::size_t>(GridYSampleCount)*(Lane+1))/GridFillLaneCount);
|
||||
for(int32 gy=FirstGy;gy<EndGy;++gy)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{bAborted.store(true,std::memory_order_relaxed);break;}
|
||||
for(const std::vector<int32>& XGroup:XGroups)
|
||||
for(const int32 gx:XGroup)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{
|
||||
bAborted.store(true,std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
float Densities[CHUNK_SIZE+3]{};
|
||||
const bool bBoundarySample=gx<0||gx>GridDim
|
||||
||gy<0||gy>GridDim||GzLo<0||GzHi>GridDim;
|
||||
VoxelForgeCore::Linux77Profiler::FScopedHalo l77Halo(
|
||||
bBoundarySample);
|
||||
VoxelForgeCore::floating_island_detail::FScopedIslandFieldCaller
|
||||
islandCaller(bBoundarySample
|
||||
?VoxelForgeCore::floating_island_detail::EIslandFieldCaller::Boundary
|
||||
:VoxelForgeCore::floating_island_detail::EIslandFieldCaller::GridFill);
|
||||
ActiveShapeWorld->DensityAtLatticeZRun(
|
||||
OriginVoxels.X+gx*Step,OriginVoxels.Y+gy*Step,
|
||||
OriginVoxels.Z+GzLo*Step,Step,GridZSampleCount,Densities);
|
||||
for(int32 ZLane=0;ZLane<GridZSampleCount;++ZLane)
|
||||
{
|
||||
const int32 gz=GzLo+ZLane;
|
||||
const int32 LocalIndex=
|
||||
((gz+1)*MDim+(gy+1))*MDim+(gx+1);
|
||||
DensityGridData[LocalIndex]=Densities[ZLane];
|
||||
if(gx<0||gx>GridDim||gy<0||gy>GridDim
|
||||
||gz<0||gz>GridDim)
|
||||
++StageTimings.DensityGridHaloSamples;
|
||||
else ++StageTimings.DensityGridInteriorSamples;
|
||||
}
|
||||
LaneSamples+=GridZSampleCount;
|
||||
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(GzHi,0,CellsPerAxis-1)
|
||||
/VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex=
|
||||
((ReachBlockZ*ReachBlocksPerAxis)+ReachBlockY)
|
||||
*ReachBlocksPerAxis+ReachBlockX;
|
||||
}
|
||||
if(bAborted.load(std::memory_order_relaxed))break;
|
||||
}
|
||||
}
|
||||
else if(bUseVerticalCacheTraversal||!bUseShapeWorldXBatch)
|
||||
{
|
||||
const int32 FirstGy=-1+static_cast<int32>(
|
||||
(static_cast<std::size_t>(GridYSampleCount)*Lane)/GridFillLaneCount);
|
||||
const int32 EndGy=-1+static_cast<int32>(
|
||||
(static_cast<std::size_t>(GridYSampleCount)*(Lane+1))/GridFillLaneCount);
|
||||
for(int32 gy=FirstGy;gy<EndGy;++gy)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{bAborted.store(true,std::memory_order_relaxed);break;}
|
||||
for(const std::vector<int32>& XGroup:XGroups)
|
||||
for(const int32 gx:XGroup)
|
||||
{
|
||||
for(int32 gz=GzLo;gz<=GzHi;++gz)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{bAborted.store(true,std::memory_order_relaxed);break;}
|
||||
SampleGridPoint(gx,gy,gz,&StageTimings,&LaneSamples,
|
||||
DensityGridData);
|
||||
}
|
||||
if(bAborted.load(std::memory_order_relaxed))break;
|
||||
}
|
||||
if(bAborted.load(std::memory_order_relaxed))break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
for(int32 gz=FirstGz;gz<EndGz;++gz)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{bAborted.store(true,std::memory_order_relaxed);break;}
|
||||
for(const std::vector<int32>& YGroup:YGroups)
|
||||
for(const std::vector<int32>& XGroup:XGroups)
|
||||
for(const int32 gy:YGroup)
|
||||
{
|
||||
if(ShouldAbortWork())
|
||||
{bAborted.store(true,std::memory_order_relaxed);break;}
|
||||
for(std::size_t Offset=0;Offset<XGroup.size();)
|
||||
{
|
||||
const int32 BatchCount=static_cast<int32>(std::min<std::size_t>(
|
||||
8,XGroup.size()-Offset));
|
||||
const int32 GX0=XGroup[Offset];
|
||||
bool bContiguous=true;
|
||||
for(int32 BatchLane=1;BatchLane<BatchCount;++BatchLane)
|
||||
bContiguous&=XGroup[
|
||||
Offset+static_cast<std::size_t>(BatchLane)]==GX0+BatchLane;
|
||||
if(!bContiguous)
|
||||
{
|
||||
for(int32 BatchLane=0;BatchLane<BatchCount;++BatchLane)
|
||||
SampleGridPoint(XGroup[
|
||||
Offset+static_cast<std::size_t>(BatchLane)],gy,gz,
|
||||
&StageTimings,&LaneSamples,DensityGridData);
|
||||
}
|
||||
else
|
||||
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz,
|
||||
&StageTimings,&LaneSamples,DensityGridData);
|
||||
Offset+=static_cast<std::size_t>(BatchCount);
|
||||
}
|
||||
}
|
||||
if(bAborted.load(std::memory_order_relaxed))break;
|
||||
}
|
||||
}
|
||||
if(bUseOperatorBlock)Generator->EndDensityBlock();
|
||||
}
|
||||
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(OutStageTimings)
|
||||
{
|
||||
OutStageTimings->GridFillWorkerCount=static_cast<uint32>(GridFillLaneCount);
|
||||
for(std::size_t Lane=0;Lane<GridFillLaneCount;++Lane)
|
||||
{
|
||||
OutStageTimings->DensityGridInteriorSamples+=
|
||||
LaneTimings[Lane].DensityGridInteriorSamples;
|
||||
OutStageTimings->DensityGridHaloSamples+=
|
||||
LaneTimings[Lane].DensityGridHaloSamples;
|
||||
OutStageTimings->X8DensityCalls+=LaneTimings[Lane].X8DensityCalls;
|
||||
OutStageTimings->X8ActiveLanes+=LaneTimings[Lane].X8ActiveLanes;
|
||||
}
|
||||
}
|
||||
if(bAborted.load(std::memory_order_relaxed))return FVoxelMeshData();
|
||||
}
|
||||
else if(bUseShapeWorldZBatch&&!bUseVerticalCacheTraversal)
|
||||
{
|
||||
for(const std::vector<int32>& YGroup:YGroups)
|
||||
for(const std::vector<int32>& XGroup:XGroups)
|
||||
@@ -1419,7 +1676,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
SampleGridPoint(gx,gy,gz);
|
||||
SampleGridPoint(gx,gy,gz,OutStageTimings,OutDensitySampleCount,
|
||||
DensityGridData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1460,8 +1718,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
if(RequiredGridSamples[NextIndex]==0)break;
|
||||
++BatchCount;
|
||||
}
|
||||
if(BatchCount<2)SampleGridPoint(GX0,gy,gz);
|
||||
else SampleShapeWorldXBatch(GX0,BatchCount,gy,gz);
|
||||
if(BatchCount<2)SampleGridPoint(
|
||||
GX0,gy,gz,OutStageTimings,OutDensitySampleCount,
|
||||
DensityGridData);
|
||||
else SampleShapeWorldXBatch(GX0,BatchCount,gy,gz,
|
||||
OutStageTimings,OutDensitySampleCount,DensityGridData);
|
||||
Offset+=static_cast<size_t>(BatchCount);
|
||||
continue;
|
||||
}
|
||||
@@ -1474,18 +1735,21 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
if(!bContiguous)
|
||||
{
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
SampleGridPoint(XGroup[Offset+static_cast<size_t>(Lane)],gy,gz);
|
||||
SampleGridPoint(XGroup[Offset+static_cast<size_t>(Lane)],gy,gz,
|
||||
OutStageTimings,OutDensitySampleCount,DensityGridData);
|
||||
Offset+=static_cast<size_t>(BatchCount);
|
||||
continue;
|
||||
}
|
||||
|
||||
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz);
|
||||
SampleShapeWorldXBatch(GX0,BatchCount,gy,gz,
|
||||
OutStageTimings,OutDensitySampleCount,DensityGridData);
|
||||
Offset+=static_cast<size_t>(BatchCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const int32 gx:XGroup)SampleGridPoint(gx,gy,gz);
|
||||
for(const int32 gx:XGroup)SampleGridPoint(
|
||||
gx,gy,gz,OutStageTimings,OutDensitySampleCount,DensityGridData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1506,7 +1770,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
GridSampleCount * sizeof(float));
|
||||
}
|
||||
}
|
||||
if (bUseOperatorBlock)
|
||||
if (bUseOperatorBlock&&!bGridFillParallel)
|
||||
{
|
||||
Generator->EndDensityBlock();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
|
||||
#include "VoxelGenerator.h"
|
||||
@@ -41,10 +42,29 @@ struct FVoxelSharedDensityGrid
|
||||
}
|
||||
};
|
||||
|
||||
// Host-owned scheduling hooks for a predicted-heavy density-grid fill. Each task owns one
|
||||
// deterministic Z slab and gets the source tile's worker-local sampling context.
|
||||
struct FVoxelGridFillExecution
|
||||
{
|
||||
using FTask = void(*)(void* taskContext, std::size_t index, std::size_t workerIndex);
|
||||
using FParallelFor = double(*)(void* schedulerContext, std::size_t count,
|
||||
std::size_t workerLimit, void* taskContext, FTask task);
|
||||
using FBeginWorkerContext = void*(*)(void* context, std::size_t workerIndex);
|
||||
using FEndWorkerContext = void(*)(void* context, void* workerState,
|
||||
std::size_t workerIndex);
|
||||
void* SchedulerContext = nullptr;
|
||||
FParallelFor ParallelFor = nullptr;
|
||||
std::size_t WorkerLimit = 1;
|
||||
void* WorkerContext = nullptr;
|
||||
FBeginWorkerContext BeginWorkerContext = nullptr;
|
||||
FEndWorkerContext EndWorkerContext = nullptr;
|
||||
};
|
||||
|
||||
// Optional, sampled timing output for the research audit; null in production calls.
|
||||
// Mesures echantillonnees facultatives pour l'audit; pointeur nul en production.
|
||||
struct FVoxelMarchingCubesStageTimings
|
||||
{
|
||||
uint32 GridFillWorkerCount = 1;
|
||||
double DensityGridMilliseconds = 0.0;
|
||||
double CellClassificationMilliseconds = 0.0;
|
||||
double GradientNormalsMilliseconds = 0.0;
|
||||
@@ -161,6 +181,8 @@ public:
|
||||
// GenerateMesh calls finish. A malformed/incomplete lattice is ignored and falls back to
|
||||
// the canonical generator path rather than changing geometry.
|
||||
void SetSharedDensityGrid(const FVoxelSharedDensityGrid* InGrid) { SharedDensityGrid = InGrid; }
|
||||
void SetGridFillExecution(const FVoxelGridFillExecution& InExecution)
|
||||
{ GridFillExecution = InExecution; }
|
||||
|
||||
// Standalone's interval world is an explicit immutable sampling context. Game builds do not
|
||||
// set this pointer and continue through Generator.
|
||||
@@ -189,6 +211,7 @@ private:
|
||||
|
||||
const std::atomic<bool>* ShutdownFlag = nullptr;
|
||||
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
|
||||
FVoxelGridFillExecution GridFillExecution;
|
||||
const FStandaloneShapeWorld* ShapeWorldContext = nullptr;
|
||||
TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures;
|
||||
bool bUseHybridSharpMesher = false;
|
||||
|
||||
Reference in new issue
Block a user