wip(l62): shared boundary-sample cache + hidden skirt skip -- PARKED: SW +8.5%, moving p50 +119..265%, full tier incomplete
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
135957831b
commit
6ae5c4460d
12 files changed
+705
-111
No files matched your search
@@ -27,6 +27,39 @@ struct FStandaloneTileKeyHash
|
||||
{ return static_cast<std::size_t>(GetTypeHash(Key)); }
|
||||
};
|
||||
|
||||
inline uint8 ComputeStandaloneVisibleSkirtFaceMask(const FVoxelTileKey& Tile,
|
||||
const std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash>& Desired)
|
||||
{
|
||||
const FIntVector Origin=Tile.OriginVoxels();
|
||||
const int32 Extent=Tile.ExtentVoxels();
|
||||
const auto FloorDiv=[](int64 Value,int64 Divisor)
|
||||
{
|
||||
int64 Quotient=Value/Divisor;
|
||||
if(Value%Divisor<0)--Quotient;
|
||||
return static_cast<int32>(Quotient);
|
||||
};
|
||||
uint8 Mask=0;
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
for(int32 Side=0;Side<2;++Side)
|
||||
{
|
||||
int64 Probe[3]={Origin.X+Extent/2,Origin.Y+Extent/2,Origin.Z+Extent/2};
|
||||
Probe[Axis]=Side?static_cast<int64>((&Origin.X)[Axis])+Extent
|
||||
:static_cast<int64>((&Origin.X)[Axis])-1;
|
||||
int32 NeighborLevel=-1;
|
||||
for(int32 Level=0;Level<=8;++Level)
|
||||
{
|
||||
const int64 NeighborExtent=static_cast<int64>(CHUNK_SIZE)<<Level;
|
||||
const FIntVector Coord(FloorDiv(Probe[0],NeighborExtent),
|
||||
FloorDiv(Probe[1],NeighborExtent),FloorDiv(Probe[2],NeighborExtent));
|
||||
if(Desired.contains(FVoxelTileKey(Coord,Level)))
|
||||
{NeighborLevel=Level;break;}
|
||||
}
|
||||
if(NeighborLevel<0||NeighborLevel!=Tile.Level)
|
||||
Mask|=static_cast<uint8>(1u<<(Axis*2+Side));
|
||||
}
|
||||
return Mask;
|
||||
}
|
||||
|
||||
struct FStandaloneDesiredTileSet
|
||||
{
|
||||
std::vector<FVoxelTileKey> Tiles;
|
||||
@@ -41,6 +74,7 @@ struct FStandaloneDesiredTileSet
|
||||
struct FStandaloneCpuTile
|
||||
{
|
||||
FVoxelTileKey Key;
|
||||
uint8 GeneratedSkirtFaceMask=0x3f;
|
||||
int32 WorkerId=-1;
|
||||
uint64 RequestedCrossing=0;
|
||||
std::vector<FStandaloneCpuVertex> Vertices;
|
||||
@@ -195,15 +229,27 @@ struct FStandaloneMeshCacheMetadata
|
||||
class FStandaloneMeshCache
|
||||
{
|
||||
public:
|
||||
static constexpr uint32 CurrentVersion=1;
|
||||
static constexpr uint32 CurrentVersion=2;
|
||||
|
||||
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
|
||||
struct FKey
|
||||
{
|
||||
const auto It=Tiles.find(Key);
|
||||
FVoxelTileKey Tile;
|
||||
uint8 SkirtFaceMask=0x3f;
|
||||
bool operator==(const FKey& Other)const
|
||||
{return Tile==Other.Tile&&SkirtFaceMask==Other.SkirtFaceMask;}
|
||||
};
|
||||
struct FKeyHash
|
||||
{
|
||||
size_t operator()(const FKey& Key)const noexcept
|
||||
{return FStandaloneTileKeyHash{}(Key.Tile)^(static_cast<size_t>(Key.SkirtFaceMask)*0x9e3779b97f4a7c15ull);}
|
||||
};
|
||||
const FStandaloneCpuTile* Find(const FVoxelTileKey& Key,uint8 SkirtFaceMask)const
|
||||
{
|
||||
const auto It=Tiles.find({Key,static_cast<uint8>(SkirtFaceMask&0x3fu)});
|
||||
return It==Tiles.end()?nullptr:&It->second;
|
||||
}
|
||||
|
||||
@@ -218,7 +264,8 @@ public:
|
||||
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;
|
||||
if(A->Key.Coord.X!=B->Key.Coord.X)return A->Key.Coord.X<B->Key.Coord.X;
|
||||
return A->GeneratedSkirtFaceMask<B->GeneratedSkirtFaceMask;
|
||||
});
|
||||
std::ofstream File(Path,std::ios::binary|std::ios::trunc);
|
||||
if(!File){Error="could not open mesh cache for writing: "+Path;return false;}
|
||||
@@ -231,7 +278,7 @@ public:
|
||||
uint64 TileCount;
|
||||
};
|
||||
static_assert(sizeof(FHeader)==80);
|
||||
const FHeader Header{{'V','F','M','E','S','H','1','\0'},1,0x01020304u,
|
||||
const FHeader Header{{'V','F','M','E','S','H','2','\0'},2,0x01020304u,
|
||||
Metadata.SourceDataHash,Metadata.Seed,Metadata.ClipRadius,Metadata.FullResClipLevels,
|
||||
Metadata.CoarseTileCells,Metadata.StrateContentCutMinLevel,Metadata.SkirtCells,
|
||||
Metadata.LODOctaveDrop,Metadata.bGenerateSkirts,Metadata.AlgorithmVersion,
|
||||
@@ -239,22 +286,33 @@ public:
|
||||
File.write(reinterpret_cast<const char*>(&Header),sizeof(Header));
|
||||
struct FRecord
|
||||
{
|
||||
int32 Level,X,Y,Z;uint64 Samples,DensityHash;uint32 VertexCount,IndexCount;
|
||||
int32 Level,X,Y,Z;uint64 Samples,DensityHash;
|
||||
uint32 VertexCount,IndexCount,SurfaceVertexCount,SkirtTriangleCount,
|
||||
TriangleFaceMaskCount,SkirtFaceMask;
|
||||
};
|
||||
static_assert(sizeof(FRecord)==40);
|
||||
static_assert(sizeof(FRecord)==56);
|
||||
for(const FStandaloneCpuTile* Tile:Ordered)
|
||||
{
|
||||
if(Tile->Vertices.size()>UINT32_MAX||Tile->Indices.size()>UINT32_MAX
|
||||
||Tile->Indices.size()%3!=0)
|
||||
||Tile->Indices.size()%3!=0
|
||||
||Tile->TriangleSkirtFaceMasks.size()!=Tile->Indices.size()/3
|
||||
||Tile->SurfaceVertexCount>Tile->Vertices.size()
|
||||
||Tile->SkirtTriangleCount>Tile->Indices.size()/3)
|
||||
{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())};
|
||||
static_cast<uint32>(Tile->Vertices.size()),static_cast<uint32>(Tile->Indices.size()),
|
||||
Tile->SurfaceVertexCount,Tile->SkirtTriangleCount,
|
||||
static_cast<uint32>(Tile->TriangleSkirtFaceMasks.size()),
|
||||
static_cast<uint32>(Tile->GeneratedSkirtFaceMask&0x3fu)};
|
||||
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(!Tile->TriangleSkirtFaceMasks.empty())File.write(
|
||||
reinterpret_cast<const char*>(Tile->TriangleSkirtFaceMasks.data()),
|
||||
static_cast<std::streamsize>(Tile->TriangleSkirtFaceMasks.size()*sizeof(uint8)));
|
||||
if(!File){Error="mesh cache write failed: "+Path;return false;}
|
||||
}
|
||||
File.flush();
|
||||
@@ -277,9 +335,9 @@ public:
|
||||
};
|
||||
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'};
|
||||
const char ExpectedMagic[8]={'V','F','M','E','S','H','2','\0'};
|
||||
if(!File||std::memcmp(H.Magic,ExpectedMagic,sizeof(ExpectedMagic))!=0
|
||||
||H.Version!=1||H.Endian!=0x01020304u||H.TileCount>1000000)
|
||||
||H.Version!=2||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
|
||||
@@ -292,24 +350,37 @@ public:
|
||||
{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;
|
||||
int32 Level,X,Y,Z;uint64 Samples,DensityHash;
|
||||
uint32 VertexCount,IndexCount,SurfaceVertexCount,SkirtTriangleCount,
|
||||
TriangleFaceMaskCount,SkirtFaceMask;
|
||||
};
|
||||
static_assert(sizeof(FRecord)==40);
|
||||
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Loaded;
|
||||
static_assert(sizeof(FRecord)==56);
|
||||
std::unordered_map<FKey,FStandaloneCpuTile,FKeyHash> 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)
|
||||
||Record.IndexCount>60000000||Record.IndexCount%3!=0
|
||||
||Record.TriangleFaceMaskCount!=Record.IndexCount/3
|
||||
||Record.SurfaceVertexCount>Record.VertexCount
|
||||
||Record.SkirtTriangleCount>Record.IndexCount/3
|
||||
||Record.SkirtFaceMask>0x3fu)
|
||||
{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.GeneratedSkirtFaceMask=static_cast<uint8>(Record.SkirtFaceMask);
|
||||
Tile.SurfaceVertexCount=Record.SurfaceVertexCount;
|
||||
Tile.SkirtTriangleCount=Record.SkirtTriangleCount;
|
||||
Tile.DensitySamples=Record.Samples;Tile.DensityHash=Record.DensityHash;Tile.bHasDensityHash=true;
|
||||
Tile.Vertices.resize(Record.VertexCount);Tile.Indices.resize(Record.IndexCount);
|
||||
Tile.TriangleSkirtFaceMasks.resize(Record.TriangleFaceMaskCount);
|
||||
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(!Tile.TriangleSkirtFaceMasks.empty())File.read(
|
||||
reinterpret_cast<char*>(Tile.TriangleSkirtFaceMasks.data()),
|
||||
static_cast<std::streamsize>(Tile.TriangleSkirtFaceMasks.size()*sizeof(uint8)));
|
||||
if(!File){Error="mesh cache ended inside tile geometry";return false;}
|
||||
for(const uint32 Index:Tile.Indices)
|
||||
if(Index>=Tile.Vertices.size())
|
||||
@@ -322,7 +393,7 @@ public:
|
||||
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)
|
||||
if(!Loaded.emplace(FKey{Tile.Key,Tile.GeneratedSkirtFaceMask},std::move(Tile)).second)
|
||||
{Error="mesh cache contains duplicate tile keys";return false;}
|
||||
}
|
||||
const std::streamoff End=File.tellg();
|
||||
@@ -332,7 +403,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<FVoxelTileKey,FStandaloneCpuTile,FStandaloneTileKeyHash> Tiles;
|
||||
std::unordered_map<FKey,FStandaloneCpuTile,FKeyHash> Tiles;
|
||||
bool bLoaded=false;
|
||||
uint64 BytesLoaded=0;
|
||||
};
|
||||
@@ -344,6 +415,8 @@ struct FStandaloneStreamingUpdate
|
||||
int32 RetainedResidentTiles=0;
|
||||
int32 NewJobs=0;
|
||||
int32 CancelledQueuedJobs=0;
|
||||
int32 SkirtMaskChanges=0;
|
||||
int32 SkirtMaskInvalidations=0;
|
||||
int32 FullResTiles=0;
|
||||
std::vector<std::pair<uint64,uint64>> CancelledByCrossing;
|
||||
};
|
||||
@@ -351,6 +424,7 @@ struct FStandaloneStreamingUpdate
|
||||
struct FStandaloneTileJob
|
||||
{
|
||||
FVoxelTileKey Key;
|
||||
uint8 SkirtFaceMask=0x3f;
|
||||
int32 BandZMin=MIN_int32;
|
||||
int32 BandZMax=MAX_int32;
|
||||
uint64 RequestedCrossing=0;
|
||||
@@ -577,6 +651,8 @@ public:
|
||||
Workers.clear();
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
Ready.clear();Outstanding.clear();Desired.clear();Running.clear();
|
||||
OutstandingSkirtMasks.clear();RunningSkirtMasks.clear();CurrentSkirtMasks.clear();
|
||||
ResidentSkirtMasks.clear();
|
||||
}
|
||||
|
||||
FStandaloneStreamingUpdate UpdateDesired(
|
||||
@@ -623,7 +699,39 @@ public:
|
||||
std::vector<FStandaloneCpuTile> Out;
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
Out.swap(Ready);
|
||||
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key);
|
||||
std::vector<FStandaloneCpuTile> Current;
|
||||
Current.reserve(Out.size());
|
||||
for(FStandaloneCpuTile& Tile:Out)
|
||||
{
|
||||
const auto Wanted=CurrentSkirtMasks.find(Tile.Key);
|
||||
if(Desired.contains(Tile.Key)&&Wanted!=CurrentSkirtMasks.end()
|
||||
&&Wanted->second==Tile.GeneratedSkirtFaceMask)
|
||||
{
|
||||
Outstanding.erase(Tile.Key);OutstandingSkirtMasks.erase(Tile.Key);
|
||||
ResidentSkirtMasks[Tile.Key]=Tile.GeneratedSkirtFaceMask;
|
||||
Current.push_back(std::move(Tile));
|
||||
continue;
|
||||
}
|
||||
const auto OutstandingMask=OutstandingSkirtMasks.find(Tile.Key);
|
||||
if(OutstandingMask!=OutstandingSkirtMasks.end()
|
||||
&&OutstandingMask->second==Tile.GeneratedSkirtFaceMask)
|
||||
{Outstanding.erase(Tile.Key);OutstandingSkirtMasks.erase(OutstandingMask);}
|
||||
if(Desired.contains(Tile.Key)&&Wanted!=CurrentSkirtMasks.end()
|
||||
&&!Outstanding.contains(Tile.Key)&&!Running.contains(Tile.Key))
|
||||
{
|
||||
FStandaloneTileJob Retry;Retry.Key=Tile.Key;Retry.SkirtFaceMask=Wanted->second;
|
||||
Retry.RequestedCrossing=Tile.RequestedCrossing;
|
||||
Retry.BandZMin=World->StrateContentCutMinLevel<9&&CurrentDesired.bHasStrate
|
||||
?CurrentDesired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
|
||||
Retry.BandZMax=World->StrateContentCutMinLevel<9&&CurrentDesired.bHasStrate
|
||||
?(CurrentDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
|
||||
Retry.QueuedAt=FClock::now();
|
||||
Queue.push_back(Retry);std::push_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
|
||||
Outstanding.insert(Tile.Key);OutstandingSkirtMasks[Tile.Key]=Retry.SkirtFaceMask;
|
||||
}
|
||||
}
|
||||
Out.swap(Current);
|
||||
Wake.notify_all();
|
||||
return Out;
|
||||
}
|
||||
|
||||
@@ -742,24 +850,64 @@ private:
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Resident;
|
||||
Resident.reserve(ResidentKeys.size()*2+1);
|
||||
for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key);
|
||||
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewDesiredKeys;
|
||||
NewDesiredKeys.reserve(NewDesired.Tiles.size()*2+1);
|
||||
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> NewSkirtMasks;
|
||||
NewSkirtMasks.reserve(NewDesired.Tiles.size()*2+1);
|
||||
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||
NewDesiredKeys.insert(Key);
|
||||
// Compute against the complete desired set; the loop above is intentionally followed by
|
||||
// a second pass so insertion order cannot affect adjacency masks.
|
||||
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||
NewSkirtMasks[Key]=ComputeStandaloneVisibleSkirtFaceMask(Key,NewDesiredKeys);
|
||||
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||
{
|
||||
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
|
||||
if(Resident.contains(Key))++Stats.RetainedResidentTiles;
|
||||
const auto PriorMask=CurrentSkirtMasks.find(Key);
|
||||
if(PriorMask!=CurrentSkirtMasks.end()&&PriorMask->second!=NewSkirtMasks.at(Key))
|
||||
++Stats.SkirtMaskChanges;
|
||||
const auto Delivered=ResidentSkirtMasks.find(Key);
|
||||
const uint8 ResidentMask=Delivered==ResidentSkirtMasks.end()?0x3fu:Delivered->second;
|
||||
if(Resident.contains(Key)&&ResidentMask==NewSkirtMasks.at(Key))
|
||||
++Stats.RetainedResidentTiles;
|
||||
else if(Resident.contains(Key)&&ResidentMask!=NewSkirtMasks.at(Key))
|
||||
++Stats.SkirtMaskInvalidations;
|
||||
}
|
||||
|
||||
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;
|
||||
const auto Wanted=NewSkirtMasks.find(Job.Key);
|
||||
if(Wanted!=NewSkirtMasks.end()&&Wanted->second==Job.SkirtFaceMask)return false;
|
||||
if(Wanted!=NewSkirtMasks.end())++Stats.SkirtMaskInvalidations;
|
||||
Outstanding.erase(Job.Key);OutstandingSkirtMasks.erase(Job.Key);
|
||||
if(Wanted==NewSkirtMasks.end())
|
||||
{++Stats.CancelledQueuedJobs;++CancelledByCrossing[Job.RequestedCrossing];}
|
||||
return true;
|
||||
});
|
||||
Queue.erase(QueueEnd,Queue.end());
|
||||
for(auto ReadyIt=Ready.begin();ReadyIt!=Ready.end();)
|
||||
{
|
||||
const auto Wanted=NewSkirtMasks.find(ReadyIt->Key);
|
||||
if(Wanted!=NewSkirtMasks.end()
|
||||
&&Wanted->second==ReadyIt->GeneratedSkirtFaceMask)
|
||||
{++ReadyIt;continue;}
|
||||
if(Wanted!=NewSkirtMasks.end())++Stats.SkirtMaskInvalidations;
|
||||
Outstanding.erase(ReadyIt->Key);OutstandingSkirtMasks.erase(ReadyIt->Key);
|
||||
ReadyIt=Ready.erase(ReadyIt);
|
||||
}
|
||||
for(const FVoxelTileKey& Key:Running)
|
||||
{
|
||||
const auto RunningMask=RunningSkirtMasks.find(Key);
|
||||
const auto Wanted=NewSkirtMasks.find(Key);
|
||||
if(RunningMask!=RunningSkirtMasks.end()&&Wanted!=NewSkirtMasks.end()
|
||||
&&RunningMask->second!=Wanted->second)
|
||||
++Stats.SkirtMaskInvalidations;
|
||||
}
|
||||
Stats.CancelledByCrossing.reserve(CancelledByCrossing.size());
|
||||
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
|
||||
|
||||
@@ -826,20 +974,28 @@ private:
|
||||
? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
|
||||
for(const FVoxelTileKey& Key:NewDesired.Tiles)
|
||||
{
|
||||
if(Resident.contains(Key)||Outstanding.contains(Key))continue;
|
||||
const uint8 WantedMask=NewSkirtMasks.at(Key);
|
||||
const auto Delivered=ResidentSkirtMasks.find(Key);
|
||||
const uint8 ResidentMask=Delivered==ResidentSkirtMasks.end()?0x3fu:Delivered->second;
|
||||
if(Resident.contains(Key)&&ResidentMask==WantedMask)continue;
|
||||
if(Outstanding.contains(Key)||Running.contains(Key))continue;
|
||||
FStandaloneTileJob Job;
|
||||
Job.Key=Key;Job.BandZMin=BandMin;Job.BandZMax=BandMax;
|
||||
Job.Key=Key;Job.SkirtFaceMask=WantedMask;
|
||||
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;
|
||||
Outstanding.insert(Key);OutstandingSkirtMasks[Key]=WantedMask;++Stats.NewJobs;
|
||||
}
|
||||
CurrentSkirtMasks=std::move(NewSkirtMasks);
|
||||
CurrentDesired=std::move(NewDesired);
|
||||
std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
|
||||
Wake.notify_all();
|
||||
std::printf("[SkirtMaskCache] changed=%d invalidated=%d desired=%d\n",
|
||||
Stats.SkirtMaskChanges,Stats.SkirtMaskInvalidations,Stats.DesiredTiles);
|
||||
return Stats;
|
||||
}
|
||||
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
|
||||
@@ -848,10 +1004,11 @@ private:
|
||||
{
|
||||
const auto StartTime=Job.StartedAt;
|
||||
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
|
||||
Out.GeneratedSkirtFaceMask=static_cast<uint8>(Job.SkirtFaceMask&0x3fu);
|
||||
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
|
||||
if(MeshCache&&!ForceGenerate)
|
||||
{
|
||||
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
|
||||
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key,Job.SkirtFaceMask))
|
||||
{
|
||||
Out=*Cached;Out.RequestedCrossing=Job.RequestedCrossing;
|
||||
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
|
||||
@@ -869,6 +1026,7 @@ private:
|
||||
Mesher.bStandaloneSkipColors=bSkipColors;
|
||||
Mesher.bStandaloneSkipMaterialGroups=bSkipMaterialGroups;
|
||||
Mesher.bGenerateSkirts=World->bGenerateSkirts;
|
||||
Mesher.SetVisibleSkirtFaceMask(Job.SkirtFaceMask);
|
||||
Mesher.SkirtCells=static_cast<float>(World->SkirtCells);
|
||||
Mesher.LODOctaveDrop=World->LODOctaveDrop;
|
||||
const int32 Level=Job.Key.Level;
|
||||
@@ -888,6 +1046,7 @@ private:
|
||||
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
||||
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||
Options.useSharedColumnCache=bSharedColumnCache;
|
||||
Options.visibleSkirtFaceMask=Job.SkirtFaceMask;
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.profileMesherStages=(bProfileMesherStages||bInstrumentTileDrain)&&!ForceGenerate;
|
||||
Options.instrumentTileDrain=bInstrumentTileDrain&&!ForceGenerate;
|
||||
@@ -1020,7 +1179,8 @@ private:
|
||||
{
|
||||
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
|
||||
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
|
||||
Running.insert(Job.Key);ConsecutivePriorityTasks=0;
|
||||
Running.insert(Job.Key);RunningSkirtMasks[Job.Key]=Job.SkirtFaceMask;
|
||||
ConsecutivePriorityTasks=0;
|
||||
DispatchQueueDepth=Queue.size();DispatchActiveWorkers=Running.size();
|
||||
}
|
||||
}
|
||||
@@ -1039,12 +1199,39 @@ private:
|
||||
std::lock_guard<std::mutex> Lock(Mutex);
|
||||
if(bStop)return;
|
||||
Running.erase(Tile.Key);
|
||||
RunningSkirtMasks.erase(Tile.Key);
|
||||
const auto WantedMask=CurrentSkirtMasks.find(Tile.Key);
|
||||
const bool bCurrentMask=Desired.contains(Tile.Key)
|
||||
&&WantedMask!=CurrentSkirtMasks.end()
|
||||
&&WantedMask->second==Tile.GeneratedSkirtFaceMask;
|
||||
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);
|
||||
Ready.push_back(std::move(Tile));
|
||||
if(bCurrentMask)
|
||||
Ready.push_back(std::move(Tile));
|
||||
else
|
||||
{
|
||||
const auto OutstandingMask=OutstandingSkirtMasks.find(Tile.Key);
|
||||
if(OutstandingMask!=OutstandingSkirtMasks.end()
|
||||
&&OutstandingMask->second==Tile.GeneratedSkirtFaceMask)
|
||||
{Outstanding.erase(Tile.Key);OutstandingSkirtMasks.erase(OutstandingMask);}
|
||||
if(Desired.contains(Tile.Key)&&WantedMask!=CurrentSkirtMasks.end()
|
||||
&&!Outstanding.contains(Tile.Key))
|
||||
{
|
||||
FStandaloneTileJob Retry=Job;Retry.SkirtFaceMask=WantedMask->second;
|
||||
Retry.QueuedAt=FClock::now();Retry.StartedAt={};
|
||||
Retry.PriorityRank=0;
|
||||
Queue.push_back(Retry);std::push_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
|
||||
Outstanding.insert(Tile.Key);OutstandingSkirtMasks[Tile.Key]=Retry.SkirtFaceMask;
|
||||
if(bColdTimelineTrace)
|
||||
RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType::Queued,
|
||||
Tile.Key,-1,Queue.size(),Running.size(),Retry.PriorityRank,
|
||||
Retry.QueuedAt,Retry.QueuedAt);
|
||||
Wake.notify_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1151,8 +1338,12 @@ private:
|
||||
std::vector<FStandaloneTileJob> Queue;
|
||||
std::vector<FStandaloneCpuTile> Ready;
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Running;
|
||||
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> RunningSkirtMasks;
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired;
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding;
|
||||
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> OutstandingSkirtMasks;
|
||||
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> CurrentSkirtMasks;
|
||||
std::unordered_map<FVoxelTileKey,uint8,FStandaloneTileKeyHash> ResidentSkirtMasks;
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> TileFilter;
|
||||
bool bUseTileFilter=false;
|
||||
FStandaloneDesiredTileSet CurrentDesired;
|
||||
|
||||
Reference in new issue
Block a user