profile: per-term legacy FI/TN sample profiler (L76); passage/support is ~75% of field time

Opt-in at build time (VOXELFORGE_PROFILE_LEGACY_TERMS=1; compiled out by default, ~2% when on).
Per legacy sample: passage/support composer ~1.05 us vs primitive scan ~0.19 us, boundary
band ~0.06 us, noise ~0.01 us. Resolved-passage constants are cached at field binding
(arithmetic order unchanged); whole-view effect within noise. Per-tile hashes identical to L75
in all 24 compiler x worker x view comparisons; fast tier PASS; Linux UE host builds.
R40 researcher notes attached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-27 16:25:19 +01:00
1 parent 9d5455ec68
commit 90378a585f
13 files changed
+1053 -69

No files matched your search

+3
View File
@@ -83,6 +83,9 @@ FLAGS+=("${LINK_FLAGS[@]}")
if [[ "$FP_CONTRACT" != "default" ]]; then if [[ "$FP_CONTRACT" != "default" ]]; then
FLAGS+=("-ffp-contract=$FP_CONTRACT") FLAGS+=("-ffp-contract=$FP_CONTRACT")
fi fi
if [[ "${VOXELFORGE_PROFILE_LEGACY_TERMS:-0}" == "1" ]]; then
FLAGS+=(-DVF_L76_PROFILE_LEGACY_TERMS=1)
fi
if [[ "$($COMPILER --version | head -n 1)" == *clang* ]]; then if [[ "$($COMPILER --version | head -n 1)" == *clang* ]]; then
FLAGS+=(-Wno-c++11-narrowing) FLAGS+=(-Wno-c++11-narrowing)
else else
@@ -166,6 +166,15 @@ namespace VoxelForgeCore
std::uint64_t densityX8PointCalls = 0; std::uint64_t densityX8PointCalls = 0;
std::uint64_t densityFieldEvalNanoseconds = 0; std::uint64_t densityFieldEvalNanoseconds = 0;
std::uint64_t densityFieldEvalSamplePoints = 0; std::uint64_t densityFieldEvalSamplePoints = 0;
std::uint64_t legacyFieldEvalCalls = 0;
std::array<std::uint64_t,9> legacyFieldTermCalls{};
std::array<std::uint64_t,9> legacyFieldTermNanoseconds{};
std::array<std::uint64_t,9> legacyFieldTermTimedCalls{};
std::uint64_t legacyPrimitiveCandidateVisits = 0;
std::uint64_t legacyPrimitiveDistanceEvaluations = 0;
std::uint64_t legacySmoothMinCalls = 0;
std::uint64_t legacySmoothMinNanoseconds = 0;
std::uint64_t legacySmoothMinTimedCalls = 0;
std::uint64_t columnCacheHits = 0; std::uint64_t columnCacheHits = 0;
std::uint64_t columnCacheMisses = 0; std::uint64_t columnCacheMisses = 0;
std::uint64_t columnCacheConflictMisses = 0; std::uint64_t columnCacheConflictMisses = 0;
+407 -69
View File
@@ -345,7 +345,8 @@ public:
BoundStratumFields.clear(); BoundStratumFields.clear();
BoundManagerLayoutChunkWidthVoxels=Owner&&Owner->Manager BoundManagerLayoutChunkWidthVoxels=Owner&&Owner->Manager
?Owner->Manager->GetStrateLayoutChunkWidthVoxels():0.0f; ?Owner->Manager->GetStrateLayoutChunkWidthVoxels():0.0f;
if(!ResolvedLayout||ResolvedLayout->strateLayoutChunkSizeMicrometres<=0)return; if(!ResolvedLayout||ResolvedLayout->strateLayoutChunkSizeMicrometres<=0)
{BuildPassageComposerConstants();return;}
const int64 ChunkWidthMicrometres=ResolvedLayout->strateLayoutChunkSizeMicrometres; const int64 ChunkWidthMicrometres=ResolvedLayout->strateLayoutChunkSizeMicrometres;
const auto FloorDiv64=[](int64 Value,int64 Divisor) const auto FloorDiv64=[](int64 Value,int64 Divisor)
{ {
@@ -430,6 +431,7 @@ public:
std::sort(BoundStratumFields.begin(),BoundStratumFields.end(), std::sort(BoundStratumFields.begin(),BoundStratumFields.end(),
[](const auto& A,const auto& B){return A.BottomChunkZ<B.BottomChunkZ;}); [](const auto& A,const auto& B){return A.BottomChunkZ<B.BottomChunkZ;});
BuildOreVeinSpatialIndex(); BuildOreVeinSpatialIndex();
BuildPassageComposerConstants();
} }
const FBoundStratumField* FindBoundStratumForChunk(int32 ChunkZ) const const FBoundStratumField* FindBoundStratumForChunk(int32 ChunkZ) const
@@ -456,6 +458,14 @@ public:
return Bound&&Bound->Kind==FBoundStratumField::EKind::FloatingIslands; return Bound&&Bound->Kind==FBoundStratumField::EKind::FloatingIslands;
} }
bool IsLegacyFloatingIslandFieldAtZ(float Z) const
{
const FBoundStratumField* Bound=FindBoundStratumForZ(Z);
return Bound&&Bound->Resolved
&&Bound->Resolved->generatorKind=="FloatingIslands"
&&Bound->IslandPlan==nullptr;
}
const FBoundStratumField* FindBoundStratumForManagerZ(float Z) const const FBoundStratumField* FindBoundStratumForManagerZ(float Z) const
{ {
if(!std::isfinite(Z)||!Owner||!(BoundManagerLayoutChunkWidthVoxels>0.0f)) if(!std::isfinite(Z)||!Owner||!(BoundManagerLayoutChunkWidthVoxels>0.0f))
@@ -992,7 +1002,7 @@ public:
?passageTileQueries->profileStats():nullptr; ?passageTileQueries->profileStats():nullptr;
if(passageProfile)++passageProfile->passageComposerQueries; if(passageProfile)++passageProfile->passageComposerQueries;
bool queryNearUpperMouthSupport=false; bool queryNearUpperMouthSupport=false;
for(const auto& passage:ResolvedLayout->passages) const auto checkUpperMouth=[&](const auto& passage)
{ {
if(passageProfile)++passageProfile->passageUpperMouthFilterChecks; if(passageProfile)++passageProfile->passageUpperMouthFilterChecks;
const double dx=qx-static_cast<double>(passage.upperMouthSubvoxels[0]); const double dx=qx-static_cast<double>(passage.upperMouthSubvoxels[0]);
@@ -1000,7 +1010,12 @@ public:
const double dz=qz-static_cast<double>(passage.upperMouthSubvoxels[2]); const double dz=qz-static_cast<double>(passage.upperMouthSubvoxels[2]);
const double reach=15.0*qPerMetre; const double reach=15.0*qPerMetre;
if(dx*dx+dy*dy<=reach*reach&&std::abs(dz)<=8.0*qPerMetre) if(dx*dx+dy*dy<=reach*reach&&std::abs(dz)<=8.0*qPerMetre)
{queryNearUpperMouthSupport=true;break;} queryNearUpperMouthSupport=true;
};
for(const auto& passage:ResolvedLayout->passages)
{
checkUpperMouth(passage);
if(queryNearUpperMouthSupport)break;
} }
if(passageTileScopeMatches&&density>=0.0f if(passageTileScopeMatches&&density>=0.0f
&&!passageTileQueries->hasRouteFeatureOverlap() &&!passageTileQueries->hasRouteFeatureOverlap()
@@ -1088,40 +1103,19 @@ public:
static_cast<float>(mouthBlendWidth/Q)); static_cast<float>(mouthBlendWidth/Q));
}; };
// The connected passage tread continues the surface route into the tunnel. // The connected passage tread continues the surface route into the tunnel.
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex) const std::size_t passageIterationCount=ResolvedLayout->passages.size();
for(std::size_t passageIteration=0;passageIteration<passageIterationCount;
++passageIteration)
{ {
const std::size_t passageIndex=passageIteration;
if(passageProfile)++passageProfile->passageIterations; if(passageProfile)++passageProfile->passageIterations;
const auto& passage=ResolvedLayout->passages[passageIndex]; const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue; if(passage.controlPoints.size()<2)continue;
const auto supportStratum=[&](std::int32_t strateIndex) const auto& passageConstants=PassageComposerConstants[passageIndex];
->const VoxelForgeCore::ResolvedLayoutStratumV2*
{
const auto it=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[strateIndex](const auto& stratum)
{return stratum.strateIndex==strateIndex;});
return it==ResolvedLayout->strates.end()?nullptr:&*it;
};
const auto* supportUpper=supportStratum(passage.upperStrateIndex);
const auto* supportLower=supportStratum(passage.lowerStrateIndex);
const auto mouthInsideContent=[&](
const VoxelForgeCore::ResolvedLayoutStratumV2* stratum,
const std::array<std::int64_t,3>& mouth)
{
if(!stratum||!stratum->content.presentInCurrentField)return false;
const auto zMicrometres=static_cast<std::int64_t>(std::llround(
static_cast<double>(mouth[2])
*ResolvedLayout->coreVoxelSizeMicrometres/Q));
return zMicrometres>=stratum->content.bottomMicrometres
&&zMicrometres<stratum->content.topMicrometres;
};
// A passage is internal when both mouths are bounded by their strate // A passage is internal when both mouths are bounded by their strate
// content and neither endpoint is a SurfaceWorld landing. Route order // content and neither endpoint is a SurfaceWorld landing. Route order
// and the total number of strata are irrelevant. // and the total number of strata are irrelevant.
const bool internalChainPassage=supportUpper&&supportLower const bool internalChainPassage=passageConstants.internalChainPassage;
&&!supportUpper->passageCapabilities.hasSurfaceLanding
&&!supportLower->passageCapabilities.hasSurfaceLanding
&&mouthInsideContent(supportUpper,passage.upperMouthSubvoxels)
&&mouthInsideContent(supportLower,passage.lowerMouthSubvoxels);
const auto laterPassageMayCarve=[&](std::ptrdiff_t supportSegment, const auto laterPassageMayCarve=[&](std::ptrdiff_t supportSegment,
double sampleX,double sampleY,double sampleZ) double sampleX,double sampleY,double sampleZ)
{ {
@@ -1129,14 +1123,10 @@ public:
return PassageSupportMayBeCarvedByLaterBore(passageIndex, return PassageSupportMayBeCarvedByLaterBore(passageIndex,
supportSegment,sampleX,sampleY,sampleZ,0.35*qPerMetre); supportSegment,sampleX,sampleY,sampleZ,0.35*qPerMetre);
}; };
const bool floatingIslandPassage=supportUpper const bool floatingIslandPassage=passageConstants.floatingIslandPassage;
&&supportUpper->passageCapabilities.hasFloatingIslandLanding; const bool surfaceToTunnelSupport=passageConstants.surfaceToTunnelSupport;
const bool surfaceToTunnelSupport=supportUpper&&supportLower const double firstFloorQ=passageConstants.firstFloorQ;
&&supportUpper->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport; const double lastFloorQ=passageConstants.lastFloorQ;
const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
for(const auto& crossing:PassageVoidCrossings) for(const auto& crossing:PassageVoidCrossings)
{ {
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
@@ -1202,15 +1192,13 @@ public:
} }
const auto floorProfile=[&](const auto& point) const auto floorProfile=[&](const auto& point)
{return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;}; {return point.positionSubvoxels[2]-point.radiusSubvoxels+playerCenterAboveFloorQ;};
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1; const std::ptrdiff_t lastIndex=passageConstants.lastIndex;
const std::ptrdiff_t lowerMouthIndex=std::clamp<std::ptrdiff_t>( const std::ptrdiff_t lowerMouthIndex=passageConstants.lowerMouthIndex;
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,lastIndex);
const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1; const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1;
// The tube radius is measured from its centerline, while floorProfile is // The tube radius is measured from its centerline, while floorProfile is
// one player-center offset away. Search the full vertical radius of the SDF; // one player-center offset away. Search the full vertical radius of the SDF;
// a narrow point window misses earlier turns of a low-pitch helix. // a narrow point window misses earlier turns of a low-pitch helix.
const double carveSearchMargin=std::max(passage.mouthRadiusSubvoxels, const double carveSearchMargin=passageConstants.carveSearchMargin;
passage.midRadiusSubvoxels)+0.25*qPerMetre;
const auto carveHigh=std::lower_bound(passage.controlPoints.begin(),mainEnd, const auto carveHigh=std::lower_bound(passage.controlPoints.begin(),mainEnd,
qz+carveSearchMargin, qz+carveSearchMargin,
[&](const auto& point,double value){return floorProfile(point)>value;}); [&](const auto& point,double value){return floorProfile(point)>value;});
@@ -1221,9 +1209,8 @@ public:
(carveHigh-passage.controlPoints.begin())-1); (carveHigh-passage.controlPoints.begin())-1);
const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1, const std::ptrdiff_t carveEnd=std::min<std::ptrdiff_t>(lowerMouthIndex-1,
(carveLow-passage.controlPoints.begin())+1); (carveLow-passage.controlPoints.begin())+1);
const std::ptrdiff_t mouthSearchBegin=std::max<std::ptrdiff_t>(0,lowerMouthIndex-8); const std::ptrdiff_t mouthSearchBegin=passageConstants.mouthSearchBegin;
const std::ptrdiff_t mouthSearchEnd=std::min<std::ptrdiff_t>(lastIndex, const std::ptrdiff_t mouthSearchEnd=passageConstants.mouthSearchEnd;
lowerMouthIndex+8);
if(density<0.0f&&tilePassageFree) if(density<0.0f&&tilePassageFree)
{ {
if(passageProfile)++passageProfile->passageNegativeBoundChecks; if(passageProfile)++passageProfile->passageNegativeBoundChecks;
@@ -1984,8 +1971,11 @@ public:
} }
// Restore the surface route where the passage cut crossed otherwise solid // Restore the surface route where the passage cut crossed otherwise solid
// native ground. The support stays native-solid-only, so it cannot float. // native ground. The support stays native-solid-only, so it cannot float.
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex) const std::size_t mouthSupportIterationCount=ResolvedLayout->passages.size();
for(std::size_t passageIteration=0;
passageIteration<mouthSupportIterationCount;++passageIteration)
{ {
const std::size_t passageIndex=passageIteration;
const auto& passage=ResolvedLayout->passages[passageIndex]; const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue; if(passage.controlPoints.size()<2)continue;
const bool tileHasSupport=passageTileScopeMatches; const bool tileHasSupport=passageTileScopeMatches;
@@ -2002,7 +1992,11 @@ public:
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
{ {
const float graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z); float graphDensity=0.0f;
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerCavern);
graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z);
}
const auto* bound=FindBoundStratumForManagerZ(Z); const auto* bound=FindBoundStratumForManagerZ(Z);
const bool nativeLattice=bound&&bound->Resolved const bool nativeLattice=bound&&bound->Resolved
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved) &&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved)
@@ -2012,14 +2006,28 @@ public:
// the generic stratum transition distance creates a discontinuity at a // the generic stratum transition distance creates a discontinuity at a
// mouth that crosses a wide transition band. Keep that opt-in field whole; // mouth that crosses a wide transition band. Keep that opt-in field whole;
// its host-rock value already supplies the ceiling and bedrock. // its host-rock value already supplies the ceiling and bedrock.
const float boundary=nativeLattice?graphDensity float boundary=graphDensity;
:ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z); if(!nativeLattice)
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerBoundary);
boundary=ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z);
}
// The native lattice owns its resolved arrival/departure portals and their // The native lattice owns its resolved arrival/departure portals and their
// supported spine. Reapplying legacy passage carves here can erase the // supported spine. Reapplying legacy passage carves here can erase the
// tread immediately above a broad legacy mouth or support column. // tread immediately above a broad legacy mouth or support column.
const float composed=nativeLattice?boundary float composed=boundary;
:ApplyResolvedStratePassageCarves(boundary,X,Y,Z); if(!nativeLattice)
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z); {
FScopedLegacyFieldTermTimer TermTimer(
ELegacyFieldTerm::ComposerPassageAndSupport);
composed=ApplyResolvedStratePassageCarves(boundary,X,Y,Z);
}
float stamped=composed;
if(!StampRuntime.empty())
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerStamps);
stamped=ApplyResolvedSdfStamps(composed,X,Y,Z);
}
static const bool bL75TraceTnComposition=[] static const bool bL75TraceTnComposition=[]
{ {
const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION"); const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION");
@@ -2040,7 +2048,12 @@ public:
} }
// The legacy landmark demo retains its compatibility floor cap. Native caves // The legacy landmark demo retains its compatibility floor cap. Native caves
// already combine each floor with its wall before the single void carve above. // already combine each floor with its wall before the single void carve above.
const float final=ApplyResolvedCavernFloorCaps(stamped,X,Y,Z); float final=0.0f;
{
FScopedLegacyFieldTermTimer TermTimer(
ELegacyFieldTerm::ComposerPassageAndSupport);
final=ApplyResolvedCavernFloorCaps(stamped,X,Y,Z);
}
if(bTraceTnComposition) if(bTraceTnComposition)
std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n", std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n",
density,graphDensity,boundary,composed,stamped,final); density,graphDensity,boundary,composed,stamped,final);
@@ -2212,6 +2225,14 @@ public:
DensityUnclampedPointCalls=0,DensityX8PointCalls=0; DensityUnclampedPointCalls=0,DensityX8PointCalls=0;
uint64 DensityFieldEvalNanoseconds=0,DensityFieldEvalSamplePoints=0; uint64 DensityFieldEvalNanoseconds=0,DensityFieldEvalSamplePoints=0;
std::array<uint64,4> DensityFieldEvalNanosecondsByKind{},DensityFieldEvalSamplePointsByKind{}; std::array<uint64,4> DensityFieldEvalNanosecondsByKind{},DensityFieldEvalSamplePointsByKind{};
static constexpr std::size_t LegacyFieldTermCount=9;
uint64 LegacyFieldEvalCalls=0;
std::array<uint64,LegacyFieldTermCount> LegacyFieldTermCalls{};
std::array<uint64,LegacyFieldTermCount> LegacyFieldTermNanoseconds{};
std::array<uint64,LegacyFieldTermCount> LegacyFieldTermTimedCalls{};
uint64 LegacyPrimitiveCandidateVisits=0,LegacyPrimitiveDistanceEvaluations=0;
uint64 LegacySmoothMinCalls=0,LegacySmoothMinNanoseconds=0,
LegacySmoothMinTimedCalls=0;
double Milliseconds=0.0; double Milliseconds=0.0;
#if defined(VF_L30_ENABLE_COST_COUNTERS) #if defined(VF_L30_ENABLE_COST_COUNTERS)
uint64 BoundaryBandCalls=0,TransitionWindowChecks=0,TransitionWindowHits=0; uint64 BoundaryBandCalls=0,TransitionWindowChecks=0,TransitionWindowHits=0;
@@ -2232,6 +2253,105 @@ public:
{static thread_local uint32 Depth=0;return Depth;} {static thread_local uint32 Depth=0;return Depth;}
static uint64& DensityQuerySampleSequence() static uint64& DensityQuerySampleSequence()
{static thread_local uint64 Sequence=0;return Sequence;} {static thread_local uint64 Sequence=0;return Sequence;}
static bool& DensityQueryTermTimingActive()
{static thread_local bool Active=false;return Active;}
static bool& LegacyFieldTermScopeActive()
{static thread_local bool Active=false;return Active;}
enum class ELegacyFieldTerm : std::size_t
{
PrimitiveScan,
Detail,
SurfaceReliefNoise,
OwnerNoiseAndWarp,
ComposerCavern,
ComposerBoundary,
ComposerPassageAndSupport,
ComposerStamps,
EditBrushes,
Count
};
struct FScopedLegacyFieldTermTimer
{
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
explicit FScopedLegacyFieldTermTimer(ELegacyFieldTerm InTerm,
uint64 InMultiplicity=1)
:Term(static_cast<std::size_t>(InTerm)),Multiplicity(InMultiplicity)
{
if(!LegacyFieldTermScopeActive())return;
FClassificationThreadStats& Stats=ThreadClassificationStats();
Stats.LegacyFieldTermCalls[Term]+=Multiplicity;
if(!DensityQueryTermTimingActive())return;
Start=std::chrono::steady_clock::now();
bTimed=true;
}
~FScopedLegacyFieldTermTimer()
{
if(!bTimed)return;
const uint64 Nanoseconds=static_cast<uint64>(std::max<std::int64_t>(0,
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now()-Start).count()));
FClassificationThreadStats& Stats=ThreadClassificationStats();
Stats.LegacyFieldTermNanoseconds[Term]+=Nanoseconds;
Stats.LegacyFieldTermTimedCalls[Term]+=Multiplicity;
}
std::size_t Term=0;
uint64 Multiplicity=1;
std::chrono::steady_clock::time_point Start{};
bool bTimed=false;
#else
explicit FScopedLegacyFieldTermTimer(ELegacyFieldTerm,uint64=1) noexcept {}
#endif
};
struct FScopedLegacyFieldTermScope
{
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
explicit FScopedLegacyFieldTermScope(bool bEligible,uint64 InMultiplicity=1)
:Previous(LegacyFieldTermScopeActive()),Multiplicity(InMultiplicity)
{
const bool bEnabled=ThreadClassificationStats().CaptureDensityQueryProfile
&&bEligible;
LegacyFieldTermScopeActive()=bEnabled;
if(bEnabled)ThreadClassificationStats().LegacyFieldEvalCalls+=Multiplicity;
}
~FScopedLegacyFieldTermScope()
{LegacyFieldTermScopeActive()=Previous;}
bool Previous=false;
uint64 Multiplicity=1;
#else
explicit FScopedLegacyFieldTermScope(bool,uint64=1) noexcept {}
#endif
};
struct FScopedLegacySmoothMinTimer
{
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
FScopedLegacySmoothMinTimer()
{
if(!LegacyFieldTermScopeActive())return;
++ThreadClassificationStats().LegacySmoothMinCalls;
if(!DensityQueryTermTimingActive())return;
Start=std::chrono::steady_clock::now();
bTimed=true;
}
~FScopedLegacySmoothMinTimer()
{
if(!bTimed)return;
auto& Stats=ThreadClassificationStats();
Stats.LegacySmoothMinNanoseconds+=static_cast<uint64>(std::max<std::int64_t>(0,
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now()-Start).count()));
++Stats.LegacySmoothMinTimedCalls;
}
std::chrono::steady_clock::time_point Start{};
bool bTimed=false;
#else
FScopedLegacySmoothMinTimer() noexcept {}
#endif
};
struct FScopedDensityQueryProfile struct FScopedDensityQueryProfile
{ {
FScopedDensityQueryProfile(EDensityQueryKind Kind,uint64 PointCount=1) FScopedDensityQueryProfile(EDensityQueryKind Kind,uint64 PointCount=1)
@@ -2240,6 +2360,10 @@ public:
{ {
if(!stats->CaptureDensityQueryProfile||DensityQuerySuppressionDepth()!=0) if(!stats->CaptureDensityQueryProfile||DensityQuerySuppressionDepth()!=0)
{stats=nullptr;return;} {stats=nullptr;return;}
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
PreviousTermTiming=DensityQueryTermTimingActive();
DensityQueryTermTimingActive()=false;
#endif
stats->DensityPointCalls+=pointCount; stats->DensityPointCalls+=pointCount;
switch(Kind) switch(Kind)
{ {
@@ -2248,13 +2372,22 @@ public:
case EDensityQueryKind::Unclamped:stats->DensityUnclampedPointCalls+=pointCount;break; case EDensityQueryKind::Unclamped:stats->DensityUnclampedPointCalls+=pointCount;break;
case EDensityQueryKind::X8:stats->DensityX8PointCalls+=pointCount;break; case EDensityQueryKind::X8:stats->DensityX8PointCalls+=pointCount;break;
} }
const uint64 Sequence=++DensityQuerySampleSequence(); const uint64 Sequence=DensityQuerySampleSequence();
if((Sequence&255u)!=0)return; DensityQuerySampleSequence()+=pointCount;
if(((Sequence+pointCount)>>8)==(Sequence>>8))return;
sampled=true;start=std::chrono::steady_clock::now(); sampled=true;start=std::chrono::steady_clock::now();
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
DensityQueryTermTimingActive()=true;
#endif
} }
~FScopedDensityQueryProfile() ~FScopedDensityQueryProfile()
{ {
if(!sampled)return; if(stats==nullptr)return;
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
DensityQueryTermTimingActive()=PreviousTermTiming;
#endif
if(sampled)
{
const uint64 nanoseconds=static_cast<uint64>(std::max<std::int64_t>(0, const uint64 nanoseconds=static_cast<uint64>(std::max<std::int64_t>(0,
std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now()-start).count())); std::chrono::steady_clock::now()-start).count()));
@@ -2263,12 +2396,16 @@ public:
stats->DensityFieldEvalNanosecondsByKind[kindIndex]+=nanoseconds; stats->DensityFieldEvalNanosecondsByKind[kindIndex]+=nanoseconds;
stats->DensityFieldEvalSamplePointsByKind[kindIndex]+=pointCount; stats->DensityFieldEvalSamplePointsByKind[kindIndex]+=pointCount;
} }
}
bool IsActive()const noexcept{return stats!=nullptr;} bool IsActive()const noexcept{return stats!=nullptr;}
FClassificationThreadStats* stats=nullptr; FClassificationThreadStats* stats=nullptr;
std::size_t kindIndex=0; std::size_t kindIndex=0;
uint64 pointCount=0; uint64 pointCount=0;
std::chrono::steady_clock::time_point start{}; std::chrono::steady_clock::time_point start{};
bool sampled=false; bool sampled=false;
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
bool PreviousTermTiming=false;
#endif
}; };
struct FScopedDensityQueryBatch struct FScopedDensityQueryBatch
{ {
@@ -3577,6 +3714,15 @@ private:
std::size_t passageIndex=0,segmentIndex=0; std::size_t passageIndex=0,segmentIndex=0;
bool bColumn=false,bMouthSupport=false; bool bColumn=false,bMouthSupport=false;
}; };
struct FPassageComposerConstants
{
bool internalChainPassage=false;
bool floatingIslandPassage=false;
bool surfaceToTunnelSupport=false;
double firstFloorQ=0.0,lastFloorQ=0.0,carveSearchMargin=0.0;
std::ptrdiff_t lowerMouthIndex=1,lastIndex=1;
std::ptrdiff_t mouthSearchBegin=0,mouthSearchEnd=0;
};
struct FPassageMouthSupportSegment struct FPassageMouthSupportSegment
{ {
std::array<std::int64_t,4> a{},b{}; std::array<std::int64_t,4> a{},b{};
@@ -4549,6 +4695,68 @@ private:
return result; return result;
} }
void BuildPassageComposerConstants()
{
PassageComposerConstants.clear();
if(!ResolvedLayout)return;
PassageComposerConstants.resize(ResolvedLayout->passages.size());
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qPerMetre=Q*1000000.0/
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double playerCenterAboveFloorQ=1.13*qPerMetre;
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue;
const auto supportStratum=[&](std::int32_t strateIndex)
->const VoxelForgeCore::ResolvedLayoutStratumV2*
{
const auto it=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[strateIndex](const auto& stratum)
{return stratum.strateIndex==strateIndex;});
return it==ResolvedLayout->strates.end()?nullptr:&*it;
};
const auto* supportUpper=supportStratum(passage.upperStrateIndex);
const auto* supportLower=supportStratum(passage.lowerStrateIndex);
const auto mouthInsideContent=[&](
const VoxelForgeCore::ResolvedLayoutStratumV2* stratum,
const std::array<std::int64_t,3>& mouth)
{
if(!stratum||!stratum->content.presentInCurrentField)return false;
const auto zMicrometres=static_cast<std::int64_t>(std::llround(
static_cast<double>(mouth[2])
*ResolvedLayout->coreVoxelSizeMicrometres/Q));
return zMicrometres>=stratum->content.bottomMicrometres
&&zMicrometres<stratum->content.topMicrometres;
};
auto& constants=PassageComposerConstants[passageIndex];
constants.internalChainPassage=supportUpper&&supportLower
&&!supportUpper->passageCapabilities.hasSurfaceLanding
&&!supportLower->passageCapabilities.hasSurfaceLanding
&&mouthInsideContent(supportUpper,passage.upperMouthSubvoxels)
&&mouthInsideContent(supportLower,passage.lowerMouthSubvoxels);
constants.floatingIslandPassage=supportUpper
&&supportUpper->passageCapabilities.hasFloatingIslandLanding;
constants.surfaceToTunnelSupport=supportUpper&&supportLower
&&supportUpper->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport;
constants.firstFloorQ=passage.controlPoints.front().positionSubvoxels[2]
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
constants.lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
constants.lastIndex=static_cast<std::ptrdiff_t>(passage.controlPoints.size())-1;
constants.lowerMouthIndex=std::clamp<std::ptrdiff_t>(
static_cast<std::ptrdiff_t>(passage.lowerMouthControlIndex),1,
constants.lastIndex);
constants.mouthSearchBegin=std::max<std::ptrdiff_t>(0,
constants.lowerMouthIndex-8);
constants.mouthSearchEnd=std::min<std::ptrdiff_t>(constants.lastIndex,
constants.lowerMouthIndex+8);
constants.carveSearchMargin=std::max(passage.mouthRadiusSubvoxels,
passage.midRadiusSubvoxels)+0.25*qPerMetre;
}
}
void BuildPassageSpatialIndices() void BuildPassageSpatialIndices()
{ {
BuildStampSpatialIndex(); BuildStampSpatialIndex();
@@ -5756,12 +5964,24 @@ private:
?ApplyResolvedStrateComposition(Density,X,Y,Z):Density; ?ApplyResolvedStrateComposition(Density,X,Y,Z):Density;
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed;
} }
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
const FBoundStratumField* LegacyTermBound=FindBoundStratumForZ(Z);
const bool bLegacyFiOrTnField=LegacyTermBound&&LegacyTermBound->Resolved
&&(LegacyTermBound->Resolved->generatorKind=="TunnelNetwork"
||(LegacyTermBound->Resolved->generatorKind=="FloatingIslands"
&&LegacyTermBound->IslandPlan==nullptr));
FScopedLegacyFieldTermScope LegacyFieldTermScope(bLegacyFiOrTnField);
#endif
const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]
&&Z>=Min[2]&&Z<=Max[2]; &&Z>=Min[2]&&Z<=Max[2];
if(!bInsideFine) if(!bInsideFine)
{ {
bool bCriticalAir=false; bool bCriticalAir=false;
float RockDistance=CoarseRockDistance(X,Y,Z,&bCriticalAir); float RockDistance=0.0f;
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::PrimitiveScan);
RockDistance=CoarseRockDistance(X,Y,Z,&bCriticalAir);
}
if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f);
const bool bRock=RockDistance>=0.0f; const bool bRock=RockDistance>=0.0f;
float Density=-RockDistance; float Density=-RockDistance;
@@ -5770,14 +5990,23 @@ private:
bClampNearZero?0.05f:1.0e-7f); bClampNearZero?0.05f:1.0e-7f);
const float composed=bApplyComposition const float composed=bApplyComposition
?ApplyResolvedStrateComposition(Density,X,Y,Z):Density; ?ApplyResolvedStrateComposition(Density,X,Y,Z):Density;
return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; if(bApplyEdits)
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::EditBrushes);
return ApplyEditBrushes(composed,X,Y,Z);
}
return composed;
} }
ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f;
bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f;
float PassageInfluence=0.0f; float PassageInfluence=0.0f;
float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, float RockDistance=0.0f;
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::PrimitiveScan);
RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF,
&bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,nullptr,false, &bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,nullptr,false,
&PassageInfluence); &PassageInfluence);
}
if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence; if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence;
if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence; if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence;
if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth
@@ -5787,8 +6016,13 @@ private:
CaveInfluence,FloorInfluence); CaveInfluence,FloorInfluence);
if(Fade>0.0f) if(Fade>0.0f)
{ {
const float Detail=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence, float DetailNoiseValue=0.0f;
FloorInfluence,PassageInfluence)*Fade; {
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::Detail);
DetailNoiseValue=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,
FloorInfluence,PassageInfluence);
}
const float Detail=DetailNoiseValue*Fade;
RockDistance+=Detail; RockDistance+=Detail;
if(OutDetailDisplacement)*OutDetailDisplacement=Detail; if(OutDetailDisplacement)*OutDetailDisplacement=Detail;
} }
@@ -5800,7 +6034,12 @@ private:
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density), Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),
bClampNearZero?0.05f:1.0e-7f); bClampNearZero?0.05f:1.0e-7f);
if(bApplyComposition)Density=ApplyResolvedStrateComposition(Density,X,Y,Z); if(bApplyComposition)Density=ApplyResolvedStrateComposition(Density,X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density; if(bApplyEdits)
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::EditBrushes);
return ApplyEditBrushes(Density,X,Y,Z);
}
return Density;
} }
public: public:
@@ -7649,7 +7888,11 @@ private:
{return N>=0?(N+D/2)/D:-((-N+D/2)/D);} {return N>=0?(N+D/2)/D:-((-N+D/2)/D);}
static float SmoothMinDistance(float A,float B,float Width) static float SmoothMinDistance(float A,float B,float Width)
{ {
if(Width<=0.0f)return std::min(A,B); FScopedLegacySmoothMinTimer SmoothMinTimer;
if(Width<=0.0f)
{
return std::min(A,B);
}
const float H=std::max(Width-std::abs(A-B),0.0f)/Width; const float H=std::max(Width-std::abs(A-B),0.0f)/Width;
return std::min(A,B)-(H*H*H)*(Width/6.0f); return std::min(A,B)-(H*H*H)*(Width/6.0f);
} }
@@ -10911,6 +11154,10 @@ private:
{ {
for(uint32 Id:*BinItems) for(uint32 Id:*BinItems)
{ {
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
++ThreadClassificationStats().LegacyPrimitiveCandidateVisits;
#endif
const FPrimitive& P=Primitives[Id]; const FPrimitive& P=Primitives[Id];
if(P.bCarve) if(P.bCarve)
{ {
@@ -10921,6 +11168,10 @@ private:
if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand
||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand ||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand
||Z<P.Min[2]-DetailBand||Z>P.Max[2]+DetailBand)continue; ||Z<P.Min[2]-DetailBand||Z>P.Max[2]+DetailBand)continue;
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations;
#endif
float Distance=ShapeDistance(P,X,Y,Z); float Distance=ShapeDistance(P,X,Y,Z);
float PrimitiveFloorInfluence=0.0f; float PrimitiveFloorInfluence=0.0f;
if(P.bHasFloorClip) if(P.bHasFloorClip)
@@ -10944,9 +11195,15 @@ private:
} }
else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1] else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
&&Z>=P.Min[2]&&Z<=P.Max[2]) &&Z>=P.Min[2]&&Z<=P.Max[2])
{
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations;
#endif
Overlay=std::max(Overlay,-ShapeDistance(P,X,Y,Z)); Overlay=std::max(Overlay,-ShapeDistance(P,X,Y,Z));
} }
} }
}
if(OutCriticalAir)*OutCriticalAir=bCriticalAir; if(OutCriticalAir)*OutCriticalAir=bCriticalAir;
const float CarveOrOverlay=std::max(Cave,Overlay); const float CarveOrOverlay=std::max(Cave,Overlay);
const ESurfaceFamily Family=Base<=CarveOrOverlay?ESurfaceFamily::Strata: const ESurfaceFamily Family=Base<=CarveOrOverlay?ESurfaceFamily::Strata:
@@ -11093,6 +11350,11 @@ private:
{ {
for(uint32 Id:*BinItems) for(uint32 Id:*BinItems)
{ {
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
ThreadClassificationStats().LegacyPrimitiveCandidateVisits+=
static_cast<uint64>(Count);
#endif
const FPrimitive& P=Primitives[Id]; const FPrimitive& P=Primitives[Id];
bool Eligible[8]{}; bool Eligible[8]{};
bool AnyEligible=false; bool AnyEligible=false;
@@ -11127,6 +11389,11 @@ private:
} }
if(!AnyEligible)continue; if(!AnyEligible)continue;
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations+=
static_cast<uint64>(Count);
#endif
float Distances[8]; float Distances[8];
ShapeDistanceZ8(P,X,Y,Z,Count,Distances); ShapeDistanceZ8(P,X,Y,Z,Count,Distances);
if(P.bCarve) if(P.bCarve)
@@ -11201,6 +11468,9 @@ public:
return; return;
} }
if(Count<=0||OutDensity==nullptr)return; if(Count<=0||OutDensity==nullptr)return;
FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Lattice,
static_cast<uint64>(Count));
FScopedDensityQueryBatch queryBatch(queryProfile.IsActive());
if(bNativeOnly) if(bNativeOnly)
{ {
for(int32 Lane=0;Lane<Count;++Lane) for(int32 Lane=0;Lane<Count;++Lane)
@@ -11260,7 +11530,27 @@ public:
&&FloorDiv(Z[BatchCount],ActiveSpatialBinSize)==BinZ)++BatchCount; &&FloorDiv(Z[BatchCount],ActiveSpatialBinSize)==BinZ)++BatchCount;
FEffectiveRockSample Samples[8]{}; FEffectiveRockSample Samples[8]{};
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
bool bLegacyTermBatch=true;
for(int32 Lane=0;Lane<BatchCount;++Lane)
{
const FBoundStratumField* Bound=FindBoundStratumForZ(
static_cast<float>(Z[Lane]));
bLegacyTermBatch=bLegacyTermBatch&&Bound&&Bound->Resolved
&&(Bound->Resolved->generatorKind=="TunnelNetwork"
||(Bound->Resolved->generatorKind=="FloatingIslands"
&&Bound->IslandPlan==nullptr));
}
#else
const bool bLegacyTermBatch=false;
#endif
FScopedLegacyFieldTermScope LegacyTermScope(bLegacyTermBatch,
static_cast<uint64>(BatchCount));
{
FScopedLegacyFieldTermTimer TermTimer(
ELegacyFieldTerm::PrimitiveScan,static_cast<uint64>(BatchCount));
EffectiveRockDistanceZ8(X,Y,Z,BatchCount,BinItems,Samples); EffectiveRockDistanceZ8(X,Y,Z,BatchCount,BinItems,Samples);
}
for(int32 Lane=0;Lane<BatchCount;++Lane) for(int32 Lane=0;Lane<BatchCount;++Lane)
{ {
const int32 SampleZ=Z[Lane]; const int32 SampleZ=Z[Lane];
@@ -11274,20 +11564,27 @@ public:
static_cast<float>(Y),static_cast<float>(SampleZ),RockDistance, static_cast<float>(Y),static_cast<float>(SampleZ),RockDistance,
Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence, Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence,
Samples[Lane].FloorInfluence); Samples[Lane].FloorInfluence);
if(Fade>0.0f)RockDistance+=DetailNoiseOnLine(static_cast<float>(X), if(Fade>0.0f)
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::Detail);
RockDistance+=DetailNoiseOnLine(static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(SampleZ), static_cast<float>(Y),static_cast<float>(SampleZ),
Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence, Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence,
Samples[Lane].FloorInfluence,Samples[Lane].PassageInfluence, Samples[Lane].FloorInfluence,Samples[Lane].PassageInfluence,
DetailNoiseLine)*Fade; DetailNoiseLine)*Fade;
} }
}
if(Samples[Lane].bCriticalAir) if(Samples[Lane].bCriticalAir)
RockDistance=std::min(RockDistance,-1.0f/16.0f); RockDistance=std::min(RockDistance,-1.0f/16.0f);
const bool bRock=RockDistance>=0.0f; const bool bRock=RockDistance>=0.0f;
float Density=-RockDistance; float Density=-RockDistance;
if((Density<0.0f)!=bRock||std::abs(Density)<0.05f) if((Density<0.0f)!=bRock||std::abs(Density)<0.05f)
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f); Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f);
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::EditBrushes);
OutDensity[Offset+Lane]=ApplyEditBrushes(Density,X,Y,SampleZ); OutDensity[Offset+Lane]=ApplyEditBrushes(Density,X,Y,SampleZ);
} }
}
Offset+=BatchCount; Offset+=BatchCount;
} }
} }
@@ -11312,6 +11609,10 @@ private:
{ {
for(uint32 Id:*QueryItems) for(uint32 Id:*QueryItems)
{ {
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
++ThreadClassificationStats().LegacyPrimitiveCandidateVisits;
#endif
const FPrimitive& P=Primitives[Id]; const FPrimitive& P=Primitives[Id];
const VF_FieldPrimitive& Shared=SharedFieldPrimitives[Id]; const VF_FieldPrimitive& Shared=SharedFieldPrimitives[Id];
if(P.bCarve) if(P.bCarve)
@@ -11323,6 +11624,10 @@ private:
if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand if(X<P.Min[0]-DetailBand||X>P.Max[0]+DetailBand
||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand ||Y<P.Min[1]-DetailBand||Y>P.Max[1]+DetailBand
||Z<P.Min[2]-DetailBand||Z>P.Max[2]+DetailBand)continue; ||Z<P.Min[2]-DetailBand||Z>P.Max[2]+DetailBand)continue;
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations;
#endif
float Distance=VF_FieldPrimitiveDistance(Shared,X,Y,Z); float Distance=VF_FieldPrimitiveDistance(Shared,X,Y,Z);
if(P.bHasFloorClip) if(P.bHasFloorClip)
{ {
@@ -11330,15 +11635,33 @@ private:
Distance=VF_FieldSmoothMax(Distance,static_cast<float>(Floor-Z), Distance=VF_FieldSmoothMax(Distance,static_cast<float>(Floor-Z),
static_cast<float>(P.FloorBlendRadius)); static_cast<float>(P.FloorBlendRadius));
} }
Cave=VF_FieldSmoothMin(Cave,Distance,static_cast<float>(P.BlendRadius)); float Combined=0.0f;
{
FScopedLegacySmoothMinTimer SmoothMinTimer;
Combined=VF_FieldSmoothMin(Cave,Distance,
static_cast<float>(P.BlendRadius));
}
Cave=Combined;
} }
else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1] else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1]
&&Z>=P.Min[2]&&Z<=P.Max[2]) &&Z>=P.Min[2]&&Z<=P.Max[2])
{
#if defined(VF_L76_PROFILE_LEGACY_TERMS)
if(LegacyFieldTermScopeActive())
++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations;
#endif
Overlay=std::max(Overlay,-VF_FieldPrimitiveDistance(Shared,X,Y,Z)); Overlay=std::max(Overlay,-VF_FieldPrimitiveDistance(Shared,X,Y,Z));
} }
} }
}
if(OutCriticalAir)*OutCriticalAir=bCriticalAir; if(OutCriticalAir)*OutCriticalAir=bCriticalAir;
return VF_FieldSmoothMin(Base,std::max(Cave,Overlay),StrataCaveBlendRadius); float RockDistance=0.0f;
{
FScopedLegacySmoothMinTimer SmoothMinTimer;
RockDistance=VF_FieldSmoothMin(Base,std::max(Cave,Overlay),
StrataCaveBlendRadius);
}
return RockDistance;
} }
struct FOwnerNoiseLineContext struct FOwnerNoiseLineContext
@@ -11543,15 +11866,22 @@ private:
*DetailSupremum; *DetailSupremum;
} }
float PassageBandRelief=0.0f; float PassageBandRelief=0.0f;
const float Relief=SurfaceRelief(static_cast<float>(X),static_cast<float>(Y), float Relief=0.0f;
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::SurfaceReliefNoise);
Relief=SurfaceRelief(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),static_cast<uint32>(Owner->Seed),&PassageBandRelief); static_cast<float>(Z),static_cast<uint32>(Owner->Seed),&PassageBandRelief);
}
const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f); const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f);
const FLayerField* Field=FindLayerField(Z); const FLayerField* Field=FindLayerField(Z);
FOwnerNoiseLineContext OwnerNoise; FOwnerNoiseLineContext OwnerNoise;
float OwnerRelief=0.0f; float OwnerRelief=0.0f;
if(Field) if(Field)
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::OwnerNoiseAndWarp);
OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field, OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field,
CaveInfluence,PassageWeight,OwnerNoise); CaveInfluence,PassageWeight,OwnerNoise);
}
const float Detail=std::clamp(BlendDetailFamilies(Relief,PassageBandRelief, const float Detail=std::clamp(BlendDetailFamilies(Relief,PassageBandRelief,
OwnerRelief,CaveInfluence,FloorInfluence,PassageWeight),-3.9f,3.9f); OwnerRelief,CaveInfluence,FloorInfluence,PassageWeight),-3.9f,3.9f);
return Detail; return Detail;
@@ -11570,8 +11900,12 @@ private:
using Clock=std::chrono::steady_clock; using Clock=std::chrono::steady_clock;
const auto ReliefStart=Profile?Clock::now():Clock::time_point{}; const auto ReliefStart=Profile?Clock::now():Clock::time_point{};
float PassageBandRelief=0.0f; float PassageBandRelief=0.0f;
const float Relief=NoiseLine.SurfaceReliefAt(static_cast<float>(Z), float Relief=0.0f;
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::SurfaceReliefNoise);
Relief=NoiseLine.SurfaceReliefAt(static_cast<float>(Z),
bMacroKnown,KnownMacro,&PassageBandRelief); bMacroKnown,KnownMacro,&PassageBandRelief);
}
if(Profile) if(Profile)
Profile->ReliefSampleNanoseconds+=static_cast<uint64>( Profile->ReliefSampleNanoseconds+=static_cast<uint64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-ReliefStart).count()); std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-ReliefStart).count());
@@ -11585,8 +11919,11 @@ private:
const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f); const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f);
float OwnerRelief=0.0f; float OwnerRelief=0.0f;
if(Field) if(Field)
{
FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::OwnerNoiseAndWarp);
OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field, OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field,
CaveInfluence,PassageWeight,NoiseLine.OwnerNoise,Profile); CaveInfluence,PassageWeight,NoiseLine.OwnerNoise,Profile);
}
if(Profile) if(Profile)
Profile->OwnerRoughnessSampleNanoseconds+=static_cast<uint64>( Profile->OwnerRoughnessSampleNanoseconds+=static_cast<uint64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-OwnerStart).count()); std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now()-OwnerStart).count());
@@ -11885,6 +12222,7 @@ private:
bool PassageFeatureBoundsFailOpen=false; bool PassageFeatureBoundsFailOpen=false;
std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes; std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes;
std::vector<std::vector<FPassageMouthSupportSegment>> PassageMouthSupportRoutes; std::vector<std::vector<FPassageMouthSupportSegment>> PassageMouthSupportRoutes;
std::vector<FPassageComposerConstants> PassageComposerConstants;
std::vector<std::vector<FPassageLaterBoreSource>> PassageLaterBoreSources; std::vector<std::vector<FPassageLaterBoreSource>> PassageLaterBoreSources;
std::vector<FPassageProofBound> PassageProofBounds; std::vector<FPassageProofBound> PassageProofBounds;
std::vector<FPassageProofNode> PassageProofNodes; std::vector<FPassageProofNode> PassageProofNodes;
+11
View File
@@ -2172,6 +2172,17 @@ namespace VoxelForgeCore
out.diagnostics.densityX8PointCalls=QueryCounts.DensityX8PointCalls; out.diagnostics.densityX8PointCalls=QueryCounts.DensityX8PointCalls;
out.diagnostics.densityFieldEvalNanoseconds=QueryCounts.DensityFieldEvalNanoseconds; out.diagnostics.densityFieldEvalNanoseconds=QueryCounts.DensityFieldEvalNanoseconds;
out.diagnostics.densityFieldEvalSamplePoints=QueryCounts.DensityFieldEvalSamplePoints; out.diagnostics.densityFieldEvalSamplePoints=QueryCounts.DensityFieldEvalSamplePoints;
out.diagnostics.legacyFieldEvalCalls=QueryCounts.LegacyFieldEvalCalls;
out.diagnostics.legacyFieldTermCalls=QueryCounts.LegacyFieldTermCalls;
out.diagnostics.legacyFieldTermNanoseconds=QueryCounts.LegacyFieldTermNanoseconds;
out.diagnostics.legacyFieldTermTimedCalls=QueryCounts.LegacyFieldTermTimedCalls;
out.diagnostics.legacyPrimitiveCandidateVisits=
QueryCounts.LegacyPrimitiveCandidateVisits;
out.diagnostics.legacyPrimitiveDistanceEvaluations=
QueryCounts.LegacyPrimitiveDistanceEvaluations;
out.diagnostics.legacySmoothMinCalls=QueryCounts.LegacySmoothMinCalls;
out.diagnostics.legacySmoothMinNanoseconds=QueryCounts.LegacySmoothMinNanoseconds;
out.diagnostics.legacySmoothMinTimedCalls=QueryCounts.LegacySmoothMinTimedCalls;
} }
if(!options.instrumentTileDrain)return; if(!options.instrumentTileDrain)return;
islandCacheInstrumentation.tileCycles=VoxelForgeCoreShared::ReadCpuCycles() islandCacheInstrumentation.tileCycles=VoxelForgeCoreShared::ReadCpuCycles()
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Measure sampled per-term profiler overhead against the same clean candidate."""
from __future__ import annotations
import json
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
DATA = ROOT / "Standalone/data/owner_world.json"
OUT = ROOT / os.environ.get("VOXELFORGE_L76_PROFILE_OVERHEAD_OUT",
"Saved/Linux/l76/term-profiler-overhead")
BINARIES = {
"clean": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l76-clang",
"profile": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l76-termprofile-clang",
}
CAMERAS = {
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
}
KV = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
def run(kind: str, strate: int, pair: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"strate{strate}-pair{pair}-{kind}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
"1740s", str(BINARIES[kind]), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--workers=8",
"--clip-radius=3", "--max-clip-level=8", "--max-level=8", f"--camera={camera}",
"--profile-lod-work", "--instrument-tile-drain", "--profile-passage-composer",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
pipeline = {}
with log.open(encoding="utf-8", errors="replace") as stream:
for line in stream:
if line.startswith("[StreamPipeline]"):
pipeline = dict(KV.findall(line))
if result.returncode != 0 or "whole_view_ready" not in pipeline:
raise SystemExit(f"profiler-overhead run failed; see {log}")
return {
"kind": kind, "strate": strate, "pair": pair, "exit_code": result.returncode,
"process_wall_s": round(time.monotonic() - started, 3),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"log": str(log.relative_to(ROOT)),
}
def main() -> int:
missing = [str(path) for path in (*BINARIES.values(), DATA) if not path.is_file()]
if missing:
raise SystemExit("missing input: " + ", ".join(missing))
rows = []
for strate in (1, 2):
for pair in range(3):
order = ("clean", "profile") if pair % 2 == 0 else ("profile", "clean")
pair_rows = {kind: run(kind, strate, pair) for kind in order}
delta = pair_rows["profile"]["whole_view_ready_s"] \
- pair_rows["clean"]["whole_view_ready_s"]
result = {
"strate": strate, "pair": pair,
"clean_s": pair_rows["clean"]["whole_view_ready_s"],
"profile_s": pair_rows["profile"]["whole_view_ready_s"],
"delta_s": round(delta, 4),
"delta_percent": round(100.0 * delta / pair_rows["clean"]["whole_view_ready_s"], 2),
"runs": [pair_rows[kind] for kind in order],
}
rows.append(result)
print("[L76TermProfilerOverhead] " + json.dumps(result, sort_keys=True), flush=True)
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,38 @@
## Revised ranked list
Ranked by opportunity, **not measured speedup**. **Changed:** I retract R40’s ranking of the per-column detail context as the clearest next win. The line-based detail pass already has that context, while L73 reports large repeated passage-composer workloads. L73 does not time individual terms, so none of these ranks predicts seconds saved.
1. **[INFERENCE] [CODE] Reuse passage relief fields within one sample.** When multiple passages reach the relief calculation at the same `(X,Y,Z)`, they call the same three functions with the same inputs: passage-wall relief and two surface-relief fields. Each function evaluates three 3D value-noise fields, so its result depends on Z; it cannot be reused across a column’s different Z samples. A sample-scoped cache could reuse each result across qualifying passages while leaving their density-composition order intact. **[REPORT]** L73 counted 54.5M FI / 46.9M TN passage relief-field calls, but did not time them separately. [Relief calls in the passage loop](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1404>), [noise definitions](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeFieldShared.h:247>), [L73 counts and their limitation](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux73/REPORT.md:61>)
**Falsifier:** term-level timing shows these calls are a small part of composer time, or the hot samples rarely reach more than one qualifying passage.
2. **[INFERENCE] [CODE] Make tile passage and crossing candidates drive the sample loops.** Tile scope already builds passage features, but turns route and column features into per-passage flags. The sample path still checks every passage for upper-mouth proximity, loops over every passage for carves, and scans all void crossings inside that loop. An ordered per-tile list could avoid those visits while preserving passage order and retaining the existing sample-level tests. Mouth-support segments already have a per-tile candidate list and are filtered by sample bounds. **[REPORT]** L73 counted 63.4M FI / 54.2M TN carve visits, 20.4M / 15.5M support visits, and 24.6M / 20.4M mouth-route checks; these are counts, not time shares. [Tile scope](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:194>), [upper-mouth scan](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1003>), [passage and crossing loops](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1099>), [per-tile support segments](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1078>)
**Falsifier:** most tiles retain nearly every passage, or direct timing shows the remaining loop and crossing checks are cheap beside route-distance work.
3. **[INFERENCE] [CODE] Extend Z-line reuse only where scalar sampling still pays for it.** The scalar analytic path calls `DetailNoise`, which creates a fresh owner-noise context per sample. Its surface relief has three 3D value-noise fields; owner roughness may add 3D Perlin calls, domain warps, or cellular searches. However, `ApplyDetailBand` already creates a detail context per column, reuses value-noise planes and owner endpoint cores, and passes its macro value from fade into detail. So this is a candidate only for hot callers that still use the scalar path, not a general missing optimization. [Scalar detail path](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:11697>), [existing per-column detail context](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:10425>), [macro reuse in that pass](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:10550>), [owner noise and Z-dependent warp inputs](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeFieldShared.h:420>)
**Falsifier:** the scalar path contributes little to L73’s field time, or owner roughness is rarely active. The current report does not separate those costs.
4. **[INFERENCE] [CODE] Cache selected XY subterms, with strict scope.** `FloorAt(P,X,Y)` is XY-only for a fixed primitive, and cavern-edge closest-point parameter search uses X and Y. But primitive selection and bounds checks depend on Z; cavern floor distances, caps, noise, and feature eligibility also depend on Z. In addition, the batched primitive path already computes a primitive’s floor once for its Z lanes. These are narrower opportunities than R40 implied. [XY-only floor function](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9166>), [batched floor reuse](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:11296>), [cavern XY projection](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:8155>), [Z-dependent cavern distance](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:8173>)
**Falsifier:** those XY subterms are a small share of time, or few features pass the per-sample bounds checks.
## Dependency and exactness audit
| Term | What the code verifies |
|---|---|
| `StrataDistance(Z)`, `FindLayerField(Z)`, bound-stratum lookup | **[CODE] Verified Z-only selectors/values.** They can be shared across X/Y samples on the same Z plane. The selected field can change when Z crosses a layer or stratum boundary. [Strata and layer lookup](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:11028>) |
| `FloorAt(P,X,Y)` | **[CODE] Verified XY-only for fixed `P`.** This does not make floor blending XY-only: the primitive’s per-sample selection, `Floor-Z`, and floor blend tests still depend on Z. [Floor function](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9166>), [Z-dependent blend](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:11086>) |
| 3D value noise and relief | **[CODE] Verified partial XY reuse only.** `X0`, `Y0`, `TX`, and `TY` use X/Y; `Z0`, `TZ`, the corner planes, and the final nested lerp use Z. Owner roughness also puts effective Z into its main and fine coordinates and into domain-warp Perlin inputs. [Value-noise interpolation](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ResearchTunnelsInternal.h:51>), [owner domain warp](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeFieldShared.h:428>), [owner core](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/VoxelForgeFieldShared.h:415>) |
| Primitive selection | **[CODE] Verified Z-dependent.** `PointBin` includes Z, and the sample checks primitive Z bounds before evaluating distances. Only the X/Y parts of the bin lookup are column-stable. [3D bin key](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:9924>), [per-sample primitive scan](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:11061>) |
| Cavern and mouth-support projections | **[CODE] Verified XY-only for the projection parameter in these paths.** Their full results are not XY-only: cavern floor height/caps and mouth-support distances use Z. Feature and primitive selection also still use per-sample bounds. [Cavern projection and floor](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:8155>), [mouth-support projection and Z distance](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:1043>) |
| Tile candidates and boundary flags | **[CODE] Verified tile-constant candidate data.** Passage, stamp, and cavern candidate sets are built from tile bounds. The boundary and transition entries are conservative tile flags, not exact per-sample eligibility; the boundary calculation still uses X, Y, and Z. [Tile scope and flags](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:190>), [boundary calculation](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:820>) |
**Bit identity:** I cannot certify any proposed code motion as bit-identical from source inspection alone. The safest candidates are memoization that calls the same helper with exactly the same inputs, or reuses its returned value without rearranging density arithmetic. The existing line-context value-noise code preserves the visible nested lerp order, but moving work into another context can change inlining or floating-point contraction. I did not verify compiler FP settings or generated code, so FMA/reassociation parity remains **inferred, not verified**. Reordering composer features is especially risky because the density updates are sequential. [Value-noise line context](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:11650>), [composition order](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2011>)
**Candidate-list correction:** primitive queries use 32-voxel spatial bins, not a per-tile primitive list; each sample scans the references in its selected 3D bin. Mouth-support segments do have per-tile candidates. The active legacy FI has no matching native `IslandPlan`, so native island feature lists and the native island column cache do not serve these FI samples. [Bin size](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:2646>), [FI plan guard](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Core/src/ShapeWorldInternal.h:7434>), [L73 path finding](</home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux73/REPORT.md:67>)
Dropping detail octaves or changing float/double arithmetic remains **[INFERENCE] [WORLD-CHANGING]**: even if visually similar, those changes alter field bits and may alter signs near zero.
Read-only source/report inspection only; no program, build, or tests were run.
@@ -0,0 +1,76 @@
# Linux76 report: legacy FI/TN sample cost
## Per-term profile
The term profiler sampled 1 in 256 density points and scaled timed calls by their observed counts. Worker-ms are summed across LODs; ns/sample divides by legacy FI/TN field evaluations. These are sampled estimates, not wall time. Before is L75; after is the final constants-only source.
| Term | FI worker-ms / ns per sample, before → after | TN worker-ms / ns per sample, before → after |
|---|---:|---:|
| Primitive scan | 1,116 / 186 → 1,133 / 189 | 997 / 179 → 1,037 / 186 |
| DetailNoise call (pre-noise fade excluded) | 38 / 6.4 → 39 / 6.5 | 63 / 11.3 → 68 / 12.2 |
| ↳ Surface relief noise, inside detail | 7.6 / 1.3 → 7.7 / 1.3 | 12.3 / 2.2 → 13.2 / 2.4 |
| ↳ Owner noise and warp, inside detail | 26.2 / 4.4 → 26.9 / 4.5 | 43.2 / 7.8 → 46.7 / 8.4 |
| Composer cavern | 142 / 23.6 → 145 / 24.1 | 122 / 21.8 → 127 / 22.8 |
| Composer boundary band | 338 / 56.3 → 350 / 58.2 | 306 / 55.0 → 321 / 57.8 |
| Composer passage/support | 6,462 / 1,076 → 6,323 / 1,053 | 5,475 / 984 → 5,522 / 993 |
| Composer stamps | 0 / 0 → 0 / 0 | 0 / 0 → 0 / 0 |
| Edit brushes | 133 / 22.1 → 135 / 22.5 | 114 / 20.5 → 119 / 21.3 |
| Smooth-min across legacy evaluation | 166 / 27.6 → 170 / 28.3 | 143 / 25.6 → 151 / 27.2 |
FI used 6,005,989 legacy samples and TN 5,562,334. The primitive bins supplied 2.75 FI / 3.85 TN candidate references per sample; only 0.251 / 0.237 per sample reached primitive-distance evaluation. The code scans references in the selected 3D bin, not the full primitive set. Smooth-min ran about 1.27 times per legacy sample and overlaps the primitive/composer rows; do not add it again. The noise subrows are inside `DetailNoise`. Its timer excludes the preceding fade and layer lookup. Surface relief evaluates 3D value-noise wavelengths 32, 16, and 4; noise timers cover helper calls, not individual octave loops.
Passage/support dominates: about 1.05–1.08 μs per sample versus 0.18–0.19 μs for primitive scan. Per-tile filtering of the upper-mouth scan matched hashes but regressed TN by 5.43% in the first whole-view timing set, so it was removed. Broad per-tile passage-loop filtering failed hashes and was rejected. The remaining relief-call counter includes path-relief calls and does not establish repeated triplets for the same sample; no sample cache was shipped.
The profiler adds about 1.9% on FI and 2.1% on TN by the median of three paired W8 runs against the same clean build. Pair deltas were FI +1.90%, −3.33%, +2.19%; TN +2.10%, +7.00%, −1.91%. This spread is visible instrumentation cost, not field speedup. Profile inputs and summaries are in `Saved/Linux/l76/term-profile-before/`, `term-profile-constants-only/`, and `term-profiler-overhead-constants-only/`.
## Invariants and changes
- `StrataDistance(Z)`, layer lookup, and bound-stratum lookup are Z-only. `FloorAt(P,X,Y)` and some projection parameters are XY-only for a fixed primitive/feature, but primitive selection, floor blending, and the resulting composer values still depend on Z.
- Surface and passage relief use 3D noise; owner warps also depend on effective Z. The SW column cache is not reused for these values.
- Cached resolved-passage metadata once at field binding: internal-chain and landing flags, first/last floor Q, lower-mouth index, carve margin, and mouth search limits. Per-sample arithmetic order is unchanged. The profiler is opt-in at build time with `VOXELFORGE_PROFILE_LEGACY_TERMS=1`; timer bodies compile out in default builds.
- The whole-view effect of the constants hoist is mixed and near noise. No FP reordering was made, hashes stayed identical, and no version bump was needed.
## Hashes
Final Clang/GCC candidate per-tile hashes matched the L75 reference for density, mesh, payload, and topology in all 24 compiler × worker × view comparisons (W1/W8/W12, four views). Each compiler/worker set covered 9,661 tiles.
Final Clang/GCC source stamp: `285080616b0c5b65a670a72858e3b7e922b302df183b247c238504d58aa2b82f`.
| View | Tiles | Density | Mesh | Payload | Topology |
|---|---:|---|---|---|---|
| SW | 2,850 | `40b76d0072a2adcd` | `8559df0850d4d7f1` | `7700965ff31ac702` | `0e7f2a16b4cc71a9` |
| FI | 2,249 | `99ca213b21ce86e4` | `2977525405b79fff` | `3d7d2efb581710bd` | `af1916c949224871` |
| TN | 2,233 | `8d23c26f5c092be1` | `15c3aad2e29d9ea0` | `3d48f68d511c21df` | `5b1584676884edd8` |
| VS | 2,329 | `876b44981d106be3` | `aaef57cb08bd6845` | `b19617df9f4acbb1` | `e8eee991260adff4` |
The rows above are the matching W8 Clang whole-view hashes; GCC and the other worker counts matched each corresponding L75 tile hash as well. Per-tile comparison summary: `Saved/Linux/l76/hashes/final-clean/summary.json`.
## Whole-view timing
Three interleaved W8 pairs per view; seconds are `(L75 → L76)` in pair order. The delta is the median of paired percentage changes.
| View | Pair times | Median paired delta |
|---|---|---:|
| SW | 2.428→2.404, 2.499→2.456, 2.343→2.347 | −0.024 s (−0.99%) |
| FI | 4.505→4.668, 4.778→4.635, 4.414→4.413 | −0.001 s (−0.02%) |
| TN | 4.389→4.289, 4.132→4.408, 4.417→4.337 | −0.080 s (−1.81%) |
| VS | 2.454→2.489, 2.353→2.426, 2.365→2.461 | +0.073 s (+3.10%) |
The largest median paired change is +0.073 s (VS); the best is −0.080 s (TN). Whole views remain 2.4–4.6 s, far from the ~1 s goal. Passage/support remains the next cost target.
Timing rows and logs are in `Saved/Linux/l76/timing-constants-only/summary.json`.
## Audits and builds
- Final `verify.py fast`: PASS in 381.8 s; passage audits, fast-tier hash gates, and controls completed.
- Final fast-tier summary: `Saved/Linux/l76/verify/fast-summary.json`.
- Linux UE host `VFHostEditor Linux Development`: succeeded in 121.98 s with `taskset -c 0-11`, `-MaxParallelActions=4`, and no address-space limit. Three existing RealtimeMesh deprecation warnings were emitted. Log: `Saved/Linux/l76/ue-build.log`.
- All headless runs used `taskset -c 0-11` and `prlimit --as=6442450944`; no run exceeded 30 minutes.
## Failed runs
- Broad per-tile passage-loop filtering failed Clang W1 SW hashes: 612 density mismatches and 24 each in mesh, payload, and topology. It was removed.
- The first fast-tier launch stopped before tests because its output-directory executable had not been built. After building the requested binaries, the final fast tier passed.
- The first UE invocation used a nonexistent direct `Engine/Binaries/DotNET/dotnet` path. The engine `Build.sh` wrapper set up bundled DotNet and the host build succeeded.
No commit, push, or stash was made.
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Recheck the final candidate against the L75 logs captured by RunHashMatrixL76."""
from __future__ import annotations
import json
from pathlib import Path
import RunHashMatrixL76 as matrix
REFERENCE_OUT = matrix.OUT
FINAL_OUT = matrix.ROOT / "Saved/Linux/l76/hashes/final-clean"
matrix.OUT = FINAL_OUT
def main() -> int:
FINAL_OUT.mkdir(parents=True, exist_ok=True)
rows, comparisons = [], []
for compiler in matrix.BINARIES:
for workers in (1, 8, 12):
for strate in range(4):
reference = REFERENCE_OUT / compiler / f"w{workers}" / f"strate{strate}-before.log"
candidate = matrix.run(compiler, workers, strate, "after")
rows.append(candidate)
comparison = matrix.compare(reference, matrix.ROOT / candidate["log"])
comparison.update(compiler=compiler, workers=workers, strate=strate)
comparisons.append(comparison)
print("[L76HashFinalCompare] " + json.dumps(comparison, sort_keys=True), flush=True)
if not comparison["equal"]:
raise SystemExit(f"hash mismatch; see {comparison['candidate']}")
result = {"candidate_runs": rows, "comparisons": comparisons,
"equal": all(row["equal"] for row in comparisons),
"reference_matrix": str(REFERENCE_OUT.relative_to(matrix.ROOT))}
(FINAL_OUT / "summary.json").write_text(json.dumps(result, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""Compare exact per-tile hashes for L75 reference and L76 on Clang/GCC W1/W8/W12."""
from __future__ import annotations
import json
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / "Saved/Linux/l76/hashes"
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"clang": {
"before": ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang",
"after": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l76-clang",
},
"gcc": {
"before": ROOT / "Saved/Linux/l75/bin-gcc/VoxelForgeCoreHeadless-l75-gcc",
"after": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l76-gcc",
},
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
HASH = re.compile(
r"\[PipelineTileHash\] lod=(\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
r"density=(0x[0-9a-fA-F]+) mesh=(0x[0-9a-fA-F]+) "
r"payload=(0x[0-9a-fA-F]+) topology=(0x[0-9a-fA-F]+)"
)
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
def read_hashes(path: Path) -> dict[tuple[int, int, int, int], dict[str, str]]:
result = {}
with path.open(encoding="utf-8", errors="replace") as stream:
for line in stream:
match = HASH.search(line)
if match:
lod, x, y, z = (int(match.group(index)) for index in range(1, 5))
result[(lod, x, y, z)] = {
"density": match.group(5).lower(), "mesh": match.group(6).lower(),
"payload": match.group(7).lower(), "topology": match.group(8).lower(),
}
return result
def run(compiler: str, workers: int, strate: int, mode: str) -> dict:
folder = OUT / compiler / f"w{workers}"
folder.mkdir(parents=True, exist_ok=True)
log = folder / f"strate{strate}-{mode}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
"1740s", str(BINARIES[compiler][mode]), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate",
f"--workers={workers}", "--clip-radius=3", "--max-clip-level=8",
"--max-level=8", f"--camera={camera}", "--stream-hash-tiles",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
hashes = read_hashes(log)
if result.returncode != 0 or not hashes:
raise SystemExit(f"hash generation failed or no tile hashes; see {log}")
row = {
"compiler": compiler, "workers": workers, "strate": strate, "mode": mode,
"exit_code": result.returncode, "wall_s": round(time.monotonic() - started, 3),
"tile_hashes": len(hashes), "log": str(log.relative_to(ROOT)),
}
print("[L76HashRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def compare(reference: Path, candidate: Path) -> dict:
expected, actual = read_hashes(reference), read_hashes(candidate)
missing, extra = expected.keys() - actual.keys(), actual.keys() - expected.keys()
mismatches = {name: 0 for name in ("density", "mesh", "payload", "topology")}
for key in expected.keys() & actual.keys():
for name in mismatches:
if expected[key][name] != actual[key][name]:
mismatches[name] += 1
return {
"reference": str(reference.relative_to(ROOT)),
"candidate": str(candidate.relative_to(ROOT)),
"reference_tiles": len(expected), "candidate_tiles": len(actual),
"missing_keys": len(missing), "extra_keys": len(extra),
"mismatches": mismatches,
"equal": not missing and not extra and not any(mismatches.values()),
}
def main() -> int:
missing = [str(path) for compiler in BINARIES.values()
for path in compiler.values() if not path.is_file()]
if not DATA.is_file():
missing.append(str(DATA))
if missing:
raise SystemExit("missing input: " + ", ".join(missing))
rows, comparisons = [], []
for compiler in BINARIES:
for workers in (1, 8, 12):
for strate in range(4):
before = run(compiler, workers, strate, "before")
after = run(compiler, workers, strate, "after")
rows.extend((before, after))
comparison = compare(ROOT / before["log"], ROOT / after["log"])
comparison.update(compiler=compiler, workers=workers, strate=strate)
comparisons.append(comparison)
print("[L76HashCompare] " + json.dumps(comparison, sort_keys=True), flush=True)
if not comparison["equal"]:
raise SystemExit(f"hash mismatch; see {comparison['candidate']}")
result = {"runs": rows, "comparisons": comparisons,
"equal": all(row["equal"] for row in comparisons)}
(OUT / "matrix-summary.json").write_text(json.dumps(result, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Capture sampled legacy FI/TN field-term costs for the four owner views."""
from __future__ import annotations
import json
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / os.environ.get("VOXELFORGE_L76_PROFILE_OUT",
"Saved/Linux/l76/term-profile-before")
DATA = ROOT / "Standalone/data/owner_world.json"
BIN = ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l76-termprofile-clang"
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
KV = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
def run(strate: int) -> dict:
OUT.mkdir(parents=True, exist_ok=True)
log = OUT / f"strate{strate}-w8.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
"1740s", str(BIN), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
"--stream-pipeline", "--force-generate", "--workers=8", "--clip-radius=3",
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
"--profile-lod-work", "--instrument-tile-drain", "--profile-passage-composer",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
rows: dict[int, dict[str, dict]] = {}
pipeline = {}
with log.open(encoding="utf-8", errors="replace") as stream:
for line in stream:
if line.startswith("[WorldAssemblerLODLegacyField]"):
row = dict(KV.findall(line))
rows.setdefault(int(row["lod"]), {})["field"] = row
elif line.startswith("[WorldAssemblerLODLegacyTerm]"):
row = dict(KV.findall(line))
rows.setdefault(int(row["lod"]), {}).setdefault("terms", {})[row["term"]] = row
elif line.startswith("[WorldAssemblerLODClassification]"):
row = dict(KV.findall(line))
rows.setdefault(int(row["lod"]), {})["classification"] = row
elif line.startswith("[WorldAssemblerLODWorker]"):
row = dict(KV.findall(line))
rows.setdefault(int(row["lod"]), {})["worker"] = row
elif line.startswith("[StreamPipeline]"):
pipeline = dict(KV.findall(line))
if result.returncode != 0 or not pipeline or not rows:
raise SystemExit(f"profile run failed or summary missing; see {log}")
row = {
"strate": strate,
"exit_code": result.returncode,
"process_wall_s": round(time.monotonic() - started, 3),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
"lod": rows,
"log": str(log.relative_to(ROOT)),
}
(OUT / f"strate{strate}-w8.json").write_text(json.dumps(row, indent=2) + "\n",
encoding="utf-8")
print("[L76TermProfileRun] " + json.dumps({k: v for k, v in row.items() if k != "lod"},
sort_keys=True), flush=True)
return row
def main() -> int:
missing = [str(path) for path in (DATA, BIN) if not path.is_file()]
if missing:
raise SystemExit("missing input: " + ", ".join(missing))
rows = [run(strate) for strate in range(4)]
(OUT / "summary.json").write_text(json.dumps(rows, indent=2) + "\n",
encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Run three interleaved whole-view timing pairs for L75 and L76 Clang builds."""
from __future__ import annotations
import json
import os
import re
import statistics
import subprocess
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4]
OUT = ROOT / os.environ.get("VOXELFORGE_L76_TIMING_OUT", "Saved/Linux/l76/timing")
DATA = ROOT / "Standalone/data/owner_world.json"
BINARIES = {
"before": ROOT / "Saved/Linux/l75/bin/VoxelForgeCoreHeadless-l75-clang",
"after": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l76-clang",
}
CAMERAS = {
0: (8.0, 16.0, -372.2626953125),
1: (25.8369140625, 3.19921875, -737.0),
2: (19.166015625, -14.2001953125, -753.3740234375),
3: (-20.0625, -5.068359375, -900.353515625),
}
ORDERS = (("before", "after"), ("after", "before"), ("before", "after"))
PIPELINE = re.compile(r"\[StreamPipeline\] (.*)")
PAIR = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
def run(strate: int, pair: int, mode: str) -> dict:
label = f"strate{strate}-pair{pair}-{mode}"
log = OUT / f"{label}.log"
camera = ",".join(f"{value:.12f}" for value in CAMERAS[strate]) + ",0,0"
command = [
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--", "timeout",
"1740s", str(BINARIES[mode]), str(DATA), "--world-assembler-prototype",
"--stream-benchmark", "--stream-pipeline", "--force-generate", "--workers=8",
"--clip-radius=3", "--max-clip-level=8", "--max-level=8", f"--camera={camera}",
]
started = time.monotonic()
with log.open("w", encoding="utf-8") as output:
result = subprocess.run(command, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT, check=False, timeout=1740)
content = log.read_text(encoding="utf-8", errors="replace")
match = PIPELINE.search(content)
stamp = re.search(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", content, re.M)
if result.returncode != 0 or match is None:
raise SystemExit(f"timing run failed or summary missing; see {log}")
pipeline = dict(PAIR.findall(match.group(1)))
row = {
"strate": strate,
"pair": pair,
"mode": mode,
"exit_code": result.returncode,
"process_wall_s": round(time.monotonic() - started, 3),
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
"stream_wall_s": float(pipeline["stream_wall"].removesuffix("s")),
"whole_tiles": int(pipeline["whole_tiles"].split("/")[0]),
"source_stamp": stamp.group(1) if stamp else None,
"log": str(log.relative_to(ROOT)),
}
print("[L76TimingPairRun] " + json.dumps(row, sort_keys=True), flush=True)
return row
def main() -> int:
OUT.mkdir(parents=True, exist_ok=True)
missing = [str(path) for path in [DATA, *BINARIES.values()] if not path.is_file()]
if missing:
raise SystemExit("missing input: " + ", ".join(missing))
rows = []
for strate in range(4):
for pair, order in enumerate(ORDERS, 1):
for mode in order:
rows.append(run(strate, pair, mode))
summary = []
for strate in range(4):
group = [row for row in rows if row["strate"] == strate]
before = [row["whole_view_ready_s"] for row in group if row["mode"] == "before"]
after = [row["whole_view_ready_s"] for row in group if row["mode"] == "after"]
summary.append({
"strate": strate,
"pairs": len(before),
"before_s": [round(value, 3) for value in before],
"after_s": [round(value, 3) for value in after],
"before_median_s": round(statistics.median(before), 3),
"after_median_s": round(statistics.median(after), 3),
"change_pct": round(100.0 * (statistics.median(after)
/ statistics.median(before) - 1.0), 2),
})
result = {"runs": rows, "summary": summary}
(OUT / "summary.json").write_text(json.dumps(result, indent=2) + "\n",
encoding="utf-8")
for row in summary:
print("[L76TimingPairSummary] " + json.dumps(row, sort_keys=True), flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -5058,6 +5058,15 @@ namespace StandaloneStreamBenchmark
std::array<std::uint64_t,9> MeshingSkippedTilesByLOD{},DensityGridTilesByLOD{}; std::array<std::uint64_t,9> MeshingSkippedTilesByLOD{},DensityGridTilesByLOD{};
std::array<std::uint64_t,9> DensityPointCallsByLOD{},DensityFieldEvalNanosecondsByLOD{}; std::array<std::uint64_t,9> DensityPointCallsByLOD{},DensityFieldEvalNanosecondsByLOD{};
std::array<std::uint64_t,9> DensityFieldEvalSamplePointsByLOD{}; std::array<std::uint64_t,9> DensityFieldEvalSamplePointsByLOD{};
std::array<std::uint64_t,9> LegacyFieldEvalCallsByLOD{};
std::array<std::array<std::uint64_t,9>,9> LegacyFieldTermCallsByLOD{};
std::array<std::array<std::uint64_t,9>,9> LegacyFieldTermNanosecondsByLOD{};
std::array<std::array<std::uint64_t,9>,9> LegacyFieldTermTimedCallsByLOD{};
std::array<std::uint64_t,9> LegacyPrimitiveCandidateVisitsByLOD{};
std::array<std::uint64_t,9> LegacyPrimitiveDistanceEvaluationsByLOD{};
std::array<std::uint64_t,9> LegacySmoothMinCallsByLOD{};
std::array<std::uint64_t,9> LegacySmoothMinNanosecondsByLOD{};
std::array<std::uint64_t,9> LegacySmoothMinTimedCallsByLOD{};
std::array<std::uint64_t,9> ProofAirCellsByLOD{},ProofSolidCellsByLOD{}; std::array<std::uint64_t,9> ProofAirCellsByLOD{},ProofSolidCellsByLOD{};
std::array<double,9> PayloadBuildMsByLOD{}; std::array<double,9> PayloadBuildMsByLOD{};
std::array<std::uint64_t,9> BaseRouteTilesByLOD{},PageRouteTilesByLOD{}; std::array<std::uint64_t,9> BaseRouteTilesByLOD{},PageRouteTilesByLOD{};
@@ -5128,6 +5137,25 @@ namespace StandaloneStreamBenchmark
TileDiagnostics.densityFieldEvalNanoseconds; TileDiagnostics.densityFieldEvalNanoseconds;
DensityFieldEvalSamplePointsByLOD[LOD]+= DensityFieldEvalSamplePointsByLOD[LOD]+=
TileDiagnostics.densityFieldEvalSamplePoints; TileDiagnostics.densityFieldEvalSamplePoints;
LegacyFieldEvalCallsByLOD[LOD]+=TileDiagnostics.legacyFieldEvalCalls;
for(std::size_t Term=0;Term<TileDiagnostics.legacyFieldTermCalls.size();++Term)
{
LegacyFieldTermCallsByLOD[LOD][Term]+=
TileDiagnostics.legacyFieldTermCalls[Term];
LegacyFieldTermNanosecondsByLOD[LOD][Term]+=
TileDiagnostics.legacyFieldTermNanoseconds[Term];
LegacyFieldTermTimedCallsByLOD[LOD][Term]+=
TileDiagnostics.legacyFieldTermTimedCalls[Term];
}
LegacyPrimitiveCandidateVisitsByLOD[LOD]+=
TileDiagnostics.legacyPrimitiveCandidateVisits;
LegacyPrimitiveDistanceEvaluationsByLOD[LOD]+=
TileDiagnostics.legacyPrimitiveDistanceEvaluations;
LegacySmoothMinCallsByLOD[LOD]+=TileDiagnostics.legacySmoothMinCalls;
LegacySmoothMinNanosecondsByLOD[LOD]+=
TileDiagnostics.legacySmoothMinNanoseconds;
LegacySmoothMinTimedCallsByLOD[LOD]+=
TileDiagnostics.legacySmoothMinTimedCalls;
ProofAirCellsByLOD[LOD]+=TileDiagnostics.marchingCubesProofSkippedAirCells; ProofAirCellsByLOD[LOD]+=TileDiagnostics.marchingCubesProofSkippedAirCells;
ProofSolidCellsByLOD[LOD]+=TileDiagnostics.marchingCubesProofSkippedSolidCells; ProofSolidCellsByLOD[LOD]+=TileDiagnostics.marchingCubesProofSkippedSolidCells;
PayloadBuildMsByLOD[LOD]+=Tile.PayloadBuildMilliseconds; PayloadBuildMsByLOD[LOD]+=Tile.PayloadBuildMilliseconds;
@@ -5603,6 +5631,45 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(ProofAirCellsByLOD[LOD]), static_cast<unsigned long long>(ProofAirCellsByLOD[LOD]),
static_cast<unsigned long long>(ProofSolidCellsByLOD[LOD]), static_cast<unsigned long long>(ProofSolidCellsByLOD[LOD]),
PayloadBuildMsByLOD[LOD]); PayloadBuildMsByLOD[LOD]);
static constexpr std::array<const char*,9> LegacyTermNames={
"primitive_scan","detail","surface_relief_noise","owner_noise_warp",
"composer_cavern","composer_boundary","composer_passage_support",
"composer_stamps","edit_brushes"};
std::uint64_t TermCalls=0;
for(const auto Calls:LegacyFieldTermCallsByLOD[LOD])TermCalls+=Calls;
if(TermCalls>0)
{
std::printf("[WorldAssemblerLODLegacyField] lod=%zu samples=%llu term_calls=%llu primitive_candidates=%llu primitive_distances=%llu smooth_min_calls=%llu smooth_min_samples=%llu smooth_min_ns=%llu smooth_min_ms_est=%.3f\n",
LOD,static_cast<unsigned long long>(LegacyFieldEvalCallsByLOD[LOD]),
static_cast<unsigned long long>(TermCalls),
static_cast<unsigned long long>(LegacyPrimitiveCandidateVisitsByLOD[LOD]),
static_cast<unsigned long long>(LegacyPrimitiveDistanceEvaluationsByLOD[LOD]),
static_cast<unsigned long long>(LegacySmoothMinCallsByLOD[LOD]),
static_cast<unsigned long long>(LegacySmoothMinTimedCallsByLOD[LOD]),
static_cast<unsigned long long>(LegacySmoothMinNanosecondsByLOD[LOD]),
LegacySmoothMinTimedCallsByLOD[LOD]
?static_cast<double>(LegacySmoothMinNanosecondsByLOD[LOD])
*static_cast<double>(LegacySmoothMinCallsByLOD[LOD])
/static_cast<double>(LegacySmoothMinTimedCallsByLOD[LOD])/1.0e6
:0.0);
for(std::size_t Term=0;Term<LegacyTermNames.size();++Term)
{
const std::uint64_t Calls=LegacyFieldTermCallsByLOD[LOD][Term];
const std::uint64_t TimedCalls=LegacyFieldTermTimedCallsByLOD[LOD][Term];
if(Calls==0)continue;
const double EstimatedMilliseconds=TimedCalls
?static_cast<double>(LegacyFieldTermNanosecondsByLOD[LOD][Term])
*static_cast<double>(Calls)
/static_cast<double>(TimedCalls)/1.0e6
:0.0;
std::printf("[WorldAssemblerLODLegacyTerm] lod=%zu term=%s calls=%llu timed_calls=%llu timed_ns=%llu worker_ms_est=%.3f\n",
LOD,LegacyTermNames[Term],
static_cast<unsigned long long>(Calls),
static_cast<unsigned long long>(TimedCalls),
static_cast<unsigned long long>(LegacyFieldTermNanosecondsByLOD[LOD][Term]),
EstimatedMilliseconds);
}
}
} }
if(bReportLodWorkProfile) if(bReportLodWorkProfile)
{ {