streaming: passage-heavy tiles first, far parents after LOD0-1 (L84, headless only)
Predicted passage-overlap tiles are scheduled earlier (FI worst tile rank 116 -> 69, dispatch 0.95 -> 0.82 s); LOD0-1 publish from the base shape before far parents, which are built before LOD2 is queued. Every tile hash identical to L83 (all four whole views). Fast tier PASS. Cold LOD0-2 W8 / W12: SW 1.02 / 0.99, FI 2.26 / 2.18, TN 2.27 / 2.15, VS 1.13 / 1.09 s. Heaviest tiles (FI/TN junction band, z=-93) still take 0.5-0.7 s each, mostly full-grid fill; idealized floor ~0.92 s. Unreal scheduler not yet updated. 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
a81b5b7d06
commit
0a98da6fd0
6 files changed
+590
-9
No files matched your search
@@ -2030,6 +2030,15 @@ namespace StandaloneStreamBenchmark
|
||||
World.OperationStorage.clear();
|
||||
World.DensitySamples.clear();
|
||||
const bool bIncludeFarWorld=MaxLevel<0||MaxLevel>=World.FullResClipLevels;
|
||||
// LOD0-1 can start from the base field while far parents compile. LOD2 can sample
|
||||
// parent transitions in owner-world views, so publish far parents before queuing LOD2.
|
||||
// Special audits retain the historical eager build because they may query the compiled
|
||||
// shape directly.
|
||||
const bool bFarWorldAudit=bAuditNativeCaves||bAuditNativeLattice||bAuditMotifIntervals
|
||||
||bAuditOreVeinYield||bAuditLandmarkReachability||bAuditStrateTransitions
|
||||
||bAuditResolvedLayoutPassages||bAuditResolvedLayoutBoundaries
|
||||
||bAuditTileShapeReadiness||bAuditBasePageEquivalence||bAuditRouteParity;
|
||||
const bool bDeferFarWorld=bIncludeFarWorld&&!bMovingCameraTrace&&!bFarWorldAudit;
|
||||
VoxelForgeCore::ShapeBuildRequest ShapeRequest;
|
||||
ShapeRequest.minVoxel={FineMin.X,FineMin.Y,FineMin.Z};
|
||||
ShapeRequest.maxVoxel={FineMax.X,FineMax.Y,FineMax.Z};
|
||||
@@ -2037,7 +2046,7 @@ namespace StandaloneStreamBenchmark
|
||||
ShapeRequest.clipMaxVoxel={ClipMax.X,ClipMax.Y,ClipMax.Z};
|
||||
ShapeRequest.workerLimit=static_cast<uint32>(std::clamp(WorkerCount,1,16));
|
||||
ShapeRequest.parallelSignEdgeRepair=bParallelSignEdgeRepair;
|
||||
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
|
||||
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld&&!bDeferFarWorld;
|
||||
ShapeRequest.roundTunnels=bRoundTunnels;
|
||||
ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls;
|
||||
ShapeRequest.validatePassageRoutes=!bCaptureSkipWalkability;
|
||||
@@ -2047,6 +2056,7 @@ namespace StandaloneStreamBenchmark
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,
|
||||
bAllowUniformSkipsWhenHashing);
|
||||
Streamer.SetPassageHeavyPriority(ResolvedLayout);
|
||||
Streamer.SetSchedulerPolicy(SchedulerPolicy);
|
||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||
Streamer.SetTileDrainInstrumentationEnabled(bInstrumentTileDrain);
|
||||
@@ -4978,13 +4988,25 @@ namespace StandaloneStreamBenchmark
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
const FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(Desired,{},0);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||
FStandaloneDesiredTileSet EarlyDesired=Desired;
|
||||
if(bDeferFarWorld)
|
||||
std::erase_if(EarlyDesired.Tiles,[](const FVoxelTileKey& Key){return Key.Level>1;});
|
||||
const int32 EarlyDesiredCount=static_cast<int32>(EarlyDesired.Tiles.size());
|
||||
const int32 InitialDesiredCount=bDeferFarWorld?EarlyDesiredCount:
|
||||
static_cast<int32>(Desired.Tiles.size());
|
||||
FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(
|
||||
bDeferFarWorld?EarlyDesired:Desired,{},0);
|
||||
if(Update.DesiredTiles!=InitialDesiredCount)
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after shape publication but queued %d.\n",
|
||||
Desired.Tiles.size(),Update.DesiredTiles);return 7;
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %d tiles after shape publication but queued %d.\n",
|
||||
InitialDesiredCount,Update.DesiredTiles);return 7;
|
||||
}
|
||||
double DeferredFarShapeSeconds=0.0;
|
||||
bool bDeferredFarTilesQueued=false;
|
||||
if(bDeferFarWorld)
|
||||
std::printf("[ShapeSchedule] defer_far_parents=1 early_tiles=%d late_tiles=%zu start=after_base_shape_compile\n",
|
||||
EarlyDesiredCount,Desired.Tiles.size()-EarlyDesired.Tiles.size());
|
||||
if(bMovingCameraTrace)
|
||||
{
|
||||
constexpr double SamplePeriodSeconds=0.25;
|
||||
@@ -5141,7 +5163,7 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
bSceneExportPassing=SceneExport.Open(MeshExportPath,World.CoreVoxelSizeMicrons,
|
||||
CameraPositionVoxels,CameraYawDegrees,CameraPitchDegrees,
|
||||
static_cast<std::uint32_t>(Update.DesiredTiles),
|
||||
static_cast<std::uint32_t>(Desired.Tiles.size()),
|
||||
ResolvedLayout&&!ResolvedLayout->oreVeins.empty());
|
||||
if(!bSceneExportPassing)
|
||||
{
|
||||
@@ -5496,6 +5518,25 @@ namespace StandaloneStreamBenchmark
|
||||
FullDetailFinished,FullDetailExpected);
|
||||
bFullDetailReported=true;
|
||||
}
|
||||
if(bDeferFarWorld&&!bDeferredFarTilesQueued&&Finished==EarlyDesiredCount
|
||||
&&Desired.Tiles.size()>EarlyDesired.Tiles.size())
|
||||
{
|
||||
const auto FarBuildStart=Clock::now();
|
||||
Shape.buildFarWorld();
|
||||
DeferredFarShapeSeconds=std::chrono::duration<double>(
|
||||
Clock::now()-FarBuildStart).count();
|
||||
std::printf("[ShapeSchedule] defer_far_parents=1 early_ready_s=%.6f far_shape_s=%.6f action=publish_before_lod2_tiles\n",
|
||||
std::chrono::duration<double>(Clock::now()-ColdStart).count(),
|
||||
DeferredFarShapeSeconds);
|
||||
Update=Streamer.UpdateDesiredWithSet(Desired,EarlyDesired.Tiles,0);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after far-parent publication but queued %d.\n",
|
||||
Desired.Tiles.size(),Update.DesiredTiles);return 7;
|
||||
}
|
||||
bDeferredFarTilesQueued=true;
|
||||
}
|
||||
}
|
||||
if(bHasCameraStep)
|
||||
{
|
||||
@@ -5746,7 +5787,7 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
if(bExportRequested)
|
||||
{
|
||||
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Update.DesiredTiles));
|
||||
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Desired.Tiles.size()));
|
||||
std::error_code FileSizeError;
|
||||
const std::uintmax_t ExportBytes=std::filesystem::file_size(MeshExportPath,FileSizeError);
|
||||
std::printf("[MeshSceneExport] tiles=%u expected=%d bytes=%llu status=%s path=%s\n",
|
||||
@@ -5962,8 +6003,9 @@ namespace StandaloneStreamBenchmark
|
||||
const double WholeViewSeconds=std::chrono::duration<double>(WholeViewReady-ColdStart).count();
|
||||
const double StreamSeconds=std::chrono::duration<double>(WholeViewReady-StreamStart).count();
|
||||
const double FirstTileSeconds=std::chrono::duration<double>(FirstTileReady-ColdStart).count();
|
||||
std::printf("[StreamPipeline] workers=%d shape=%.3fs first_tile_ready=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=0 full_payload=1\n",
|
||||
WorkerCount,ShapeSeconds,FirstTileSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds,
|
||||
const double FarShapeSeconds=Shape.buildStats().farShapeSeconds;
|
||||
std::printf("[StreamPipeline] workers=%d shape=%.3fs far_shape=%.3fs first_tile_ready=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=0 full_payload=1\n",
|
||||
WorkerCount,ShapeSeconds,FarShapeSeconds,FirstTileSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds,
|
||||
FullDetailSeconds,WholeViewSeconds,NeighborhoodFinished,
|
||||
FullDetailFinished,FullDetailExpected,Finished,Desired.Tiles.size(),StreamSeconds);
|
||||
if(bReportMeshCoverage)
|
||||
|
||||
@@ -437,6 +437,13 @@ namespace StandaloneWorldMesher
|
||||
|
||||
class FStandaloneStreamingGenerator
|
||||
{
|
||||
private:
|
||||
struct FPassagePrioritySegment
|
||||
{
|
||||
std::array<std::int64_t,3> Minimum{};
|
||||
std::array<std::int64_t,3> Maximum{};
|
||||
};
|
||||
|
||||
public:
|
||||
static void SetCoarseFirstSchedulingEnabled(bool bEnabled)
|
||||
{bCoarseFirstSchedulingEnabled.store(bEnabled,std::memory_order_relaxed);}
|
||||
@@ -553,6 +560,35 @@ public:
|
||||
|
||||
void SetSchedulerPolicy(int32 Policy)
|
||||
{SchedulerPolicy=std::clamp(Policy,-1,3);}
|
||||
void SetPassageHeavyPriority(const VoxelForgeCore::ResolvedLayoutSnapshotV3* Layout)
|
||||
{
|
||||
PassagePrioritySegments.clear();
|
||||
if(Layout)
|
||||
{
|
||||
constexpr std::int64_t SubvoxelsPerVoxel=
|
||||
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr std::int64_t ExtraInfluenceVoxels=32;
|
||||
for(const auto& Passage:Layout->passages)
|
||||
for(std::size_t Index=1;Index<Passage.controlPoints.size();++Index)
|
||||
{
|
||||
const auto& A=Passage.controlPoints[Index-1];
|
||||
const auto& B=Passage.controlPoints[Index];
|
||||
const std::int64_t Radius=std::max(A.radiusSubvoxels,B.radiusSubvoxels)
|
||||
/SubvoxelsPerVoxel+ExtraInfluenceVoxels;
|
||||
FPassagePrioritySegment Segment;
|
||||
for(std::size_t Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
const std::int64_t AVoxel=A.positionSubvoxels[Axis]/SubvoxelsPerVoxel;
|
||||
const std::int64_t BVoxel=B.positionSubvoxels[Axis]/SubvoxelsPerVoxel;
|
||||
Segment.Minimum[Axis]=std::min(AVoxel,BVoxel)-Radius;
|
||||
Segment.Maximum[Axis]=std::max(AVoxel,BVoxel)+Radius;
|
||||
}
|
||||
PassagePrioritySegments.push_back(Segment);
|
||||
}
|
||||
}
|
||||
std::printf("[PassageHeavyPriority] route_segments=%zu enabled=%d\n",
|
||||
PassagePrioritySegments.size(),PassagePrioritySegments.empty()?0:1);
|
||||
}
|
||||
void SetSchedulerMotionSpeedMetersPerSecond(double Speed)
|
||||
{
|
||||
const double ClampedSpeed=std::max(0.0,Speed);
|
||||
@@ -911,6 +947,16 @@ private:
|
||||
else if(Key.Level<=2)NearTiles.push_back(Key);
|
||||
else FarTiles.push_back(Key);
|
||||
}
|
||||
std::unordered_map<FVoxelTileKey,std::size_t,FStandaloneTileKeyHash>
|
||||
PassageScores;
|
||||
if(!PassagePrioritySegments.empty())
|
||||
{
|
||||
PassageScores.reserve((WalkableRing.size()+NearTiles.size())*2+1);
|
||||
for(const FVoxelTileKey& Key:WalkableRing)
|
||||
PassageScores.emplace(Key,PassageOverlapScore(Key));
|
||||
for(const FVoxelTileKey& Key:NearTiles)
|
||||
PassageScores.emplace(Key,PassageOverlapScore(Key));
|
||||
}
|
||||
const auto IsAhead=[&](const FVoxelTileKey& Key)
|
||||
{
|
||||
if(TravelDirection.X*TravelDirection.X+TravelDirection.Y*TravelDirection.Y<=1.0e-12f)
|
||||
@@ -925,6 +971,12 @@ private:
|
||||
std::sort(Group.begin(),Group.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
{
|
||||
if(A.Level!=B.Level)return bNearFirst?A.Level<B.Level:A.Level>B.Level;
|
||||
if(A.Level<=2&&!PassageScores.empty())
|
||||
{
|
||||
const std::size_t ScoreA=PassageScores.at(A);
|
||||
const std::size_t ScoreB=PassageScores.at(B);
|
||||
if(ScoreA!=ScoreB)return ScoreA>ScoreB;
|
||||
}
|
||||
const bool bAheadA=IsAhead(A),bAheadB=IsAhead(B);
|
||||
if(bAheadA!=bAheadB)return bAheadA;
|
||||
const double DA=StandaloneWorldMesher::TileDistanceSquaredChunks(A,NewDesired.CenterChunk);
|
||||
@@ -1253,6 +1305,25 @@ private:
|
||||
{
|
||||
return TimelineFrameOrdinal?TimelineFrameOrdinal->load(std::memory_order_relaxed):0;
|
||||
}
|
||||
std::size_t PassageOverlapScore(const FVoxelTileKey& Key) const
|
||||
{
|
||||
const FIntVector Origin=Key.OriginVoxels();
|
||||
const std::int64_t TileMinimum[3]={Origin.X,Origin.Y,Origin.Z};
|
||||
const std::int64_t TileMaximum[3]={
|
||||
static_cast<std::int64_t>(Origin.X)+Key.ExtentVoxels(),
|
||||
static_cast<std::int64_t>(Origin.Y)+Key.ExtentVoxels(),
|
||||
static_cast<std::int64_t>(Origin.Z)+Key.ExtentVoxels()};
|
||||
std::size_t Score=0;
|
||||
for(const FPassagePrioritySegment& Segment:PassagePrioritySegments)
|
||||
{
|
||||
bool bOverlap=true;
|
||||
for(std::size_t Axis=0;Axis<3;++Axis)
|
||||
bOverlap=bOverlap&&Segment.Maximum[Axis]>=TileMinimum[Axis]
|
||||
&&Segment.Minimum[Axis]<=TileMaximum[Axis];
|
||||
Score+=bOverlap?1u:0u;
|
||||
}
|
||||
return Score;
|
||||
}
|
||||
static void PrintTileStageEvent(const FStandaloneCpuTileTimelineEvent& Event)
|
||||
{
|
||||
const auto& D=Event.Diagnostics;
|
||||
@@ -1386,6 +1457,7 @@ private:
|
||||
bool bActiveLaneBatching=false;
|
||||
int32 ProofBlockCells=8;
|
||||
bool bAuditUniformBlockProofs=false;
|
||||
std::vector<FPassagePrioritySegment> PassagePrioritySegments;
|
||||
std::atomic<uint64> CacheHitCount{0};
|
||||
mutable std::mutex Mutex;
|
||||
std::condition_variable Wake;
|
||||
|
||||
Reference in new issue
Block a user