wip(standalone): GPU step 17 -- far skirt-depth fix (LOD3/4 back on GPU); adaptive start trades cached speed for first-launch

Coverage: the broad missing patch on far tiles was GPU skirt depth using the LOD key
step instead of the exported coarse mesher step (800 vs 1,600 cm at (6,2,0,lod3)).
Fixed; tile pixel-identical to CPU, 680/680 + 64/64 triangles, 1,434 GPU-eligible
tiles (was 1,082). Audits: hashes w1/w8, walk 5,642/5,642, thin/protected 0 diffs
vs CPU and WARP, survival 6.15 GB.

Adaptive start: CPU owns the first view while the shader queue is pending.
My runs outside the sandbox (Step17.exe, interleaved):
  CPU 1.375 / 1.363 / 1.354 s
  GPU 2.040 (first launch of build; 0 GPU tiles, CPU worker sum 6.98 vs 5.05 s)
      / 1.279 / 1.337 / 1.328 s
Cached warm-up queue now 329-632 ms (step 16: ~3 ms), so GPU took 258 tiles
once and 0 twice: cached GPU ~= CPU, a regression from step 16's 1.26 s.
First launch improved 3.09 -> 2.04 s but still loses to CPU. Also adds
STEP16_REPORT.md, missed in 666dfb8.

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-07 20:36:40 +02:00
1 parent 666dfb811c
commit 6f2d33e0a9
23 files changed
+1701 -104

No files matched your search

+73 -1
View File
@@ -478,7 +478,7 @@ public:
for(std::thread& Worker:Workers)if(Worker.joinable())Worker.join();
Workers.clear();
std::lock_guard<std::mutex> Lock(Mutex);
Ready.clear();Outstanding.clear();Desired.clear();
Ready.clear();Outstanding.clear();Desired.clear();Running.clear();
}
FStandaloneStreamingUpdate UpdateDesired(
@@ -512,6 +512,75 @@ public:
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;
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
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
@@ -739,6 +808,7 @@ private:
if(bStop)return;
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
Running.insert(Job.Key);
}
if(bColdTimelineTrace)TraceTileEvent("started",Job.Key,Job.StartedAt,Job.QueuedAt,Job.StartedAt);
FStandaloneCpuTile Tile=Generate(Job);
@@ -747,6 +817,7 @@ private:
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return;
Running.erase(Tile.Key);
Ready.push_back(std::move(Tile));
}
}
@@ -788,6 +859,7 @@ private:
std::vector<std::thread> Workers;
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;