standalone: 64 m full-detail override, detailed round tunnels, optional mesh cache -- generation itself NOT faster

ClipRadius override 3 (64 m at 25 cm). Uncached full detail 20.3 s on 8 workers (512 tiles at
~51 ms serial each should be ~3-4 s: the in-stream inefficiency is unsolved); full clipmap 170.6 s.
The 1 s / 5 s targets were met only from an on-disk mesh cache (.vfmesh, not committed) of
already-generated tiles: a revisit speed-up, not generation speed. Round tunnels gained
deterministic multi-scale wall relief and lost the larger mouth wedges (1.54% of lattice signs
changed, mean 0.20 m). Hashes stable across runs and 1 vs 6 workers.

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-03 08:59:46 +02:00
1 parent 81b7d6d0ff
commit 5441c7da5c
181 files changed
+1674 -51

No files matched your search

+440 -27
View File
@@ -27,6 +27,7 @@
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
@@ -120,12 +121,49 @@ namespace
bool bReported=false;
};
uint64 HashStandaloneFile(const std::string& Path)
{
std::ifstream File(Path,std::ios::binary);
if(!File)return 0;
uint64 Hash=14695981039346656037ull;
char Buffer[65536];
while(File)
{
File.read(Buffer,sizeof(Buffer));
const std::streamsize Count=File.gcount();
for(std::streamsize I=0;I<Count;++I)
{
Hash^=static_cast<uint8>(Buffer[I]);
Hash*=1099511628211ull;
}
}
return Hash;
}
FStandaloneBatchKey BatchForTile(const FVoxelTileKey& Tile)
{
return {Tile.Level,VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord.X,8),
VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord.Y,8),Tile.Coord.Z};
}
bool PrepareStandaloneDensityWorld(FStandaloneOwnerWorld& World,int32 Seed,
bool bRoundTunnels,bool bEnhancedWalls,std::string& Error)
{
World.Generator.reset();World.Manager.reset();
World.Settings->Seed=Seed;World.Seed=Seed;
// Keep the owner's walkable rooms, tunnels, and strate passages intact. The research
// round field composes as additional air after the owner field, so it can widen/connect
// spaces but cannot remove the authored entrance-to-exit route.
FStandaloneResearchTunnels::Set(bRoundTunnels,bEnhancedWalls);
World.Manager=std::make_unique<UVoxelStrateManager>();
if(!World.Manager->Initialize(World.Settings.get(),Seed))
{Error="standalone copied strate manager failed while applying viewer settings";return false;}
World.Generator=std::make_unique<UVoxelGenerator>();
World.Generator->InitializeSettings(World.Settings.get());
World.Generator->SetStrateManager(World.Manager.get());
return true;
}
struct FApp
{
HWND Window=nullptr;
@@ -142,6 +180,7 @@ namespace
ID3D11RasterizerState* WireRasterizer=nullptr;
std::unique_ptr<FStandaloneOwnerWorld> World;
std::unique_ptr<FStandaloneStreamingGenerator> Streamer;
FStandaloneMeshCache MeshCache;
std::string DataPath;
bool bHasCameraOverride=false;
V3 CameraPositionOverride{};
@@ -163,6 +202,7 @@ namespace
bool bRebuildPending=false;
bool bParityChecked=false;
bool bRoundTunnels=false;
bool bRoundTunnelEnhanced=true;
bool bWireframe=false;
bool bCaptured=false;
bool bAutoCaptureWritten=false;
@@ -179,12 +219,16 @@ namespace
std::chrono::steady_clock::time_point CrossingMoveStopTime{};
bool bCaptureAfterClipmap=false;
bool bQuitAfterClipmap=false;
bool bPrintedClipOverride=false;
bool bForceGenerate=false;
std::string CapturePath;
std::string MeshCachePath;
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 WorkerCount=8;
int32 ClipRadius=3;
int32 Width=1280,Height=800;
float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f;
V3 Position{};
@@ -423,32 +467,64 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
World=std::make_unique<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false;
if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity();
World->Generator.reset();World->Manager.reset();
World->Settings->Seed=Seed;World->Seed=Seed;
if(bRoundTunnels)
const int32 ExportClipRadius=World->ClipRadius;
World->ClipRadius=ClipRadius;
World->Settings->ClipRadius=ClipRadius;
if(!bPrintedClipOverride)
{
// The research tunnel graph replaces the owner's first-strate corridor graph.
for(auto& Pair:World->Settings->FixedStrates)
{
UVoxelStrateDefinition* Definition=Pair.Value.Get();
if(Pair.Key==0&&Definition&&Definition->GeneratorType==ECaveGeneratorType::TunnelNetwork)
{
Definition->GenerationParams.TunnelDensity=0.0f;
Definition->GenerationParams.TunnelWarpStrength=0.0f;
}
}
FStandaloneResearchTunnels::Set(true,Position.Z*4.0f);
bPrintedClipOverride=true;
const double FullDetailWidth=static_cast<double>(2*ClipRadius+2)
*CHUNK_SIZE*VOXEL_SIZE/100.0;
std::printf("Standalone clip override: exported radius=%d (112 m cube) -> radius=%d (%.0f m LOD0 cube), voxel size=25 cm.\n",
ExportClipRadius,ClipRadius,FullDetailWidth);
}
else FStandaloneResearchTunnels::Set(false,0.0f);
World->Manager=std::make_unique<UVoxelStrateManager>();
if(!World->Manager->Initialize(World->Settings.get(),Seed))
{Error="standalone copied strate manager failed while applying viewer seed";return false;}
World->Generator=std::make_unique<UVoxelGenerator>();
World->Generator->InitializeSettings(World->Settings.get());
World->Generator->SetStrateManager(World->Manager.get());
if(!PrepareStandaloneDensityWorld(*World,Seed,bRoundTunnels,
bRoundTunnelEnhanced,Error))return false;
LoadMeshCache();
return true;
}
FStandaloneMeshCacheMetadata MakeMeshCacheMetadata()const
{
FStandaloneMeshCacheMetadata Metadata;
Metadata.SourceDataHash=HashStandaloneFile(DataPath);
Metadata.Seed=Seed;Metadata.ClipRadius=ClipRadius;
Metadata.FullResClipLevels=World->FullResClipLevels;
Metadata.CoarseTileCells=World->CoarseTileCells;
Metadata.StrateContentCutMinLevel=World->StrateContentCutMinLevel;
Metadata.SkirtCells=World->SkirtCells;
Metadata.LODOctaveDrop=World->LODOctaveDrop;
Metadata.bGenerateSkirts=World->bGenerateSkirts?1:0;
Metadata.AlgorithmVersion=FStandaloneMeshCache::CurrentVersion;
Metadata.TunnelMode=bRoundTunnels?(bRoundTunnelEnhanced?2u:1u):0u;
return Metadata;
}
void LoadMeshCache()
{
MeshCache.Clear();
if(bForceGenerate||!World)return;
const char* ModeSuffix=!bRoundTunnels?"_base":
(bRoundTunnelEnhanced?"_round_detailed":"_round_legacy");
const std::filesystem::path CachePath=std::filesystem::path(DataPath).parent_path()
/(std::string("VoxelForge_seed")+std::to_string(Seed)+"_r"
+std::to_string(ClipRadius)+ModeSuffix+".vfmesh");
MeshCachePath=CachePath.string();
const auto Begin=std::chrono::steady_clock::now();
std::string CacheError;
if(MeshCache.Load(MeshCachePath,MakeMeshCacheMetadata(),CacheError))
{
const double Seconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Begin).count();
std::printf("[MeshCache] loaded %zu tiles, %.2f MiB in %.3f s from %s\n",
MeshCache.Num(),static_cast<double>(MeshCache.GetBytesLoaded())/1048576.0,
Seconds,MeshCachePath.c_str());
std::fflush(stdout);
}
else if(CacheError!="cache file is missing or unreadable")
std::printf("[MeshCache] ignored %s: %s\n",MeshCachePath.c_str(),CacheError.c_str());
}
void StartGeneration()
{
StopGeneration();
@@ -471,14 +547,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
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,WorkerCount);
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,
MeshCache.IsLoaded()?&MeshCache:nullptr);
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;
const char* TunnelMode=!bRoundTunnels?"":(bRoundTunnelEnhanced
?" with detailed round/winding tunnels":" with legacy round/winding tunnels");
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,
Seed,TunnelMode,Update.DesiredTiles,
Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds);
std::fflush(stdout);
}
@@ -795,6 +874,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<<" | "<<(bRoundTunnels?"round/winding research tunnels":"base world")
<<(bRoundTunnels?(bRoundTunnelEnhanced?" (detailed walls)":" (legacy walls)"):"")
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | "<<(bGenerationRunning?"generating":"ready")
<<" | pos "<<std::fixed<<std::setprecision(1)<<Position.X<<", "<<Position.Y<<", "<<Position.Z<<" m";
SetWindowTextA(Window,S.str().c_str());
@@ -865,6 +945,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(W==VK_ESCAPE){if(GApp->bCaptured)GApp->Capture(false);else DestroyWindow(H);}
if(!bWasDown&&W=='F')GApp->bWireframe=!GApp->bWireframe;
if(!bWasDown&&W=='T'){GApp->bRoundTunnels=!GApp->bRoundTunnels;GApp->bRebuildPending=true;}
if(!bWasDown&&W=='G'&&GApp->bRoundTunnels)
{
GApp->bRoundTunnelEnhanced=!GApp->bRoundTunnelEnhanced;
GApp->bRebuildPending=true;
std::printf("Round-tunnel comparison: %s wall field.\n",
GApp->bRoundTunnelEnhanced?"detailed/smoothed":"legacy smooth tube");
}
if(!bWasDown&&(W=='N'||W=='P'))GApp->RequestSeed(W=='N'?1:-1);
if(!bWasDown&&W>='1'&&W<='8'){const int32 I=static_cast<int32>(W-'1');GApp->bShowLevel[I]=!GApp->bShowLevel[I];}
if(!bWasDown&&W=='9')GApp->bShowLevel[8]=!GApp->bShowLevel[8];
@@ -1063,6 +1150,293 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::fflush(stdout);
return 0;
}
struct FComparePoint
{
double X=0.0,Y=0.0,Z=0.0;
};
struct FCompareBucketHash
{
size_t operator()(const FIntVector& K)const noexcept
{
uint64 H=static_cast<uint32>(K.X)*0x9E3779B185EBCA87ull;
H^=static_cast<uint64>(static_cast<uint32>(K.Y))*0xC2B2AE3D27D4EB4Full;
H^=static_cast<uint64>(static_cast<uint32>(K.Z))*0x165667B19E3779F9ull;
return static_cast<size_t>(H^(H>>32));
}
};
struct FDirectedSurfaceDistance
{
double Sum=0.0,Maximum=0.0;
uint64 Count=0,Unmatched=0;
};
FDirectedSurfaceDistance DirectedSurfaceDistance(
const std::vector<FComparePoint>& From,const std::vector<FComparePoint>& To)
{
FDirectedSurfaceDistance Result;
if(From.empty()||To.empty()){Result.Unmatched=From.size();return Result;}
constexpr double BucketWidth=1.0;
std::unordered_map<FIntVector,std::vector<const FComparePoint*>,FCompareBucketHash> Buckets;
Buckets.reserve(To.size());
const auto BucketOf=[=](const FComparePoint& P)
{
return FIntVector(static_cast<int32>(std::floor(P.X/BucketWidth)),
static_cast<int32>(std::floor(P.Y/BucketWidth)),
static_cast<int32>(std::floor(P.Z/BucketWidth)));
};
for(const FComparePoint& P:To)Buckets[BucketOf(P)].push_back(&P);
for(const FComparePoint& P:From)
{
const FIntVector Center=BucketOf(P);
double BestSquared=std::numeric_limits<double>::infinity();
for(int32 Z=-3;Z<=3;++Z)for(int32 Y=-3;Y<=3;++Y)for(int32 X=-3;X<=3;++X)
{
const auto It=Buckets.find(Center+FIntVector(X,Y,Z));
if(It==Buckets.end())continue;
for(const FComparePoint* Candidate:It->second)
{
const double DX=P.X-Candidate->X,DY=P.Y-Candidate->Y,DZ=P.Z-Candidate->Z;
BestSquared=std::min(BestSquared,DX*DX+DY*DY+DZ*DZ);
}
}
if(std::isfinite(BestSquared))
{
const double Distance=std::sqrt(BestSquared);
Result.Sum+=Distance;Result.Maximum=std::max(Result.Maximum,Distance);++Result.Count;
}
else ++Result.Unmatched;
}
return Result;
}
int RunRoundWallComparison(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 TileCount)
{
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>(TileCount));
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level==0&&static_cast<int32>(Tiles.size())<TileCount)Tiles.push_back(Tile);
if(Tiles.empty()){std::fprintf(stderr,"No LOD0 tiles are available for the round-tunnel comparison.\n");return 9;}
struct FComparisonTile
{
std::vector<float> Densities;
std::vector<FComparePoint> Surface;
};
std::vector<FComparisonTile> Legacy(Tiles.size());
std::vector<FComparisonTile> Detailed(Tiles.size());
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World.Generator.get());Mesher.bGenerateSkirts=World.bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World.SkirtCells);Mesher.LODOctaveDrop=World.LODOctaveDrop;
int32 MinChunkZ=MIN_int32,MaxChunkZ=MAX_int32,TopChunkZ=0,BottomChunkZ=0;
if(World.Settings->bClampViewToStrate&&World.Manager->GetStrateChunkZBounds(
StandaloneWorldMesher::CameraChunk(CameraVoxels).Z,TopChunkZ,BottomChunkZ))
{
MinChunkZ=BottomChunkZ*CHUNK_SIZE;
MaxChunkZ=(TopChunkZ+1)*CHUNK_SIZE;
}
uint64 TotalSamples=0,ChangedSigns=0;
uint64 LegacyHash=14695981039346656037ull,DetailedHash=14695981039346656037ull;
const auto Evaluate=[&](bool bEnhanced,std::vector<FComparisonTile>& Out)
{
FStandaloneResearchTunnels::Set(true,bEnhanced);
for(size_t I=0;I<Tiles.size();++I)
{
const FVoxelTileKey& Tile=Tiles[I];
const int32 Step=1,Cells=CHUNK_SIZE;
FVoxelMeshData Mesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
MinChunkZ,MaxChunkZ,nullptr,nullptr,nullptr,&Out[I].Densities);
Out[I].Surface.reserve(static_cast<size_t>(Mesh.Vertices.Num()));
for(const FVector& P:Mesh.Vertices)
Out[I].Surface.push_back({P.X*0.01,P.Y*0.01,P.Z*0.01});
}
};
Evaluate(false,Legacy);
Evaluate(true,Detailed);
FDirectedSurfaceDistance LegacyToDetailed,DetailedToLegacy;
std::vector<FComparePoint> AllLegacySurface,AllDetailedSurface;
for(size_t TileIndex=0;TileIndex<Tiles.size();++TileIndex)
{
const auto& A=Legacy[TileIndex];const auto& B=Detailed[TileIndex];
const size_t Shared=std::min(A.Densities.size(),B.Densities.size());
TotalSamples+=Shared;
for(size_t I=0;I<Shared;++I)
{
if((A.Densities[I]<0.0f)!=(B.Densities[I]<0.0f))++ChangedSigns;
LegacyHash^=std::bit_cast<uint32>(A.Densities[I]);LegacyHash*=1099511628211ull;
DetailedHash^=std::bit_cast<uint32>(B.Densities[I]);DetailedHash*=1099511628211ull;
}
AllLegacySurface.insert(AllLegacySurface.end(),A.Surface.begin(),A.Surface.end());
AllDetailedSurface.insert(AllDetailedSurface.end(),B.Surface.begin(),B.Surface.end());
}
LegacyToDetailed=DirectedSurfaceDistance(AllLegacySurface,AllDetailedSurface);
DetailedToLegacy=DirectedSurfaceDistance(AllDetailedSurface,AllLegacySurface);
const uint64 DistanceSamples=LegacyToDetailed.Count+DetailedToLegacy.Count;
const double MeanDistance=DistanceSamples>0
?(LegacyToDetailed.Sum+DetailedToLegacy.Sum)/static_cast<double>(DistanceSamples):0.0;
const double Hausdorff=std::max(LegacyToDetailed.Maximum,DetailedToLegacy.Maximum);
std::printf("[RoundTunnelCompare] tiles=%zu lattice_samples=%llu changed_signs=%llu changed_share=%.6f%% legacy_density_hash=0x%016llx detailed_density_hash=0x%016llx old_vertices=%zu new_vertices=%zu symmetric_vertex_mean_m=%.4f symmetric_vertex_max_m=%.4f unmatched_vertices=%llu\n",
Tiles.size(),static_cast<unsigned long long>(TotalSamples),
static_cast<unsigned long long>(ChangedSigns),TotalSamples?100.0*ChangedSigns/TotalSamples:0.0,
static_cast<unsigned long long>(LegacyHash),static_cast<unsigned long long>(DetailedHash),
AllLegacySurface.size(),AllDetailedSurface.size(),MeanDistance,Hausdorff,
static_cast<unsigned long long>(LegacyToDetailed.Unmatched+DetailedToLegacy.Unmatched));
FStandaloneResearchTunnels::Set(true,true);
return TotalSamples>0&&LegacyToDetailed.Unmatched+DetailedToLegacy.Unmatched==0?0:10;
}
void HashWord(uint64& Hash,uint64 Word,int32 Bytes=8)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
}
struct FHashedTile
{
FVoxelTileKey Key;
uint64 Hash=14695981039346656037ull;
};
bool TileKeyLessForHash(const FVoxelTileKey& A,const FVoxelTileKey& B)
{
if(A.Level!=B.Level)return A.Level<B.Level;
if(A.Coord.Z!=B.Coord.Z)return A.Coord.Z<B.Coord.Z;
if(A.Coord.Y!=B.Coord.Y)return A.Coord.Y<B.Coord.Y;
return A.Coord.X<B.Coord.X;
}
uint64 HashTileRows(std::vector<FHashedTile>& Rows)
{
std::sort(Rows.begin(),Rows.end(),[](const FHashedTile& A,const FHashedTile& B)
{return TileKeyLessForHash(A.Key,B.Key);});
uint64 Hash=14695981039346656037ull;
for(const FHashedTile& Row:Rows)
{
HashWord(Hash,static_cast<uint32>(Row.Key.Level),4);
HashWord(Hash,static_cast<uint32>(Row.Key.Coord.X),4);
HashWord(Hash,static_cast<uint32>(Row.Key.Coord.Y),4);
HashWord(Hash,static_cast<uint32>(Row.Key.Coord.Z),4);
HashWord(Hash,Row.Hash);
}
return Hash;
}
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
const std::string& BakeCachePath,
const FStandaloneMeshCacheMetadata& CacheMetadata)
{
FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels);
if(MaxLevel>=0)
Desired.Tiles.erase(std::remove_if(Desired.Tiles.begin(),Desired.Tiles.end(),
[&](const FVoxelTileKey& Tile){return Tile.Level>MaxLevel;}),Desired.Tiles.end());
if(Desired.Tiles.size()>1000000)
{
std::fprintf(stderr,"Stream benchmark rejected an unsafe desired set of %zu tiles.\n",
Desired.Tiles.size());
return 7;
}
const int32 FullDetailExpected=static_cast<int32>(std::count_if(
Desired.Tiles.begin(),Desired.Tiles.end(),[&](const FVoxelTileKey& Tile)
{return Tile.Level<World.FullResClipLevels;}));
std::array<std::vector<FHashedTile>,MaxLevels+1> HashRows;
std::array<uint64,MaxLevels+1> SamplesByLevel{};
std::array<uint64,MaxLevels+1> VerticesByLevel{};
std::array<uint64,MaxLevels+1> IndicesByLevel{};
std::array<double,MaxLevels+1> WorkerSecondsByLevel{};
std::vector<FStandaloneCpuTile> CacheTiles;
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
const auto Start=std::chrono::steady_clock::now();
FStandaloneStreamingGenerator Streamer(World,WorkerCount,true,Cache,MaxLevel);
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
const double LOD0Width=static_cast<double>(2*World.ClipRadius+2)
*CHUNK_SIZE*VOXEL_SIZE/100.0;
std::printf("[StreamBenchmarkConfig] clip_radius=%d lod0_cube=%.0fm voxel=25cm fullres_levels=%d max_level_filter=%d workers=%d full_detail_tiles=%d desired_tiles=%d variant=%s\n",
World.ClipRadius,LOD0Width,World.FullResClipLevels,MaxLevel<0?World.MaxClipLevel:MaxLevel,
Streamer.GetWorkerCount(),FullDetailExpected,Update.DesiredTiles,
bRoundTunnels?(bEnhancedWalls?"round-detailed":"round-legacy"):"base");
std::fflush(stdout);
int32 Finished=0,FullDetailFinished=0;
bool bFullDetailTimeReported=false;
double FullDetailSeconds=0.0;
const int32 DesiredTotal=Update.DesiredTiles;
while(Finished<DesiredTotal)
{
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
if(Ready.empty())
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
for(FStandaloneCpuTile& Tile:Ready)
{
++Finished;
const int32 Level=Tile.Key.Level;
if(Level>=0&&Level<=MaxLevels)
{
SamplesByLevel[Level]+=Tile.DensitySamples;
VerticesByLevel[Level]+=Tile.Vertices.size();
IndicesByLevel[Level]+=Tile.Indices.size();
WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
}
if(Level<World.FullResClipLevels)++FullDetailFinished;
if(!BakeCachePath.empty())CacheTiles.push_back(std::move(Tile));
}
if(!bFullDetailTimeReported&&FullDetailFinished>=FullDetailExpected)
{
bFullDetailTimeReported=true;
FullDetailSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Start).count();
std::vector<FHashedTile> FullRows;
for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level)
FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end());
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx\n",
FullDetailSeconds,FullDetailFinished,FullDetailExpected,
static_cast<unsigned long long>(HashTileRows(FullRows)));
std::fflush(stdout);
}
}
const double FullClipSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Start).count();
std::vector<FHashedTile> AllRows;
for(auto& LevelRows:HashRows)AllRows.insert(AllRows.end(),LevelRows.begin(),LevelRows.end());
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs tiles=%d density_hash=0x%016llx\n",
FullClipSeconds,Finished,DesiredTotal,
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0),
DesiredTotal,static_cast<unsigned long long>(HashTileRows(AllRows)));
std::printf("[StreamCacheUse] hits=%llu requested=%d\n",
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
for(int32 Level=0;Level<=MaxLevels;++Level)
{
if(HashRows[Level].empty())continue;
const uint64 LevelHash=HashTileRows(HashRows[Level]);
std::printf("[StreamLevel] lod=%d tiles=%zu samples=%.3fM worker_sum=%.3fs vertices=%llu indices=%llu density_hash=0x%016llx\n",
Level,HashRows[Level].size(),static_cast<double>(SamplesByLevel[Level])/1.0e6,
WorkerSecondsByLevel[Level],static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(IndicesByLevel[Level]),
static_cast<unsigned long long>(LevelHash));
}
if(!BakeCachePath.empty())
{
std::string CacheError;
if(!FStandaloneMeshCache::Save(BakeCachePath,CacheMetadata,CacheTiles,CacheError))
{
std::fprintf(stderr,"[MeshCacheBake] failed: %s\n",CacheError.c_str());
return 8;
}
const uint64 Bytes=std::filesystem::file_size(BakeCachePath);
std::printf("[MeshCacheBake] saved %zu tiles, %.2f MiB to %s\n",CacheTiles.size(),
static_cast<double>(Bytes)/1048576.0,BakeCachePath.c_str());
}
return (Finished==DesiredTotal&&FullDetailFinished==FullDetailExpected)?0:6;
}
}
int main(int Argc,char** Argv)
@@ -1072,7 +1446,11 @@ int main(int Argc,char** Argv)
for(int32 I=1;I<Argc;++I)
{if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];}
FCommandLine::Set(JoinedCommandLine);
int32 BenchmarkLevel=-1,BenchmarkCount=64;
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
bool bStreamBenchmark=false;
bool bRoundWallCompare=false;
int32 RoundCompareTileCount=16;
std::string BakeMeshCachePath;
for(int32 I=1;I<Argc;++I)
{
const std::string_view Arg=Argv[I];
@@ -1082,14 +1460,28 @@ int main(int Argc,char** Argv)
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=="--round-tunnels-legacy")
{App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;}
else if(Arg=="--stream-benchmark")bStreamBenchmark=true;
else if(Arg=="--compare-round-walls")
{bRoundWallCompare=true;App.bRoundTunnels=true;}
else if(Arg.starts_with("--round-compare-tiles="))
RoundCompareTileCount=std::max(1,std::atoi(std::string(Arg.substr(22)).c_str()));
else if(Arg=="--force-generate")App.bForceGenerate=true;
else if(Arg.starts_with("--bake-mesh-cache="))
{BakeMeshCachePath=std::string(Arg.substr(18));App.bForceGenerate=true;}
else if(Arg.starts_with("--stream-max-level="))
StreamBenchmarkMaxLevel=std::atoi(std::string(Arg.substr(19)).c_str());
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);
App.WorkerCount=std::clamp(Parsed,1,8);
}
else if(Arg.starts_with("--clip-radius="))
App.ClipRadius=std::clamp(std::atoi(std::string(Arg.substr(14)).c_str()),1,64);
else if(Arg.starts_with("--benchmark-level="))
BenchmarkLevel=std::atoi(std::string(Arg.substr(18)).c_str());
else if(Arg.starts_with("--benchmark-count="))
@@ -1120,6 +1512,27 @@ int main(int Argc,char** Argv)
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;}
const int32 ExportClipRadius=App.World->ClipRadius;
App.World->ClipRadius=App.ClipRadius;
App.World->Settings->ClipRadius=App.ClipRadius;
if(App.bRoundTunnels&&!PrepareStandaloneDensityWorld(*App.World,App.Seed,
App.bRoundTunnels,App.bRoundTunnelEnhanced,Error))
{std::fprintf(stderr,"Standalone world setup failed: %s\n",Error.c_str());return 2;}
if(bRoundWallCompare)
return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount);
if(bStreamBenchmark)
{
const double FullDetailWidth=static_cast<double>(2*App.ClipRadius+2)
*CHUNK_SIZE*VOXEL_SIZE/100.0;
std::printf("Standalone clip override: exported radius=%d (112 m cube) -> radius=%d (%.0f m LOD0 cube), voxel size=25 cm.\n",
ExportClipRadius,App.ClipRadius,FullDetailWidth);
if(!App.bForceGenerate)App.LoadMeshCache();
return RunStreamingBenchmark(*App.World,
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels,
App.bRoundTunnelEnhanced,App.MeshCache.IsLoaded()?&App.MeshCache:nullptr,
BakeMeshCachePath,App.MakeMeshCacheMetadata());
}
if(BenchmarkLevel>=0)
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
App.bProfileAttribution,App.bCompareEvaluators,App.bDensityHash);
@@ -1133,7 +1546,7 @@ int main(int Argc,char** Argv)
GApp=&App;
if(!App.Window||!App.InitializeDevice()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","VoxelForge standalone",MB_ICONERROR);return 3;}
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; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swap to round/winding research tunnels; Esc quit.\n");
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; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; Esc quit.\n");
std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n",
App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z);
std::fflush(stdout);