wip(standalone): GPU step 6 -- near field on GPU, exact; cold start still slower than CPU
Near LOD0/1 (detail band, relief, root refinement, normals) now evaluated and meshed on the GPU. 960 near tiles: zero sign/topology differences vs CPU and WARP, vertex max 0.0049 mm (was 15 cm); far survival audit passes; CPU hashes 44/44 unchanged. Far connector-panel outlier (64.56 cm, LOD8) remains a tessellation difference, and remote captures show missing far geometry on GPU. Cold readiness (viewer clock, my interleaved runs): CPU 2.51/2.49/2.40 s, GPU 5.05/2.68/2.80 s. The ~2.1 s Present stall hit only the first GPU launch. Viewer spends ~1.13 s before shape compile starts (startup checks), which is the gap between the 0.95 s benchmark and ~2.4 s on-screen. 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
0e730cdbba
commit
b0a14262b1
50 files changed
+1681
-236
No files matched your search
@@ -1372,6 +1372,7 @@ namespace
|
||||
uint32 TileCount=0;
|
||||
uint64 TimestampFrequency=0;
|
||||
double GpuDispatchMs=0.0;
|
||||
std::chrono::steady_clock::time_point SubmittedAt{};
|
||||
bool bGpuComplete=false,bTimingResolved=false;
|
||||
};
|
||||
|
||||
@@ -1502,6 +1503,14 @@ namespace
|
||||
std::unordered_map<FVoxelTileKey,VoxelForgeCore::FarFieldTileInput,FStandaloneTileKeyHash> GpuFarPreparedInputs;
|
||||
std::unordered_map<FVoxelTileKey,std::string,FStandaloneTileKeyHash> GpuFarPreparationErrors;
|
||||
double GpuFarPreparationWallMs=0.0,GpuFarPreparationWorkerMs=0.0;
|
||||
uint64 GpuFarPumpCalls=0,GpuFarCompletionPolls=0;
|
||||
double GpuFarFirstPumpMs=-1.0,GpuFarLastPumpMs=-1.0;
|
||||
double GpuFarFirstSubmitMs=-1.0,GpuFarLastSubmitMs=-1.0;
|
||||
double GpuFarFirstCompletionPollMs=-1.0,GpuFarLastCompletionPollMs=-1.0;
|
||||
double GpuFarPumpWallMs=0.0,GpuFarPumpMaxWallMs=0.0;
|
||||
double GpuFarPumpGapMs=0.0,GpuFarPumpGapMaxMs=0.0;
|
||||
std::chrono::steady_clock::time_point LastGpuFarPumpAt{};
|
||||
std::chrono::steady_clock::time_point LastDrawEndAt{};
|
||||
VoxelForgeCore::DetailVariant ShapeDetailVariant=VoxelForgeCore::DetailVariant::coherent;
|
||||
bool bWireframe=false;
|
||||
bool bCaptured=false;
|
||||
@@ -1610,6 +1619,12 @@ namespace
|
||||
GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
|
||||
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();
|
||||
GpuFarPreparationWallMs=0.0;GpuFarPreparationWorkerMs=0.0;
|
||||
GpuFarPumpCalls=0;GpuFarCompletionPolls=0;
|
||||
GpuFarFirstPumpMs=GpuFarLastPumpMs=GpuFarFirstSubmitMs=GpuFarLastSubmitMs=-1.0;
|
||||
GpuFarFirstCompletionPollMs=GpuFarLastCompletionPollMs=-1.0;
|
||||
GpuFarPumpWallMs=GpuFarPumpMaxWallMs=GpuFarPumpGapMs=GpuFarPumpGapMaxMs=0.0;
|
||||
LastGpuFarPumpAt={};
|
||||
LastDrawEndAt={};
|
||||
GpuFarAudit.reset();bGpuFarAuditReported=false;
|
||||
GpuFarTilesSubmitted=0;GpuFarEnqueueWallSeconds=0.0;GpuFarGpuDispatchMs=0.0;
|
||||
GpuFarGpuDispatchMaxMs=0.0;GpuFarSubmissionSpanMs=0.0;GpuFarSubmissionSpanMaxMs=0.0;
|
||||
@@ -1916,8 +1931,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
Request.includeFarWorld=bIncludeFarWorld;Request.roundTunnels=bRoundTunnels;
|
||||
Request.enhancedTunnelWalls=bRoundTunnelEnhanced;
|
||||
Request.profileDetailBand=bProfileDetailBand;
|
||||
const auto ShapeCompileStart=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=shape_compile begin_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(ShapeCompileStart-ColdProcessStart).count());
|
||||
if(!ShapeWorld->compile(Request,CoreScheduler,Error))
|
||||
{ShapeWorld.reset();return false;}
|
||||
const auto ShapeCompileEnd=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=shape_compile end_ms=%.3f duration_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(ShapeCompileEnd-ColdProcessStart).count(),
|
||||
std::chrono::duration<double,std::milli>(ShapeCompileEnd-ShapeCompileStart).count());
|
||||
bShapePipelineActive=bNeighborhoodOnly;
|
||||
bNeighborhoodReadyReported=false;
|
||||
bFarShapeStagePending=bShapeWorld&&!bNeighborhoodOnly&&!ShapeWorld->hasFarWorld();
|
||||
@@ -2014,9 +2036,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
StopGeneration();
|
||||
ReleaseMeshes();
|
||||
GenerationStart=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=generation_setup begin_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(GenerationStart-ColdProcessStart).count());
|
||||
std::string Error;
|
||||
if(!ConfigureWorld(Error))
|
||||
{std::fprintf(stderr,"World load failed: %s\n",Error.c_str());MessageBoxA(Window,Error.c_str(),"VoxelForge viewer",MB_ICONERROR);return;}
|
||||
std::printf("[ColdTimeline] stage=generation_setup configured_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count());
|
||||
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
|
||||
BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels);
|
||||
bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false;
|
||||
@@ -2085,7 +2111,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarEligibleTiles.size());
|
||||
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||
if(GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
if(Key.Level>=6&&GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
GpuFarAudit=std::make_unique<FFarSurvivalAudit>(*World,*ShapeWorld,AuditKeys,false);
|
||||
}
|
||||
}
|
||||
@@ -2104,6 +2130,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
InitialWork.SubmittedJobs=Update.NewJobs;InitialWork.PendingJobs=Update.NewJobs;
|
||||
InitialWork.StartTime=GenerationStart;
|
||||
bGenerationRunning=true;bRebuildPending=false;
|
||||
std::printf("[ColdTimeline] stage=cpu_stream_start submitted_ms=%.3f cpu_jobs=%d gpu_queued=%zu\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count(),
|
||||
Update.NewJobs,GpuFarQueue.size());
|
||||
const char* TunnelMode=!bRoundTunnels?"":(bRoundTunnelEnhanced
|
||||
?" with detailed round/winding tunnels":" with legacy round/winding tunnels");
|
||||
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",
|
||||
@@ -2147,7 +2176,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarEligibleTiles.size());
|
||||
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||
if(GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
if(Key.Level>=6&&GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
GpuFarAudit=std::make_unique<FFarSurvivalAudit>(*World,*ShapeWorld,AuditKeys,false);
|
||||
}
|
||||
}
|
||||
@@ -2163,6 +2192,18 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
void RecordGpuCrossingTileReady(const FVoxelTileKey& Key)
|
||||
{
|
||||
const auto ReadyAt=std::chrono::steady_clock::now();
|
||||
for(auto& Pair:CrossingWork)
|
||||
{
|
||||
FCrossingWork& Work=Pair.second;
|
||||
if(Work.bHasNearestNewTile&&Work.NearestNewTile==Key
|
||||
&&Work.NearestReadySeconds<0.0&&Work.StartTime.time_since_epoch().count()!=0)
|
||||
Work.NearestReadySeconds=std::chrono::duration<double>(ReadyAt-Work.StartTime).count();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRenderedTileReady(const FVoxelTileKey& Key)
|
||||
{
|
||||
if(bGpuFarEnabled&&ShapeWorld&&ShapeWorld->hasFarWorld()
|
||||
@@ -2178,6 +2219,10 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
&&Context->GetData(Batch.CompletionEvent,nullptr,0,D3D11_ASYNC_GETDATA_DONOTFLUSH)==S_OK)
|
||||
{
|
||||
Batch.bGpuComplete=true;
|
||||
const auto PollAt=std::chrono::steady_clock::now();
|
||||
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(Batch.bGpuComplete&&!Batch.bTimingResolved)
|
||||
@@ -2202,6 +2247,7 @@ 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);
|
||||
return Batch.bGpuComplete;
|
||||
}
|
||||
if(!Tile.bGpuComplete&&Tile.CompletionEvent&&Context
|
||||
@@ -2224,6 +2270,7 @@ 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);
|
||||
return Tile.bGpuComplete;
|
||||
}
|
||||
return ResidentTiles.contains(Key);
|
||||
@@ -2233,6 +2280,8 @@ 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());
|
||||
std::vector<std::optional<VoxelForgeCore::FarFieldTileInput>> Inputs(NeedPreparation.size());
|
||||
std::vector<std::string> Errors(NeedPreparation.size());
|
||||
const std::size_t ThreadCount=std::min<std::size_t>(8,NeedPreparation.size());
|
||||
@@ -2250,7 +2299,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
if(Index>=NeedPreparation.size())break;
|
||||
const FVoxelTileKey& Key=NeedPreparation[Index];
|
||||
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
|
||||
try { Inputs[Index]=ShapeWorld->exportFarFieldTile(CoreKey,Errors[Index]); }
|
||||
try
|
||||
{
|
||||
Inputs[Index]=Key.Level<World->FullResClipLevels
|
||||
?ShapeWorld->exportGpuFieldTile(CoreKey,Errors[Index])
|
||||
:ShapeWorld->exportFarFieldTile(CoreKey,Errors[Index]);
|
||||
}
|
||||
catch(const std::exception& Error) { Errors[Index]=Error.what(); }
|
||||
}
|
||||
WorkerElapsed[Worker]=std::chrono::duration<double,std::milli>(
|
||||
@@ -2275,6 +2329,9 @@ 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(),
|
||||
PreparationMs,WorkerSumMs);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -2287,9 +2344,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::vector<FVoxelTileKey> Eligible;Eligible.reserve(Keys.size());
|
||||
for(const FVoxelTileKey& Key:Keys)
|
||||
{
|
||||
if(Key.Level<World->FullResClipLevels){CpuTiles.push_back(Key);continue;}
|
||||
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
|
||||
if(ShapeWorld->isFarFieldTileEligible(CoreKey))
|
||||
const bool NearGpuEligible=Key.Level<World->FullResClipLevels;
|
||||
if(NearGpuEligible||ShapeWorld->isFarFieldTileEligible(CoreKey))
|
||||
{
|
||||
Eligible.push_back(Key);
|
||||
}
|
||||
@@ -2306,6 +2363,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
if(!Eligible.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))
|
||||
{
|
||||
@@ -2315,6 +2375,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
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();
|
||||
}
|
||||
std::vector<FVoxelTileKey> NeedPreparation;NeedPreparation.reserve(Eligible.size());
|
||||
@@ -2322,24 +2385,58 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
Eligible.swap(PreparedEligible);
|
||||
std::sort(GpuFarQueue.begin(),GpuFarQueue.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
{
|
||||
if(A.Level!=B.Level)return A.Level<B.Level;
|
||||
const FIntVector AO=A.OriginVoxels(),BO=B.OriginVoxels();
|
||||
const double AC=(AO.X+0.5*A.ExtentVoxels()-Position.X*4.0)*(AO.X+0.5*A.ExtentVoxels()-Position.X*4.0)
|
||||
+(AO.Y+0.5*A.ExtentVoxels()-Position.Y*4.0)*(AO.Y+0.5*A.ExtentVoxels()-Position.Y*4.0)
|
||||
+(AO.Z+0.5*A.ExtentVoxels()-Position.Z*4.0)*(AO.Z+0.5*A.ExtentVoxels()-Position.Z*4.0);
|
||||
const double BC=(BO.X+0.5*B.ExtentVoxels()-Position.X*4.0)*(BO.X+0.5*B.ExtentVoxels()-Position.X*4.0)
|
||||
+(BO.Y+0.5*B.ExtentVoxels()-Position.Y*4.0)*(BO.Y+0.5*B.ExtentVoxels()-Position.Y*4.0)
|
||||
+(BO.Z+0.5*B.ExtentVoxels()-Position.Z*4.0)*(BO.Z+0.5*B.ExtentVoxels()-Position.Z*4.0);
|
||||
return AC<BC;
|
||||
});
|
||||
GpuFarQueued.clear();for(const FVoxelTileKey& Key:GpuFarQueue)GpuFarQueued.insert(Key);
|
||||
if(Streamer)Streamer->SetDesiredTileFilter(CpuTiles);
|
||||
const std::size_t CpuTransitionFallbacks=static_cast<std::size_t>(std::count_if(
|
||||
Keys.begin(),Keys.end(),[&](const FVoxelTileKey& Key)
|
||||
{return Key.Level>=World->FullResClipLevels&&!GpuFarEligibleTiles.contains(Key);}));
|
||||
std::printf("[GpuFarEligibility] eligible_gpu_tiles=%zu cpu_transition_fallback_tiles=%zu\n",
|
||||
GpuFarEligibleTiles.size(),CpuTransitionFallbacks);
|
||||
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());
|
||||
}
|
||||
|
||||
void PumpGpuFarGeneration()
|
||||
{
|
||||
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context
|
||||
||GpuFarQueue.empty())return;
|
||||
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;
|
||||
if(LastGpuFarPumpAt.time_since_epoch().count()!=0)
|
||||
{
|
||||
const double GapMs=std::chrono::duration<double,std::milli>(PumpStart-LastGpuFarPumpAt).count();
|
||||
GpuFarPumpGapMs+=GapMs;GpuFarPumpGapMaxMs=std::max(GpuFarPumpGapMaxMs,GapMs);
|
||||
}
|
||||
LastGpuFarPumpAt=PumpStart;GpuFarLastPumpMs=PumpMs;++GpuFarPumpCalls;
|
||||
const bool bDeferredBatch=!bGpuFarAuditEnabled;
|
||||
if(bDeferredBatch)
|
||||
{
|
||||
@@ -2413,8 +2510,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
AuditTile.Indices=std::move(AuditMesh.indices);
|
||||
GpuFarAudit->AuditTile(AuditTile);
|
||||
}
|
||||
const uint64 Cells=static_cast<uint64>(World->CoarseTileCells);
|
||||
Tile.VertexCapacityBytes=Cells*Cells*Cells*15u*32u;
|
||||
Tile.VertexCapacityBytes=static_cast<uint64>(
|
||||
VoxelForgeGpuFarViewer::PooledOutputVertexCapacityPerTile)*32u;
|
||||
Tile.GpuDispatchMs=GpuDispatchMs;
|
||||
GpuFarEnqueueWallSeconds+=EnqueueMs/1000.0;
|
||||
if(GpuDispatchMs>0.0)
|
||||
@@ -2451,6 +2548,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
else
|
||||
{
|
||||
Batch.SubmittedAt=std::chrono::steady_clock::now();
|
||||
const double SubmitMs=std::chrono::duration<double,std::milli>(
|
||||
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)
|
||||
@@ -2471,6 +2573,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
}
|
||||
SampleGpuFarMemory();
|
||||
const double PumpWallMs=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-PumpStart).count();
|
||||
GpuFarPumpWallMs+=PumpWallMs;GpuFarPumpMaxWallMs=std::max(GpuFarPumpMaxWallMs,PumpWallMs);
|
||||
}
|
||||
|
||||
void ToggleFarRenderer()
|
||||
@@ -2505,7 +2610,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
FCrossingWork& Work=CrossingWork[CrossingIndex];
|
||||
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
|
||||
Work.StartTime=std::chrono::steady_clock::now();
|
||||
std::printf("[FarRenderer] mode=%s cpu_far_jobs=%d gpu_far_ready=%zu gpu_far_queued=%zu (H toggles)\n",
|
||||
std::printf("[FarRenderer] mode=%s cpu_field_jobs=%d gpu_field_ready=%zu gpu_field_queued=%zu (H toggles)\n",
|
||||
bGpuFarEnabled?"GPU":"CPU",Update.NewJobs,GpuFarTiles.size(),GpuFarQueue.size());
|
||||
}
|
||||
|
||||
@@ -2541,8 +2646,6 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
|
||||
const double FullShapeSeconds=std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-FullShapeBuildStart).count();
|
||||
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
|
||||
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
|
||||
if(Streamer)Streamer->Stop();
|
||||
Streamer.reset();
|
||||
ShapeWorld=std::move(PendingFullShapeWorld);
|
||||
@@ -2551,6 +2654,24 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
|
||||
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,nullptr,-1,
|
||||
false,false,false,false,ShapeWorld.get());
|
||||
if(bGpuFarEnabled&&ShapeWorld->hasFarWorld())
|
||||
{
|
||||
ScheduleGpuFarTiles(DesiredTiles);
|
||||
if(bGpuFarAuditEnabled)
|
||||
{
|
||||
std::vector<FVoxelTileKey> AuditKeys;AuditKeys.reserve(GpuFarEligibleTiles.size());
|
||||
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||
if(Key.Level>=6&&GpuFarEligibleTiles.contains(Key))AuditKeys.push_back(Key);
|
||||
GpuFarAudit=std::make_unique<FFarSurvivalAudit>(*World,*ShapeWorld,AuditKeys,false);
|
||||
}
|
||||
std::vector<FVoxelTileKey> CpuResidentNear;
|
||||
for(const auto& Pair:ResidentTiles)
|
||||
if(Pair.first.Level<World->FullResClipLevels
|
||||
&&GpuFarEligibleTiles.contains(Pair.first))CpuResidentNear.push_back(Pair.first);
|
||||
for(const FVoxelTileKey& Key:CpuResidentNear)RemoveResidentTile(Key);
|
||||
}
|
||||
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
|
||||
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
|
||||
++CrossingIndex;
|
||||
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,ResidentKeys,CrossingIndex);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(DesiredTiles.size()))
|
||||
@@ -2847,13 +2968,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsFullDetailUploaded()const
|
||||
bool IsFullDetailUploaded()
|
||||
{
|
||||
if(!Streamer||World->FullResClipLevels<=0)return false;
|
||||
int32 ResidentFullRes=0;
|
||||
int32 DesiredFullRes=0,ReadyFullRes=0;
|
||||
for(const FVoxelTileKey& Key:DesiredTiles)
|
||||
if(Key.Level<World->FullResClipLevels&&ResidentTiles.contains(Key))++ResidentFullRes;
|
||||
if(ResidentFullRes!=Streamer->FullResDesiredCount())return false;
|
||||
{
|
||||
if(Key.Level>=World->FullResClipLevels)continue;
|
||||
++DesiredFullRes;
|
||||
if(IsRenderedTileReady(Key))++ReadyFullRes;
|
||||
}
|
||||
if(ReadyFullRes!=DesiredFullRes)return false;
|
||||
for(const FStandaloneBatchKey& Key:DirtyBatchSet)if(Key.Level<World->FullResClipLevels)return false;
|
||||
return true;
|
||||
}
|
||||
@@ -3041,8 +3166,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
bFullDetailReported=true;
|
||||
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-ColdProcessStart).count();
|
||||
std::printf("[FullDetailReady] %.3f s after cold process start; resident=%zu/%d tiles through LOD%d.\n",
|
||||
Elapsed,ResidentTiles.size(),Streamer->FullResDesiredCount(),World->FullResClipLevels-1);
|
||||
const std::size_t DesiredNear=static_cast<std::size_t>(std::count_if(
|
||||
DesiredTiles.begin(),DesiredTiles.end(),[&](const FVoxelTileKey& Key)
|
||||
{return Key.Level<World->FullResClipLevels;}));
|
||||
std::printf("[FullDetailReady] %.3f s after cold process start; rendered=%zu/%zu tiles through LOD%d.\n",
|
||||
Elapsed,DesiredNear,DesiredNear,World->FullResClipLevels-1);
|
||||
std::printf("[ColdTimeline] event=near_all_tiles_ready at_ms=%.3f waits_for=all_%zu_LOD0_1_gpu_or_cpu_tiles\n",
|
||||
Elapsed*1000.0,DesiredNear);
|
||||
ReportTileTimings();std::fflush(stdout);
|
||||
StartFarShapeGeneration();
|
||||
}
|
||||
@@ -3056,6 +3186,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::printf("[InitialClipmapReady] %.3f s after cold process start; %zu tiles, %llu vertices, %llu indices, %zu GPU batches.\n",
|
||||
Elapsed,DesiredResidentCount,static_cast<unsigned long long>(TotalVertices),
|
||||
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
|
||||
std::printf("[ColdTimeline] event=whole_clipmap_ready at_ms=%.3f waits_for=%zu_GPU_tiles_plus_CPU_fallback_and_uploads\n",
|
||||
Elapsed*1000.0,GpuFarEligibleTiles.size());
|
||||
if(bGpuFarEnabled)
|
||||
{
|
||||
SampleGpuFarMemory();
|
||||
@@ -3068,6 +3200,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
GpuFarEnqueueWallSeconds*1000.0,
|
||||
GpuFarGpuDispatchMs,AverageGpuMs,GpuFarGpuDispatchMaxMs,
|
||||
GpuFarSubmissionSpanMs,GpuFarSubmissionSpanMaxMs);
|
||||
std::printf("[GpuFarTimeline] pump_calls=%llu pump_first_ms=%.3f pump_last_ms=%.3f pump_gap_sum_ms=%.3f pump_gap_max_ms=%.3f pump_cpu_sum_ms=%.3f pump_cpu_max_ms=%.3f submit_first_ms=%.3f submit_last_ms=%.3f completion_polls=%llu completion_first_ms=%.3f completion_last_ms=%.3f queue_envelope_ms=%.3f gpu_busy_ms=%.3f estimated_idle_or_poll_ms=%.3f\n",
|
||||
static_cast<unsigned long long>(GpuFarPumpCalls),GpuFarFirstPumpMs,GpuFarLastPumpMs,
|
||||
GpuFarPumpGapMs,GpuFarPumpGapMaxMs,GpuFarPumpWallMs,GpuFarPumpMaxWallMs,
|
||||
GpuFarFirstSubmitMs,GpuFarLastSubmitMs,
|
||||
static_cast<unsigned long long>(GpuFarCompletionPolls),GpuFarFirstCompletionPollMs,
|
||||
GpuFarLastCompletionPollMs,
|
||||
GpuFarFirstSubmitMs>=0.0&&GpuFarLastCompletionPollMs>=0.0
|
||||
?GpuFarLastCompletionPollMs-GpuFarFirstSubmitMs:0.0,
|
||||
GpuFarSubmissionSpanMs,
|
||||
GpuFarFirstSubmitMs>=0.0&&GpuFarLastCompletionPollMs>=0.0
|
||||
?std::max(0.0,GpuFarLastCompletionPollMs-GpuFarFirstSubmitMs-GpuFarSubmissionSpanMs):0.0);
|
||||
std::printf("[GpuFarMemory] dxgi_local_sampled_peak_mb=%.1f dxgi_local_budget_mb=%.1f\n",
|
||||
GpuFarLocalMemoryPeakBytes/(1024.0*1024.0),
|
||||
GpuFarLocalMemoryBudgetBytes/(1024.0*1024.0));
|
||||
@@ -3114,7 +3257,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
uint64 VisibleIndices=0;for(const auto& Pair:StreamingBatches)if(bShowLevel[Pair.first.Level])VisibleIndices+=Pair.second.IndexCount;
|
||||
std::ostringstream S;S<<"VoxelForge standalone | seed "<<Seed<<" | "<<(bShapeWorld?"shape-interval owner world":(bRoundTunnels?"round/winding research tunnels":"base world"))
|
||||
<<(bRoundTunnels?(bRoundTunnelEnhanced?" (detailed walls)":" (legacy walls)"):"")
|
||||
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | far "<<(bGpuFarEnabled?"GPU":"CPU")
|
||||
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | field "<<(bGpuFarEnabled?"GPU":"CPU")
|
||||
<<" | "<<(bGenerationRunning?"generating":"ready")
|
||||
<<" | pos "<<std::fixed<<std::setprecision(1)<<Position.X<<", "<<Position.Y<<", "<<Position.Z<<" m";
|
||||
SetWindowTextA(Window,S.str().c_str());
|
||||
@@ -3122,8 +3265,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
|
||||
void Draw()
|
||||
{
|
||||
const auto DrawStart=std::chrono::steady_clock::now();
|
||||
const double InterDrawGapMs=LastDrawEndAt.time_since_epoch().count()!=0
|
||||
?std::chrono::duration<double,std::milli>(DrawStart-LastDrawEndAt).count():0.0;
|
||||
PumpGeneration();
|
||||
const auto CpuPumpEnd=std::chrono::steady_clock::now();
|
||||
PumpGpuFarGeneration();
|
||||
const auto GpuPumpEnd=std::chrono::steady_clock::now();
|
||||
const auto Now=std::chrono::steady_clock::now();
|
||||
if(FlightTargetCrossings>0&&bFullDetailReported&&!bFlightStarted)
|
||||
{
|
||||
@@ -3179,7 +3327,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
// Avoid issuing hundreds of tiny indirect draw calls while the
|
||||
// initial far batches are still being built. Once the clipmap is
|
||||
// ready the pooled GPU meshes draw directly as before.
|
||||
if(bGpuFarEnabled&&bInitialClipmapReported&&ShapeWorld&&ShapeWorld->hasFarWorld())
|
||||
if(bGpuFarEnabled&&ShapeWorld&&ShapeWorld->hasFarWorld())
|
||||
{
|
||||
C.Padding[0]=0.01f;
|
||||
Context->UpdateSubresource(Constants,0,nullptr,&C,0,0);
|
||||
@@ -3188,6 +3336,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
const FGpuFarTile& Tile=Pair.second;
|
||||
if(!bShowLevel[Pair.first.Level]||!Tile.VertexBuffer||!Tile.IndexBuffer||!Tile.IndirectArgs)continue;
|
||||
if(!bInitialClipmapReported&&Pair.first.Level>=World->FullResClipLevels)continue;
|
||||
if(!IsRenderedTileReady(Pair.first))continue;
|
||||
const UINT Stride=32,Offset=0;
|
||||
Context->IASetVertexBuffers(0,1,&Tile.VertexBuffer,&Stride,&Offset);
|
||||
Context->IASetIndexBuffer(Tile.IndexBuffer,DXGI_FORMAT_R32_UINT,0);
|
||||
@@ -3202,7 +3352,25 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
}
|
||||
}
|
||||
CaptureRenderedFrame();
|
||||
SwapChain->Present(1,0);UpdateTitle();
|
||||
const auto BeforePresent=std::chrono::steady_clock::now();
|
||||
const UINT PresentSyncInterval=1u;
|
||||
const auto PresentCallStart=std::chrono::steady_clock::now();
|
||||
SwapChain->Present(PresentSyncInterval,0);
|
||||
const auto PresentCallEnd=std::chrono::steady_clock::now();
|
||||
UpdateTitle();
|
||||
const auto DrawEnd=std::chrono::steady_clock::now();
|
||||
const double CpuPumpMs=std::chrono::duration<double,std::milli>(CpuPumpEnd-DrawStart).count();
|
||||
const double GpuPumpMs=std::chrono::duration<double,std::milli>(GpuPumpEnd-CpuPumpEnd).count();
|
||||
const double PresentCallMs=std::chrono::duration<double,std::milli>(PresentCallEnd-PresentCallStart).count();
|
||||
const double TitleMs=std::chrono::duration<double,std::milli>(DrawEnd-PresentCallEnd).count();
|
||||
const double RenderPresentMs=PresentCallMs+TitleMs;
|
||||
if(InterDrawGapMs>50.0||CpuPumpMs>50.0||GpuPumpMs>50.0||RenderPresentMs>50.0)
|
||||
std::printf("[LongViewerFrame] at_ms=%.3f inter_draw_gap_ms=%.3f cpu_pump_ms=%.3f gpu_pump_ms=%.3f pre_present_ms=%.3f present_sync=%u present_call_ms=%.3f title_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(DrawStart-ColdProcessStart).count(),
|
||||
InterDrawGapMs,CpuPumpMs,GpuPumpMs,
|
||||
std::chrono::duration<double,std::milli>(BeforePresent-GpuPumpEnd).count(),
|
||||
PresentSyncInterval,PresentCallMs,TitleMs);
|
||||
LastDrawEndAt=DrawEnd;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5106,6 +5274,8 @@ int main(int Argc,char** Argv)
|
||||
}
|
||||
App.bProfileDetailBand=bProfileDetailBand;
|
||||
std::string Error;
|
||||
std::printf("[ColdTimeline] stage=owner_data_load begin_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count());
|
||||
App.World=std::make_shared<FStandaloneOwnerWorld>();
|
||||
{
|
||||
std::ifstream JsonFile(App.DataPath,std::ios::binary);
|
||||
@@ -5118,6 +5288,8 @@ int main(int Argc,char** Argv)
|
||||
}
|
||||
if(!StandaloneOwnerLoader::Resolve(App.OwnerJsonRoot,*App.World,Error))
|
||||
{std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;}
|
||||
std::printf("[ColdTimeline] stage=owner_data_load end_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count());
|
||||
App.Seed=App.World->Seed;
|
||||
if(App.bParityOnly)
|
||||
{
|
||||
@@ -5213,7 +5385,13 @@ int main(int Argc,char** Argv)
|
||||
App.Window=CreateWindowA(Class.lpszClassName,"VoxelForge standalone world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
|
||||
Rect.right-Rect.left,Rect.bottom-Rect.top,nullptr,nullptr,Class.hInstance,nullptr);
|
||||
GApp=&App;
|
||||
const auto DeviceInitStart=std::chrono::steady_clock::now();
|
||||
std::printf("[ColdTimeline] stage=viewer_device_init begin_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(DeviceInitStart-ColdProcessStart).count());
|
||||
if(!App.Window||!App.InitializeDevice()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","VoxelForge standalone",MB_ICONERROR);return 3;}
|
||||
std::printf("[ColdTimeline] stage=viewer_device_init end_ms=%.3f duration_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count(),
|
||||
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-DeviceInitStart).count());
|
||||
ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window);
|
||||
std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; G toggles legacy/detailed round walls; H toggles CPU/GPU far tiles; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; V swaps owner density/shape intervals; Esc quit.\n");
|
||||
std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n",
|
||||
|
||||
Reference in new issue
Block a user