streaming: far LODs keep a share while near work is pending (L79); no more horizon starvation
L78's strict near-first order starved far LODs at 30 m/s (100% far holes, never completed). Unreal policy 2: once the collision ring is ready, LOD0-2 keep their reserve and far tiles are admitted as low-priority Horizon work; headless gives far work 1 of every 16 requests. Moving camera, far holes while moving: 1.4 m/s L77 8% / L78 29% / L79 31%; 30 m/s L77 8% / L78 100% / L79 72% (completes on dwell). Near readiness while moving at 30 m/s: 52 / 51 / 67%. Still worse than L77 for fast travel -> speed-adaptive policy next. World unchanged (v213). 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
ec20994a8e
commit
2c8a26c0d0
11 files changed
+833
-30
No files matched your search
@@ -1874,7 +1874,9 @@ namespace StandaloneStreamBenchmark
|
||||
bool bParallelSignEdgeRepair=false,bool bAuditNativeLattice=false,
|
||||
bool bLatticeOrphanControl=false,bool bCaptureSkipWalkability=false,
|
||||
bool bProfilePassageComposer=false,bool bAllowUniformSkipsWhenHashing=false,
|
||||
bool bLinux77Profile=false)
|
||||
bool bLinux77Profile=false,int32 SchedulerPolicy=-1,
|
||||
bool bMovingCameraTrace=false,double MovingCameraSpeedMetersPerSecond=0.0,
|
||||
double MovingCameraDurationSeconds=12.0,double MovingCameraDwellSeconds=5.0)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||
@@ -2038,6 +2040,7 @@ namespace StandaloneStreamBenchmark
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,
|
||||
bAllowUniformSkipsWhenHashing);
|
||||
Streamer.SetSchedulerPolicy(SchedulerPolicy);
|
||||
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
||||
Streamer.SetTileDrainInstrumentationEnabled(bInstrumentTileDrain);
|
||||
Streamer.SetLinux77ProfileEnabled(bLinux77Profile);
|
||||
@@ -4975,6 +4978,152 @@ namespace StandaloneStreamBenchmark
|
||||
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after shape publication but queued %d.\n",
|
||||
Desired.Tiles.size(),Update.DesiredTiles);return 7;
|
||||
}
|
||||
if(bMovingCameraTrace)
|
||||
{
|
||||
constexpr double SamplePeriodSeconds=0.25;
|
||||
using FTileSet=std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash>;
|
||||
FTileSet Resident;
|
||||
std::unordered_map<FVoxelTileKey,Clock::time_point,FStandaloneTileKeyHash> FarMissingSince;
|
||||
const double VoxelMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
||||
const auto TotalSeconds=MovingCameraDurationSeconds+MovingCameraDwellSeconds;
|
||||
const auto TraceStart=Clock::now();
|
||||
int32 SampleIndex=0;
|
||||
int32 GenerationFailures=0;
|
||||
double FirstAllFarReadySeconds=-1.0;
|
||||
int32 Samples=0;
|
||||
std::printf("[L79MovingCheckpoint] stage=start desired=%zu policy=%d speed_mps=%.3f\n",
|
||||
Desired.Tiles.size(),SchedulerPolicy,MovingCameraSpeedMetersPerSecond);
|
||||
std::fflush(stdout);
|
||||
auto DrainReady=[&]()
|
||||
{
|
||||
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
|
||||
for(const FStandaloneCpuTile& Tile:Ready)
|
||||
{
|
||||
if(Tile.bGenerationFailed)++GenerationFailures;
|
||||
else Resident.insert(Tile.Key);
|
||||
}
|
||||
};
|
||||
for(;;++SampleIndex)
|
||||
{
|
||||
const double ScheduledSeconds=std::min(TotalSeconds,
|
||||
static_cast<double>(SampleIndex)*SamplePeriodSeconds);
|
||||
std::this_thread::sleep_until(TraceStart+std::chrono::duration<double>(ScheduledSeconds));
|
||||
const auto SampleAt=Clock::now();
|
||||
const double ElapsedSeconds=std::min(TotalSeconds,
|
||||
std::chrono::duration<double>(SampleAt-TraceStart).count());
|
||||
const double MovingSeconds=std::min(MovingCameraDurationSeconds,ElapsedSeconds);
|
||||
FVector Camera=CameraPositionVoxels;
|
||||
Camera.X+=static_cast<float>(MovingCameraSpeedMetersPerSecond*MovingSeconds/VoxelMetres);
|
||||
DrainReady();
|
||||
if(SampleIndex==0)
|
||||
{
|
||||
std::printf("[L79MovingCheckpoint] stage=drained resident=%zu failures=%d\n",
|
||||
Resident.size(),GenerationFailures);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
FStandaloneDesiredTileSet CurrentDesired=Desired;
|
||||
CurrentDesired.CenterChunk=FIntVector(
|
||||
static_cast<int32>(std::floor(Camera.X/CHUNK_SIZE)),
|
||||
static_cast<int32>(std::floor(Camera.Y/CHUNK_SIZE)),
|
||||
static_cast<int32>(std::floor(Camera.Z/CHUNK_SIZE)));
|
||||
const auto CoreDesired=Shape.desiredTiles({Camera.X,Camera.Y,Camera.Z});
|
||||
CurrentDesired.Tiles.clear();CurrentDesired.Tiles.reserve(CoreDesired.size());
|
||||
for(const auto& Key:CoreDesired)
|
||||
{
|
||||
if(MaxLevel>=0&&Key.lod>MaxLevel)continue;
|
||||
CurrentDesired.Tiles.emplace_back(
|
||||
FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
||||
}
|
||||
const auto BeforeUpdate=Clock::now();
|
||||
const std::vector<FVoxelTileKey> ResidentKeys(Resident.begin(),Resident.end());
|
||||
const FStandaloneStreamingUpdate MovingUpdate=Streamer.UpdateDesiredWithSet(
|
||||
CurrentDesired,ResidentKeys,static_cast<uint64>(SampleIndex),Camera);
|
||||
if(SampleIndex==0)
|
||||
{
|
||||
std::printf("[L79MovingCheckpoint] stage=updated desired=%d new_jobs=%d\n",
|
||||
MovingUpdate.DesiredTiles,MovingUpdate.NewJobs);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
const double UpdateMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
Clock::now()-BeforeUpdate).count();
|
||||
if(MovingUpdate.DesiredTiles!=static_cast<int32>(CurrentDesired.Tiles.size()))
|
||||
{
|
||||
Streamer.Stop();
|
||||
std::fprintf(stderr,"[MovingCameraTrace] desired-set mismatch expected=%zu actual=%d\n",
|
||||
CurrentDesired.Tiles.size(),MovingUpdate.DesiredTiles);
|
||||
return 9;
|
||||
}
|
||||
std::size_t NearDesired=0,NearResident=0,FarDesired=0,FarResident=0,FarCovered=0;
|
||||
double FarStaleAgeSum=0.0,FarStaleAgeMax=0.0;
|
||||
const auto FloorDiv=[](int32 Value,int32 Divisor)
|
||||
{
|
||||
const int32 Quotient=Value/Divisor, Remainder=Value%Divisor;
|
||||
return Quotient-((Remainder!=0&&Remainder<0)?1:0);
|
||||
};
|
||||
std::unordered_map<FVoxelTileKey,Clock::time_point,FStandaloneTileKeyHash> CurrentFarMissing;
|
||||
for(const FVoxelTileKey& Key:CurrentDesired.Tiles)
|
||||
{
|
||||
const bool bResident=Resident.contains(Key);
|
||||
if(Key.Level<=2)
|
||||
{
|
||||
++NearDesired;
|
||||
NearResident+=bResident?1u:0u;
|
||||
}
|
||||
else
|
||||
{
|
||||
++FarDesired;
|
||||
FarResident+=bResident?1u:0u;
|
||||
bool bFarCovered=bResident;
|
||||
for(int32 ParentLOD=Key.Level+1;
|
||||
!bFarCovered&&ParentLOD<=World.MaxClipLevel;++ParentLOD)
|
||||
{
|
||||
const int32 Scale=1<<(ParentLOD-Key.Level);
|
||||
const FVoxelTileKey Parent(FIntVector(
|
||||
FloorDiv(Key.Coord.X,Scale),FloorDiv(Key.Coord.Y,Scale),
|
||||
FloorDiv(Key.Coord.Z,Scale)),ParentLOD);
|
||||
bFarCovered=Resident.contains(Parent);
|
||||
}
|
||||
FarCovered+=bFarCovered?1u:0u;
|
||||
if(!bResident)
|
||||
{
|
||||
auto [It,bInserted]=FarMissingSince.try_emplace(Key,SampleAt);
|
||||
(void)bInserted;
|
||||
const double Age=std::chrono::duration<double>(SampleAt-It->second).count();
|
||||
FarStaleAgeSum+=Age;
|
||||
FarStaleAgeMax=std::max(FarStaleAgeMax,Age);
|
||||
CurrentFarMissing.emplace(Key,It->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
FarMissingSince.swap(CurrentFarMissing);
|
||||
const double NearReadyPercent=100.0*static_cast<double>(NearResident)
|
||||
/static_cast<double>(std::max<std::size_t>(1,NearDesired));
|
||||
const double FarMissingPercent=100.0*static_cast<double>(FarDesired-FarResident)
|
||||
/static_cast<double>(std::max<std::size_t>(1,FarDesired));
|
||||
const double FarHolePercent=100.0*static_cast<double>(FarDesired-FarCovered)
|
||||
/static_cast<double>(std::max<std::size_t>(1,FarDesired));
|
||||
const double FarStaleAgeMean=FarDesired>FarResident
|
||||
?FarStaleAgeSum/static_cast<double>(FarDesired-FarResident):0.0;
|
||||
if(FarDesired>0&&FarDesired==FarResident&&FirstAllFarReadySeconds<0.0)
|
||||
FirstAllFarReadySeconds=ElapsedSeconds;
|
||||
const char* Phase=ElapsedSeconds<MovingCameraDurationSeconds?"move":"dwell";
|
||||
std::printf("[L79MovingSample] policy=%d speed_mps=%.3f t_s=%.3f phase=%s x_m=%.3f near=%zu/%zu near_ready_pct=%.3f far=%zu/%zu far_missing_pct=%.3f far_covered=%zu/%zu far_hole_pct=%.3f far_stale_mean_s=%.3f far_stale_max_s=%.3f queued_jobs=%d queue_update_ms=%.3f generation_failures=%d\n",
|
||||
SchedulerPolicy,MovingCameraSpeedMetersPerSecond,ElapsedSeconds,Phase,
|
||||
Camera.X*VoxelMetres,NearResident,NearDesired,NearReadyPercent,
|
||||
FarResident,FarDesired,FarMissingPercent,FarCovered,FarDesired,
|
||||
FarHolePercent,FarStaleAgeMean,FarStaleAgeMax,
|
||||
MovingUpdate.NewJobs,UpdateMilliseconds,GenerationFailures);
|
||||
std::fflush(stdout);
|
||||
++Samples;
|
||||
if(ElapsedSeconds>=TotalSeconds)break;
|
||||
}
|
||||
std::printf("[L79MovingSummary] policy=%d speed_mps=%.3f move_s=%.3f dwell_s=%.3f samples=%d resident_tiles=%zu first_all_far_ready_s=%.3f generation_failures=%d priority=background_workers_only status=%s\n",
|
||||
SchedulerPolicy,MovingCameraSpeedMetersPerSecond,MovingCameraDurationSeconds,
|
||||
MovingCameraDwellSeconds,Samples,Resident.size(),FirstAllFarReadySeconds,
|
||||
GenerationFailures,GenerationFailures==0?"PASS":"FAIL");
|
||||
Streamer.Stop();
|
||||
return GenerationFailures==0?0:9;
|
||||
}
|
||||
FSceneExportWriter SceneExport;
|
||||
const bool bExportRequested=!MeshExportPath.empty();
|
||||
bool bSceneExportPassing=true;
|
||||
|
||||
Reference in new issue
Block a user