wip(standalone): GPU step 4 -- batched far generation, connector panels on GPU; far survival passes

Up to 32 tiles per deferred command list, one polled event per batch, no per-tile waits; CPU
upload/enqueue 723 -> 46 ms. GPU far meshes pass far survival (7,200 strata boundaries, 10,976
connectors, 0 missing/holes). But hybrid cold 4.36 s vs CPU 2.29 s: whole-tile GPU timestamps sum to
646 ms (0.58 ms/tile; step 3's 0.03 ms covered part of the work only), divergent per-thread shape
loops; CPU far-input export 1.6 worker-s. CPU 44/44 hashes unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-06 11:01:03 +02:00
1 parent 782f195bdb
commit 247682a8e0
18 files changed
+1519 -191

No files matched your search

+312 -31
View File
@@ -33,6 +33,7 @@
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
@@ -1352,10 +1353,24 @@ namespace
ID3D11Buffer* VertexBuffer=nullptr;
ID3D11Buffer* IndexBuffer=nullptr;
ID3D11Buffer* IndirectArgs=nullptr;
ID3D11Buffer* ConnectorVertexBuffer=nullptr;
ID3D11Buffer* ConnectorIndexBuffer=nullptr;
ID3D11Query* CompletionEvent=nullptr;
VoxelForgeGpuFarViewer::GpuTimingQueries TimingQueries{};
uint64 VertexCapacityBytes=0;
uint32 OutputSlot=0,IndirectArgsByteOffset=0,BatchIndex=UINT32_MAX;
double GpuDispatchMs=0.0;
bool bGpuComplete=false;
bool bTimingResolved=false;
};
struct FGpuFarBatch
{
ID3D11Query* CompletionEvent=nullptr;
VoxelForgeGpuFarViewer::GpuTimingQueries TimingQueries{};
uint32 TileCount=0;
uint64 TimestampFrequency=0;
double GpuDispatchMs=0.0;
bool bGpuComplete=false,bTimingResolved=false;
};
uint64 HashStandaloneFile(const std::string& Path)
@@ -1439,6 +1454,7 @@ namespace
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
std::unordered_map<FVoxelTileKey,FGpuFarTile,FStandaloneTileKeyHash> GpuFarTiles;
std::vector<FGpuFarBatch> GpuFarBatches;
std::deque<FVoxelTileKey> GpuFarQueue;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> GpuFarQueued;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> GpuFarEligibleTiles;
@@ -1474,6 +1490,16 @@ namespace
uint64 GpuFarTilesSubmitted=0;
double GpuFarGpuDispatchMs=0.0;
double GpuFarGpuDispatchMaxMs=0.0;
double GpuFarSubmissionSpanMs=0.0,GpuFarSubmissionSpanMaxMs=0.0;
uint64 GpuFarLocalMemoryPeakBytes=0,GpuFarLocalMemoryBudgetBytes=0;
double GpuFarPoolReserveMs=0.0;
uint64 GpuFarSubmissionBatches=0;
uint32 GpuFarMaxTilesPerSubmissionBatch=0;
uint32 GpuFarOutputCapacitySlots=0,GpuFarNextOutputSlot=0;
std::vector<uint32> GpuFarFreeOutputSlots;
std::unordered_map<FVoxelTileKey,VoxelForgeCore::FarFieldTileInput,FStandaloneTileKeyHash> GpuFarPreparedInputs;
std::unordered_map<FVoxelTileKey,std::string,FStandaloneTileKeyHash> GpuFarPreparationErrors;
double GpuFarPreparationWallMs=0.0,GpuFarPreparationWorkerMs=0.0;
VoxelForgeCore::DetailVariant ShapeDetailVariant=VoxelForgeCore::DetailVariant::coherent;
bool bWireframe=false;
bool bCaptured=false;
@@ -1531,6 +1557,41 @@ namespace
if(SwapChain)SwapChain->Release();if(Context)Context->Release();if(Device)Device->Release();
}
static void ReleaseGpuFarTile(FGpuFarTile& Tile)
{
if(Tile.VertexBuffer)Tile.VertexBuffer->Release();
if(Tile.IndexBuffer)Tile.IndexBuffer->Release();
if(Tile.IndirectArgs)Tile.IndirectArgs->Release();
if(Tile.ConnectorVertexBuffer)Tile.ConnectorVertexBuffer->Release();
if(Tile.ConnectorIndexBuffer)Tile.ConnectorIndexBuffer->Release();
if(Tile.CompletionEvent)Tile.CompletionEvent->Release();
if(Tile.TimingQueries.start)Tile.TimingQueries.start->Release();
if(Tile.TimingQueries.end)Tile.TimingQueries.end->Release();
if(Tile.TimingQueries.disjoint)Tile.TimingQueries.disjoint->Release();
Tile={};
}
static void ReleaseGpuFarBatch(FGpuFarBatch& Batch)
{
if(Batch.CompletionEvent)Batch.CompletionEvent->Release();
if(Batch.TimingQueries.start)Batch.TimingQueries.start->Release();
if(Batch.TimingQueries.end)Batch.TimingQueries.end->Release();
if(Batch.TimingQueries.disjoint)Batch.TimingQueries.disjoint->Release();
Batch={};
}
void SampleGpuFarMemory()
{
if(!bGpuFarEnabled||!Device||!Context)return;
uint64 Usage=0,Budget=0;std::string Error;
if(VoxelForgeGpuFarViewer::QueryFarGpuMemory(Device,Context,GpuFarShaderPath,
Usage,Budget,Error))
{
GpuFarLocalMemoryPeakBytes=std::max(GpuFarLocalMemoryPeakBytes,Usage);
GpuFarLocalMemoryBudgetBytes=Budget;
}
}
void ReleaseMeshes()
{
for(auto& Pair:StreamingBatches)
@@ -1540,16 +1601,20 @@ namespace
}
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
for(auto& Pair:GpuFarTiles)
{
if(Pair.second.VertexBuffer)Pair.second.VertexBuffer->Release();
if(Pair.second.IndexBuffer)Pair.second.IndexBuffer->Release();
if(Pair.second.IndirectArgs)Pair.second.IndirectArgs->Release();
if(Pair.second.CompletionEvent)Pair.second.CompletionEvent->Release();
}
GpuFarTiles.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
ReleaseGpuFarTile(Pair.second);
GpuFarTiles.clear();
for(FGpuFarBatch& Batch:GpuFarBatches)ReleaseGpuFarBatch(Batch);
GpuFarBatches.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();
GpuFarPreparationWallMs=0.0;GpuFarPreparationWorkerMs=0.0;
GpuFarAudit.reset();bGpuFarAuditReported=false;
GpuFarTilesSubmitted=0;GpuFarEnqueueWallSeconds=0.0;GpuFarGpuDispatchMs=0.0;
GpuFarGpuDispatchMaxMs=0.0;bGpuFarFirstTileReported=false;
GpuFarGpuDispatchMaxMs=0.0;GpuFarSubmissionSpanMs=0.0;GpuFarSubmissionSpanMaxMs=0.0;
GpuFarLocalMemoryPeakBytes=0;GpuFarLocalMemoryBudgetBytes=0;
bGpuFarFirstTileReported=false;
GpuFarPoolReserveMs=0.0;
GpuFarSubmissionBatches=0;GpuFarMaxTilesPerSubmissionBatch=0;
DirtyBatchSet.clear();DesiredTiles.clear();DesiredTileSet.clear();CrossingWork.clear();
DeferredLeaverTiles.clear();
for(int32 I=0;I<=MaxLevels;++I)
@@ -2104,13 +2169,59 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
auto It=GpuFarTiles.find(Key);
if(It==GpuFarTiles.end())return false;
FGpuFarTile& Tile=It->second;
if(Tile.bGpuComplete)return true;
if(Tile.CompletionEvent&&Context
if(Tile.BatchIndex!=UINT32_MAX&&Tile.BatchIndex<GpuFarBatches.size())
{
FGpuFarBatch& Batch=GpuFarBatches[Tile.BatchIndex];
if(!Batch.bGpuComplete&&Batch.CompletionEvent&&Context
&&Context->GetData(Batch.CompletionEvent,nullptr,0,D3D11_ASYNC_GETDATA_DONOTFLUSH)==S_OK)
{
Batch.bGpuComplete=true;
Batch.CompletionEvent->Release();Batch.CompletionEvent=nullptr;
}
if(Batch.bGpuComplete&&!Batch.bTimingResolved)
{
if(VoxelForgeGpuFarViewer::ResolveGpuTiming(Context,Batch.TimingQueries,
Batch.GpuDispatchMs,&Batch.TimestampFrequency))
{
Batch.bTimingResolved=true;
GpuFarSubmissionSpanMs+=Batch.GpuDispatchMs;
GpuFarSubmissionSpanMaxMs=std::max(GpuFarSubmissionSpanMaxMs,Batch.GpuDispatchMs);
}
}
if(Batch.bGpuComplete&&Batch.bTimingResolved&&!Tile.bTimingResolved
&&Batch.TimestampFrequency)
{
double TileDispatchMs=0.0;
if(VoxelForgeGpuFarViewer::ResolveGpuTimestampPair(Context,Tile.TimingQueries,
Batch.TimestampFrequency,TileDispatchMs))
{
Tile.GpuDispatchMs=TileDispatchMs;Tile.bTimingResolved=true;
GpuFarGpuDispatchMs+=TileDispatchMs;
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,TileDispatchMs);
}
}
return Batch.bGpuComplete;
}
if(!Tile.bGpuComplete&&Tile.CompletionEvent&&Context
&&Context->GetData(Tile.CompletionEvent,nullptr,0,D3D11_ASYNC_GETDATA_DONOTFLUSH)==S_OK)
{
Tile.bGpuComplete=true;
Tile.CompletionEvent->Release();Tile.CompletionEvent=nullptr;
}
if(Tile.bGpuComplete&&!Tile.bTimingResolved)
{
double AsyncDispatchMs=0.0;
if(VoxelForgeGpuFarViewer::ResolveGpuTiming(Context,Tile.TimingQueries,AsyncDispatchMs))
{
if(Tile.GpuDispatchMs==0.0)
{
Tile.GpuDispatchMs=AsyncDispatchMs;
GpuFarGpuDispatchMs+=AsyncDispatchMs;
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,AsyncDispatchMs);
}
Tile.bTimingResolved=true;
}
}
return Tile.bGpuComplete;
}
return ResidentTiles.contains(Key);
@@ -2119,20 +2230,98 @@ 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();
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld())return;
std::vector<FVoxelTileKey> CpuTiles;CpuTiles.reserve(Keys.size());
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))
{
GpuFarEligibleTiles.insert(Key);
if(!GpuFarTiles.contains(Key))
{GpuFarQueue.push_back(Key);GpuFarQueued.insert(Key);}
Eligible.push_back(Key);
}
else CpuTiles.push_back(Key);
}
if(Eligible.size()>GpuFarOutputCapacitySlots)
{
for(auto& Pair:GpuFarTiles)ReleaseGpuFarTile(Pair.second);
GpuFarTiles.clear();GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
GpuFarOutputCapacitySlots=static_cast<uint32>(Eligible.size());
}
if(!Eligible.empty()&&GpuFarOutputCapacitySlots==0)
GpuFarOutputCapacitySlots=static_cast<uint32>(Eligible.size());
if(!Eligible.empty())
{
std::string PoolError;const auto ReserveStart=std::chrono::steady_clock::now();
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();
SampleGpuFarMemory();
}
std::vector<FVoxelTileKey> NeedPreparation;NeedPreparation.reserve(Eligible.size());
for(const FVoxelTileKey& Key:Eligible)
if(!GpuFarTiles.contains(Key))NeedPreparation.push_back(Key);
if(!NeedPreparation.empty())
{
const auto PrepareStart=std::chrono::steady_clock::now();
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());
std::atomic<std::size_t> Next{0};
std::vector<double> WorkerElapsed(ThreadCount,0.0);
std::vector<std::thread> Workers;Workers.reserve(ThreadCount);
for(std::size_t Worker=0;Worker<ThreadCount;++Worker)
{
Workers.emplace_back([&,Worker]
{
const auto WorkerStart=std::chrono::steady_clock::now();
for(;;)
{
const std::size_t Index=Next.fetch_add(1,std::memory_order_relaxed);
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]); }
catch(const std::exception& Error) { Errors[Index]=Error.what(); }
}
WorkerElapsed[Worker]=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-WorkerStart).count();
});
}
for(std::thread& Worker:Workers)Worker.join();
const double PreparationMs=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-PrepareStart).count();
double WorkerSumMs=0.0;for(const double Elapsed:WorkerElapsed)WorkerSumMs+=Elapsed;
GpuFarPreparationWallMs+=PreparationMs;GpuFarPreparationWorkerMs+=WorkerSumMs;
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]));
else
{
++Failed;
GpuFarPreparationErrors.emplace(NeedPreparation[Index],Errors[Index].empty()
?"far-field input export failed":Errors[Index]);
}
}
std::printf("[GpuFarInputPrepare] tiles=%zu workers=%zu wall_ms=%.3f worker_sum_ms=%.3f failed=%zu\n",
NeedPreparation.size(),ThreadCount,PreparationMs,WorkerSumMs,Failed);
std::fflush(stdout);
}
for(const FVoxelTileKey& Key:Eligible)
{
GpuFarEligibleTiles.insert(Key);
if(!GpuFarTiles.contains(Key))
{GpuFarQueue.push_back(Key);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)
@@ -2145,27 +2334,67 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context
||GpuFarQueue.empty())return;
const bool bDeferredBatch=!bGpuFarAuditEnabled;
if(bDeferredBatch)
{
std::string BatchError;
if(!VoxelForgeGpuFarViewer::BeginFarTileBatch(Device,Context,GpuFarShaderPath,BatchError))
{
std::fprintf(stderr,"[GpuFarBatchError] begin: %s\n",BatchError.c_str());
bGpuFarEnabled=false;return;
}
}
const auto Start=std::chrono::steady_clock::now();
int32 Built=0;
std::vector<FVoxelTileKey> BuiltKeys;BuiltKeys.reserve(32);
do
{
const FVoxelTileKey Key=GpuFarQueue.front();GpuFarQueue.pop_front();GpuFarQueued.erase(Key);
if(!DesiredTileSet.contains(Key)||GpuFarTiles.contains(Key))continue;
FGpuFarTile Tile;
if(!GpuFarFreeOutputSlots.empty())
{Tile.OutputSlot=GpuFarFreeOutputSlots.back();GpuFarFreeOutputSlots.pop_back();}
else if(GpuFarNextOutputSlot<GpuFarOutputCapacitySlots)
Tile.OutputSlot=GpuFarNextOutputSlot++;
else
{
std::fprintf(stderr,"[GpuFarPoolError] no free far-output slot for queued tile\n");
bGpuFarEnabled=false;break;
}
VoxelForgeGpuFarViewer::CpuMesh AuditMesh;
std::string Error;double EnqueueMs=0.0,GpuDispatchMs=0.0;
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
if(!VoxelForgeGpuFarViewer::BuildFarTile(Device,Context,*ShapeWorld,CoreKey,
GpuFarShaderPath,&Tile.VertexBuffer,&Tile.IndexBuffer,&Tile.IndirectArgs,
&Tile.CompletionEvent,
(bGpuFarAuditEnabled&&Key.Level>=6)?&AuditMesh:nullptr,
&EnqueueMs,&GpuDispatchMs,Error))
const auto PreparedInputIt=GpuFarPreparedInputs.find(Key);
const VoxelForgeCore::FarFieldTileInput* PreparedInput=PreparedInputIt==GpuFarPreparedInputs.end()
?nullptr:&PreparedInputIt->second;
if(!PreparedInput)
{
std::fprintf(stderr,"[GpuFarTileError] key=(%d,%d,%d,lod%d): %s\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,Error.c_str());
const auto PrepError=GpuFarPreparationErrors.find(Key);
std::fprintf(stderr,"[GpuFarInputError] key=(%d,%d,%d,lod%d): %s\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
PrepError==GpuFarPreparationErrors.end()?"prepared far input is missing":PrepError->second.c_str());
GpuFarFreeOutputSlots.push_back(Tile.OutputSlot);
bGpuFarEnabled=false;
break;
}
if(!VoxelForgeGpuFarViewer::BuildFarTile(Device,Context,*ShapeWorld,CoreKey,
PreparedInput,
GpuFarShaderPath,&Tile.VertexBuffer,&Tile.IndexBuffer,&Tile.IndirectArgs,
&Tile.ConnectorVertexBuffer,&Tile.ConnectorIndexBuffer,
Tile.OutputSlot,GpuFarOutputCapacitySlots,&Tile.IndirectArgsByteOffset,
bDeferredBatch?nullptr:&Tile.CompletionEvent,
&Tile.TimingQueries,
(bGpuFarAuditEnabled&&Key.Level>=6)?&AuditMesh:nullptr,
&EnqueueMs,&GpuDispatchMs,Error))
{
GpuFarFreeOutputSlots.push_back(Tile.OutputSlot);
std::fprintf(stderr,"[GpuFarTileError] key=(%d,%d,%d,lod%d): %s\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,Error.c_str());
if(PreparedInputIt!=GpuFarPreparedInputs.end())GpuFarPreparedInputs.erase(PreparedInputIt);
bGpuFarEnabled=false;
break;
}
if(PreparedInputIt!=GpuFarPreparedInputs.end())GpuFarPreparedInputs.erase(PreparedInputIt);
if(bGpuFarAuditEnabled&&GpuFarAudit&&Key.Level>=6)
{
FStandaloneCpuTile AuditTile;AuditTile.Key=Key;
@@ -2182,10 +2411,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
Tile.VertexCapacityBytes=Cells*Cells*Cells*15u*32u;
Tile.GpuDispatchMs=GpuDispatchMs;
GpuFarEnqueueWallSeconds+=EnqueueMs/1000.0;
GpuFarGpuDispatchMs+=GpuDispatchMs;
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,GpuDispatchMs);
if(GpuDispatchMs>0.0)
{
GpuFarGpuDispatchMs+=GpuDispatchMs;
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,GpuDispatchMs);
Tile.bTimingResolved=true;
}
++GpuFarTilesSubmitted;++Built;
GpuFarTiles.emplace(Key,Tile);
BuiltKeys.push_back(Key);
if(!bGpuFarFirstTileReported)
{
bGpuFarFirstTileReported=true;
@@ -2195,8 +2429,42 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::fflush(stdout);
}
}
while(Built<1&& !GpuFarQueue.empty()
&&std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-Start).count()<1.0);
while(Built<32&&!GpuFarQueue.empty()
&&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);
std::string BatchError;
if(!VoxelForgeGpuFarViewer::SubmitFarTileBatch(Device,Context,GpuFarShaderPath,
&Batch.CompletionEvent,&Batch.TimingQueries,BatchError))
{
std::fprintf(stderr,"[GpuFarBatchError] submit: %s\n",BatchError.c_str());
bGpuFarEnabled=false;
}
else
{
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;
}
++GpuFarSubmissionBatches;
GpuFarMaxTilesPerSubmissionBatch=std::max<uint32>(GpuFarMaxTilesPerSubmissionBatch,
static_cast<uint32>(Built));
}
}
else
{
++GpuFarSubmissionBatches;
GpuFarMaxTilesPerSubmissionBatch=std::max<uint32>(GpuFarMaxTilesPerSubmissionBatch,
static_cast<uint32>(Built));
}
}
SampleGpuFarMemory();
}
void ToggleFarRenderer()
@@ -2423,10 +2691,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
for(auto It=GpuFarTiles.begin();It!=GpuFarTiles.end();)
{
if(DesiredTileSet.contains(It->first)){++It;continue;}
if(It->second.VertexBuffer)It->second.VertexBuffer->Release();
if(It->second.IndexBuffer)It->second.IndexBuffer->Release();
if(It->second.IndirectArgs)It->second.IndirectArgs->Release();
if(It->second.CompletionEvent)It->second.CompletionEvent->Release();
GpuFarFreeOutputSlots.push_back(It->second.OutputSlot);
ReleaseGpuFarTile(It->second);
It=GpuFarTiles.erase(It);
}
ScheduleGpuFarTiles(DesiredTiles);
@@ -2786,11 +3052,19 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
if(bGpuFarEnabled)
{
SampleGpuFarMemory();
const double AverageGpuMs=GpuFarTilesSubmitted
?GpuFarGpuDispatchMs/static_cast<double>(GpuFarTilesSubmitted):0.0;
std::printf("[GpuFarTiming] submitted_tiles=%llu cpu_export_upload_enqueue_wall_ms=%.3f gpu_timestamp_total_ms=%.3f gpu_timestamp_mean_per_one_tile_frame_ms=%.6f gpu_timestamp_max_tile_ms=%.6f frame_budget_90hz_ms=11.111\n",
std::printf("[GpuFarTiming] submitted_tiles=%llu submission_batches=%llu max_tiles_per_batch=%u pooled_output_reserve_wall_ms=%.3f cpu_input_prepare_wall_ms=%.3f cpu_input_worker_sum_ms=%.3f cpu_upload_enqueue_sum_ms=%.3f gpu_dispatch_timestamp_total_ms=%.3f gpu_dispatch_mean_per_tile_ms=%.6f gpu_dispatch_max_tile_ms=%.6f gpu_submission_span_total_ms=%.3f gpu_submission_span_max_batch_ms=%.3f frame_budget_90hz_ms=11.111\n",
static_cast<unsigned long long>(GpuFarTilesSubmitted),
GpuFarEnqueueWallSeconds*1000.0,GpuFarGpuDispatchMs,AverageGpuMs,GpuFarGpuDispatchMaxMs);
static_cast<unsigned long long>(GpuFarSubmissionBatches),GpuFarMaxTilesPerSubmissionBatch,
GpuFarPoolReserveMs,GpuFarPreparationWallMs,GpuFarPreparationWorkerMs,
GpuFarEnqueueWallSeconds*1000.0,
GpuFarGpuDispatchMs,AverageGpuMs,GpuFarGpuDispatchMaxMs,
GpuFarSubmissionSpanMs,GpuFarSubmissionSpanMaxMs);
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));
}
ReportTileTimings();std::fflush(stdout);
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
@@ -2908,7 +3182,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
const UINT Stride=32,Offset=0;
Context->IASetVertexBuffers(0,1,&Tile.VertexBuffer,&Stride,&Offset);
Context->IASetIndexBuffer(Tile.IndexBuffer,DXGI_FORMAT_R32_UINT,0);
Context->DrawIndexedInstancedIndirect(Tile.IndirectArgs,0);
Context->DrawIndexedInstancedIndirect(Tile.IndirectArgs,Tile.IndirectArgsByteOffset);
if(Tile.ConnectorVertexBuffer&&Tile.ConnectorIndexBuffer)
{
Context->IASetVertexBuffers(0,1,&Tile.ConnectorVertexBuffer,&Stride,&Offset);
Context->IASetIndexBuffer(Tile.ConnectorIndexBuffer,DXGI_FORMAT_R32_UINT,0);
Context->DrawIndexedInstancedIndirect(Tile.IndirectArgs,
Tile.IndirectArgsByteOffset+5u*sizeof(uint32));
}
}
}
CaptureRenderedFrame();