wip(standalone): GPU step 27 -- slot-overflow fallback, -SkipCpuDetailBand (slower), -AffinityMask; full machine cached 0.93 s

Luna: normal completion path reads the raw vertex count before residency; an
over-cap count or failed readback falls back to CPU meshing. Max seen 18,153 of
19,456 vertices at (2,-9,-4,lod1) -- 1,303 headroom for this seed only.
Consumer map: 320 GPU near, 640 CPU near (skirted), 1,114 GPU far, 152 CPU far.
Exceptions are exported separately from roughness. Opt-in -SkipCpuDetailBand skips
CPU roughness for GPU-owned near visuals (lazy exact reconstruction on query).
Runner gains -AffinityMask (default 0xFF00) and -NormalPriority.

My runs outside the sandbox (Step27.exe, marker removed first):
  half machine (0xFF00, BelowNormal), interleaved:
    CPU 1.374 / 1.285 / 1.296 s
    GPU miss 1.330 s; cached 1.151 / 1.143 / 1.129 s (1,434 GPU tiles)
    GPU cached + -SkipCpuDetailBand 1.316 / 1.303 / 1.307 s -- SLOWER: detail apply
      331 -> 297 ms, but intervals+signs 112 -> 286 ms (the plan), shape 557 -> 692 ms
  full machine (0xFFFF, Normal), interleaved:
    CPU 1.004 / 1.015 / 0.974 s
    GPU cached 0.931 / 0.930 / 0.925 s -- first sub-second launches
Audits (Luna): hashes w1/w8, walk 5,642/5,642, near (skip) / far 0 sign/topology
diffs vs CPU and WARP, auto + cold survival pass, ten captures pass.

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-08 04:11:23 +02:00
1 parent 7d92e148a9
commit 7d1808fdf2
16 files changed
+796 -37

No files matched your search

