standalone: per-tile speed at plugin level, streaming like the game, softer lighting
Cause found by profiling: room/SDF graph-cache churn (exact XY bounds + sample order rebuilt it constantly). Region grouping (one LOD3 tile: 1,084 -> 229 builds), taller-tile traversal for LOD6 (36,480 -> 12,160), extent-dependent cache slots, lambdas instead of recursive std::function. Serial ms/tile, 64 nearest tiles per level (before -> after): L0 50.4 -> 50.6, L1 112 -> 110, L2 24 -> 22, L3 223 -> 73, L4 269 -> 134, L5 393 -> 127, L6 283 -> 145. Hashes of all levels unchanged; parity 3,436/3,436 bit-identical. Full clipmap, 16 workers BelowNormal: full detail 66.6 s, all 12,054 tiles 175 s. In-stream L0 tail is high (median 83 ms, mean 198 ms, p95 669 ms) vs 51 ms serial. Streaming: recentre keeps the intersection, generates only missing tiles nearest/finest first; one crossing = exactly 441 new tiles in 6.1 s, no stale results. Lighting: normals already come from the density gradient; the striping was the squared headlamp; broad linear fill + warmer palette. Daytime CPU rule documented in Standalone/README.md. 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
3f300b912a
commit
81b7d6d0ff
24 files changed
+1096
-196
No files matched your search
@@ -1,6 +1,7 @@
|
||||
#include "OwnerWorldLoader.h"
|
||||
#include "StreamingWorld.h"
|
||||
#include "StandaloneResearchTunnels.h"
|
||||
#include "VoxelDensityProfile.h"
|
||||
|
||||
#ifdef TEXT
|
||||
#undef TEXT
|
||||
@@ -18,6 +19,8 @@
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
@@ -105,9 +108,16 @@ namespace
|
||||
};
|
||||
struct FCrossingWork
|
||||
{
|
||||
uint64 SubmittedJobs=0;
|
||||
uint64 PendingJobs=0;
|
||||
uint64 FinishedJobs=0;
|
||||
uint64 CancelledQueuedJobs=0;
|
||||
uint64 StaleResults=0;
|
||||
uint64 Tiles=0;
|
||||
uint64 Samples=0;
|
||||
double WorkerSeconds=0.0;
|
||||
std::chrono::steady_clock::time_point StartTime{};
|
||||
bool bReported=false;
|
||||
};
|
||||
|
||||
FStandaloneBatchKey BatchForTile(const FVoxelTileKey& Tile)
|
||||
@@ -133,6 +143,9 @@ namespace
|
||||
std::unique_ptr<FStandaloneOwnerWorld> World;
|
||||
std::unique_ptr<FStandaloneStreamingGenerator> Streamer;
|
||||
std::string DataPath;
|
||||
bool bHasCameraOverride=false;
|
||||
V3 CameraPositionOverride{};
|
||||
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
|
||||
std::unordered_map<FVoxelTileKey,FResidentTileMesh,FStandaloneTileKeyHash> ResidentTiles;
|
||||
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
|
||||
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
|
||||
@@ -143,6 +156,7 @@ namespace
|
||||
uint64 GeneratedByLevel[MaxLevels+1]{};
|
||||
uint64 SamplesByLevel[MaxLevels+1]{};
|
||||
double WorkerSecondsByLevel[MaxLevels+1]{};
|
||||
uint64 ProfileCyclesByLevel[MaxLevels+1][VoxelDensityProfile::BucketCount]{};
|
||||
std::vector<double> TileSecondsByLevel[MaxLevels+1];
|
||||
uint64 CrossingIndex=0;
|
||||
bool bGenerationRunning=false;
|
||||
@@ -154,11 +168,23 @@ namespace
|
||||
bool bAutoCaptureWritten=false;
|
||||
bool bFullDetailReported=false;
|
||||
bool bInitialClipmapReported=false;
|
||||
bool bProfileAttribution=false;
|
||||
bool bCompareEvaluators=false;
|
||||
bool bParityOnly=false;
|
||||
bool bDensityHash=false;
|
||||
bool bTestOneChunkCrossing=false;
|
||||
bool bCrossingMoveStarted=false;
|
||||
bool bCrossingMoveFinished=false;
|
||||
bool bCrossingClosePosted=false;
|
||||
std::chrono::steady_clock::time_point CrossingMoveStopTime{};
|
||||
bool bCaptureAfterClipmap=false;
|
||||
bool bQuitAfterClipmap=false;
|
||||
std::string CapturePath;
|
||||
std::chrono::steady_clock::time_point GenerationStart{};
|
||||
bool bShowLevel[MaxLevels+1]={true,true,true,true,true,true,true,true,true};
|
||||
bool Keys[256]{};
|
||||
int32 Seed=0;
|
||||
int32 WorkerCount=6;
|
||||
int32 Width=1280,Height=800;
|
||||
float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f;
|
||||
V3 Position{};
|
||||
@@ -190,7 +216,9 @@ namespace
|
||||
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
|
||||
DirtyBatchSet.clear();DesiredTiles.clear();CrossingWork.clear();
|
||||
for(int32 I=0;I<=MaxLevels;++I)
|
||||
{DesiredByLevel[I]=0;GeneratedByLevel[I]=0;SamplesByLevel[I]=0;WorkerSecondsByLevel[I]=0.0;TileSecondsByLevel[I].clear();}
|
||||
{DesiredByLevel[I]=0;GeneratedByLevel[I]=0;SamplesByLevel[I]=0;WorkerSecondsByLevel[I]=0.0;
|
||||
std::fill(std::begin(ProfileCyclesByLevel[I]),std::end(ProfileCyclesByLevel[I]),0);
|
||||
TileSecondsByLevel[I].clear();}
|
||||
TotalVertices=0;TotalIndices=0;
|
||||
}
|
||||
|
||||
@@ -335,9 +363,12 @@ O VS(I i){O o;o.p=mul(float4(i.p,1),VP);o.world=i.p;o.n=normalize(i.n);return o;
|
||||
float4 PS(O i):SV_TARGET{
|
||||
float3 N=normalize(i.n);float3 V=normalize(Camera-i.world);float3 L=normalize(-LightDir);
|
||||
float Diffuse=saturate(dot(N,L));float Head=saturate(dot(N,V));
|
||||
float Dist=length(Camera-i.world);float Atten=1.0-saturate(Dist/95.0);Head=Head*Head*Atten;
|
||||
float3 Rock=lerp(float3(0.28,0.34,0.38),float3(0.43,0.47,0.48),saturate(N.z*0.5+0.5));
|
||||
float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.62,0.76,0.9)*Head*1.25;
|
||||
float Dist=length(Camera-i.world);float Atten=1.0-saturate(Dist/95.0);
|
||||
float3 Rock=lerp(float3(0.30,0.22,0.16),float3(0.48,0.43,0.34),saturate(N.z*0.5+0.5));
|
||||
// Keep the camera fill broad and low-amplitude. The old squared, high-intensity
|
||||
// lobe made the 25 cm density-lattice normal variation read as horizontal bands.
|
||||
Head=Head*Atten;
|
||||
float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
float Fog=saturate((Dist-FogStart)/(FogEnd-FogStart));
|
||||
Lit=lerp(Lit,float3(0.10,0.14,0.19),Fog);return float4(Lit,1);
|
||||
})";
|
||||
@@ -361,9 +392,9 @@ float4 PS(O i):SV_TARGET{
|
||||
return Resize(Width,Height);
|
||||
}
|
||||
|
||||
void PrintParity()
|
||||
size_t PrintParity()
|
||||
{
|
||||
if(bParityChecked||!World)return;
|
||||
if(bParityChecked||!World)return 0;
|
||||
bParityChecked=true;size_t MismatchCount=0;
|
||||
std::map<std::string,size_t> Kinds;
|
||||
for(size_t I=0;I<World->DensitySamples.size();++I)
|
||||
@@ -384,6 +415,7 @@ float4 PS(O i):SV_TARGET{
|
||||
for(const auto& [Kind,Count]:Kinds)std::printf(" %-24s %zu\n",Kind.c_str(),Count);
|
||||
std::fflush(stdout);
|
||||
std::fflush(stdout);
|
||||
return MismatchCount;
|
||||
}
|
||||
|
||||
bool ConfigureWorld(std::string& Error)
|
||||
@@ -428,12 +460,22 @@ float4 PS(O i):SV_TARGET{
|
||||
BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels);
|
||||
GenerationStart=std::chrono::steady_clock::now();
|
||||
bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false;
|
||||
if(bProfileAttribution)
|
||||
{
|
||||
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Attribution,128);
|
||||
VoxelDensityProfile::CalibrateTimer();
|
||||
VoxelDensityProfile::Reset();
|
||||
std::printf("Density stage profiling enabled (Attribution, sample interval 128).\n");
|
||||
}
|
||||
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
|
||||
DesiredTiles=Desired.Tiles;
|
||||
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
|
||||
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
|
||||
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,6);
|
||||
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount);
|
||||
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,{},CrossingIndex);
|
||||
FCrossingWork& InitialWork=CrossingWork[CrossingIndex];
|
||||
InitialWork.SubmittedJobs=Update.NewJobs;InitialWork.PendingJobs=Update.NewJobs;
|
||||
InitialWork.StartTime=GenerationStart;
|
||||
bGenerationRunning=true;bRebuildPending=false;
|
||||
std::printf("Streaming seed %d%s: %d desired tiles, %d full-detail tiles, %d workers; upload budget %d batches / %.1f ms per frame.\n",
|
||||
Seed,bRoundTunnels?" with research round/winding tunnels":"",Update.DesiredTiles,
|
||||
@@ -495,6 +537,15 @@ float4 PS(O i):SV_TARGET{
|
||||
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
|
||||
++CrossingIndex;
|
||||
const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(CameraVoxels,ResidentKeys,CrossingIndex);
|
||||
for(const auto& Pair:Update.CancelledByCrossing)
|
||||
{
|
||||
FCrossingWork& Work=CrossingWork[Pair.first];
|
||||
Work.PendingJobs=Work.PendingJobs>=Pair.second?Work.PendingJobs-Pair.second:0;
|
||||
Work.CancelledQueuedJobs+=Pair.second;
|
||||
}
|
||||
FCrossingWork& Work=CrossingWork[CrossingIndex];
|
||||
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
|
||||
Work.StartTime=std::chrono::steady_clock::now();
|
||||
BuildTargetChunk=Update.CenterChunk;bGenerationRunning=true;
|
||||
std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d dropped_resident=%zu cancelled_queued=%d full_detail=%d\n",
|
||||
static_cast<unsigned long long>(CrossingIndex),Update.CenterChunk.X,Update.CenterChunk.Y,Update.CenterChunk.Z,
|
||||
@@ -502,6 +553,7 @@ float4 PS(O i):SV_TARGET{
|
||||
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,Leavers.size(),
|
||||
Update.CancelledQueuedJobs,Update.FullResTiles);
|
||||
std::fflush(stdout);
|
||||
ReportCrossingWork();
|
||||
}
|
||||
|
||||
bool RebuildBatch(const FStandaloneBatchKey& Key)
|
||||
@@ -621,10 +673,44 @@ float4 PS(O i):SV_TARGET{
|
||||
std::printf("[TileTiming] LOD=%d generated=%llu target=%d samples=%.2fM worker_sum=%.3fs avg=%.3fms p50=%.3fms p95=%.3fms\n",
|
||||
L,static_cast<unsigned long long>(GeneratedByLevel[L]),DesiredByLevel[L],
|
||||
static_cast<double>(SamplesByLevel[L])/1.0e6,WorkerSecondsByLevel[L],Average*1000.0,P50*1000.0,P95*1000.0);
|
||||
if(bProfileAttribution)
|
||||
{
|
||||
std::printf("[TileProfile] LOD=%d",L);
|
||||
for(size_t I=0;I<VoxelDensityProfile::BucketCount;++I)
|
||||
{
|
||||
const double Ms=static_cast<double>(ProfileCyclesByLevel[L][I])
|
||||
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||
if(Ms>0.001)std::printf(" %s=%.3fms",
|
||||
VoxelDensityProfile::BucketName(static_cast<VoxelDensityProfile::EBucket>(I)),Ms);
|
||||
}
|
||||
std::printf("\n");
|
||||
}
|
||||
Times.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ReportCrossingWork()
|
||||
{
|
||||
for(auto& Pair:CrossingWork)
|
||||
{
|
||||
FCrossingWork& Work=Pair.second;
|
||||
if(Work.bReported||Work.PendingJobs!=0)continue;
|
||||
Work.bReported=true;
|
||||
const double AverageMs=Work.FinishedJobs>0
|
||||
?Work.WorkerSeconds*1000.0/static_cast<double>(Work.FinishedJobs):0.0;
|
||||
const double WallSeconds=Work.StartTime.time_since_epoch().count()!=0
|
||||
?std::chrono::duration<double>(std::chrono::steady_clock::now()-Work.StartTime).count():0.0;
|
||||
std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM worker_sum=%.3fs wall=%.3fs avg=%.3fms\n",
|
||||
static_cast<unsigned long long>(Pair.first),
|
||||
static_cast<unsigned long long>(Work.SubmittedJobs),
|
||||
static_cast<unsigned long long>(Work.FinishedJobs),
|
||||
static_cast<unsigned long long>(Work.Tiles),
|
||||
static_cast<unsigned long long>(Work.CancelledQueuedJobs),
|
||||
static_cast<unsigned long long>(Work.StaleResults),
|
||||
static_cast<double>(Work.Samples)/1.0e6,Work.WorkerSeconds,WallSeconds,AverageMs);
|
||||
}
|
||||
}
|
||||
|
||||
void PumpGeneration()
|
||||
{
|
||||
if(bRebuildPending){StartGeneration();return;}
|
||||
@@ -632,11 +718,16 @@ float4 PS(O i):SV_TARGET{
|
||||
std::vector<FStandaloneCpuTile> Ready=Streamer->TakeReady();
|
||||
for(FStandaloneCpuTile& Tile:Ready)
|
||||
{
|
||||
if(!Streamer->IsDesired(Tile.Key)||ResidentTiles.contains(Tile.Key))continue;
|
||||
const int32 Level=Tile.Key.Level;
|
||||
FCrossingWork& Crossing=CrossingWork[Tile.RequestedCrossing];
|
||||
++Crossing.Tiles;Crossing.Samples+=Tile.DensitySamples;Crossing.WorkerSeconds+=Tile.WorkerSeconds;
|
||||
if(Crossing.PendingJobs>0)--Crossing.PendingJobs;
|
||||
++Crossing.FinishedJobs;Crossing.Samples+=Tile.DensitySamples;Crossing.WorkerSeconds+=Tile.WorkerSeconds;
|
||||
if(!Streamer->IsDesired(Tile.Key)||ResidentTiles.contains(Tile.Key))
|
||||
{++Crossing.StaleResults;continue;}
|
||||
++Crossing.Tiles;
|
||||
++GeneratedByLevel[Level];SamplesByLevel[Level]+=Tile.DensitySamples;WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
|
||||
if(bProfileAttribution)for(size_t I=0;I<Tile.ProfileCycles.size();++I)
|
||||
ProfileCyclesByLevel[Level][I]+=Tile.ProfileCycles[I];
|
||||
TileSecondsByLevel[Level].push_back(Tile.WorkerSeconds);
|
||||
FResidentTileMesh Resident;Resident.Vertices=std::move(Tile.Vertices);Resident.Indices=std::move(Tile.Indices);
|
||||
Resident.Samples=Tile.DensitySamples;Resident.WorkerSeconds=Tile.WorkerSeconds;
|
||||
@@ -644,7 +735,20 @@ float4 PS(O i):SV_TARGET{
|
||||
const FStandaloneBatchKey Batch=BatchForTile(Tile.Key);
|
||||
BatchMembers[Batch].push_back(Tile.Key);MarkBatchDirty(Batch);
|
||||
}
|
||||
ReportCrossingWork();
|
||||
UploadDirtyBatches();
|
||||
if(bTestOneChunkCrossing&&bCrossingMoveFinished&&!bCrossingClosePosted
|
||||
&&DirtyBatchSet.empty())
|
||||
{
|
||||
const auto Crossing=CrossingWork.find(1);
|
||||
if(Crossing!=CrossingWork.end()&&Crossing->second.bReported)
|
||||
{
|
||||
bCrossingClosePosted=true;
|
||||
std::printf("[CrossingTestComplete] crossing work and its bounded uploads drained; closing this test viewer.\n");
|
||||
std::fflush(stdout);
|
||||
PostMessageA(Window,WM_CLOSE,0,0);
|
||||
}
|
||||
}
|
||||
if(!bFullDetailReported&&IsFullDetailUploaded())
|
||||
{
|
||||
bFullDetailReported=true;
|
||||
@@ -661,13 +765,15 @@ float4 PS(O i):SV_TARGET{
|
||||
Elapsed,ResidentTiles.size(),static_cast<unsigned long long>(TotalVertices),
|
||||
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
|
||||
ReportTileTimings();std::fflush(stdout);
|
||||
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
|
||||
}
|
||||
bGenerationRunning=ResidentTiles.size()!=DesiredTiles.size()||!DirtyBatchSet.empty();
|
||||
}
|
||||
|
||||
void CaptureRenderedFrame()
|
||||
{
|
||||
if(CapturePath.empty()||bAutoCaptureWritten||!IsFullDetailUploaded())return;
|
||||
const bool bReadyToCapture=bCaptureAfterClipmap?bInitialClipmapReported:IsFullDetailUploaded();
|
||||
if(CapturePath.empty()||bAutoCaptureWritten||!bReadyToCapture)return;
|
||||
bAutoCaptureWritten=SaveBackBufferPng(CapturePath);
|
||||
if(bAutoCaptureWritten)
|
||||
{
|
||||
@@ -698,6 +804,21 @@ float4 PS(O i):SV_TARGET{
|
||||
{
|
||||
PumpGeneration();
|
||||
const auto Now=std::chrono::steady_clock::now();
|
||||
if(bTestOneChunkCrossing&&bFullDetailReported&&!bCrossingMoveStarted&&!bCrossingMoveFinished)
|
||||
{
|
||||
bCrossingMoveStarted=true;
|
||||
CrossingMoveStopTime=Now+std::chrono::milliseconds(350);
|
||||
Keys[VK_SHIFT]=true;Keys['W']=true;
|
||||
std::printf("[CrossingTestMove] moving forward for 350 ms after full detail.\n");
|
||||
std::fflush(stdout);
|
||||
}
|
||||
if(bCrossingMoveStarted&&Now>=CrossingMoveStopTime)
|
||||
{
|
||||
Keys[VK_SHIFT]=false;Keys['W']=false;
|
||||
bCrossingMoveStarted=false;bCrossingMoveFinished=true;
|
||||
std::printf("[CrossingTestMove] movement stopped; waiting for crossing work and uploads.\n");
|
||||
std::fflush(stdout);
|
||||
}
|
||||
const float Dt=std::clamp(std::chrono::duration<float>(Now-PreviousFrame).count(),0.0f,0.1f);
|
||||
PreviousFrame=Now;FrameMs=Dt*1000.0;
|
||||
const V3 F=Forward(),R=Normalize(Cross(F,{0,0,1})),U=Normalize(Cross(R,F));
|
||||
@@ -767,23 +888,241 @@ float4 PS(O i):SV_TARGET{
|
||||
const std::filesystem::path Exe(Module);
|
||||
return Exe.parent_path().parent_path()/L"data"/L"owner_world.json";
|
||||
}
|
||||
|
||||
int RunTileBenchmark(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 Level,
|
||||
int32 Count,bool bProfile,bool bCompareEvaluators,bool bDensityHash)
|
||||
{
|
||||
const FVector CameraVoxels(CameraPosition.X*4.0,CameraPosition.Y*4.0,CameraPosition.Z*4.0);
|
||||
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraVoxels);
|
||||
std::vector<FVoxelTileKey> Tiles;Tiles.reserve(static_cast<size_t>(std::max(Count,1)));
|
||||
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||
if(Tile.Level==Level&&static_cast<int32>(Tiles.size())<Count)Tiles.push_back(Tile);
|
||||
if(Tiles.empty())
|
||||
{std::fprintf(stderr,"No LOD %d tiles are in the current desired set.\n",Level);return 4;}
|
||||
|
||||
VoxelGenLOD::InitializeConsoleSwitches();
|
||||
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
|
||||
int32 TileCacheWindow=1,TileCacheWindowMinLOD=3,RoomGraphCacheSlots=1;
|
||||
FParse::Value(FCommandLine::Get(),"voxel.TileCacheWindow=",TileCacheWindow);
|
||||
FParse::Value(FCommandLine::Get(),"voxel.TileCacheWindowMinLOD=",TileCacheWindowMinLOD);
|
||||
FParse::Value(FCommandLine::Get(),"voxel.RoomGraphCacheSlots=",RoomGraphCacheSlots);
|
||||
std::printf("[BenchmarkConfig] tile_cache_window=%d fused_min_lod=%d room_graph_cache_slots=%d command='%s'\n",
|
||||
TileCacheWindow,TileCacheWindowMinLOD,RoomGraphCacheSlots,FCommandLine::Get());
|
||||
VoxelDensityProfile::SetMode(bProfile?VoxelDensityProfile::EMode::Attribution
|
||||
:VoxelDensityProfile::EMode::Disabled,128);
|
||||
if(bProfile)VoxelDensityProfile::CalibrateTimer();
|
||||
VoxelDensityProfile::Reset();
|
||||
const VoxelDensityProfile::FSnapshot Before=VoxelDensityProfile::SnapshotCurrentThread();
|
||||
|
||||
UVoxelMarchingCubesMesher Mesher;
|
||||
Mesher.SetGenerator(World.Generator.get());Mesher.bGenerateSkirts=World.bGenerateSkirts;
|
||||
Mesher.SkirtCells=static_cast<float>(World.SkirtCells);Mesher.LODOctaveDrop=World.LODOctaveDrop;
|
||||
const int32 BandMin=World.StrateContentCutMinLevel<9&&Desired.bHasStrate
|
||||
?Desired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
|
||||
const int32 BandMax=World.StrateContentCutMinLevel<9&&Desired.bHasStrate
|
||||
?(Desired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
|
||||
if(bCompareEvaluators)
|
||||
{
|
||||
const bool bOriginalFused=VoxelGenLOD::IsUseFusedEvaluatorEnabled();
|
||||
uint64 Mismatches=0,ComparedSamples=0,MeshCountMismatches=0;
|
||||
float MaxAbsoluteDifference=0.0f;
|
||||
size_t FirstMismatch=SIZE_MAX;
|
||||
FVoxelTileKey FirstMismatchTile{};
|
||||
VoxelGenLOD::SetUseFusedEvaluatorEnabled(true);
|
||||
for(const FVoxelTileKey& Tile:Tiles)
|
||||
{
|
||||
const int32 Cells=Level<World.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World.CoarseTileCells);
|
||||
const int32 Step=FMath::Max(1,Tile.ExtentVoxels()/Cells);
|
||||
std::vector<float> FusedDensities,OpStackDensities;
|
||||
int64 FusedSampleCalls=0,OpStackSampleCalls=0;
|
||||
FVoxelMeshData FusedMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
|
||||
BandMin,BandMax,&FusedSampleCalls,nullptr,nullptr,&FusedDensities);
|
||||
VoxelGenLOD::SetUseFusedEvaluatorEnabled(false);
|
||||
FVoxelMeshData OpStackMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
|
||||
BandMin,BandMax,&OpStackSampleCalls,nullptr,nullptr,&OpStackDensities);
|
||||
VoxelGenLOD::SetUseFusedEvaluatorEnabled(true);
|
||||
if(FusedMesh.Vertices.Num()!=OpStackMesh.Vertices.Num()
|
||||
|| FusedMesh.Triangles.Num()!=OpStackMesh.Triangles.Num())++MeshCountMismatches;
|
||||
const size_t SharedSamples=std::min(FusedDensities.size(),OpStackDensities.size());
|
||||
ComparedSamples+=SharedSamples;
|
||||
for(size_t I=0;I<SharedSamples;++I)
|
||||
{
|
||||
if(std::bit_cast<uint32>(FusedDensities[I])!=std::bit_cast<uint32>(OpStackDensities[I]))
|
||||
{
|
||||
if(FirstMismatch==SIZE_MAX){FirstMismatch=I;FirstMismatchTile=Tile;}
|
||||
++Mismatches;
|
||||
MaxAbsoluteDifference=std::max(MaxAbsoluteDifference,
|
||||
std::abs(FusedDensities[I]-OpStackDensities[I]));
|
||||
}
|
||||
}
|
||||
Mismatches+=static_cast<uint64>(std::max(FusedDensities.size(),OpStackDensities.size())-SharedSamples);
|
||||
}
|
||||
std::printf("[EvaluatorCompare] LOD=%d tiles=%zu compared_samples=%llu density_mismatches=%llu first_tile=(%d,%d,%d) first_index=%llu max_abs=%.9g mesh_count_mismatch_tiles=%llu original_fused=%d\n",
|
||||
Level,Tiles.size(),static_cast<unsigned long long>(ComparedSamples),
|
||||
static_cast<unsigned long long>(Mismatches),
|
||||
FirstMismatchTile.Coord.X,FirstMismatchTile.Coord.Y,FirstMismatchTile.Coord.Z,
|
||||
static_cast<unsigned long long>(FirstMismatch),MaxAbsoluteDifference,
|
||||
static_cast<unsigned long long>(MeshCountMismatches),bOriginalFused?1:0);
|
||||
VoxelGenLOD::SetUseFusedEvaluatorEnabled(bOriginalFused);
|
||||
std::fflush(stdout);
|
||||
return Mismatches==0?0:5;
|
||||
}
|
||||
std::vector<double> TileSeconds;TileSeconds.reserve(Tiles.size());
|
||||
uint64 Samples=0,Vertices=0,Indices=0;
|
||||
uint64 DensityHash=14695981039346656037ull;
|
||||
const auto TotalStart=std::chrono::steady_clock::now();
|
||||
for(const FVoxelTileKey& Tile:Tiles)
|
||||
{
|
||||
const auto TileStart=std::chrono::steady_clock::now();
|
||||
const int32 Cells=Level<World.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World.CoarseTileCells);
|
||||
const int32 Step=FMath::Max(1,Tile.ExtentVoxels()/Cells);
|
||||
int64 TileSamples=0;
|
||||
std::vector<float> DebugDensitySamples;
|
||||
FVoxelMeshData Mesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
|
||||
BandMin,BandMax,&TileSamples,nullptr,nullptr,
|
||||
bDensityHash?&DebugDensitySamples:nullptr);
|
||||
Samples+=static_cast<uint64>(FMath::Max<int64>(0,TileSamples));
|
||||
Vertices+=static_cast<uint64>(Mesh.Vertices.Num());
|
||||
Indices+=static_cast<uint64>(Mesh.Triangles.Num());
|
||||
if(bDensityHash)
|
||||
{
|
||||
for(const float Density:DebugDensitySamples)
|
||||
{
|
||||
DensityHash^=std::bit_cast<uint32>(Density);
|
||||
DensityHash*=1099511628211ull;
|
||||
}
|
||||
}
|
||||
TileSeconds.push_back(std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-TileStart).count());
|
||||
}
|
||||
const double TotalSeconds=std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-TotalStart).count();
|
||||
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
|
||||
std::sort(TileSeconds.begin(),TileSeconds.end());
|
||||
const double Average=TotalSeconds/static_cast<double>(Tiles.size());
|
||||
std::printf("[TileBenchmark] LOD=%d generated=%zu samples=%.2fM worker_sum=%.3fs avg=%.3fms p50=%.3fms p95=%.3fms vertices=%llu indices=%llu profiling=%s\n",
|
||||
Level,Tiles.size(),static_cast<double>(Samples)/1.0e6,TotalSeconds,Average*1000.0,
|
||||
TileSeconds[(TileSeconds.size()-1)*50/100]*1000.0,
|
||||
TileSeconds[(TileSeconds.size()-1)*95/100]*1000.0,
|
||||
static_cast<unsigned long long>(Vertices),static_cast<unsigned long long>(Indices),
|
||||
bProfile?"Attribution":"off");
|
||||
if(bDensityHash)
|
||||
{
|
||||
std::printf("[TileDensityHash] LOD=%d tiles=%zu hash=0x%016llx\n",Level,Tiles.size(),
|
||||
static_cast<unsigned long long>(DensityHash));
|
||||
}
|
||||
if(bProfile)
|
||||
{
|
||||
std::printf("[TileProfile] LOD=%d",Level);
|
||||
for(int32 I=0;I<VoxelDensityProfile::BucketCount;++I)
|
||||
{
|
||||
const uint64 CycleDelta=After.Cycles[I]>=Before.Cycles[I]
|
||||
?After.Cycles[I]-Before.Cycles[I]:0;
|
||||
const double Ms=static_cast<double>(CycleDelta)
|
||||
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||
if(Ms>0.001)std::printf(" %s=%.3fms",VoxelDensityProfile::BucketName(
|
||||
static_cast<VoxelDensityProfile::EBucket>(I)),Ms);
|
||||
}
|
||||
std::printf("\n");
|
||||
const auto CounterDelta=[&](VoxelDensityProfile::ECounter Counter)
|
||||
{
|
||||
const size_t I=static_cast<size_t>(Counter);
|
||||
return After.Counters[I]>=Before.Counters[I]
|
||||
?After.Counters[I]-Before.Counters[I]:0;
|
||||
};
|
||||
std::printf("[TileCounters] sdf_builds=%llu graph_tile_builds=%llu graph_core_builds=%llu op_block_builds=%llu\n",
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfCacheBuild)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::RoomGraphBuildGeneratorTile)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::RoomGraphBuildGeneratorTunnelCore)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::OpBlockBuilds)));
|
||||
std::printf("[SdfRebuildCauses] strate=%llu seed=%llu fingerprint=%llu layout=%llu manager=%llu terrain_ops=%llu fused_policy=%llu tile_policy=%llu tile_identity=%llu bounds_x=%llu bounds_y=%llu tile_window_accepted=%llu tile_window_rejected=%llu\n",
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildStrate)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildSeed)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildFingerprint)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildLayout)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildManagerLifetime)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildTerrainOps)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildFusedPolicy)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildTilePolicy)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildTileIdentity)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildBoundsX)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildBoundsY)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfTileWindowAccepted)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfTileWindowRejected)));
|
||||
std::printf("[TilePolicyKeyChanges] origin=%llu step=%llu cells=%llu fingerprint=%llu layout=%llu manager=%llu terrain_ops=%llu fused_path=%llu context=%llu\n",
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyOrigin)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyStep)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyCells)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyFingerprint)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyLayout)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyManager)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyTerrainOps)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyFusedPath)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyContext)));
|
||||
}
|
||||
std::fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int Argc,char** Argv)
|
||||
{
|
||||
FApp App;App.DataPath=DefaultDataPath().string();
|
||||
std::string JoinedCommandLine;
|
||||
for(int32 I=1;I<Argc;++I)
|
||||
{if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];}
|
||||
FCommandLine::Set(JoinedCommandLine);
|
||||
int32 BenchmarkLevel=-1,BenchmarkCount=64;
|
||||
for(int32 I=1;I<Argc;++I)
|
||||
{
|
||||
const std::string_view Arg=Argv[I];
|
||||
if(Arg.starts_with("--capture="))App.CapturePath=std::string(Arg.substr(10));
|
||||
else if(Arg=="--profile-attribution")App.bProfileAttribution=true;
|
||||
else if(Arg=="--compare-evaluators")App.bCompareEvaluators=true;
|
||||
else if(Arg=="--parity-only")App.bParityOnly=true;
|
||||
else if(Arg=="--density-hash")App.bDensityHash=true;
|
||||
else if(Arg=="--round-tunnels")App.bRoundTunnels=true;
|
||||
else if(Arg=="--test-one-chunk-crossing")App.bTestOneChunkCrossing=true;
|
||||
else if(Arg=="--capture-after-clipmap")App.bCaptureAfterClipmap=true;
|
||||
else if(Arg=="--quit-after-clipmap")App.bQuitAfterClipmap=true;
|
||||
else if(Arg.starts_with("--workers="))
|
||||
{
|
||||
const int32 Parsed=std::atoi(std::string(Arg.substr(10)).c_str());
|
||||
App.WorkerCount=std::clamp(Parsed,1,32);
|
||||
}
|
||||
else if(Arg.starts_with("--benchmark-level="))
|
||||
BenchmarkLevel=std::atoi(std::string(Arg.substr(18)).c_str());
|
||||
else if(Arg.starts_with("--benchmark-count="))
|
||||
BenchmarkCount=std::max(1,std::atoi(std::string(Arg.substr(18)).c_str()));
|
||||
else if(Arg.starts_with("--camera="))
|
||||
{
|
||||
const std::string Values(Arg.substr(9));
|
||||
float X=0,Y=0,Z=0,YawDegrees=0,PitchDegrees=0;
|
||||
if(std::sscanf(Values.c_str(),"%f,%f,%f,%f,%f",&X,&Y,&Z,&YawDegrees,&PitchDegrees)==5)
|
||||
{
|
||||
App.bHasCameraOverride=true;App.CameraPositionOverride={X,Y,Z};
|
||||
App.CameraYawOverride=YawDegrees*0.017453292519943295f;
|
||||
App.CameraPitchOverride=PitchDegrees*0.017453292519943295f;
|
||||
}
|
||||
else {std::fprintf(stderr,"Invalid --camera value: %s\n",Values.c_str());return 2;}
|
||||
}
|
||||
else if(!Arg.empty()&&Arg.front()!='-')App.DataPath=std::string(Arg);
|
||||
}
|
||||
std::string Error;
|
||||
App.World=std::make_unique<FStandaloneOwnerWorld>();
|
||||
if(!StandaloneOwnerLoader::Load(App.DataPath,*App.World,Error)){std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;}
|
||||
App.Seed=App.World->Seed;
|
||||
if(App.bParityOnly)
|
||||
{
|
||||
VoxelGenLOD::InitializeConsoleSwitches();
|
||||
return App.PrintParity()==0?0:1;
|
||||
}
|
||||
App.Position={static_cast<float>(App.World->ViewerStartVoxels.X*0.25),
|
||||
static_cast<float>(App.World->ViewerStartVoxels.Y*0.25),static_cast<float>(App.World->ViewerStartVoxels.Z*0.25)};
|
||||
if(App.bHasCameraOverride){App.Position=App.CameraPositionOverride;App.Yaw=App.CameraYawOverride;App.Pitch=App.CameraPitchOverride;}
|
||||
if(BenchmarkLevel>=0)
|
||||
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
|
||||
App.bProfileAttribution,App.bCompareEvaluators,App.bDensityHash);
|
||||
App.World.reset();
|
||||
|
||||
WNDCLASSA Class{};Class.lpfnWndProc=WindowProc;Class.hInstance=GetModuleHandleA(nullptr);Class.lpszClassName="VoxelForgeStandaloneWorld";
|
||||
|
||||
Reference in new issue
Block a user