Files
VoxelForge/Standalone/Source/StreamingWorld.h
T
Fr0zkaandClaude Opus 5.5 9d5455ec68 skip: passage proof bounds include wall/floor relief (L75 soundness fix); FI -7.6%
L74's passage bound omitted the 0.21 m wall relief and 0.20 m floor relief, so its whole-tile
proof was unsound in principle (no tested view hit it). Passage support is now bounded per route
segment by four parameter-subinterval boxes using Catmull-Rom derivative extrema, local max
radius and relief padding. Stratum slicing only at LOD0-2 in FI/VS. Legacy sub-tile block
certificates are audit-only (runtime version was slower). TN regression (-80,-20,-3218) kept.

Exact full-grid vs skip hashes on every tile, 4 views, Clang/GCC, W1/W8/W12; control caught;
80-camera sweep 0 violations. Whole view vs c67b200: SW -1.3%, FI -7.6%, TN -2.3%, VS +0.2%.
Compiles in the Linux UE host (VFHostEditor, 27/27 actions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-27 14:34:49 +01:00

1224 lines
62 KiB
C++

#pragma once
#include "WorldMesher.h"
#include "StandaloneWorkerThread.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <condition_variable>
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <deque>
#include <functional>
#include <fstream>
#include <limits>
#include <unordered_set>
#include <utility>
struct FStandaloneTileKeyHash
{
std::size_t operator()(const FVoxelTileKey& Key) const noexcept
{ return static_cast<std::size_t>(GetTypeHash(Key)); }
};
struct FStandaloneDesiredTileSet
{
std::vector<FVoxelTileKey> Tiles;
FIntVector CenterChunk=FIntVector::ZeroValue;
int32 MinimumChunkZ=MIN_int32;
int32 MaximumChunkZ=MAX_int32;
int32 StrateTopChunkZ=0;
int32 StrateBottomChunkZ=0;
bool bHasStrate=false;
};
struct FStandaloneCpuTile
{
FVoxelTileKey Key;
int32 WorkerId=-1;
uint64 RequestedCrossing=0;
std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices;
std::vector<uint8> TriangleSkirtFaceMasks;
uint32 SurfaceVertexCount=0;
uint32 SkirtTriangleCount=0;
uint64 DensitySamples=0;
double WorkerSeconds=0.0;
double PayloadBuildMilliseconds=0.0;
std::chrono::steady_clock::time_point QueuedAt{};
std::chrono::steady_clock::time_point StartedAt{};
std::chrono::steady_clock::time_point CompletedAt{};
uint64 DensityHash=14695981039346656037ull;
bool bHasDensityHash=false;
uint64 MeshHash=14695981039346656037ull;
bool bHasMeshHash=false;
uint64 PayloadHash=14695981039346656037ull;
bool bHasPayloadHash=false;
uint64 TopologyHash=14695981039346656037ull;
bool bHasTopologyHash=false;
uint64 MaterialHash=14695981039346656037ull;
bool bHasMaterialHash=false;
bool bGenerationFailed=false;
uint64 MeshPayloadAllocatedBytes=0;
VoxelForgeCore::TileGenerationStats CoreDiagnostics{};
std::array<uint64,VoxelDensityProfile::BucketCount> ProfileCycles{};
};
inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
const auto AddDouble=[&](double Value)
{ AddWord(std::bit_cast<uint64>(Value),8); };
AddWord(static_cast<uint64>(Mesh.Vertices.Num()),8);
for(const FVector& Position:Mesh.Vertices)
{ AddDouble(Position.X);AddDouble(Position.Y);AddDouble(Position.Z); }
AddWord(static_cast<uint64>(Mesh.Normals.Num()),8);
for(const FVector& Normal:Mesh.Normals)
{ AddDouble(Normal.X);AddDouble(Normal.Y);AddDouble(Normal.Z); }
AddWord(static_cast<uint64>(Mesh.UVs.Num()),8);
for(const auto& UV:Mesh.UVs)
{ AddDouble(static_cast<double>(UV.X));AddDouble(static_cast<double>(UV.Y)); }
AddWord(static_cast<uint64>(Mesh.Colors.Num()),8);
for(const FColor& Color:Mesh.Colors)
{ AddWord(Color.R,1);AddWord(Color.G,1);AddWord(Color.B,1);AddWord(Color.A,1); }
AddWord(static_cast<uint64>(Mesh.Triangles.Num()),8);
for(const int32 Index:Mesh.Triangles)AddWord(static_cast<uint32>(Index),4);
AddWord(static_cast<uint64>(Mesh.TriangleSkirtFaceMasks.Num()),8);
for(const uint8 Faces:Mesh.TriangleSkirtFaceMasks)AddWord(Faces,1);
AddWord(static_cast<uint32>(Mesh.NumSurfaceVertices),4);
AddWord(static_cast<uint32>(Mesh.NumSkirtTriangles),4);
AddWord(static_cast<uint32>(Mesh.NumCeilingTriangles),4);
return Hash;
}
inline uint64 HashStandaloneMeshTopology(const FVoxelMeshData& Mesh)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
using FTriangleKey=std::array<uint64,3>;
const auto VertexKey=[&](int32 Index)
{
uint64 VertexHash=14695981039346656037ull;
const auto AddVertexWord=[&](uint64 Word)
{
for(int32 Byte=0;Byte<8;++Byte)
{
VertexHash^=static_cast<uint8>(Word>>(Byte*8));
VertexHash*=1099511628211ull;
}
};
if(Index<0||Index>=Mesh.Vertices.Num()||Index>=Mesh.Normals.Num())
{ AddVertexWord(UINT64_MAX);return VertexHash; }
const FVector& Position=Mesh.Vertices[Index];
const FVector& Normal=Mesh.Normals[Index];
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.X)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.Y)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.Z)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.X)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.Y)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.Z)));
return VertexHash;
};
std::vector<FTriangleKey> Triangles;
Triangles.reserve(static_cast<size_t>(Mesh.Triangles.Num()/3));
for(int32 I=0;I+2<Mesh.Triangles.Num();I+=3)
Triangles.push_back({VertexKey(Mesh.Triangles[I]),
VertexKey(Mesh.Triangles[I+1]),VertexKey(Mesh.Triangles[I+2])});
std::sort(Triangles.begin(),Triangles.end());
AddWord(static_cast<uint64>(Triangles.size()),8);
for(const FTriangleKey& Triangle:Triangles)
for(const uint64 VertexHash:Triangle)AddWord(VertexHash,8);
return Hash;
}
inline uint64 HashStandaloneCpuTileMesh(const FStandaloneCpuTile& Tile)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
AddWord(Tile.Vertices.size(),8);
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
{
for(const float Value:Vertex.Position)AddWord(std::bit_cast<uint32>(Value),4);
for(const float Value:Vertex.Normal)AddWord(std::bit_cast<uint32>(Value),4);
}
AddWord(Tile.Indices.size(),8);
for(const uint32 Index:Tile.Indices)AddWord(Index,4);
return Hash;
}
// Far parent repairs and shell stitching are implemented by VoxelForgeCore.
struct FStandaloneMeshCacheMetadata
{
uint64 SourceDataHash=0;
int32 Seed=0;
int32 ClipRadius=3;
int32 FullResClipLevels=2;
int32 CoarseTileCells=16;
int32 StrateContentCutMinLevel=9;
int32 SkirtCells=0;
int32 LODOctaveDrop=0;
int32 bGenerateSkirts=0;
uint32 AlgorithmVersion=1;
uint32 TunnelMode=0;
uint32 VoxelSizeCm=25;
};
class FStandaloneMeshCache
{
public:
static constexpr uint32 CurrentVersion=1;
void Clear(){Tiles.clear();bLoaded=false;BytesLoaded=0;}
bool IsLoaded()const{return bLoaded;}
size_t Num()const{return Tiles.size();}
uint64 GetBytesLoaded()const{return BytesLoaded;}
const FStandaloneCpuTile* Find(const FVoxelTileKey& Key)const
{
const auto It=Tiles.find(Key);
return It==Tiles.end()?nullptr:&It->second;
}
static bool Save(const std::string& Path,const FStandaloneMeshCacheMetadata& Metadata,
std::vector<FStandaloneCpuTile>& InTiles,std::string& Error)
{
std::vector<FStandaloneCpuTile*> Ordered;
Ordered.reserve(InTiles.size());
for(FStandaloneCpuTile& Tile:InTiles)Ordered.push_back(&Tile);
std::sort(Ordered.begin(),Ordered.end(),[](const FStandaloneCpuTile* A,const FStandaloneCpuTile* B)
{
if(A->Key.Level!=B->Key.Level)return A->Key.Level<B->Key.Level;
if(A->Key.Coord.Z!=B->Key.Coord.Z)return A->Key.Coord.Z<B->Key.Coord.Z;
if(A->Key.Coord.Y!=B->Key.Coord.Y)return A->Key.Coord.Y<B->Key.Coord.Y;
return A->Key.Coord.X<B->Key.Coord.X;
});
std::ofstream File(Path,std::ios::binary|std::ios::trunc);
if(!File){Error="could not open mesh cache for writing: "+Path;return false;}
struct FHeader
{
char Magic[8];uint32 Version;uint32 Endian;uint64 SourceDataHash;
int32 Seed,ClipRadius,FullResClipLevels,CoarseTileCells,StrateContentCutMinLevel;
int32 SkirtCells,LODOctaveDrop,bGenerateSkirts;
uint32 AlgorithmVersion,TunnelMode,VoxelSizeCm;
uint64 TileCount;
};
static_assert(sizeof(FHeader)==80);
const FHeader Header{{'V','F','M','E','S','H','1','\0'},1,0x01020304u,
Metadata.SourceDataHash,Metadata.Seed,Metadata.ClipRadius,Metadata.FullResClipLevels,
Metadata.CoarseTileCells,Metadata.StrateContentCutMinLevel,Metadata.SkirtCells,
Metadata.LODOctaveDrop,Metadata.bGenerateSkirts,Metadata.AlgorithmVersion,
Metadata.TunnelMode,Metadata.VoxelSizeCm,static_cast<uint64>(Ordered.size())};
File.write(reinterpret_cast<const char*>(&Header),sizeof(Header));
struct FRecord
{
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount;
};
static_assert(sizeof(FRecord)==40);
for(const FStandaloneCpuTile* Tile:Ordered)
{
if(Tile->Vertices.size()>UINT32_MAX||Tile->Indices.size()>UINT32_MAX
||Tile->Indices.size()%3!=0)
{Error="mesh cache tile exceeds the 32-bit record limits";return false;}
const FRecord Record{Tile->Key.Level,Tile->Key.Coord.X,Tile->Key.Coord.Y,
Tile->Key.Coord.Z,Tile->DensitySamples,Tile->DensityHash,
static_cast<uint32>(Tile->Vertices.size()),static_cast<uint32>(Tile->Indices.size())};
File.write(reinterpret_cast<const char*>(&Record),sizeof(Record));
if(!Tile->Vertices.empty())File.write(reinterpret_cast<const char*>(Tile->Vertices.data()),
static_cast<std::streamsize>(Tile->Vertices.size()*sizeof(FStandaloneCpuVertex)));
if(!Tile->Indices.empty())File.write(reinterpret_cast<const char*>(Tile->Indices.data()),
static_cast<std::streamsize>(Tile->Indices.size()*sizeof(uint32)));
if(!File){Error="mesh cache write failed: "+Path;return false;}
}
File.flush();
if(!File){Error="mesh cache flush failed: "+Path;return false;}
return true;
}
bool Load(const std::string& Path,const FStandaloneMeshCacheMetadata& Expected,std::string& Error)
{
Clear();
std::ifstream File(Path,std::ios::binary);
if(!File){Error="cache file is missing or unreadable";return false;}
struct FHeader
{
char Magic[8];uint32 Version;uint32 Endian;uint64 SourceDataHash;
int32 Seed,ClipRadius,FullResClipLevels,CoarseTileCells,StrateContentCutMinLevel;
int32 SkirtCells,LODOctaveDrop,bGenerateSkirts;
uint32 AlgorithmVersion,TunnelMode,VoxelSizeCm;
uint64 TileCount;
};
static_assert(sizeof(FHeader)==80);
FHeader H{};File.read(reinterpret_cast<char*>(&H),sizeof(H));
const char ExpectedMagic[8]={'V','F','M','E','S','H','1','\0'};
if(!File||std::memcmp(H.Magic,ExpectedMagic,sizeof(ExpectedMagic))!=0
||H.Version!=1||H.Endian!=0x01020304u||H.TileCount>1000000)
{Error="mesh cache header is invalid";return false;}
if(H.SourceDataHash!=Expected.SourceDataHash||H.Seed!=Expected.Seed
||H.ClipRadius!=Expected.ClipRadius||H.FullResClipLevels!=Expected.FullResClipLevels
||H.CoarseTileCells!=Expected.CoarseTileCells
||H.StrateContentCutMinLevel!=Expected.StrateContentCutMinLevel
||H.SkirtCells!=Expected.SkirtCells||H.LODOctaveDrop!=Expected.LODOctaveDrop
||H.bGenerateSkirts!=Expected.bGenerateSkirts
||H.AlgorithmVersion!=Expected.AlgorithmVersion||H.TunnelMode!=Expected.TunnelMode
||H.VoxelSizeCm!=Expected.VoxelSizeCm)
{Error="mesh cache metadata does not match this world configuration";return false;}
struct FRecord
{
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount;
};
static_assert(sizeof(FRecord)==40);
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Loaded;
Loaded.reserve(static_cast<size_t>(H.TileCount)*2+1);
for(uint64 I=0;I<H.TileCount;++I)
{
FRecord Record{};File.read(reinterpret_cast<char*>(&Record),sizeof(Record));
if(!File||Record.Level<0||Record.Level>8||Record.VertexCount>10000000
||Record.IndexCount>60000000)
{Error="mesh cache contains an invalid tile record";return false;}
FStandaloneCpuTile Tile;Tile.Key=FVoxelTileKey(FIntVector(Record.X,Record.Y,Record.Z),Record.Level);
Tile.DensitySamples=Record.Samples;Tile.DensityHash=Record.DensityHash;Tile.bHasDensityHash=true;
Tile.Vertices.resize(Record.VertexCount);Tile.Indices.resize(Record.IndexCount);
if(!Tile.Vertices.empty())File.read(reinterpret_cast<char*>(Tile.Vertices.data()),
static_cast<std::streamsize>(Tile.Vertices.size()*sizeof(FStandaloneCpuVertex)));
if(!Tile.Indices.empty())File.read(reinterpret_cast<char*>(Tile.Indices.data()),
static_cast<std::streamsize>(Tile.Indices.size()*sizeof(uint32)));
if(!File){Error="mesh cache ended inside tile geometry";return false;}
for(const uint32 Index:Tile.Indices)
if(Index>=Tile.Vertices.size())
{Error="mesh cache contains an out-of-range mesh index";return false;}
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Position)
if(!std::isfinite(Value))
{Error="mesh cache contains a non-finite vertex";return false;}
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Normal)
if(!std::isfinite(Value))
{Error="mesh cache contains a non-finite normal";return false;}
if(!Loaded.emplace(Tile.Key,std::move(Tile)).second)
{Error="mesh cache contains duplicate tile keys";return false;}
}
const std::streamoff End=File.tellg();
if(End<0){Error="could not determine mesh cache size";return false;}
BytesLoaded=static_cast<uint64>(End);Tiles.swap(Loaded);bLoaded=true;
return true;
}
private:
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Tiles;
bool bLoaded=false;
uint64 BytesLoaded=0;
};
struct FStandaloneStreamingUpdate
{
FIntVector CenterChunk=FIntVector::ZeroValue;
int32 DesiredTiles=0;
int32 RetainedResidentTiles=0;
int32 NewJobs=0;
int32 CancelledQueuedJobs=0;
int32 FullResTiles=0;
std::vector<std::pair<uint64,uint64>> CancelledByCrossing;
};
struct FStandaloneTileJob
{
FVoxelTileKey Key;
int32 BandZMin=MIN_int32;
int32 BandZMax=MAX_int32;
uint64 RequestedCrossing=0;
size_t PriorityRank=0;
std::chrono::steady_clock::time_point QueuedAt{};
std::chrono::steady_clock::time_point StartedAt{};
};
struct FStandaloneCpuTileTimelineEvent
{
enum class EType : uint8 { Queued, Dispatch, Finish };
EType Type=EType::Queued;
FVoxelTileKey Key;
int32 WorkerId=-1;
size_t QueueDepth=0;
size_t ActiveWorkers=0;
size_t PriorityRank=0;
std::chrono::steady_clock::time_point At{};
std::chrono::steady_clock::time_point QueuedAt{};
std::chrono::steady_clock::time_point StartedAt{};
std::chrono::steady_clock::time_point FinishedAt{};
double WorkerMilliseconds=-1.0;
double PayloadBuildMilliseconds=0.0;
uint64 DensitySamples=0;
uint32 SkirtTriangles=0;
VoxelForgeCore::TileGenerationStats Diagnostics{};
};
struct FStandaloneTileJobHeapOrder
{
bool operator()(const FStandaloneTileJob& A,const FStandaloneTileJob& B)const
{
if(A.PriorityRank!=B.PriorityRank)return A.PriorityRank>B.PriorityRank;
if(A.Key.Level!=B.Key.Level)return A.Key.Level>B.Key.Level;
if(A.Key.Coord.Z!=B.Key.Coord.Z)return A.Key.Coord.Z>B.Key.Coord.Z;
if(A.Key.Coord.Y!=B.Key.Coord.Y)return A.Key.Coord.Y>B.Key.Coord.Y;
return A.Key.Coord.X>B.Key.Coord.X;
}
};
namespace StandaloneWorldMesher
{
inline double TileDistanceSquaredChunks(const FVoxelTileKey& Tile,const FIntVector& Center)
{
const double Scale=static_cast<double>(1<<Tile.Level);
const double DX=(static_cast<double>(Tile.Coord.X)+0.5)*Scale-Center.X;
const double DY=(static_cast<double>(Tile.Coord.Y)+0.5)*Scale-Center.Y;
const double DZ=(static_cast<double>(Tile.Coord.Z)+0.5)*Scale-Center.Z;
return DX*DX+DY*DY+DZ*DZ;
}
inline FStandaloneDesiredTileSet BuildDesiredSet(
const FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels)
{
FStandaloneDesiredTileSet Desired;
Desired.CenterChunk=CameraChunk(CameraPositionVoxels);
if(World.Settings->bClampViewToStrate)
Desired.bHasStrate=World.Manager->GetStrateChunkZBounds(
Desired.CenterChunk.Z,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ);
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
Desired.CenterChunk.Z,World.Settings->ViewDistanceUp,World.Settings->ViewDistanceDown,
World.Settings->StrateViewMarginChunks,World.Settings->bClampViewToStrate,
Desired.bHasStrate,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ,
Desired.MinimumChunkZ,Desired.MaximumChunkZ);
VoxelClipmapDesiredTiles::FParameters Parameters;
Parameters.ClipRadius=World.ClipRadius;
Parameters.MaxClipLevel=FMath::Clamp(World.MaxClipLevel,0,8);
Parameters.RenderDistanceChunks=World.RenderDistanceChunks;
TArray<FVoxelTileKey> Tiles;
VoxelClipmapDesiredTiles::Build(Desired.CenterChunk,Desired.MinimumChunkZ,
Desired.MaximumChunkZ,Parameters,Tiles);
Desired.Tiles.reserve(static_cast<size_t>(Tiles.Num()));
for(const FVoxelTileKey& Tile:Tiles)Desired.Tiles.push_back(Tile);
std::sort(Desired.Tiles.begin(),Desired.Tiles.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
const double DA=TileDistanceSquaredChunks(A,Desired.CenterChunk);
const double DB=TileDistanceSquaredChunks(B,Desired.CenterChunk);
return DA==DB?A.Level<B.Level:DA<DB;
});
return Desired;
}
}
class FStandaloneStreamingGenerator
{
public:
static void SetCoarseFirstSchedulingEnabled(bool bEnabled)
{bCoarseFirstSchedulingEnabled.store(bEnabled,std::memory_order_relaxed);}
static bool IsCoarseFirstSchedulingEnabled()
{return bCoarseFirstSchedulingEnabled.load(std::memory_order_relaxed);}
explicit FStandaloneStreamingGenerator(FStandaloneOwnerWorld& InWorld,int32 InWorkerCount=8,
bool bInHashDensity=false,
const FStandaloneMeshCache* InMeshCache=nullptr,
int32 InMaxLevelFilter=-1,
bool bInSkipUvAndColors=false,
bool bInSkipMaterialGroups=false,
bool bInSkipUV=false,
bool bInSkipColors=false,
VoxelForgeCore::World* InCoreWorld=nullptr,
bool bInCaptureColumnCoordinates=false,
int32 InLod8HorizonInteriorStepVoxels=0,
bool bInSharedColumnCache=true,
bool bInAllowUniformSkipsWhenHashing=false)
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
bSkipUV(bInSkipUV),bSkipColors(bInSkipColors),
bCaptureColumnCoordinates(bInCaptureColumnCoordinates),
bSharedColumnCache(bInSharedColumnCache),
bAllowUniformSkipsWhenHashing(bInAllowUniformSkipsWhenHashing),
Lod8HorizonInteriorStepVoxels(std::max(0,InLod8HorizonInteriorStepVoxels))
{
VoxelGenLOD::InitializeConsoleSwitches();
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
}
FStandaloneStreamingGenerator(const FStandaloneStreamingGenerator&)=delete;
FStandaloneStreamingGenerator& operator=(const FStandaloneStreamingGenerator&)=delete;
~FStandaloneStreamingGenerator(){Stop();}
void EnableColdTimelineTrace(std::chrono::steady_clock::time_point Origin,
const std::atomic<uint64>* FrameOrdinal)
{
TimelineOrigin=Origin;
TimelineFrameOrdinal=FrameOrdinal;
TimelineQueueEvents.clear();
TimelineWorkerEvents.clear();
TimelineWorkerEvents.resize(static_cast<size_t>(WorkerCount));
bColdTimelineTrace=true;
}
void DumpColdTimelineTrace() const
{
if(!bColdTimelineTrace)return;
std::vector<FStandaloneCpuTileTimelineEvent> Events=TimelineQueueEvents;
size_t Total=Events.size();
for(const auto& WorkerEvents:TimelineWorkerEvents)Total+=WorkerEvents.size();
Events.reserve(Total);
for(const auto& WorkerEvents:TimelineWorkerEvents)
Events.insert(Events.end(),WorkerEvents.begin(),WorkerEvents.end());
std::stable_sort(Events.begin(),Events.end(),[](const auto& A,const auto& B)
{return A.At<B.At;});
std::printf("[CpuTileTimeline] mode=instrumented events=%zu workers=%d flush=after_run\n",
Events.size(),WorkerCount);
for(const auto& Event:Events)
{
const char* Type=Event.Type==FStandaloneCpuTileTimelineEvent::EType::Queued
?"queued":Event.Type==FStandaloneCpuTileTimelineEvent::EType::Dispatch
?"dispatch":"finish";
std::printf("[CpuTileTimeline] event=%s key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu worker_id=%d queue_depth=%zu active_workers=%zu priority_rank=%zu queued_ms=%.3f dispatch_ms=%.3f finish_ms=%.3f service_ms=%.3f worker_ms=%.3f\n",
Type,Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
TimelineMilliseconds(Event.At),static_cast<unsigned long long>(TimelineFrame()),
Event.WorkerId,Event.QueueDepth,Event.ActiveWorkers,Event.PriorityRank,
TimelineMilliseconds(Event.QueuedAt),TimelineMilliseconds(Event.StartedAt),
TimelineMilliseconds(Event.FinishedAt),
Event.StartedAt.time_since_epoch().count()!=0
?std::chrono::duration<double,std::milli>(Event.At-Event.StartedAt).count():-1.0,
Event.WorkerMilliseconds);
if(Event.Type==FStandaloneCpuTileTimelineEvent::EType::Finish)
PrintTileStageEvent(Event);
}
std::fflush(stdout);
}
FStandaloneStreamingUpdate Start(const FVector& CameraPositionVoxels,
const std::vector<FVoxelTileKey>& ResidentKeys={},
uint64 CrossingIndex=0)
{
Stop();
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=false;
}
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this,I](){WorkerLoop(I);});
bHasLastCameraPosition=false;TravelDirection=FVector::ZeroVector;
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
}
FStandaloneStreamingUpdate StartWithDesired(const FStandaloneDesiredTileSet& InitialDesired,
const std::vector<FVoxelTileKey>& ResidentKeys={},
uint64 CrossingIndex=0)
{
Stop();
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=false;
}
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this,I](){WorkerLoop(I);});
bHasLastCameraPosition=false;TravelDirection=FVector::ZeroVector;
return UpdateDesiredSet(InitialDesired,ResidentKeys,CrossingIndex);
}
void SetDesiredTileFilter(const std::vector<FVoxelTileKey>& Keys)
{
TileFilter.clear();TileFilter.reserve(Keys.size()*2+1);
for(const FVoxelTileKey& Key:Keys)TileFilter.insert(Key);
bUseTileFilter=true;
}
bool QueuePriorityTasks(std::vector<std::function<void()>> Tasks)
{
if(Tasks.empty())return true;
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop||Workers.empty())return false;
for(auto& Task:Tasks)PriorityTasks.push_back(std::move(Task));
}
Wake.notify_all();
return true;
}
void ClearPriorityTasks()
{
std::lock_guard<std::mutex> Lock(Mutex);
PriorityTasks.clear();
}
void Stop()
{
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=true;
Queue.clear();PriorityTasks.clear();
}
Wake.notify_all();
for(FStandaloneWorkerThread& Worker:Workers)if(Worker.joinable())Worker.join();
Workers.clear();
std::lock_guard<std::mutex> Lock(Mutex);
Ready.clear();Outstanding.clear();Desired.clear();Running.clear();
}
FStandaloneStreamingUpdate UpdateDesired(
const FVector& CameraPositionVoxels,const std::vector<FVoxelTileKey>& ResidentKeys,
uint64 CrossingIndex=0)
{
if(bHasLastCameraPosition)
{
const FVector Delta=CameraPositionVoxels-LastCameraPositionVoxels;
const double LengthSquared=static_cast<double>(Delta.X)*Delta.X
+static_cast<double>(Delta.Y)*Delta.Y;
if(LengthSquared>1.0e-12)
{
const double InverseLength=1.0/std::sqrt(LengthSquared);
TravelDirection=FVector(static_cast<float>(Delta.X*InverseLength),
static_cast<float>(Delta.Y*InverseLength),0.0f);
}
else TravelDirection=FVector::ZeroVector;
}
else
TravelDirection=FVector::ZeroVector;
LastCameraPositionVoxels=CameraPositionVoxels;
bHasLastCameraPosition=true;
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraPositionVoxels);
if(CoreWorld)
{
const auto CoreTiles=CoreWorld->desiredTiles({CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z});
NewDesired.Tiles.clear();NewDesired.Tiles.reserve(CoreTiles.size());
for(const auto& Key:CoreTiles)
NewDesired.Tiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
}
return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex);
}
FStandaloneStreamingUpdate UpdateDesiredWithSet(
FStandaloneDesiredTileSet NewDesired,const std::vector<FVoxelTileKey>& ResidentKeys,
uint64 CrossingIndex=0)
{
return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex);
}
std::vector<FStandaloneCpuTile> TakeReady()
{
std::vector<FStandaloneCpuTile> Out;
std::lock_guard<std::mutex> Lock(Mutex);
Out.swap(Ready);
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key);
return Out;
}
FStandaloneStreamingUpdate RestrictDesiredTileFilterKeepingStarted(
const std::vector<FVoxelTileKey>& BaseFilter,
const std::vector<FVoxelTileKey>& CandidateKeys,
const std::vector<FVoxelTileKey>& ResidentKeys,
std::vector<FVoxelTileKey>& RetainedCandidateKeys)
{
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Candidates;
Candidates.reserve(CandidateKeys.size()*2+1);
for(const FVoxelTileKey& Key:CandidateKeys)Candidates.insert(Key);
FStandaloneStreamingUpdate Stats;
std::lock_guard<std::mutex> Lock(Mutex);
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Retained;
Retained.reserve(CandidateKeys.size()*2+1);
for(const FVoxelTileKey& Key:Running)
if(Candidates.contains(Key))Retained.insert(Key);
for(const FStandaloneCpuTile& Tile:Ready)
if(Candidates.contains(Tile.Key))Retained.insert(Tile.Key);
for(const FVoxelTileKey& Key:ResidentKeys)
if(Candidates.contains(Key))Retained.insert(Key);
TileFilter.clear();
TileFilter.reserve((BaseFilter.size()+Retained.size())*2+1);
for(const FVoxelTileKey& Key:BaseFilter)TileFilter.insert(Key);
for(const FVoxelTileKey& Key:Retained)TileFilter.insert(Key);
bUseTileFilter=true;
FStandaloneDesiredTileSet NewDesired=CurrentDesired;
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
[&](const FVoxelTileKey& Key){return !TileFilter.contains(Key);}),NewDesired.Tiles.end());
Stats.CenterChunk=NewDesired.CenterChunk;
Stats.DesiredTiles=static_cast<int32>(NewDesired.Tiles.size());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> ResidentSet;
ResidentSet.reserve(ResidentKeys.size()*2+1);
for(const FVoxelTileKey& Key:ResidentKeys)ResidentSet.insert(Key);
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
if(ResidentSet.contains(Key))++Stats.RetainedResidentTiles;
}
Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key);
FullResTileCount=Stats.FullResTiles;
std::unordered_map<uint64,uint64> CancelledByCrossing;
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
++CancelledByCrossing[Job.RequestedCrossing];return true;
});
Queue.erase(QueueEnd,Queue.end());
std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Stats.CancelledByCrossing.reserve(CancelledByCrossing.size());
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
CurrentDesired=std::move(NewDesired);
RetainedCandidateKeys.assign(Retained.begin(),Retained.end());
std::sort(RetainedCandidateKeys.begin(),RetainedCandidateKeys.end(),
[](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;
});
return Stats;
}
bool IsDesired(const FVoxelTileKey& Key)const
{std::lock_guard<std::mutex> Lock(Mutex);return Desired.contains(Key);}
int32 DesiredCount()const
{std::lock_guard<std::mutex> Lock(Mutex);return static_cast<int32>(Desired.size());}
int32 FullResDesiredCount()const
{std::lock_guard<std::mutex> Lock(Mutex);return FullResTileCount;}
FIntVector CenterChunk()const
{std::lock_guard<std::mutex> Lock(Mutex);return CurrentDesired.CenterChunk;}
int32 GetWorkerCount()const{return WorkerCount;}
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
void SetMesherStageProfilingEnabled(bool Enabled)
{bProfileMesherStages=Enabled;}
void SetTileDrainInstrumentationEnabled(bool Enabled)
{bInstrumentTileDrain=Enabled;}
void SetPassageComposerProfilingEnabled(bool Enabled)
{bProfilePassageComposer=Enabled;}
void SetExactMidpointDensityMemoEnabled(bool Enabled)
{bExactMidpointDensityMemo=Enabled;}
void SetActiveLaneBatchingEnabled(bool Enabled)
{bActiveLaneBatching=Enabled;}
void SetProofBlockCells(int32 Cells)
{ProofBlockCells=std::clamp(Cells,1,32);}
void SetUniformBlockProofAuditEnabled(bool Enabled)
{bAuditUniformBlockProofs=Enabled;}
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
bool ForceGenerate=false)
{
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate);
}
private:
FStandaloneStreamingUpdate UpdateDesiredSet(FStandaloneDesiredTileSet NewDesired,
const std::vector<FVoxelTileKey>& ResidentKeys,uint64 CrossingIndex)
{
if(MaxLevelFilter>=0)
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
[&](const FVoxelTileKey& Key){return Key.Level>MaxLevelFilter;}),NewDesired.Tiles.end());
if(bUseTileFilter)
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
[&](const FVoxelTileKey& Key){return !TileFilter.contains(Key);}),NewDesired.Tiles.end());
FStandaloneStreamingUpdate Stats;
Stats.CenterChunk=NewDesired.CenterChunk;
Stats.DesiredTiles=static_cast<int32>(NewDesired.Tiles.size());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Resident;
Resident.reserve(ResidentKeys.size()*2+1);
for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key);
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
if(Resident.contains(Key))++Stats.RetainedResidentTiles;
}
std::lock_guard<std::mutex> Lock(Mutex);
Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key);
FullResTileCount=Stats.FullResTiles;
std::unordered_map<uint64,uint64> CancelledByCrossing;
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
++CancelledByCrossing[Job.RequestedCrossing];return true;
});
Queue.erase(QueueEnd,Queue.end());
Stats.CancelledByCrossing.reserve(CancelledByCrossing.size());
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
std::vector<FVoxelTileKey> Prioritized=NewDesired.Tiles;
if(IsCoarseFirstSchedulingEnabled())
{
// The local walkable ring leads, the coarse enclosure follows, then target-detail work.
// L'anneau praticable local passe d'abord, puis l'enveloppe grossiere, puis le detail cible.
const int32 FirstCoarseLevel=FMath::Max(2,World->FullResClipLevels);
auto Tier=[&](const FVoxelTileKey& Key)
{
if(Key.Level==0&&std::abs(Key.Coord.X-NewDesired.CenterChunk.X)<=1
&&std::abs(Key.Coord.Y-NewDesired.CenterChunk.Y)<=1
&&std::abs(Key.Coord.Z-NewDesired.CenterChunk.Z)<=1)return 0;
return Key.Level>=FirstCoarseLevel?1:2;
};
auto IsAhead=[&](const FVoxelTileKey& Key)
{
if(TravelDirection.X*TravelDirection.X+TravelDirection.Y*TravelDirection.Y<=1.0e-12f)
return false;
const double Scale=static_cast<double>(1<<Key.Level);
const double OffsetX=(static_cast<double>(Key.Coord.X)+0.5)*Scale-NewDesired.CenterChunk.X;
const double OffsetY=(static_cast<double>(Key.Coord.Y)+0.5)*Scale-NewDesired.CenterChunk.Y;
return OffsetX*TravelDirection.X+OffsetY*TravelDirection.Y>0.0;
};
std::sort(Prioritized.begin(),Prioritized.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
const int32 TierA=Tier(A),TierB=Tier(B);
if(TierA!=TierB)return TierA<TierB;
if(TierA==1&&A.Level!=B.Level)return A.Level>B.Level;
const bool bAheadA=IsAhead(A),bAheadB=IsAhead(B);
if(bAheadA!=bAheadB)return bAheadA;
const double DA=StandaloneWorldMesher::TileDistanceSquaredChunks(A,NewDesired.CenterChunk);
const double DB=StandaloneWorldMesher::TileDistanceSquaredChunks(B,NewDesired.CenterChunk);
if(DA!=DB)return DA<DB;
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;
});
}
else
{
std::vector<VoxelForgeCore::TileKey> Unordered;Unordered.reserve(NewDesired.Tiles.size());
for(const FVoxelTileKey& Key:NewDesired.Tiles)
Unordered.push_back({{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level});
const auto CorePrioritized=VoxelForgeCore::World::prioritizeTiles(Unordered,
{NewDesired.CenterChunk.X,NewDesired.CenterChunk.Y,NewDesired.CenterChunk.Z});
Prioritized.clear();Prioritized.reserve(CorePrioritized.size());
for(const auto& Key:CorePrioritized)
Prioritized.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
}
std::unordered_map<FVoxelTileKey,size_t,FStandaloneTileKeyHash> PriorityRanks;
PriorityRanks.reserve(Prioritized.size()*2+1);
for(size_t Rank=0;Rank<Prioritized.size();++Rank)
{
PriorityRanks.emplace(Prioritized[Rank],Rank);
}
for(FStandaloneTileJob& Job:Queue)Job.PriorityRank=PriorityRanks.at(Job.Key);
const int32 BandMin=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate
? NewDesired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
const int32 BandMax=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate
? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Resident.contains(Key)||Outstanding.contains(Key))continue;
FStandaloneTileJob Job;
Job.Key=Key;Job.BandZMin=BandMin;Job.BandZMax=BandMax;
Job.RequestedCrossing=CrossingIndex;Job.PriorityRank=PriorityRanks.at(Key);
Job.QueuedAt=FClock::now();
Queue.push_back(Job);
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,Key,-1,
Queue.size(),Running.size(),Job.PriorityRank,Job.QueuedAt,Job.QueuedAt);
Outstanding.insert(Key);++Stats.NewJobs;
}
CurrentDesired=std::move(NewDesired);
std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Wake.notify_all();
return Stats;
}
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
bool ForceGenerate=false)
{
const auto StartTime=Job.StartedAt;
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
if(MeshCache&&!ForceGenerate)
{
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
{
Out=*Cached;Out.RequestedCrossing=Job.RequestedCrossing;
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
Out.CompletedAt=FClock::now();
if(bHashDensity)
{Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;}
CacheHitCount.fetch_add(1,std::memory_order_relaxed);
return Out;
}
}
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World->Generator.get());
Mesher.bStandaloneSkipUvAndColors=bSkipUvAndColors;
Mesher.bStandaloneSkipUV=bSkipUV;
Mesher.bStandaloneSkipColors=bSkipColors;
Mesher.bStandaloneSkipMaterialGroups=bSkipMaterialGroups;
Mesher.bGenerateSkirts=World->bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World->SkirtCells);
Mesher.LODOctaveDrop=World->LODOctaveDrop;
const int32 Level=Job.Key.Level;
const int32 Extent=Job.Key.ExtentVoxels();
const int32 Cells=Level<World->FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World->CoarseTileCells);
const int32 Step=FMath::Max(1,Extent/Cells);
int64 SampleCount=0;
const bool bProfile=VoxelDensityProfile::GetMode()!=VoxelDensityProfile::EMode::Disabled;
const VoxelDensityProfile::FSnapshot Before=bProfile
? VoxelDensityProfile::SnapshotCurrentThread():VoxelDensityProfile::FSnapshot{};
if(CoreWorld)
{
VoxelForgeCore::TileGenerationOptions Options;
Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax;
Options.hashDensity=bHashDensity;
Options.allowUniformSkipsWhenHashing=bHashDensity&&bAllowUniformSkipsWhenHashing;
// The reference hash mode must materialize every density sample, including
// tiles whose sub-block intervals independently prove a uniform sign.
Options.forceFullDensityCapture=bHashDensity&&!bAllowUniformSkipsWhenHashing;
Options.skipUvAndColors=bSkipUvAndColors;
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.useSharedColumnCache=bSharedColumnCache;
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
Options.profilePassageComposer=bProfilePassageComposer;
Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate;
Options.exactMidpointDensityMemo=bExactMidpointDensityMemo&&!ForceGenerate;
Options.activeLaneBatching=bActiveLaneBatching&&!ForceGenerate;
Options.proofBlockCells=static_cast<std::uint32_t>(ProofBlockCells);
Options.auditUniformBlockProofs=bAuditUniformBlockProofs&&!ForceGenerate;
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
Options.diagnosticColumnInterpolationStride=
std::max<std::uint32_t>(1,DiagnosticCellStride);
std::string Error;
auto Result=CoreWorld->generateTile(
{{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error);
if(!Result)
{
std::fprintf(stderr,"[VoxelForgeCore] tile generation failed: %s\n",Error.c_str());
Out.bGenerationFailed=true;
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
Out.CompletedAt=FClock::now();return Out;
}
Out.DensitySamples=Result->densitySampleCount;
Out.CoreDiagnostics=Result->diagnostics;
Out.SurfaceVertexCount=Result->groups.surfaceVertexCount;
Out.SkirtTriangleCount=Result->groups.skirtTriangleCount;
Out.MeshPayloadAllocatedBytes=Result->meshPayloadAllocatedBytes;
Out.DensityHash=Result->densityHash;Out.MeshHash=Result->meshHash;
Out.PayloadHash=Result->payloadHash;Out.TopologyHash=Result->topologyHash;
Out.MaterialHash=Result->materialHash;
Out.bHasDensityHash=Result->hasDensityHash;Out.bHasMeshHash=Result->hasMeshHash;
Out.bHasPayloadHash=Result->hasPayloadHash;Out.bHasTopologyHash=Result->hasTopologyHash;
Out.bHasMaterialHash=Result->hasMaterialHash;
const auto PayloadBuildStart=bInstrumentTileDrain?FClock::now():FClock::time_point{};
Out.Vertices.reserve(Result->vertices.size());
for(const auto& Vertex:Result->vertices)
Out.Vertices.push_back({
{static_cast<float>(Vertex.position.x*0.01),static_cast<float>(Vertex.position.y*0.01),
static_cast<float>(Vertex.position.z*0.01)},
{static_cast<float>(Vertex.normal.x),static_cast<float>(Vertex.normal.y),
static_cast<float>(Vertex.normal.z)},
Vertex.hostMaterialId,Vertex.resourceMaterialIds,Vertex.resourceWeightsQ16});
Out.Indices=std::move(Result->indices);
Out.TriangleSkirtFaceMasks=std::move(Result->skirtFaceMasks);
if(bInstrumentTileDrain)
Out.PayloadBuildMilliseconds=std::chrono::duration<double,std::milli>(
FClock::now()-PayloadBuildStart).count();
if(bProfile)
{
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
for(size_t I=0;I<Out.ProfileCycles.size();++I)
Out.ProfileCycles[I]=After.Cycles[I]>=Before.Cycles[I]?After.Cycles[I]-Before.Cycles[I]:0;
}
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
Out.CompletedAt=FClock::now();return Out;
}
std::vector<float> DensityValues;
FVoxelMeshData Mesh=Mesher.GenerateMesh(Job.Key.OriginVoxels(),Step,Cells,nullptr,
Job.BandZMin,Job.BandZMax,&SampleCount,nullptr,nullptr,
bHashDensity?&DensityValues:nullptr);
if(bHashDensity)
{
Out.bHasDensityHash=true;
for(const float Density:DensityValues)
{
Out.DensityHash^=std::bit_cast<uint32>(Density);
Out.DensityHash*=1099511628211ull;
}
}
if(bProfile)
{
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
for(size_t I=0;I<Out.ProfileCycles.size();++I)
{
const size_t Bucket=I;
Out.ProfileCycles[I]=After.Cycles[Bucket]>=Before.Cycles[Bucket]
?After.Cycles[Bucket]-Before.Cycles[Bucket]:0;
}
}
Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount));
Out.SurfaceVertexCount=static_cast<uint32>(std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num()));
Out.SkirtTriangleCount=static_cast<uint32>(std::max(0,Mesh.NumSkirtTriangles));
Out.MeshPayloadAllocatedBytes=static_cast<uint64>(Mesh.Vertices.GetAllocatedSize())
+static_cast<uint64>(Mesh.Normals.GetAllocatedSize())
+static_cast<uint64>(Mesh.UVs.GetAllocatedSize())
+static_cast<uint64>(Mesh.Colors.GetAllocatedSize())
+static_cast<uint64>(Mesh.TriangleSkirtFaceMasks.GetAllocatedSize())
+static_cast<uint64>(Mesh.Triangles.GetAllocatedSize());
if(bHashDensity)
{
Out.PayloadHash=HashStandaloneMeshPayload(Mesh);Out.bHasPayloadHash=true;
Out.TopologyHash=HashStandaloneMeshTopology(Mesh);Out.bHasTopologyHash=true;
}
Out.Vertices.reserve(static_cast<size_t>(Mesh.Vertices.Num()));
for(int32 I=0;I<Mesh.Vertices.Num();++I)
{
const FVector& P=Mesh.Vertices[I];const FVector& N=Mesh.Normals[I];
Out.Vertices.push_back({
{static_cast<float>(P.X*0.01),static_cast<float>(P.Y*0.01),static_cast<float>(P.Z*0.01)},
{static_cast<float>(N.X),static_cast<float>(N.Y),static_cast<float>(N.Z)}});
}
Out.Indices.reserve(static_cast<size_t>(Mesh.Triangles.Num()));
for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast<uint32>(Index));
Out.TriangleSkirtFaceMasks.assign(Mesh.TriangleSkirtFaceMasks.begin(),
Mesh.TriangleSkirtFaceMasks.end());
Out.TriangleSkirtFaceMasks.resize(Out.Indices.size()/3,0);
if(bHashDensity)
{Out.MeshHash=HashStandaloneCpuTileMesh(Out);Out.bHasMeshHash=true;}
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
Out.CompletedAt=FClock::now();
return Out;
}
void WorkerLoop(int32 WorkerId)
{
constexpr uint32 PriorityTaskBurst=3;
uint32 ConsecutivePriorityTasks=0;
for(;;)
{
FStandaloneTileJob Job;
std::function<void()> PriorityTask;
size_t DispatchQueueDepth=0,DispatchActiveWorkers=0;
{
std::unique_lock<std::mutex> Lock(Mutex);
Wake.wait(Lock,[&](){return bStop||!PriorityTasks.empty()||!Queue.empty();});
if(bStop)return;
const bool bPreferPriority=!PriorityTasks.empty()
&&(Queue.empty()||ConsecutivePriorityTasks<PriorityTaskBurst);
if(bPreferPriority)
{
PriorityTask=std::move(PriorityTasks.front());PriorityTasks.pop_front();
++ConsecutivePriorityTasks;
}
else
{
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
Running.insert(Job.Key);ConsecutivePriorityTasks=0;
DispatchQueueDepth=Queue.size();DispatchActiveWorkers=Running.size();
}
}
if(PriorityTask)
{
PriorityTask();
continue;
}
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Dispatch,Job.Key,
WorkerId,DispatchQueueDepth,DispatchActiveWorkers,Job.PriorityRank,
Job.StartedAt,Job.QueuedAt,Job.StartedAt);
FStandaloneCpuTile Tile=Generate(Job);
Tile.WorkerId=WorkerId;
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return;
Running.erase(Tile.Key);
if(bColdTimelineTrace)
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Finish,Tile.Key,
WorkerId,Queue.size(),Running.size(),Job.PriorityRank,Tile.CompletedAt,
Tile.QueuedAt,Tile.StartedAt,Tile.CompletedAt,Tile.WorkerSeconds*1000.0,
&Tile.CoreDiagnostics,Tile.DensitySamples,Tile.SkirtTriangleCount,
Tile.PayloadBuildMilliseconds);
Ready.push_back(std::move(Tile));
}
}
}
using FClock=std::chrono::steady_clock;
double TimelineMilliseconds(FClock::time_point At)const
{
return At.time_since_epoch().count()==0?-1.0:
std::chrono::duration<double,std::milli>(At-TimelineOrigin).count();
}
uint64 TimelineFrame()const
{
return TimelineFrameOrdinal?TimelineFrameOrdinal->load(std::memory_order_relaxed):0;
}
static void PrintTileStageEvent(const FStandaloneCpuTileTimelineEvent& Event)
{
const auto& D=Event.Diagnostics;
const char* TileClassName=D.tileClass==VoxelForgeCore::TileClass::allAir?"air":
D.tileClass==VoxelForgeCore::TileClass::allSolid?"solid":
D.tileClass==VoxelForgeCore::TileClass::mixed?"mixed":"unknown";
std::printf("[CpuTileStages] key=(%d,%d,%d,lod%d) worker_id=%d worker_ms=%.3f payload_ms=%.3f tile_classified=%d tile_class=%s tile_skip=%d density_point_calls=%llu density_float_points=%llu density_lattice_points=%llu density_unclamped_points=%llu density_x8_points=%llu field_eval_sample_ns=%llu field_eval_sample_points=%llu samples=%llu classify_ms=%.3f column_ms=%.3f column_exclusive_ms=%.3f mc_ms=%.3f mc_grid_ms=%.3f mc_cell_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_skirt_ms=%.3f mc_other_ms=%.3f connector_ms=%.3f ground_ms=%.3f cap_ms=%.3f horizon_ms=%.3f mesh_assembly_ms=%.3f candidate_cells=%llu classified_cells=%llu surface_cells=%llu proof_solid_cells=%llu proof_air_cells=%llu proof_density_samples=%llu zband_reject_cells=%llu x8_calls=%llu x8_active_lanes=%llu grad_calls=%llu grad_memo_hits=%llu grad_fallback_calls=%llu midpoint_attempts=%llu midpoint_memo_hits=%llu skirt_edges=%llu skirt_boundary_edges=%llu skirt_density_probes=%llu skirt_triangles=%u fi_local_hit=%llu fi_local_miss=%llu fi_local_conflicts=%llu fi_shared_hit=%llu fi_shared_miss=%llu fi_shared_computes=%llu fi_shared_conflicts=%llu fi_lock_wait_us=%.3f\n",
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
Event.WorkerId,Event.WorkerMilliseconds,Event.PayloadBuildMilliseconds,
D.tileWasClassified?1:0,TileClassName,D.meshingSkipped?1:0,
static_cast<unsigned long long>(D.densityPointCalls),
static_cast<unsigned long long>(D.densityFloatPointCalls),
static_cast<unsigned long long>(D.densityLatticePointCalls),
static_cast<unsigned long long>(D.densityUnclampedPointCalls),
static_cast<unsigned long long>(D.densityX8PointCalls),
static_cast<unsigned long long>(D.densityFieldEvalNanoseconds),
static_cast<unsigned long long>(D.densityFieldEvalSamplePoints),
static_cast<unsigned long long>(Event.DensitySamples),D.classificationMilliseconds,
D.columnComputeMilliseconds,D.columnEvaluationExclusiveMilliseconds,
D.marchingCubesExclusiveMilliseconds+D.marchingCubesSkirtMilliseconds,
D.marchingCubesDensityGridMilliseconds,D.marchingCubesCellClassificationMilliseconds,
D.marchingCubesGradientNormalsMilliseconds,D.marchingCubesEdgeInterpolationMilliseconds,
D.marchingCubesVertexDedupStreamMilliseconds,D.marchingCubesSkirtMilliseconds,
D.marchingCubesOtherMilliseconds,D.farConnectorShellMilliseconds,
D.groundRefinementExclusiveMilliseconds,D.capRefinementExclusiveMilliseconds,
D.farHorizonMilliseconds,D.meshAssemblyMilliseconds,
static_cast<unsigned long long>(D.marchingCubesCandidateCells),
static_cast<unsigned long long>(D.marchingCubesCellClassificationCalls),
static_cast<unsigned long long>(D.marchingCubesSurfaceCellCalls),
static_cast<unsigned long long>(D.marchingCubesProofSkippedSolidCells),
static_cast<unsigned long long>(D.marchingCubesProofSkippedAirCells),
static_cast<unsigned long long>(D.marchingCubesProofSkippedDensitySamples),
static_cast<unsigned long long>(D.marchingCubesZBandRejectedCells),
static_cast<unsigned long long>(D.marchingCubesX8DensityCalls),
static_cast<unsigned long long>(D.marchingCubesX8ActiveLanes),
static_cast<unsigned long long>(D.marchingCubesGradientCalls),
static_cast<unsigned long long>(D.marchingCubesGradientMemoHits),
static_cast<unsigned long long>(D.marchingCubesGradientFallbackDensityCalls),
static_cast<unsigned long long>(D.marchingCubesEdgeMidpointDensityCalls),
static_cast<unsigned long long>(D.marchingCubesEdgeMidpointDensityMemoHits),
static_cast<unsigned long long>(D.marchingCubesSkirtEdgeCandidates),
static_cast<unsigned long long>(D.marchingCubesSkirtBoundaryCandidates),
static_cast<unsigned long long>(D.marchingCubesSkirtDensityProbes),
Event.SkirtTriangles,
static_cast<unsigned long long>(D.islandLocalCacheHits),
static_cast<unsigned long long>(D.islandLocalCacheMisses),
static_cast<unsigned long long>(D.islandLocalCacheConflictReplacements),
static_cast<unsigned long long>(D.islandSharedCacheHits),
static_cast<unsigned long long>(D.islandSharedCacheMisses),
static_cast<unsigned long long>(D.islandSharedCacheComputes),
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
if(D.passageComposerProfiled)
std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu mouth_candidates=%llu queries=%llu passage_iterations=%llu upper_mouth_checks=%llu column_candidates=%llu column_distance_calls=%llu negative_bounds=%llu negative_rejects=%llu positive_bounds=%llu positive_rejects=%llu carve_segment_visits=%llu smooth_carve_joins=%llu support_segment_visits=%llu smooth_support_joins=%llu support_capsule_checks=%llu later_bore_checks=%llu other_capsule_checks=%llu mouth_support_calls=%llu mouth_route_segment_checks=%llu relief_field_calls=%llu smoothstep_evaluations=%llu\n",
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
Event.WorkerMilliseconds,static_cast<unsigned long long>(Event.DensitySamples),
static_cast<unsigned long long>(D.fieldCalls),
static_cast<unsigned long long>(D.passageTileFeatureCount),
static_cast<unsigned long long>(D.passageTileRouteSegmentCandidates),
static_cast<unsigned long long>(D.passageTileMouthSegmentCandidates),
static_cast<unsigned long long>(D.passageComposerQueries),
static_cast<unsigned long long>(D.passageIterations),
static_cast<unsigned long long>(D.passageUpperMouthFilterChecks),
static_cast<unsigned long long>(D.passageColumnCandidates),
static_cast<unsigned long long>(D.passageColumnDistanceCalls),
static_cast<unsigned long long>(D.passageNegativeBoundChecks),
static_cast<unsigned long long>(D.passageNegativeBoundRejects),
static_cast<unsigned long long>(D.passagePositiveBoundChecks),
static_cast<unsigned long long>(D.passagePositiveBoundRejects),
static_cast<unsigned long long>(D.passageCarveSegmentVisits),
static_cast<unsigned long long>(D.passageSmoothCarveJoins),
static_cast<unsigned long long>(D.passageSupportSegmentVisits),
static_cast<unsigned long long>(D.passageSmoothSupportJoins),
static_cast<unsigned long long>(D.passageSupportCapsuleChecks),
static_cast<unsigned long long>(D.passageLaterBoreChecks),
static_cast<unsigned long long>(D.passageOtherCapsuleChecks),
static_cast<unsigned long long>(D.passageMouthSupportCalls),
static_cast<unsigned long long>(D.passageMouthRouteSegmentChecks),
static_cast<unsigned long long>(D.passageReliefFieldCalls),
static_cast<unsigned long long>(D.passageSmoothstepEvaluations));
}
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,
size_t PriorityRank,FClock::time_point At,FClock::time_point Queued={},
FClock::time_point Started={},FClock::time_point Finished={},double WorkerMs=-1.0,
const VoxelForgeCore::TileGenerationStats* Diagnostics=nullptr,
uint64 DensitySamples=0,uint32 SkirtTriangles=0,double PayloadBuildMs=0.0)
{
FStandaloneCpuTileTimelineEvent Event;
Event.Type=Type;Event.Key=Key;Event.WorkerId=WorkerId;
Event.QueueDepth=QueueDepth;Event.ActiveWorkers=ActiveWorkers;
Event.PriorityRank=PriorityRank;Event.At=At;Event.QueuedAt=Queued;
Event.StartedAt=Started;Event.FinishedAt=Finished;Event.WorkerMilliseconds=WorkerMs;
Event.PayloadBuildMilliseconds=PayloadBuildMs;
Event.DensitySamples=DensitySamples;Event.SkirtTriangles=SkirtTriangles;
if(Diagnostics)Event.Diagnostics=*Diagnostics;
if(WorkerId>=0&&static_cast<size_t>(WorkerId)<TimelineWorkerEvents.size())
TimelineWorkerEvents[static_cast<size_t>(WorkerId)].push_back(Event);
else TimelineQueueEvents.push_back(Event);
}
FStandaloneOwnerWorld* World=nullptr;
inline static std::atomic<bool> bCoarseFirstSchedulingEnabled{true};
FVector LastCameraPositionVoxels=FVector::ZeroVector;
FVector TravelDirection=FVector::ZeroVector;
bool bHasLastCameraPosition=false;
int32 WorkerCount=8;
bool bHashDensity=false;
const FStandaloneMeshCache* MeshCache=nullptr;
int32 MaxLevelFilter=-1;
VoxelForgeCore::World* CoreWorld=nullptr;
bool bSkipUvAndColors=false;
bool bSkipMaterialGroups=false;
bool bSkipUV=false;
bool bSkipColors=false;
bool bCaptureColumnCoordinates=false;
bool bSharedColumnCache=true;
bool bAllowUniformSkipsWhenHashing=false;
int32 Lod8HorizonInteriorStepVoxels=0;
bool bProfileMesherStages=false;
bool bInstrumentTileDrain=false;
bool bProfilePassageComposer=false;
bool bExactMidpointDensityMemo=false;
bool bActiveLaneBatching=false;
int32 ProofBlockCells=8;
bool bAuditUniformBlockProofs=false;
std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex;
std::condition_variable Wake;
bool bStop=true;
std::vector<FStandaloneWorkerThread> Workers;
std::deque<std::function<void()>> PriorityTasks;
std::vector<FStandaloneTileJob> Queue;
std::vector<FStandaloneCpuTile> Ready;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Running;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> TileFilter;
bool bUseTileFilter=false;
FStandaloneDesiredTileSet CurrentDesired;
int32 FullResTileCount=0;
bool bColdTimelineTrace=false;
FClock::time_point TimelineOrigin{};
const std::atomic<uint64>* TimelineFrameOrdinal=nullptr;
std::vector<FStandaloneCpuTileTimelineEvent> TimelineQueueEvents;
std::vector<std::vector<FStandaloneCpuTileTimelineEvent>> TimelineWorkerEvents;
};