+292 -4
View File
@@ -64,6 +64,9 @@ public:
{
uint64 Lines=0,Candidates=0,DetailEvaluations=0,Flips=0;
uint64 LocalBandSkips=0;
uint64 GpuVisualCandidates=0,CpuVisualCandidates=0,GpuOnlyCandidates=0;
uint64 GpuVisualDetailEvaluations=0,GpuOnlyDetailEvaluations=0;
uint64 GpuOnlySkippedCandidateMasks=0;
uint64 DistanceSamples=0,DistanceSampleNanoseconds=0;
uint64 DetailSamples=0,DetailSampleNanoseconds=0;
uint64 ReliefSampleNanoseconds=0,LayerLookupSampleNanoseconds=0;
@@ -277,7 +280,10 @@ public:
std::string& Error,
const VoxelForgeCore::JobScheduler* InScheduler=nullptr,
bool bInRoundTunnels=false,bool bInProfileDetailBand=false,
bool bValidatePassageRoutes=true)
bool bValidatePassageRoutes=true,
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
bool bInSkipCpuDetailBandForGpuVisuals=false)
{
using Clock=std::chrono::steady_clock;
const auto TotalStart=Clock::now();
@@ -285,6 +291,9 @@ public:
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
bAnalyticOnly=false;
bProfileDetailBand=bInProfileDetailBand;
bSkipCpuDetailBandForGpuVisuals=bInSkipCpuDetailBandForGpuVisuals;
if(bSkipCpuDetailBandForGpuVisuals&&InGpuVisualDetailTiles.empty())
{Error="GPU visual detail-band skipping requires at least one GPU near tile region";return false;}
for(int32 A=0;A<3;++A)
{
Min[A]=(&MinVoxels.X)[A]-2;
@@ -322,6 +331,11 @@ public:
BuildPackedRockIntervals(Stats.RockIntervalLines);
BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines,
Stats.CandidateMergeSeconds);
if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error))
return false;
GpuVisualProxyValidWords.clear();
if(bSkipCpuDetailBandForGpuVisuals)
GpuVisualProxyValidWords.assign(CandidateWords.size(),0u);
Stats.IntervalSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
Stage=Clock::now();
@@ -763,8 +777,17 @@ public:
const uint64 Mask=uint64{1}<<(Zi&63u);
if((CandidateWords[Word]&Mask)==0)
Out.nearPackedProxy[SampleIndex]=0u;
else if(IsSkippedGpuVisualDetailNode(WX,WY,WZ)
&&(GpuVisualProxyValidWords[Word]&Mask)==0)
{
// UINT_MAX asks ApplyNearFieldDetailCS to use its own exact
// GPU roughness result instead of a CPU packed proxy.
Out.nearPackedProxy[SampleIndex]=std::numeric_limits<uint32>::max();
++Out.nearPackedSkippedSampleCount;
}
else
{
++Out.nearPackedCandidateSampleCount;
const size_t Node=Zi*static_cast<size_t>(Size[1])*Size[0]+Column;
const int32 Q=std::abs(static_cast<int32>(SurfaceDistanceQ[Node]));
const uint32 Magnitude=static_cast<uint32>(std::clamp(Q,1,32767));
@@ -910,6 +933,8 @@ public:
{
int32 IX=0,IY=0,IZ=0;
const bool bInteger=NearInteger(X,IX)&&NearInteger(Y,IY)&&NearInteger(Z,IZ);
if(bInteger&&IsSkippedGpuVisualDetailNode(IX,IY,IZ))
return GpuSkippedDetailDensityAt(IX,IY,IZ,X,Y,Z);
const bool bPacked=!bAnalyticOnly&&bInteger&&Contains(IX,IY,IZ);
if(bPacked)
return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z);
@@ -918,6 +943,9 @@ public:
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
{
if(IsSkippedGpuVisualDetailNode(X,Y,Z))
return GpuSkippedDetailDensityAt(X,Y,Z,static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(Z));
const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z);
if(bPacked)
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast<float>(X),
@@ -931,6 +959,39 @@ public:
return AnalyticDensityAt(X,Y,Z,false);
}
float GpuSkippedDetailDensityAt(int32 X,int32 Y,int32 Z,
float FX,float FY,float FZ) const
{
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
+static_cast<size_t>(X-Min[0]);
const size_t Zi=static_cast<size_t>(Z-Min[2]);
const size_t Word=Column*WordsPerZ+(Zi>>6);
const uint64 Mask=uint64{1}<<(Zi&63u);
if(!GpuVisualProxyValidWords.empty()&&(GpuVisualProxyValidWords[Word]&Mask))
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),FX,FY,FZ);
if(CandidateWords[Word]&Mask)
{
// The GPU-only compile omitted this packed cache entry. Reconstruct the
// CPU reference's un-clamped detail density and the same signed Q8.8
// proxy when an exceptional CPU mesh or field query needs the sample.
const float ExactDensity=AnalyticDensityAt(FX,FY,FZ,false,
nullptr,nullptr,nullptr,false);
int32 Q=static_cast<int32>(std::lround(ExactDensity*256.0f));
Q=std::clamp(Q,-32767,32767);
if(Q==0)Q=ExactDensity<0.0f?-1:1;
float Density=static_cast<float>(Q)*(1.0f/256.0f);
const bool bRock=ExactDensity<=0.0f;
if((Density<0.0f)!=bRock)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f);
return ApplyEditBrushes(Density,FX,FY,FZ);
}
const bool bRock=((RockWords[Word]&Mask)!=0);
constexpr float ProxyBandEdge=DetailSupremum+0.125f;
return ApplyEditBrushes(bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ);
}
VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const
{
VoxelForgeCore::DetailFieldSample Sample;
@@ -957,6 +1018,18 @@ public:
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
return;
}
if(bSkipCpuDetailBandForGpuVisuals)
{
bool bContainsSkippedNode=false;
for(int32 Lane=0;Lane<Count;++Lane)
bContainsSkippedNode|=IsSkippedGpuVisualDetailNode(XStart+Lane*XStep,Y,Z);
if(bContainsSkippedNode)
{
for(int32 Lane=0;Lane<Count;++Lane)
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
return;
}
}
bool bFast=EditBrushes.empty()&&Y>=Min[1]&&Y<=Max[1]
&&Z>=Min[2]&&Z<=Max[2]
&&XStart>=Min[0]
@@ -1028,7 +1101,8 @@ private:
float AnalyticDensityAt(float X,float Y,float Z,bool bClampNearZero=true,
float* OutDetailDisplacement=nullptr,
float* OutCaveInfluence=nullptr,
float* OutFloorInfluence=nullptr) const
float* OutFloorInfluence=nullptr,
bool bApplyEdits=true) const
{
if(OutDetailDisplacement)*OutDetailDisplacement=0.0f;
if(OutCaveInfluence)*OutCaveInfluence=0.0f;
@@ -1045,7 +1119,7 @@ private:
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
return ApplyEditBrushes(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
}
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
@@ -1074,7 +1148,7 @@ private:
if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f))
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f);
return ApplyEditBrushes(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
}
public:
@@ -1111,6 +1185,9 @@ public:
bool IsRock(int32 X,int32 Y,int32 Z) const
{
if(IsSkippedGpuVisualDetailNode(X,Y,Z))
return AnalyticDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z))<=0.0f;
if(!Contains(X,Y,Z))return EvaluateFast(X,Y,Z);
const size_t Line=static_cast<size_t>(Y-Min[1])*Size[0]
+static_cast<size_t>(X-Min[0]);
@@ -3257,6 +3334,167 @@ private:
MergeSeconds=std::max(0.0,MergeElapsed-MergeTiming.WallSeconds);
}
bool BuildGpuVisualDetailNodeSets(
const std::vector<VoxelForgeCore::DetailBandTileRegion>& GpuTiles,
const std::vector<VoxelForgeCore::DetailBandTileRegion>& CpuTiles,
std::string& Error)
{
if(GpuTiles.empty()&&CpuTiles.empty())
{
GpuVisualDetailWords.clear();CpuVisualDetailWords.clear();
CpuVisualDetailGuardWords.clear();
if(bProfileDetailBand)
{
GpuVisualDetailWords.assign(CandidateWords.size(),0u);
CpuVisualDetailWords.assign(CandidateWords.size(),0u);
CpuVisualDetailGuardWords.assign(CandidateWords.size(),0u);
}
GpuVisualDetailTileCount=CpuVisualDetailTileCount=0;
GpuVisualDetailGridSamples=CpuVisualDetailGridSamples=0;
GpuVisualDetailNodeCount=CpuVisualDetailNodeCount=0;
CpuVisualDetailGuardNodeCount=0;
return true;
}
const auto BuildSet=[&](const std::vector<VoxelForgeCore::DetailBandTileRegion>& Tiles,
std::vector<uint64>& Nodes,uint64& GridSamples)->bool
{
Nodes.clear();GridSamples=0;
uint64 ReserveSamples=0;
for(const auto& Tile:Tiles)
{
if(Tile.cells<=0||Tile.cells>256||Tile.step<=0)
{Error="GPU visual detail-band tile region has invalid cells or step";return false;}
const uint64 Dim=static_cast<uint64>(Tile.cells)+3u;
const uint64 TileSamples=Dim*Dim*Dim;
if(TileSamples>std::numeric_limits<uint64>::max()-ReserveSamples)
{Error="GPU visual detail-band tile sample count overflowed 64 bits";return false;}
ReserveSamples+=TileSamples;
}
GridSamples=ReserveSamples;
const auto IsCandidate=[&](int32 X,int32 Y,int32 Z)
{
if(X<Min[0]||X>Max[0]||Y<Min[1]||Y>Max[1]||Z<Min[2]||Z>Max[2])
return false;
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
+static_cast<size_t>(X-Min[0]);
const size_t Zi=static_cast<size_t>(Z-Min[2]);
return (CandidateWords[Column*WordsPerZ+(Zi>>6)]
&(uint64{1}<<(Zi&63u)))!=0;
};
Nodes.assign(CandidateWords.size(),0u);
for(const auto& Tile:Tiles)
for(int32 Z=-1;Z<=Tile.cells+1;++Z)
for(int32 Y=-1;Y<=Tile.cells+1;++Y)
for(int32 X=-1;X<=Tile.cells+1;++X)
{
const int32 WX=Tile.originVoxel.x+X*Tile.step;
const int32 WY=Tile.originVoxel.y+Y*Tile.step;
const int32 WZ=Tile.originVoxel.z+Z*Tile.step;
if(!IsCandidate(WX,WY,WZ))continue;
const size_t Column=static_cast<size_t>(WY-Min[1])*Size[0]
+static_cast<size_t>(WX-Min[0]);
const size_t Zi=static_cast<size_t>(WZ-Min[2]);
Nodes[Column*WordsPerZ+(Zi>>6)]|=uint64{1}<<(Zi&63u);
}
return true;
};
if(!BuildSet(GpuTiles,GpuVisualDetailWords,GpuVisualDetailGridSamples)
||!BuildSet(CpuTiles,CpuVisualDetailWords,CpuVisualDetailGridSamples))return false;
GpuVisualDetailNodeCount=0;CpuVisualDetailNodeCount=0;
for(const uint64 Bits:GpuVisualDetailWords)GpuVisualDetailNodeCount+=std::popcount(Bits);
for(const uint64 Bits:CpuVisualDetailWords)CpuVisualDetailNodeCount+=std::popcount(Bits);
CpuVisualDetailGuardWords.assign(CandidateWords.size(),0u);
if(bSkipCpuDetailBandForGpuVisuals&&!CpuTiles.empty())
{
constexpr int32 AxisOffsets[6][3]={{-1,0,0},{1,0,0},{0,-1,0},
{0,1,0},{0,0,-1},{0,0,1}};
for(size_t Word=0;Word<CpuVisualDetailWords.size();++Word)
{
uint64 Bits=CpuVisualDetailWords[Word];
const size_t Column=Word/WordsPerZ;
const int32 X=Min[0]+static_cast<int32>(Column%Size[0]);
const int32 Y=Min[1]+static_cast<int32>(Column/Size[0]);
const size_t ZiBase=(Word%WordsPerZ)*64u;
while(Bits)
{
const uint32 Bit=std::countr_zero(Bits);Bits&=Bits-1;
const size_t Zi=ZiBase+Bit;
if(Zi>=static_cast<size_t>(Size[2]))continue;
const int32 Z=Min[2]+static_cast<int32>(Zi);
for(const auto& Offset:AxisOffsets)
{
const int32 GX=X+Offset[0],GY=Y+Offset[1],GZ=Z+Offset[2];
if(GX<Min[0]||GX>Max[0]||GY<Min[1]||GY>Max[1]
||GZ<Min[2]||GZ>Max[2])continue;
const size_t GuardColumn=static_cast<size_t>(GY-Min[1])*Size[0]
+static_cast<size_t>(GX-Min[0]);
const size_t GuardZi=static_cast<size_t>(GZ-Min[2]);
const size_t GuardWord=GuardColumn*WordsPerZ+(GuardZi>>6);
const uint64 GuardMask=uint64{1}<<(GuardZi&63u);
if(CandidateWords[GuardWord]&GuardMask)
CpuVisualDetailGuardWords[GuardWord]|=GuardMask;
}
}
}
}
CpuVisualDetailGuardNodeCount=0;
for(const uint64 Bits:CpuVisualDetailGuardWords)
CpuVisualDetailGuardNodeCount+=std::popcount(Bits);
GpuVisualDetailTileCount=GpuTiles.size();CpuVisualDetailTileCount=CpuTiles.size();
if(!GpuTiles.empty()||!CpuTiles.empty())
{
std::printf("[GpuVisualDetailBandPlan] gpu_tiles=%llu gpu_sample_nodes=%llu gpu_unique_candidate_nodes=%llu cpu_near_tiles=%llu cpu_sample_nodes=%llu cpu_unique_candidate_nodes=%llu cpu_boundary_guard_candidate_nodes=%llu skip_enabled=%d\n",
static_cast<unsigned long long>(GpuVisualDetailTileCount),
static_cast<unsigned long long>(GpuVisualDetailGridSamples),
static_cast<unsigned long long>(GpuVisualDetailNodeCount),
static_cast<unsigned long long>(CpuVisualDetailTileCount),
static_cast<unsigned long long>(CpuVisualDetailGridSamples),
static_cast<unsigned long long>(CpuVisualDetailNodeCount),
static_cast<unsigned long long>(CpuVisualDetailGuardNodeCount),
bSkipCpuDetailBandForGpuVisuals?1:0);
}
return true;
}
bool IsGpuVisualDetailNode(int32 X,int32 Y,int32 Z) const
{
if(!Contains(X,Y,Z)||GpuVisualDetailWords.empty())return false;
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
+static_cast<size_t>(X-Min[0]);
const size_t Zi=static_cast<size_t>(Z-Min[2]);
return (GpuVisualDetailWords[Column*WordsPerZ+(Zi>>6)]
&(uint64{1}<<(Zi&63u)))!=0;
}
bool IsCpuVisualDetailNode(int32 X,int32 Y,int32 Z) const
{
if(!Contains(X,Y,Z)||CpuVisualDetailWords.empty())return false;
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
+static_cast<size_t>(X-Min[0]);
const size_t Zi=static_cast<size_t>(Z-Min[2]);
return (CpuVisualDetailWords[Column*WordsPerZ+(Zi>>6)]
&(uint64{1}<<(Zi&63u)))!=0;
}
bool IsSkippedGpuVisualDetailWord(size_t Word,uint64 Mask) const
{
return bSkipCpuDetailBandForGpuVisuals
&&(GpuVisualDetailWords[Word]&Mask)!=0
&&(CpuVisualDetailWords[Word]&Mask)==0
&&(CpuVisualDetailGuardWords[Word]&Mask)==0;
}
bool IsSkippedGpuVisualDetailNode(int32 X,int32 Y,int32 Z) const
{
if(!bSkipCpuDetailBandForGpuVisuals||!Contains(X,Y,Z)
||GpuVisualDetailWords.empty())return false;
const size_t Column=static_cast<size_t>(Y-Min[1])*Size[0]
+static_cast<size_t>(X-Min[0]);
const size_t Zi=static_cast<size_t>(Z-Min[2]);
return IsSkippedGpuVisualDetailWord(Column*WordsPerZ+(Zi>>6),
uint64{1}<<(Zi&63u));
}
void ApplyDetailBand(FParallelTiming& Timing)
{
using Clock=std::chrono::steady_clock;
@@ -3326,6 +3564,27 @@ private:
const FEffectiveRockSample& Sample=Samples[Lane];
const float SDF=Sample.Distance;
if(std::abs(SDF)>DetailSupremum+0.125f)continue;
const size_t VisualWordIndex=WordBase+(Zi>>6);
const uint64 VisualMask=uint64{1}<<(Zi&63u);
const bool bGpuVisualNode=(bProfile||bSkipCpuDetailBandForGpuVisuals)
&&(GpuVisualDetailWords[VisualWordIndex]&VisualMask)!=0;
const bool bCpuVisualNode=(bProfile||bSkipCpuDetailBandForGpuVisuals)
&&(CpuVisualDetailWords[VisualWordIndex]&VisualMask)!=0;
const bool bSkipDetail=bSkipCpuDetailBandForGpuVisuals&&bGpuVisualNode
&&!bCpuVisualNode
&&(CpuVisualDetailGuardWords[VisualWordIndex]&VisualMask)==0;
if(bProfile)
{
if(bGpuVisualNode)++Profile.GpuVisualCandidates;
if(bCpuVisualNode)++Profile.CpuVisualCandidates;
if(bGpuVisualNode&&!bCpuVisualNode)++Profile.GpuOnlyCandidates;
}
if(bSkipDetail)
{
ValidBits|=uint64{1}<<(Zi&63u);
if(bProfile)++Profile.GpuOnlySkippedCandidateMasks;
continue;
}
const float SurfaceSDF=Sample.SurfaceSDF;
const float CaveInfluence=Sample.CaveInfluence;
const float FloorInfluence=Sample.FloorInfluence;
@@ -3354,6 +3613,9 @@ private:
if(DetailFade>0.0f)
{
if(bProfile)++Profile.DetailEvaluations;
if(bProfile&&bGpuVisualNode)++Profile.GpuVisualDetailEvaluations;
if(bProfile&&bGpuVisualNode&&!bCpuVisualNode)
++Profile.GpuOnlyDetailEvaluations;
Displaced+=DetailNoiseOnLine(X,Y,Z,Sample.SurfaceSDF,
CaveInfluence,FloorInfluence,PassageInfluence,NoiseLine,bTimeDetail?&Profile:nullptr,
DetailField,bDetailFieldKnown,bMacroKnown,MacroValue)*DetailFade;
@@ -3418,6 +3680,8 @@ private:
uint64 DetailSamples=0,DetailSampleNs=0,ReliefSampleNs=0,LocalBandSkips=0;
uint64 LayerLookupSampleNs=0,OwnerSampleNs=0,OwnerFieldCalls=0,OwnerActiveCalls=0;
uint64 OwnerCacheHits=0,OwnerCacheMisses=0;
uint64 GpuVisualCandidates=0,CpuVisualCandidates=0,GpuOnlyCandidates=0;
uint64 GpuVisualDetailCalls=0,GpuOnlyDetailCalls=0,GpuOnlySkippedMasks=0;
for(int32 Worker=0;Worker<WorkerCount;++Worker)
{
const FDetailWorkerProfile& P=WorkerProfile[static_cast<size_t>(Worker)];
@@ -3432,6 +3696,12 @@ private:
OwnerActiveCalls+=P.OwnerRoughnessActiveCalls;
OwnerCacheHits+=P.OwnerNoiseCacheHits;
OwnerCacheMisses+=P.OwnerNoiseCacheMisses;
GpuVisualCandidates+=P.GpuVisualCandidates;
CpuVisualCandidates+=P.CpuVisualCandidates;
GpuOnlyCandidates+=P.GpuOnlyCandidates;
GpuVisualDetailCalls+=P.GpuVisualDetailEvaluations;
GpuOnlyDetailCalls+=P.GpuOnlyDetailEvaluations;
GpuOnlySkippedMasks+=P.GpuOnlySkippedCandidateMasks;
}
const double DistanceAverageNs=DistanceSamples?static_cast<double>(DistanceSampleNs)/DistanceSamples:0.0;
const double DetailAverageNs=DetailSamples?static_cast<double>(DetailSampleNs)/DetailSamples:0.0;
@@ -3456,6 +3726,14 @@ private:
static_cast<unsigned long long>(OwnerCacheHits),
static_cast<unsigned long long>(OwnerCacheMisses),
static_cast<unsigned long long>(LocalBandSkips));
std::printf("[GpuDetailConsumerProfile] gpu_near_proxy_nodes=%llu cpu_near_mesh_nodes=%llu gpu_only_nodes=%llu gpu_near_detail_noise_evaluations=%llu gpu_only_detail_noise_evaluations=%llu gpu_only_skipped_candidate_masks=%llu skip_enabled=%d\n",
static_cast<unsigned long long>(GpuVisualCandidates),
static_cast<unsigned long long>(CpuVisualCandidates),
static_cast<unsigned long long>(GpuOnlyCandidates),
static_cast<unsigned long long>(GpuVisualDetailCalls),
static_cast<unsigned long long>(GpuOnlyDetailCalls),
static_cast<unsigned long long>(GpuOnlySkippedMasks),
bSkipCpuDetailBandForGpuVisuals?1:0);
for(int32 Worker=0;Worker<WorkerCount;++Worker)
{
const FDetailWorkerProfile& P=WorkerProfile[static_cast<size_t>(Worker)];
@@ -3491,6 +3769,8 @@ private:
+static_cast<size_t>(X-Min[0]);
StoreSurfaceDistance(Node,Density);
CandidateWords[Word]|=Mask;
if(IsSkippedGpuVisualDetailNode(X,Y,Z))
GpuVisualProxyValidWords[Word]|=Mask;
++AddedThisPass;
const bool bExactRock=Density<0.0f;
@@ -4541,6 +4821,14 @@ private:
std::unique_ptr<int16[]> SurfaceDistanceQ;
std::array<std::vector<uint64>,3> CandidateAxisWords;
std::vector<uint64> CandidateWords;
bool bSkipCpuDetailBandForGpuVisuals=false;
std::vector<uint64> GpuVisualDetailWords,CpuVisualDetailWords;
std::vector<uint64> CpuVisualDetailGuardWords;
uint64 GpuVisualDetailTileCount=0,CpuVisualDetailTileCount=0;
uint64 GpuVisualDetailGridSamples=0,CpuVisualDetailGridSamples=0;
uint64 GpuVisualDetailNodeCount=0,CpuVisualDetailNodeCount=0;
uint64 CpuVisualDetailGuardNodeCount=0;
std::vector<uint64> GpuVisualProxyValidWords;
FBuildStats Stats;
bool bAnalyticOnly=false;
};