wip(standalone): GPU step 15 -- cold-start drain: hybrid whole view 3.2 -> 1.25-1.68 s
Timeline showed the post-export window was frame-paced: the 4-batch / 2 ms per-frame upload budget held CPU-fallback tiles up to 755 ms after they finished, and 35 GPU batches drained one per frame. Before the first whole view, GPU work now drains as one batch and CPU fallback starts with GPU input preparation (--gpu-far-no-cold-drain = paced control). Luna's race: CPU 1.393 s vs GPU 1.677 s. My interleaved runs (Step15Timeline, RunGpuFarViewer): GPU 2.902 (first launch) / 1.252 / 1.209 s; CPU 2.196 / 2.250 / 2.061 s. The CPU viewer still uses the paced upload budget at cold start (stream 674 -> 2250 ms in my runs vs 741 -> 1422 in Luna's), so the race is not yet fair. Audits: hashes, walk 5,642/5,642, 0 diffs vs CPU/WARP. 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
f322b1c089
commit
c152708a67
16 files changed
+3378
-86
No files matched your search
@@ -1317,6 +1317,10 @@ namespace
|
||||
uint8 ActiveSkirtFaceMask=0x3f;
|
||||
uint64 Samples=0;
|
||||
double WorkerSeconds=0.0;
|
||||
std::chrono::steady_clock::time_point CpuQueuedAt{};
|
||||
std::chrono::steady_clock::time_point CpuStartedAt{};
|
||||
std::chrono::steady_clock::time_point CpuFinishedAt{};
|
||||
bool bColdTimelineResidentReported=false;
|
||||
};
|
||||
struct FStreamingGpuBatch
|
||||
{
|
||||
@@ -1365,17 +1369,25 @@ namespace
|
||||
uint8 VisibleSkirtFaceMask=0;
|
||||
bool bSkirtDrawArgsAudited=false;
|
||||
double GpuDispatchMs=0.0;
|
||||
std::chrono::steady_clock::time_point PreparedAt{};
|
||||
std::chrono::steady_clock::time_point SubmittedAt{};
|
||||
std::chrono::steady_clock::time_point CompletedAt{};
|
||||
bool bGpuComplete=false;
|
||||
bool bTimingResolved=false;
|
||||
bool bColdTimelineResidentReported=false;
|
||||
};
|
||||
struct FGpuFarBatch
|
||||
{
|
||||
ID3D11Query* CompletionEvent=nullptr;
|
||||
VoxelForgeGpuFarViewer::GpuTimingQueries TimingQueries{};
|
||||
uint32 TileCount=0;
|
||||
uint32 ResidentCount=0;
|
||||
std::vector<FVoxelTileKey> Keys;
|
||||
uint64 TimestampFrequency=0;
|
||||
double GpuDispatchMs=0.0;
|
||||
std::chrono::steady_clock::time_point PreparedAt{};
|
||||
std::chrono::steady_clock::time_point SubmittedAt{};
|
||||
std::chrono::steady_clock::time_point CompletedAt{};
|
||||
bool bGpuComplete=false,bTimingResolved=false;
|
||||
};
|
||||
|
||||
@@ -1487,6 +1499,7 @@ namespace
|
||||
bool bShapeCompare=false;
|
||||
bool bFarShapeStagePending=false;
|
||||
bool bGpuFarEnabled=false;
|
||||
bool bDisableGpuColdStartDrain=false;
|
||||
bool bGpuFarAuditEnabled=false;
|
||||
bool bGpuFarShaderPrewarm=false,bDisableGpuWarmupPresentHold=false;
|
||||
bool bGpuFarWarmupResourcesOnly=false;
|
||||
@@ -1495,6 +1508,10 @@ namespace
|
||||
bool bGpuFarWarmupOnly=false;
|
||||
std::string GpuWarmupEntryFilter;
|
||||
std::thread GpuShaderWarmupThread;
|
||||
std::thread GpuFarPreparationThread;
|
||||
std::atomic<bool> bGpuFarPreparationFinished{false};
|
||||
bool bGpuFarPreparationStarted=false,bGpuFarInputsFinalized=false;
|
||||
bool bGpuWarmupCompleted=false,bGpuFarTimelineEnabled=false;
|
||||
bool bGpuShaderWarmupQueueSucceeded=false;
|
||||
std::string GpuShaderWarmupQueueError;
|
||||
bool bGpuFarAuditReported=false;
|
||||
@@ -1529,6 +1546,8 @@ namespace
|
||||
std::vector<uint32> GpuFarFreeOutputSlots;
|
||||
std::unordered_map<FVoxelTileKey,VoxelForgeCore::FarFieldTileInput,FStandaloneTileKeyHash> GpuFarPreparedInputs;
|
||||
std::unordered_map<FVoxelTileKey,std::string,FStandaloneTileKeyHash> GpuFarPreparationErrors;
|
||||
std::unordered_map<FVoxelTileKey,std::chrono::steady_clock::time_point,FStandaloneTileKeyHash> GpuFarPreparedAt;
|
||||
std::vector<FVoxelTileKey> GpuFarCandidateTiles,GpuFarCpuFallbackTiles,GpuFarNeedPreparation;
|
||||
double GpuFarPreparationWallMs=0.0,GpuFarPreparationWorkerMs=0.0;
|
||||
uint64 GpuFarPumpCalls=0,GpuFarCompletionPolls=0;
|
||||
double GpuFarFirstPumpMs=-1.0,GpuFarLastPumpMs=-1.0;
|
||||
@@ -1568,6 +1587,7 @@ namespace
|
||||
std::string CapturePath;
|
||||
std::string MeshCachePath;
|
||||
std::chrono::steady_clock::time_point ColdProcessStart{};
|
||||
std::atomic<uint64> ColdTimelineFrameOrdinal{0};
|
||||
std::chrono::steady_clock::time_point GenerationStart{};
|
||||
bool bShowLevel[MaxLevels+1]={true,true,true,true,true,true,true,true,true};
|
||||
bool Keys[256]{};
|
||||
@@ -1595,6 +1615,7 @@ namespace
|
||||
double BatchUploadBudgetMilliseconds=2.0;
|
||||
|
||||
~FApp(){
|
||||
if(GpuFarPreparationThread.joinable())GpuFarPreparationThread.join();
|
||||
if(GpuShaderWarmupThread.joinable())GpuShaderWarmupThread.join();
|
||||
StopGeneration(true);ReleaseMeshes();
|
||||
if(SolidRasterizer)SolidRasterizer->Release();if(WireRasterizer)WireRasterizer->Release();
|
||||
@@ -1619,6 +1640,15 @@ namespace
|
||||
Tile={};
|
||||
}
|
||||
|
||||
double ColdTimelineMilliseconds(std::chrono::steady_clock::time_point At)const
|
||||
{
|
||||
return At.time_since_epoch().count()==0?-1.0:
|
||||
std::chrono::duration<double,std::milli>(At-ColdProcessStart).count();
|
||||
}
|
||||
|
||||
uint64 ColdTimelineFrame()const
|
||||
{return ColdTimelineFrameOrdinal.load(std::memory_order_relaxed);}
|
||||
|
||||
static void ReleaseGpuFarBatch(FGpuFarBatch& Batch)
|
||||
{
|
||||
if(Batch.CompletionEvent)Batch.CompletionEvent->Release();
|
||||
@@ -1654,7 +1684,11 @@ namespace
|
||||
for(FGpuFarBatch& Batch:GpuFarBatches)ReleaseGpuFarBatch(Batch);
|
||||
GpuFarBatches.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
||||
GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
|
||||
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();
|
||||
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();GpuFarPreparedAt.clear();
|
||||
GpuFarCandidateTiles.clear();GpuFarCpuFallbackTiles.clear();GpuFarNeedPreparation.clear();
|
||||
bGpuFarPreparationStarted=false;bGpuFarInputsFinalized=false;
|
||||
bGpuFarPreparationFinished.store(false,std::memory_order_relaxed);
|
||||
bGpuWarmupCompleted=false;
|
||||
GpuFarPreparationWallMs=0.0;GpuFarPreparationWorkerMs=0.0;
|
||||
GpuFarPumpCalls=0;GpuFarCompletionPolls=0;
|
||||
GpuFarFirstPumpMs=GpuFarLastPumpMs=GpuFarFirstSubmitMs=GpuFarLastSubmitMs=-1.0;
|
||||
@@ -1683,6 +1717,8 @@ namespace
|
||||
(void)bCancel;
|
||||
if(Streamer)Streamer->Stop();
|
||||
Streamer.reset();
|
||||
if(GpuFarPreparationThread.joinable())GpuFarPreparationThread.join();
|
||||
bGpuFarPreparationStarted=false;
|
||||
if(FullShapeBuildThread.joinable())FullShapeBuildThread.join();
|
||||
PendingFullShapeWorld.reset();
|
||||
bFullShapeBuildFinished.store(false,std::memory_order_relaxed);
|
||||
@@ -2259,14 +2295,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
(!bShapeWorld&&MeshCache.IsLoaded())?&MeshCache:nullptr,
|
||||
bFarShapeStagePending?World->FullResClipLevels-1:-1,
|
||||
false,false,false,false,ShapeWorld.get());
|
||||
if(bGpuFarTimelineEnabled)
|
||||
Streamer->EnableColdTimelineTrace(ColdProcessStart,&ColdTimelineFrameOrdinal);
|
||||
if(bGpuFarEnabled&&ShapeWorld&&ShapeWorld->hasFarWorld())
|
||||
{
|
||||
ScheduleGpuFarTiles(DesiredTiles);
|
||||
if(bGpuFarAuditEnabled)
|
||||
{
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarEligibleTiles.size());
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarCandidateTiles.size());
|
||||
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||
if(Key.Level>=6&&GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
if(Key.Level>=6&&std::find(GpuFarCandidateTiles.begin(),GpuFarCandidateTiles.end(),Key)
|
||||
!=GpuFarCandidateTiles.end())AuditKeys.push_back(Key);
|
||||
GpuFarAudit=std::make_unique<FFarSurvivalAudit>(*World,*ShapeWorld,AuditKeys,false);
|
||||
}
|
||||
}
|
||||
@@ -2293,6 +2332,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::printf("Streaming seed %d%s%s: %d desired tiles, %d full-detail tiles, %d workers; upload budget %d batches / %.1f ms per frame.\n",
|
||||
Seed,TunnelMode,bShapeWorld?" [shape intervals]":"",Update.DesiredTiles,
|
||||
Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds);
|
||||
StartGpuFarPreparation();
|
||||
if(bShapePipelineActive)
|
||||
{
|
||||
std::string ShapeError;
|
||||
@@ -2316,6 +2356,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
void StartFarShapeGeneration()
|
||||
{
|
||||
if(!bFarShapeStagePending||!ShapeWorld||ShapeWorld->hasFarWorld()||!Streamer)return;
|
||||
if(GpuFarPreparationThread.joinable())GpuFarPreparationThread.join();
|
||||
ShapeWorld->buildFarWorld();
|
||||
bFarShapeStagePending=false;
|
||||
Streamer->Stop();
|
||||
@@ -2324,14 +2365,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
Streamer.reset();
|
||||
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,nullptr,-1,
|
||||
false,false,false,false,ShapeWorld.get());
|
||||
if(bGpuFarTimelineEnabled)
|
||||
Streamer->EnableColdTimelineTrace(ColdProcessStart,&ColdTimelineFrameOrdinal);
|
||||
if(bGpuFarEnabled)
|
||||
{
|
||||
ScheduleGpuFarTiles(DesiredTiles);
|
||||
if(bGpuFarAuditEnabled&&World)
|
||||
{
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarEligibleTiles.size());
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarCandidateTiles.size());
|
||||
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||
if(Key.Level>=6&&GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
if(Key.Level>=6&&std::find(GpuFarCandidateTiles.begin(),GpuFarCandidateTiles.end(),Key)
|
||||
!=GpuFarCandidateTiles.end())AuditKeys.push_back(Key);
|
||||
GpuFarAudit=std::make_unique<FFarSurvivalAudit>(*World,*ShapeWorld,AuditKeys,false);
|
||||
}
|
||||
}
|
||||
@@ -2344,6 +2388,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
bGenerationRunning=true;
|
||||
std::printf("[FarStreamStart] desired=%d retained=%d new_jobs=%d workers=%d\n",
|
||||
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,WorkerCount);
|
||||
StartGpuFarPreparation();
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -2359,6 +2404,46 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
}
|
||||
|
||||
void ReportGpuTileCompletion(const FVoxelTileKey& Key,FGpuFarTile& Tile,
|
||||
uint32 BatchIndex=UINT32_MAX)
|
||||
{
|
||||
if(Tile.CompletedAt.time_since_epoch().count()!=0)return;
|
||||
Tile.CompletedAt=std::chrono::steady_clock::now();
|
||||
if(!bGpuFarTimelineEnabled)return;
|
||||
std::printf("[GpuTileTimeline] event=completed key=(%d,%d,%d,lod%d) batch=%u at_ms=%.3f frame=%llu submitted_ms=%.3f\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,BatchIndex,
|
||||
ColdTimelineMilliseconds(Tile.CompletedAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
ColdTimelineMilliseconds(Tile.SubmittedAt));
|
||||
std::printf("[GpuTileTimeline] event=uploaded key=(%d,%d,%d,lod%d) batch=%u at_ms=%.3f frame=%llu method=direct_gpu_compute_output copy_upload=none\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,BatchIndex,
|
||||
ColdTimelineMilliseconds(Tile.CompletedAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()));
|
||||
}
|
||||
|
||||
void ReportGpuTileResident(const FVoxelTileKey& Key,FGpuFarTile& Tile)
|
||||
{
|
||||
if(Tile.bColdTimelineResidentReported)return;
|
||||
Tile.bColdTimelineResidentReported=true;
|
||||
const auto ResidentAt=std::chrono::steady_clock::now();
|
||||
if(bGpuFarTimelineEnabled)
|
||||
std::printf("[GpuTileTimeline] event=resident key=(%d,%d,%d,lod%d) batch=%u at_ms=%.3f frame=%llu submitted_ms=%.3f completed_ms=%.3f\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,Tile.BatchIndex,
|
||||
ColdTimelineMilliseconds(ResidentAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
ColdTimelineMilliseconds(Tile.SubmittedAt),ColdTimelineMilliseconds(Tile.CompletedAt));
|
||||
if(Tile.BatchIndex!=UINT32_MAX&&Tile.BatchIndex<GpuFarBatches.size())
|
||||
{
|
||||
FGpuFarBatch& Batch=GpuFarBatches[Tile.BatchIndex];
|
||||
++Batch.ResidentCount;
|
||||
if(bGpuFarTimelineEnabled&&Batch.ResidentCount==Batch.TileCount)
|
||||
std::printf("[GpuBatchTimeline] event=resident batch=%u at_ms=%.3f frame=%llu tile_count=%u submitted_ms=%.3f completed_ms=%.3f\n",
|
||||
Tile.BatchIndex,ColdTimelineMilliseconds(ResidentAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),Batch.TileCount,
|
||||
ColdTimelineMilliseconds(Batch.SubmittedAt),ColdTimelineMilliseconds(Batch.CompletedAt));
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRenderedTileReady(const FVoxelTileKey& Key)
|
||||
{
|
||||
if(bGpuFarEnabled&&ShapeWorld&&ShapeWorld->hasFarWorld()
|
||||
@@ -2375,10 +2460,30 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
Batch.bGpuComplete=true;
|
||||
const auto PollAt=std::chrono::steady_clock::now();
|
||||
Batch.CompletedAt=PollAt;
|
||||
const double PollMs=std::chrono::duration<double,std::milli>(PollAt-ColdProcessStart).count();
|
||||
if(GpuFarFirstCompletionPollMs<0.0)GpuFarFirstCompletionPollMs=PollMs;
|
||||
GpuFarLastCompletionPollMs=PollMs;++GpuFarCompletionPolls;
|
||||
Batch.CompletionEvent->Release();Batch.CompletionEvent=nullptr;
|
||||
if(bGpuFarTimelineEnabled)
|
||||
{
|
||||
std::printf("[GpuBatchTimeline] event=completed batch=%u at_ms=%.3f frame=%llu tile_count=%u submitted_ms=%.3f\n",
|
||||
Tile.BatchIndex,ColdTimelineMilliseconds(PollAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),Batch.TileCount,
|
||||
ColdTimelineMilliseconds(Batch.SubmittedAt));
|
||||
std::printf("[GpuBatchTimeline] event=uploaded batch=%u at_ms=%.3f frame=%llu method=direct_gpu_compute_output copy_upload=none\n",
|
||||
Tile.BatchIndex,ColdTimelineMilliseconds(PollAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()));
|
||||
}
|
||||
for(const FVoxelTileKey& BatchKey:Batch.Keys)
|
||||
{
|
||||
auto BatchTileIt=GpuFarTiles.find(BatchKey);
|
||||
if(BatchTileIt!=GpuFarTiles.end())
|
||||
{
|
||||
BatchTileIt->second.bGpuComplete=true;
|
||||
ReportGpuTileCompletion(BatchKey,BatchTileIt->second,Tile.BatchIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Batch.bGpuComplete&&!Batch.bTimingResolved)
|
||||
{
|
||||
@@ -2402,7 +2507,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,TileDispatchMs);
|
||||
}
|
||||
}
|
||||
if(Batch.bGpuComplete)RecordGpuCrossingTileReady(Key);
|
||||
if(Batch.bGpuComplete)
|
||||
{
|
||||
RecordGpuCrossingTileReady(Key);
|
||||
ReportGpuTileResident(Key,Tile);
|
||||
}
|
||||
return Batch.bGpuComplete;
|
||||
}
|
||||
if(!Tile.bGpuComplete&&Tile.CompletionEvent&&Context
|
||||
@@ -2410,6 +2519,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
Tile.bGpuComplete=true;
|
||||
Tile.CompletionEvent->Release();Tile.CompletionEvent=nullptr;
|
||||
ReportGpuTileCompletion(Key,Tile);
|
||||
}
|
||||
if(Tile.bGpuComplete&&!Tile.bTimingResolved)
|
||||
{
|
||||
@@ -2425,7 +2535,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
Tile.bTimingResolved=true;
|
||||
}
|
||||
}
|
||||
if(Tile.bGpuComplete)RecordGpuCrossingTileReady(Key);
|
||||
if(Tile.bGpuComplete)
|
||||
{
|
||||
RecordGpuCrossingTileReady(Key);
|
||||
ReportGpuTileResident(Key,Tile);
|
||||
}
|
||||
return Tile.bGpuComplete;
|
||||
}
|
||||
return ResidentTiles.contains(Key);
|
||||
@@ -2436,9 +2550,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
if(NeedPreparation.empty())return;
|
||||
const auto PrepareStart=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=gpu_input_export begin_ms=%.3f tiles=%zu\n",
|
||||
std::chrono::duration<double,std::milli>(PrepareStart-ColdProcessStart).count(),NeedPreparation.size());
|
||||
ColdTimelineMilliseconds(PrepareStart),NeedPreparation.size());
|
||||
std::vector<std::optional<VoxelForgeCore::FarFieldTileInput>> Inputs(NeedPreparation.size());
|
||||
std::vector<std::string> Errors(NeedPreparation.size());
|
||||
std::vector<std::chrono::steady_clock::time_point> FinishedAt(NeedPreparation.size());
|
||||
std::vector<uint64> FinishedFrame(NeedPreparation.size());
|
||||
const std::size_t ThreadCount=std::min<std::size_t>(8,NeedPreparation.size());
|
||||
std::atomic<std::size_t> Next{0};
|
||||
std::vector<double> WorkerElapsed(ThreadCount,0.0);
|
||||
@@ -2475,6 +2591,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
}
|
||||
catch(const std::exception& Error) { Errors[Index]=Error.what(); }
|
||||
FinishedAt[Index]=std::chrono::steady_clock::now();
|
||||
FinishedFrame[Index]=ColdTimelineFrame();
|
||||
}
|
||||
WorkerElapsed[Worker]=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-WorkerStart).count();
|
||||
@@ -2488,7 +2606,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::size_t Failed=0;
|
||||
for(std::size_t Index=0;Index<NeedPreparation.size();++Index)
|
||||
{
|
||||
if(Inputs[Index])GpuFarPreparedInputs.emplace(NeedPreparation[Index],std::move(*Inputs[Index]));
|
||||
if(Inputs[Index])
|
||||
{
|
||||
GpuFarPreparedInputs.emplace(NeedPreparation[Index],std::move(*Inputs[Index]));
|
||||
GpuFarPreparedAt.emplace(NeedPreparation[Index],FinishedAt[Index]);
|
||||
const FVoxelTileKey& Key=NeedPreparation[Index];
|
||||
if(bGpuFarTimelineEnabled)
|
||||
std::printf("[GpuTileTimeline] event=prepared key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
|
||||
ColdTimelineMilliseconds(FinishedAt[Index]),
|
||||
static_cast<unsigned long long>(FinishedFrame[Index]));
|
||||
}
|
||||
else
|
||||
{
|
||||
++Failed;
|
||||
@@ -2499,7 +2627,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::printf("[GpuFarInputPrepare] tiles=%zu workers=%zu wall_ms=%.3f worker_sum_ms=%.3f failed=%zu\n",
|
||||
NeedPreparation.size(),ThreadCount,PreparationMs,WorkerSumMs,Failed);
|
||||
std::printf("[ColdTimeline] stage=gpu_input_export end_ms=%.3f duration_ms=%.3f worker_sum_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count(),
|
||||
ColdTimelineMilliseconds(std::chrono::steady_clock::now()),
|
||||
PreparationMs,WorkerSumMs);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
@@ -2507,27 +2635,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
void ScheduleGpuFarTiles(const std::vector<FVoxelTileKey>& Keys)
|
||||
{
|
||||
GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
||||
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();
|
||||
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();GpuFarPreparedAt.clear();
|
||||
GpuFarCandidateTiles.clear();GpuFarCpuFallbackTiles.clear();GpuFarNeedPreparation.clear();
|
||||
bGpuFarPreparationStarted=false;bGpuFarInputsFinalized=false;
|
||||
bGpuFarPreparationFinished.store(false,std::memory_order_relaxed);
|
||||
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld())return;
|
||||
if(bGpuFarShaderPrewarm)
|
||||
{
|
||||
if(GpuShaderWarmupThread.joinable())GpuShaderWarmupThread.join();
|
||||
if(!bGpuShaderWarmupQueueSucceeded)
|
||||
{
|
||||
std::fprintf(stderr,"[GpuShaderWarmupError] queue: %s\n",
|
||||
GpuShaderWarmupQueueError.empty()?"warmup queue failed":GpuShaderWarmupQueueError.c_str());
|
||||
bGpuFarEnabled=false;
|
||||
return;
|
||||
}
|
||||
std::string WarmupError;
|
||||
if(!VoxelForgeGpuFarViewer::CompleteFarShaderWarmup(Device,Context,
|
||||
GpuFarShaderPath,WarmupError))
|
||||
{
|
||||
std::fprintf(stderr,"[GpuShaderWarmupError] %s\n",WarmupError.c_str());
|
||||
bGpuFarEnabled=false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::vector<FVoxelTileKey> CpuTiles;CpuTiles.reserve(Keys.size());
|
||||
std::vector<FVoxelTileKey> Eligible;Eligible.reserve(Keys.size());
|
||||
uint64 SkirtTransitionFallbackTiles=0;
|
||||
@@ -2606,49 +2718,102 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::printf("[CaptureOutputSlotProbe] slot=%d pool_slots=%u\n",
|
||||
CaptureOutputSlot,GpuFarOutputCapacitySlots);
|
||||
}
|
||||
if(!Eligible.empty())
|
||||
GpuFarCandidateTiles=std::move(Eligible);
|
||||
GpuFarCpuFallbackTiles=std::move(CpuTiles);
|
||||
GpuFarNeedPreparation.reserve(GpuFarCandidateTiles.size());
|
||||
for(const FVoxelTileKey& Key:GpuFarCandidateTiles)
|
||||
if(!GpuFarTiles.contains(Key))GpuFarNeedPreparation.push_back(Key);
|
||||
if(Streamer)Streamer->SetDesiredTileFilter(GpuFarCpuFallbackTiles);
|
||||
const std::size_t NearGpuTiles=static_cast<std::size_t>(std::count_if(
|
||||
GpuFarCandidateTiles.begin(),GpuFarCandidateTiles.end(),[&](const FVoxelTileKey& Key)
|
||||
{return Key.Level<World->FullResClipLevels;}));
|
||||
std::printf("[GpuFieldEligibility] candidate_gpu_tiles=%zu near_gpu_tiles=%zu cpu_fallback_tiles=%zu\n",
|
||||
GpuFarCandidateTiles.size(),NearGpuTiles,GpuFarCpuFallbackTiles.size());
|
||||
std::printf("[GpuSkirtSeamFallback] near_tiles=%llu reason=fine_visible_lod_skirts gpu_far_skirts=active\n",
|
||||
static_cast<unsigned long long>(SkirtTransitionFallbackTiles));
|
||||
}
|
||||
|
||||
void AddGpuFarCpuFallbackTiles(const std::vector<FVoxelTileKey>& Additional)
|
||||
{
|
||||
if(Additional.empty()||!Streamer)return;
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Seen(
|
||||
GpuFarCpuFallbackTiles.begin(),GpuFarCpuFallbackTiles.end());
|
||||
std::size_t Added=0;
|
||||
for(const FVoxelTileKey& Key:Additional)
|
||||
if(Seen.insert(Key).second){GpuFarCpuFallbackTiles.push_back(Key);++Added;}
|
||||
if(Added==0)return;
|
||||
Streamer->SetDesiredTileFilter(GpuFarCpuFallbackTiles);
|
||||
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
|
||||
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
|
||||
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
|
||||
const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(
|
||||
CameraVoxels,ResidentKeys,CrossingIndex);
|
||||
FCrossingWork& Work=CrossingWork[CrossingIndex];
|
||||
Work.SubmittedJobs+=Update.NewJobs;Work.PendingJobs+=Update.NewJobs;
|
||||
std::printf("[GpuFarPrepFallbackAdded] tiles=%zu queued_jobs=%d cpu_fallback_total=%zu\n",
|
||||
Added,Update.NewJobs,GpuFarCpuFallbackTiles.size());
|
||||
}
|
||||
|
||||
void StartGpuFarPreparation()
|
||||
{
|
||||
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||bGpuFarPreparationStarted)return;
|
||||
bGpuFarPreparationStarted=true;
|
||||
if(GpuFarCandidateTiles.empty())
|
||||
{
|
||||
std::string PoolError;const auto ReserveStart=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=gpu_output_pool begin_ms=%.3f slots=%u\n",
|
||||
std::chrono::duration<double,std::milli>(ReserveStart-ColdProcessStart).count(),
|
||||
GpuFarOutputCapacitySlots);
|
||||
if(!VoxelForgeGpuFarViewer::ReserveFarOutputPool(Device,Context,GpuFarShaderPath,
|
||||
GpuFarOutputCapacitySlots,static_cast<uint32>(World->CoarseTileCells),PoolError))
|
||||
{
|
||||
std::fprintf(stderr,"[GpuFarPoolError] %s\n",PoolError.c_str());
|
||||
bGpuFarEnabled=false;GpuFarOutputCapacitySlots=0;
|
||||
CpuTiles=Keys;Eligible.clear();GpuFarTiles.clear();
|
||||
}
|
||||
GpuFarPoolReserveMs+=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-ReserveStart).count();
|
||||
std::printf("[ColdTimeline] stage=gpu_output_pool end_ms=%.3f duration_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count(),
|
||||
GpuFarPoolReserveMs);
|
||||
SampleGpuFarMemory();
|
||||
bGpuFarInputsFinalized=true;
|
||||
bGpuFarPreparationFinished.store(true,std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
std::vector<FVoxelTileKey> NeedPreparation;NeedPreparation.reserve(Eligible.size());
|
||||
for(const FVoxelTileKey& Key:Eligible)
|
||||
if(!GpuFarTiles.contains(Key))NeedPreparation.push_back(Key);
|
||||
if(!NeedPreparation.empty())
|
||||
PrepareGpuFarInputs(std::move(NeedPreparation));
|
||||
std::vector<FVoxelTileKey> PreparedEligible;PreparedEligible.reserve(Eligible.size());
|
||||
for(const FVoxelTileKey& Key:Eligible)
|
||||
const auto ReserveStart=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=gpu_output_pool begin_ms=%.3f slots=%u\n",
|
||||
ColdTimelineMilliseconds(ReserveStart),GpuFarOutputCapacitySlots);
|
||||
std::string PoolError;
|
||||
if(!VoxelForgeGpuFarViewer::ReserveFarOutputPool(Device,Context,GpuFarShaderPath,
|
||||
GpuFarOutputCapacitySlots,static_cast<uint32>(World->CoarseTileCells),PoolError))
|
||||
{
|
||||
if(!GpuFarTiles.contains(Key)&&!GpuFarPreparedInputs.contains(Key))
|
||||
{
|
||||
CpuTiles.push_back(Key);
|
||||
const auto Error=GpuFarPreparationErrors.find(Key);
|
||||
std::fprintf(stderr,"[GpuFieldInputFallback] key=(%d,%d,%d,lod%d) reason=%s\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
|
||||
Error==GpuFarPreparationErrors.end()?"missing prepared input":Error->second.c_str());
|
||||
continue;
|
||||
}
|
||||
GpuFarEligibleTiles.insert(Key);
|
||||
if(!GpuFarTiles.contains(Key))
|
||||
{GpuFarQueue.push_back(Key);GpuFarQueued.insert(Key);}
|
||||
PreparedEligible.push_back(Key);
|
||||
std::fprintf(stderr,"[GpuFarPoolError] %s\n",PoolError.c_str());
|
||||
bGpuFarEnabled=false;GpuFarOutputCapacitySlots=0;
|
||||
for(auto& Pair:GpuFarTiles)ReleaseGpuFarTile(Pair.second);
|
||||
GpuFarTiles.clear();GpuFarCandidateTiles.clear();GpuFarQueue.clear();GpuFarQueued.clear();
|
||||
GpuFarNeedPreparation.clear();GpuFarEligibleTiles.clear();
|
||||
AddGpuFarCpuFallbackTiles(DesiredTiles);
|
||||
bGpuFarInputsFinalized=true;
|
||||
bGpuFarPreparationFinished.store(true,std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
Eligible.swap(PreparedEligible);
|
||||
GpuFarPoolReserveMs+=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-ReserveStart).count();
|
||||
std::printf("[ColdTimeline] stage=gpu_output_pool end_ms=%.3f duration_ms=%.3f\n",
|
||||
ColdTimelineMilliseconds(std::chrono::steady_clock::now()),GpuFarPoolReserveMs);
|
||||
SampleGpuFarMemory();
|
||||
if(GpuFarNeedPreparation.empty())
|
||||
{
|
||||
bGpuFarPreparationFinished.store(true,std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
bGpuFarPreparationFinished.store(false,std::memory_order_relaxed);
|
||||
const std::vector<FVoxelTileKey> NeedPreparation=GpuFarNeedPreparation;
|
||||
GpuFarPreparationThread=std::thread([this,NeedPreparation]()
|
||||
{
|
||||
try { PrepareGpuFarInputs(NeedPreparation); }
|
||||
catch(const std::exception& Error)
|
||||
{
|
||||
for(const FVoxelTileKey& Key:NeedPreparation)
|
||||
GpuFarPreparationErrors.emplace(Key,Error.what());
|
||||
std::fprintf(stderr,"[GpuFarInputPrepareError] %s\n",Error.what());
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
for(const FVoxelTileKey& Key:NeedPreparation)
|
||||
GpuFarPreparationErrors.emplace(Key,"unknown input preparation failure");
|
||||
std::fprintf(stderr,"[GpuFarInputPrepareError] unknown failure\n");
|
||||
}
|
||||
bGpuFarPreparationFinished.store(true,std::memory_order_release);
|
||||
});
|
||||
}
|
||||
|
||||
void SortGpuFarQueue()
|
||||
{
|
||||
std::sort(GpuFarQueue.begin(),GpuFarQueue.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
{
|
||||
if(A.Level!=B.Level)return A.Level<B.Level;
|
||||
@@ -2662,20 +2827,79 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
return AC<BC;
|
||||
});
|
||||
GpuFarQueued.clear();for(const FVoxelTileKey& Key:GpuFarQueue)GpuFarQueued.insert(Key);
|
||||
if(Streamer)Streamer->SetDesiredTileFilter(CpuTiles);
|
||||
}
|
||||
|
||||
bool FinalizeGpuFarPreparation()
|
||||
{
|
||||
if(bGpuFarInputsFinalized)return true;
|
||||
if(!bGpuFarPreparationStarted
|
||||
||!bGpuFarPreparationFinished.load(std::memory_order_acquire))return false;
|
||||
if(GpuFarPreparationThread.joinable())GpuFarPreparationThread.join();
|
||||
if(bGpuFarShaderPrewarm&&!bGpuWarmupCompleted)
|
||||
{
|
||||
if(GpuShaderWarmupThread.joinable())GpuShaderWarmupThread.join();
|
||||
if(!bGpuShaderWarmupQueueSucceeded)
|
||||
{
|
||||
std::fprintf(stderr,"[GpuShaderWarmupError] queue: %s\n",
|
||||
GpuShaderWarmupQueueError.empty()?"warmup queue failed":GpuShaderWarmupQueueError.c_str());
|
||||
bGpuFarEnabled=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string WarmupError;
|
||||
if(!VoxelForgeGpuFarViewer::CompleteFarShaderWarmup(Device,Context,
|
||||
GpuFarShaderPath,WarmupError))
|
||||
{
|
||||
std::fprintf(stderr,"[GpuShaderWarmupError] %s\n",WarmupError.c_str());
|
||||
bGpuFarEnabled=false;
|
||||
}
|
||||
else bGpuWarmupCompleted=true;
|
||||
}
|
||||
}
|
||||
if(!bGpuFarEnabled)
|
||||
{
|
||||
GpuFarCandidateTiles.clear();GpuFarQueue.clear();GpuFarQueued.clear();
|
||||
GpuFarEligibleTiles.clear();
|
||||
for(auto& Pair:GpuFarTiles)ReleaseGpuFarTile(Pair.second);
|
||||
GpuFarTiles.clear();
|
||||
AddGpuFarCpuFallbackTiles(DesiredTiles);
|
||||
bGpuFarInputsFinalized=true;
|
||||
return false;
|
||||
}
|
||||
std::vector<FVoxelTileKey> Failed;
|
||||
for(const FVoxelTileKey& Key:GpuFarCandidateTiles)
|
||||
{
|
||||
if(GpuFarTiles.contains(Key))
|
||||
{GpuFarEligibleTiles.insert(Key);continue;}
|
||||
if(!GpuFarPreparedInputs.contains(Key))
|
||||
{
|
||||
Failed.push_back(Key);
|
||||
const auto Error=GpuFarPreparationErrors.find(Key);
|
||||
std::fprintf(stderr,"[GpuFieldInputFallback] key=(%d,%d,%d,lod%d) reason=%s\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
|
||||
Error==GpuFarPreparationErrors.end()?"missing prepared input":Error->second.c_str());
|
||||
continue;
|
||||
}
|
||||
GpuFarEligibleTiles.insert(Key);
|
||||
GpuFarQueue.push_back(Key);
|
||||
}
|
||||
SortGpuFarQueue();
|
||||
AddGpuFarCpuFallbackTiles(Failed);
|
||||
bGpuFarInputsFinalized=true;
|
||||
const std::size_t NearGpuTiles=static_cast<std::size_t>(std::count_if(
|
||||
GpuFarEligibleTiles.begin(),GpuFarEligibleTiles.end(),[&](const FVoxelTileKey& Key)
|
||||
{return Key.Level<World->FullResClipLevels;}));
|
||||
std::printf("[GpuFieldEligibility] eligible_gpu_tiles=%zu near_gpu_tiles=%zu cpu_fallback_tiles=%zu\n",
|
||||
GpuFarEligibleTiles.size(),NearGpuTiles,Keys.size()-GpuFarEligibleTiles.size());
|
||||
std::printf("[GpuSkirtSeamFallback] near_tiles=%llu reason=fine_visible_lod_skirts gpu_far_skirts=active\n",
|
||||
static_cast<unsigned long long>(SkirtTransitionFallbackTiles));
|
||||
GpuFarEligibleTiles.size(),NearGpuTiles,GpuFarCpuFallbackTiles.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void PumpGpuFarGeneration()
|
||||
{
|
||||
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context
|
||||
||GpuFarQueue.empty())return;
|
||||
||!FinalizeGpuFarPreparation()||GpuFarQueue.empty())return;
|
||||
const bool bColdDrainBeforeWholeView=!bInitialClipmapReported&&!bGpuFarAuditEnabled
|
||||
&&!bDisableGpuColdStartDrain;
|
||||
const auto PumpStart=std::chrono::steady_clock::now();
|
||||
const double PumpMs=std::chrono::duration<double,std::milli>(PumpStart-ColdProcessStart).count();
|
||||
if(GpuFarFirstPumpMs<0.0)GpuFarFirstPumpMs=PumpMs;
|
||||
@@ -2697,7 +2921,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
const auto Start=std::chrono::steady_clock::now();
|
||||
int32 Built=0;
|
||||
std::vector<FVoxelTileKey> BuiltKeys;BuiltKeys.reserve(32);
|
||||
std::vector<FVoxelTileKey> BuiltKeys;
|
||||
BuiltKeys.reserve(bColdDrainBeforeWholeView?GpuFarQueue.size():32);
|
||||
do
|
||||
{
|
||||
const FVoxelTileKey Key=GpuFarQueue.front();GpuFarQueue.pop_front();GpuFarQueued.erase(Key);
|
||||
@@ -2762,6 +2987,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
Tile.VertexCapacityBytes=static_cast<uint64>(
|
||||
VoxelForgeGpuFarViewer::PooledOutputVertexCapacityPerTile)*32u;
|
||||
Tile.VisibleSkirtFaceMask=PreparedSkirtFaceMask;
|
||||
const auto PreparedAtIt=GpuFarPreparedAt.find(Key);
|
||||
if(PreparedAtIt!=GpuFarPreparedAt.end())Tile.PreparedAt=PreparedAtIt->second;
|
||||
Tile.SubmittedAt=std::chrono::steady_clock::now();
|
||||
Tile.GpuDispatchMs=GpuDispatchMs;
|
||||
GpuFarEnqueueWallSeconds+=EnqueueMs/1000.0;
|
||||
if(GpuDispatchMs>0.0)
|
||||
@@ -2773,6 +3001,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
++GpuFarTilesSubmitted;++Built;
|
||||
GpuFarTiles.emplace(Key,Tile);
|
||||
BuiltKeys.push_back(Key);
|
||||
if(!bDeferredBatch&&bGpuFarTimelineEnabled)
|
||||
std::printf("[GpuTileTimeline] event=submitted key=(%d,%d,%d,lod%d) batch=none at_ms=%.3f frame=%llu prepared_ms=%.3f path=direct_dispatch\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
|
||||
ColdTimelineMilliseconds(Tile.SubmittedAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
ColdTimelineMilliseconds(Tile.PreparedAt));
|
||||
if(!bGpuFarFirstTileReported)
|
||||
{
|
||||
bGpuFarFirstTileReported=true;
|
||||
@@ -2782,13 +3016,21 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
while(Built<32&&!GpuFarQueue.empty()
|
||||
&&std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-Start).count()<4.0);
|
||||
while(!GpuFarQueue.empty()&&(bColdDrainBeforeWholeView
|
||||
||(Built<32&&std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-Start).count()<4.0)));
|
||||
if(Built>0)
|
||||
{
|
||||
if(bDeferredBatch)
|
||||
{
|
||||
FGpuFarBatch Batch;Batch.TileCount=static_cast<uint32>(Built);
|
||||
Batch.PreparedAt=std::chrono::steady_clock::now();
|
||||
Batch.Keys=BuiltKeys;
|
||||
const uint32 BatchIndex=static_cast<uint32>(GpuFarBatches.size());
|
||||
if(bGpuFarTimelineEnabled)
|
||||
std::printf("[GpuBatchTimeline] event=prepared batch=%u at_ms=%.3f frame=%llu tile_count=%u path=direct_gpu_output\n",
|
||||
BatchIndex,ColdTimelineMilliseconds(Batch.PreparedAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),Batch.TileCount);
|
||||
std::string BatchError;
|
||||
if(!VoxelForgeGpuFarViewer::SubmitFarTileBatch(Device,Context,GpuFarShaderPath,
|
||||
&Batch.CompletionEvent,&Batch.TimingQueries,BatchError))
|
||||
@@ -2803,13 +3045,24 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
Batch.SubmittedAt-ColdProcessStart).count();
|
||||
if(GpuFarFirstSubmitMs<0.0)GpuFarFirstSubmitMs=SubmitMs;
|
||||
GpuFarLastSubmitMs=SubmitMs;
|
||||
const uint32 BatchIndex=static_cast<uint32>(GpuFarBatches.size());
|
||||
GpuFarBatches.push_back(Batch);
|
||||
for(const FVoxelTileKey& Key:BuiltKeys)
|
||||
{
|
||||
auto It=GpuFarTiles.find(Key);
|
||||
if(It!=GpuFarTiles.end())It->second.BatchIndex=BatchIndex;
|
||||
if(It!=GpuFarTiles.end())
|
||||
{
|
||||
It->second.BatchIndex=BatchIndex;
|
||||
It->second.SubmittedAt=Batch.SubmittedAt;
|
||||
if(bGpuFarTimelineEnabled)std::printf("[GpuTileTimeline] event=submitted key=(%d,%d,%d,lod%d) batch=%u at_ms=%.3f frame=%llu prepared_ms=%.3f\n",
|
||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,BatchIndex,
|
||||
ColdTimelineMilliseconds(Batch.SubmittedAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
ColdTimelineMilliseconds(It->second.PreparedAt));
|
||||
}
|
||||
}
|
||||
if(bGpuFarTimelineEnabled)std::printf("[GpuBatchTimeline] event=submitted batch=%u at_ms=%.3f frame=%llu tile_count=%u\n",
|
||||
BatchIndex,ColdTimelineMilliseconds(Batch.SubmittedAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),Batch.TileCount);
|
||||
++GpuFarSubmissionBatches;
|
||||
GpuFarMaxTilesPerSubmissionBatch=std::max<uint32>(GpuFarMaxTilesPerSubmissionBatch,
|
||||
static_cast<uint32>(Built));
|
||||
@@ -3219,14 +3472,46 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
};
|
||||
std::sort(Dirty.begin(),Dirty.end(),[&](const auto& A,const auto& B)
|
||||
{const double DA=Distance(A),DB=Distance(B);return DA==DB?A.Level<B.Level:DA<DB;});
|
||||
const bool bDrainColdStart=bGpuFarEnabled&&!bInitialClipmapReported
|
||||
&&!bDisableGpuColdStartDrain;
|
||||
const auto Start=std::chrono::steady_clock::now();int32 Uploaded=0;
|
||||
for(const FStandaloneBatchKey& Key:Dirty)
|
||||
{
|
||||
if(Uploaded>=MaxBatchUploadsPerFrame)break;
|
||||
if(Uploaded>0&&BatchUploadBudgetMilliseconds>0.0
|
||||
if(!bDrainColdStart&&Uploaded>=MaxBatchUploadsPerFrame)break;
|
||||
if(!bDrainColdStart&&Uploaded>0&&BatchUploadBudgetMilliseconds>0.0
|
||||
&&std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-Start).count()>=BatchUploadBudgetMilliseconds)break;
|
||||
if(!DirtyBatchSet.contains(Key))continue;
|
||||
if(RebuildBatch(Key)){DirtyBatchSet.erase(Key);++Uploaded;}
|
||||
if(RebuildBatch(Key))
|
||||
{
|
||||
DirtyBatchSet.erase(Key);++Uploaded;
|
||||
if(bGpuFarTimelineEnabled)
|
||||
{
|
||||
const auto UploadAt=std::chrono::steady_clock::now();
|
||||
const bool bHasUploadBuffer=StreamingBatches.contains(Key);
|
||||
std::printf("[CpuUploadBatchTimeline] event=uploaded batch=(lod%d,%d,%d,%d) at_ms=%.3f frame=%llu has_buffer=%s\n",
|
||||
Key.Level,Key.X,Key.Y,Key.Z,ColdTimelineMilliseconds(UploadAt),
|
||||
static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
bHasUploadBuffer?"yes":"no_empty_mesh");
|
||||
const auto MembersIt=BatchMembers.find(Key);
|
||||
if(MembersIt!=BatchMembers.end())for(const FVoxelTileKey& TileKey:MembersIt->second)
|
||||
{
|
||||
auto ResidentIt=ResidentTiles.find(TileKey);
|
||||
if(ResidentIt==ResidentTiles.end()||ResidentIt->second.bColdTimelineResidentReported)continue;
|
||||
FResidentTileMesh& Resident=ResidentIt->second;
|
||||
Resident.bColdTimelineResidentReported=true;
|
||||
std::printf("[CpuTileTimeline] event=uploaded key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu batch=(lod%d,%d,%d,%d) queued_ms=%.3f started_ms=%.3f finished_ms=%.3f method=%s\n",
|
||||
TileKey.Coord.X,TileKey.Coord.Y,TileKey.Coord.Z,TileKey.Level,
|
||||
ColdTimelineMilliseconds(UploadAt),static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
Key.Level,Key.X,Key.Y,Key.Z,ColdTimelineMilliseconds(Resident.CpuQueuedAt),
|
||||
ColdTimelineMilliseconds(Resident.CpuStartedAt),ColdTimelineMilliseconds(Resident.CpuFinishedAt),
|
||||
bHasUploadBuffer?"cpu_batch_vertex_index":"empty_surface_no_gpu_copy");
|
||||
std::printf("[CpuTileTimeline] event=resident key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu batch=(lod%d,%d,%d,%d)\n",
|
||||
TileKey.Coord.X,TileKey.Coord.Y,TileKey.Coord.Z,TileKey.Level,
|
||||
ColdTimelineMilliseconds(UploadAt),static_cast<unsigned long long>(ColdTimelineFrame()),
|
||||
Key.Level,Key.X,Key.Y,Key.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3346,6 +3631,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
Resident.TriangleSkirtFaceMasks=std::move(Tile.TriangleSkirtFaceMasks);
|
||||
Resident.ActiveSkirtFaceMask=ComputeVisibleSkirtFaceMask(Tile.Key,DesiredTileSet);
|
||||
Resident.Samples=Tile.DensitySamples;Resident.WorkerSeconds=Tile.WorkerSeconds;
|
||||
Resident.CpuQueuedAt=Tile.QueuedAt;Resident.CpuStartedAt=Tile.StartedAt;
|
||||
Resident.CpuFinishedAt=Tile.CompletedAt;
|
||||
ResidentTiles.emplace(Tile.Key,std::move(Resident));
|
||||
const FStandaloneBatchKey Batch=BatchForTile(Tile.Key);
|
||||
BatchMembers[Batch].push_back(Tile.Key);MarkBatchDirty(Batch);
|
||||
@@ -3534,7 +3821,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
|
||||
void Draw()
|
||||
{
|
||||
const uint64 FrameOrdinal=ColdTimelineFrameOrdinal.fetch_add(1,std::memory_order_relaxed)+1;
|
||||
const auto DrawStart=std::chrono::steady_clock::now();
|
||||
const bool bTraceColdFrame=bGpuFarTimelineEnabled&&!bInitialClipmapReported;
|
||||
if(bTraceColdFrame)
|
||||
std::printf("[ColdFrame] stage=begin frame=%llu at_ms=%.3f inter_draw_gap_ms=%.3f\n",
|
||||
static_cast<unsigned long long>(FrameOrdinal),ColdTimelineMilliseconds(DrawStart),
|
||||
LastDrawEndAt.time_since_epoch().count()!=0
|
||||
?std::chrono::duration<double,std::milli>(DrawStart-LastDrawEndAt).count():0.0);
|
||||
const double InterDrawGapMs=LastDrawEndAt.time_since_epoch().count()!=0
|
||||
?std::chrono::duration<double,std::milli>(DrawStart-LastDrawEndAt).count():0.0;
|
||||
PumpGeneration();
|
||||
@@ -3706,8 +4000,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
++GpuWarmupHeldPresents;
|
||||
// Keep pumping CPU and GPU work at the display cadence without
|
||||
// calling the driver present path that stalls on its first GPU frame.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(11));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(
|
||||
bGpuFarEnabled&&!bDisableGpuColdStartDrain?1:11));
|
||||
LastDrawEndAt=std::chrono::steady_clock::now();
|
||||
if(bTraceColdFrame)
|
||||
std::printf("[ColdFrame] stage=end frame=%llu at_ms=%.3f cpu_pump_ms=%.3f gpu_pump_ms=%.3f held=1 present_ms=0.000\n",
|
||||
static_cast<unsigned long long>(FrameOrdinal),ColdTimelineMilliseconds(LastDrawEndAt),
|
||||
std::chrono::duration<double,std::milli>(CpuPumpEnd-DrawStart).count(),
|
||||
std::chrono::duration<double,std::milli>(GpuPumpEnd-CpuPumpEnd).count());
|
||||
return;
|
||||
}
|
||||
const auto BeforePresent=std::chrono::steady_clock::now();
|
||||
@@ -3786,6 +4086,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
InterDrawGapMs,CpuPumpMs,GpuPumpMs,
|
||||
std::chrono::duration<double,std::milli>(BeforePresent-GpuPumpEnd).count(),
|
||||
PresentSyncInterval,PresentCallMs,TitleMs);
|
||||
if(bTraceColdFrame)
|
||||
std::printf("[ColdFrame] stage=end frame=%llu at_ms=%.3f cpu_pump_ms=%.3f gpu_pump_ms=%.3f pre_present_ms=%.3f present_ms=%.3f present_sync=%u present_hr=0x%08lx held=0\n",
|
||||
static_cast<unsigned long long>(FrameOrdinal),ColdTimelineMilliseconds(DrawEnd),
|
||||
CpuPumpMs,GpuPumpMs,
|
||||
std::chrono::duration<double,std::milli>(BeforePresent-GpuPumpEnd).count(),
|
||||
PresentCallMs,PresentSyncInterval,static_cast<unsigned long>(PresentResult));
|
||||
LastDrawEndAt=DrawEnd;
|
||||
}
|
||||
};
|
||||
@@ -5632,6 +5938,8 @@ int main(int Argc,char** Argv)
|
||||
else if(Arg=="--affinity-all-cpu"){}
|
||||
else if(Arg=="--gpu-far-no-present-hold")
|
||||
App.bDisableGpuWarmupPresentHold=true;
|
||||
else if(Arg=="--gpu-far-no-cold-drain")App.bDisableGpuColdStartDrain=true;
|
||||
else if(Arg=="--gpu-far-timeline")App.bGpuFarTimelineEnabled=true;
|
||||
else if(Arg=="--gpu-far-no-prewarm")
|
||||
App.bGpuFarShaderPrewarm=false;
|
||||
else if(Arg=="--cpu-far")App.bGpuFarEnabled=false,App.bShapeWorld=true,App.bForceGenerate=true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
@@ -41,6 +42,8 @@ struct FStandaloneCpuTile
|
||||
uint32 SkirtTriangleCount=0;
|
||||
uint64 DensitySamples=0;
|
||||
double WorkerSeconds=0.0;
|
||||
std::chrono::steady_clock::time_point QueuedAt{};
|
||||
std::chrono::steady_clock::time_point StartedAt{};
|
||||
std::chrono::steady_clock::time_point CompletedAt{};
|
||||
uint64 DensityHash=14695981039346656037ull;
|
||||
bool bHasDensityHash=false;
|
||||
@@ -340,6 +343,8 @@ struct FStandaloneTileJob
|
||||
int32 BandZMax=MAX_int32;
|
||||
uint64 RequestedCrossing=0;
|
||||
size_t PriorityRank=0;
|
||||
std::chrono::steady_clock::time_point QueuedAt{};
|
||||
std::chrono::steady_clock::time_point StartedAt{};
|
||||
};
|
||||
|
||||
struct FStandaloneTileJobHeapOrder
|
||||
@@ -421,6 +426,14 @@ public:
|
||||
FStandaloneStreamingGenerator& operator=(const FStandaloneStreamingGenerator&)=delete;
|
||||
~FStandaloneStreamingGenerator(){Stop();}
|
||||
|
||||
void EnableColdTimelineTrace(std::chrono::steady_clock::time_point Origin,
|
||||
const std::atomic<uint64>* FrameOrdinal)
|
||||
{
|
||||
TimelineOrigin=Origin;
|
||||
TimelineFrameOrdinal=FrameOrdinal;
|
||||
bColdTimelineTrace=true;
|
||||
}
|
||||
|
||||
FStandaloneStreamingUpdate Start(const FVector& CameraPositionVoxels,
|
||||
const std::vector<FVoxelTileKey>& ResidentKeys={},
|
||||
uint64 CrossingIndex=0)
|
||||
@@ -540,6 +553,7 @@ private:
|
||||
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
|
||||
{
|
||||
if(Desired.contains(Job.Key))return false;
|
||||
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
|
||||
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
|
||||
++CancelledByCrossing[Job.RequestedCrossing];return true;
|
||||
});
|
||||
@@ -568,7 +582,12 @@ private:
|
||||
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||
{
|
||||
if(Resident.contains(Key)||Outstanding.contains(Key))continue;
|
||||
Queue.push_back({Key,BandMin,BandMax,CrossingIndex,PriorityRanks.at(Key)});
|
||||
FStandaloneTileJob Job;
|
||||
Job.Key=Key;Job.BandZMin=BandMin;Job.BandZMax=BandMax;
|
||||
Job.RequestedCrossing=CrossingIndex;Job.PriorityRank=PriorityRanks.at(Key);
|
||||
Job.QueuedAt=FClock::now();
|
||||
Queue.push_back(Job);
|
||||
if(bColdTimelineTrace)TraceTileEvent("queued",Key,Job.QueuedAt);
|
||||
Outstanding.insert(Key);++Stats.NewJobs;
|
||||
}
|
||||
CurrentDesired=std::move(NewDesired);
|
||||
@@ -578,8 +597,9 @@ private:
|
||||
}
|
||||
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
|
||||
{
|
||||
const auto StartTime=FClock::now();
|
||||
const auto StartTime=Job.StartedAt;
|
||||
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
|
||||
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
|
||||
if(MeshCache)
|
||||
{
|
||||
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
|
||||
@@ -718,9 +738,12 @@ private:
|
||||
Wake.wait(Lock,[&](){return bStop||!Queue.empty();});
|
||||
if(bStop)return;
|
||||
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
|
||||
Job=Queue.back();Queue.pop_back();
|
||||
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
|
||||
}
|
||||
if(bColdTimelineTrace)TraceTileEvent("started",Job.Key,Job.StartedAt,Job.QueuedAt,Job.StartedAt);
|
||||
FStandaloneCpuTile Tile=Generate(Job);
|
||||
if(bColdTimelineTrace)TraceTileEvent("finished",Tile.Key,Tile.CompletedAt,
|
||||
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0);
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
if(bStop)return;
|
||||
@@ -730,6 +753,24 @@ private:
|
||||
}
|
||||
|
||||
using FClock=std::chrono::steady_clock;
|
||||
double TimelineMilliseconds(FClock::time_point At)const
|
||||
{
|
||||
return At.time_since_epoch().count()==0?-1.0:
|
||||
std::chrono::duration<double,std::milli>(At-TimelineOrigin).count();
|
||||
}
|
||||
uint64 TimelineFrame()const
|
||||
{
|
||||
return TimelineFrameOrdinal?TimelineFrameOrdinal->load(std::memory_order_relaxed):0;
|
||||
}
|
||||
void TraceTileEvent(const char* Event,const FVoxelTileKey& Key,FClock::time_point At,
|
||||
FClock::time_point Queued={},FClock::time_point Started={},FClock::time_point Finished={},
|
||||
double WorkerMs=-1.0)const
|
||||
{
|
||||
std::printf("[CpuTileTimeline] event=%s key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu queued_ms=%.3f started_ms=%.3f finished_ms=%.3f worker_ms=%.3f\n",
|
||||
Event,Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,TimelineMilliseconds(At),
|
||||
static_cast<unsigned long long>(TimelineFrame()),TimelineMilliseconds(Queued),
|
||||
TimelineMilliseconds(Started),TimelineMilliseconds(Finished),WorkerMs);
|
||||
}
|
||||
FStandaloneOwnerWorld* World=nullptr;
|
||||
int32 WorkerCount=8;
|
||||
bool bHashDensity=false;
|
||||
@@ -753,4 +794,7 @@ private:
|
||||
bool bUseTileFilter=false;
|
||||
FStandaloneDesiredTileSet CurrentDesired;
|
||||
int32 FullResTileCount=0;
|
||||
bool bColdTimelineTrace=false;
|
||||
FClock::time_point TimelineOrigin{};
|
||||
const std::atomic<uint64>* TimelineFrameOrdinal=nullptr;
|
||||
};
|
||||
Reference in new issue
Block a user