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:
1 parent
7d92e148a9
commit
7d1808fdf2
16 files changed
+796
-37
No files matched your search
@@ -124,6 +124,13 @@ namespace VoxelForgeCore
|
||||
enum class DetailVariant : std::uint8_t { coherent, smooth, perVoxel };
|
||||
enum class TileClass : std::uint8_t { allSolid, allAir, mixed };
|
||||
|
||||
struct DetailBandTileRegion
|
||||
{
|
||||
Vec3i originVoxel{};
|
||||
std::int32_t cells = 0;
|
||||
std::int32_t step = 1;
|
||||
};
|
||||
|
||||
struct ShapeBuildRequest
|
||||
{
|
||||
Vec3i minVoxel{};
|
||||
@@ -139,6 +146,12 @@ namespace VoxelForgeCore
|
||||
// World-wide walkability is a validation pass, not an input to the compiled field.
|
||||
// Keep it enabled for benchmarks/audits; the interactive viewer can defer it.
|
||||
bool validatePassageRoutes = true;
|
||||
// Near GPU visual tiles evaluate owner roughness in the compute shader. Their exact
|
||||
// sample lattice may omit CPU proxy-band work; CPU density queries still use the exact
|
||||
// analytic field for those omitted samples. CPU visual tiles remain fully packed.
|
||||
std::vector<DetailBandTileRegion> gpuVisualDetailTiles;
|
||||
std::vector<DetailBandTileRegion> cpuVisualDetailTiles;
|
||||
bool skipCpuDetailBandForGpuVisuals = false;
|
||||
};
|
||||
|
||||
struct ShapeBuildStats
|
||||
@@ -281,6 +294,8 @@ namespace VoxelForgeCore
|
||||
std::uint64_t protectedSealSampleCount = 0;
|
||||
std::uint64_t protectedThinSampleCount = 0;
|
||||
std::uint64_t protectedSolidOverlaySampleCount = 0;
|
||||
std::uint64_t nearPackedCandidateSampleCount = 0;
|
||||
std::uint64_t nearPackedSkippedSampleCount = 0;
|
||||
};
|
||||
|
||||
struct EditBrush
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -306,7 +306,8 @@ namespace VoxelForgeCore
|
||||
ToNative(request.clipMinVoxel),ToNative(request.clipMaxVoxel),
|
||||
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,8)),ToNative(request.detail),
|
||||
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand,
|
||||
request.validatePassageRoutes);
|
||||
request.validatePassageRoutes,request.gpuVisualDetailTiles,
|
||||
request.cpuVisualDetailTiles,request.skipCpuDetailBandForGpuVisuals);
|
||||
if(ok)
|
||||
{
|
||||
impl_->shape=std::move(shape);
|
||||
|
||||
Reference in new issue
Block a user