Tagged the passage bucket: per-tile prepareSweptTubeBase dominates (0.47-0.92 s summed per view), smooth-min second. Segment lower-bound pruning (-17% pairs) and deferred fallback floor work stayed hash-exact but moved near-ready <3%. Still +17..+26% vs L109. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
558 lines
32 KiB
Diff
558 lines
32 KiB
Diff
diff --git a/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h b/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h
|
|
index cbb0885..3784247 100644
|
|
--- a/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h
|
|
+++ b/Standalone/Core/include/VoxelForgeCore/SharedColumnCache.h
|
|
@@ -2,6 +2,7 @@
|
|
|
|
#include "VoxelForgeCore/CpuCycleCounter.h"
|
|
|
|
+#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
@@ -24,12 +25,15 @@ namespace VoxelForgeCoreShared
|
|
std::uint64_t lockWaitNanoseconds = 0;
|
|
std::uint64_t sampledInitialReadCycles = 0;
|
|
std::uint64_t sampledInitialReads = 0;
|
|
+ std::uint64_t sampledInitialReadNanoseconds = 0;
|
|
std::uint64_t sampledLockedReadCycles = 0;
|
|
std::uint64_t sampledLockedReads = 0;
|
|
+ std::uint64_t sampledLockedReadNanoseconds = 0;
|
|
std::uint64_t sampledLockCycles = 0;
|
|
std::uint64_t sampledLocks = 0;
|
|
std::uint64_t sampledBuildCycles = 0;
|
|
std::uint64_t sampledBuilds = 0;
|
|
+ bool captureNanoseconds = false;
|
|
};
|
|
|
|
// Exact input identity for a pure XY column value. LOD is deliberately not part of this key:
|
|
@@ -140,12 +144,19 @@ namespace VoxelForgeCoreShared
|
|
static thread_local std::uint64_t timingSequence=0;
|
|
const bool sampleCycles=metrics&&((++timingSequence&127u)==0);
|
|
const std::uint64_t initialReadStart=sampleCycles?ReadCpuCycles():0;
|
|
+ const auto initialReadStartNs=sampleCycles&&metrics->captureNanoseconds
|
|
+ ?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
|
|
const bool initialHit=TryReadSet(set, keyWords, tag, value);
|
|
if(sampleCycles)
|
|
{
|
|
metrics->sampledInitialReadCycles+=AdjustedCpuCycleDelta(
|
|
initialReadStart,ReadCpuCycles());
|
|
++metrics->sampledInitialReads;
|
|
+ if(metrics->captureNanoseconds)
|
|
+ metrics->sampledInitialReadNanoseconds+=static_cast<std::uint64_t>(
|
|
+ std::max<std::int64_t>(0,std::chrono::duration_cast<
|
|
+ std::chrono::nanoseconds>(std::chrono::steady_clock::now()
|
|
+ -initialReadStartNs).count()));
|
|
}
|
|
if (initialHit)
|
|
{
|
|
@@ -156,12 +167,19 @@ namespace VoxelForgeCoreShared
|
|
|
|
FSpinGuard lock(set.Writer, metrics,sampleCycles);
|
|
const std::uint64_t lockedReadStart=sampleCycles?ReadCpuCycles():0;
|
|
+ const auto lockedReadStartNs=sampleCycles&&metrics->captureNanoseconds
|
|
+ ?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
|
|
const bool lockedHit=TryReadSet(set, keyWords, tag, value);
|
|
if(sampleCycles)
|
|
{
|
|
metrics->sampledLockedReadCycles+=AdjustedCpuCycleDelta(
|
|
lockedReadStart,ReadCpuCycles());
|
|
++metrics->sampledLockedReads;
|
|
+ if(metrics->captureNanoseconds)
|
|
+ metrics->sampledLockedReadNanoseconds+=static_cast<std::uint64_t>(
|
|
+ std::max<std::int64_t>(0,std::chrono::duration_cast<
|
|
+ std::chrono::nanoseconds>(std::chrono::steady_clock::now()
|
|
+ -lockedReadStartNs).count()));
|
|
}
|
|
if (lockedHit)
|
|
{
|
|
diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
index ac27df1..ad6e8c2 100644
|
|
--- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
+++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
@@ -147,6 +147,7 @@ namespace VoxelForgeCore
|
|
// by the opt-in passage composer profile, except the mesh composition itself.
|
|
std::uint64_t passageSweepRouteSegmentCandidates = 0;
|
|
std::uint64_t passageSweepSegmentSamplePairs = 0;
|
|
+ std::uint64_t passageSweepBoundPrunedPairs = 0;
|
|
std::uint64_t passageSweepOutsideInfluencePairs = 0;
|
|
std::uint64_t passageSweepMinimumUpdates = 0;
|
|
std::uint64_t passageSweepBaseInfluenceSamples = 0;
|
|
@@ -158,6 +159,19 @@ namespace VoxelForgeCore
|
|
std::uint64_t passageSweepBaseInfluenceHits = 0;
|
|
std::uint64_t passageSweepOutOfInfluenceHits = 0;
|
|
std::uint64_t passageSweepMissingSamples = 0;
|
|
+ double passageSweepPrepareMilliseconds = 0.0;
|
|
+ std::uint64_t passageSmoothMinCalls = 0;
|
|
+ std::uint64_t passageSmoothMinSampledNanoseconds = 0;
|
|
+ std::uint64_t passageSmoothMinSamples = 0;
|
|
+ std::uint64_t heightfieldPointSampledNanoseconds = 0;
|
|
+ std::uint64_t heightfieldPointSamples = 0;
|
|
+ std::uint64_t heightfieldCachedColumnSampledNanoseconds = 0;
|
|
+ std::uint64_t heightfieldCachedColumnSamples = 0;
|
|
+ std::uint64_t heightfieldSharedInitialReadSampledNanoseconds = 0;
|
|
+ std::uint64_t heightfieldSharedInitialReadSamples = 0;
|
|
+ std::uint64_t heightfieldSharedLockedReadSampledNanoseconds = 0;
|
|
+ std::uint64_t heightfieldSharedLockedReadSamples = 0;
|
|
+ bool l118FieldTagsCaptured = false;
|
|
std::array<std::uint64_t,12> passageSweepMissingLookupsByCaller{};
|
|
std::array<std::uint64_t,12> passageSweepExactRouteFallbacksByCaller{};
|
|
std::array<std::uint64_t,12> passageSweepFeatureBoundSkipsByCaller{};
|
|
@@ -1394,6 +1408,16 @@ namespace VoxelForgeCore
|
|
std::uint64_t sharedColumnHits = 0;
|
|
std::uint64_t columnEvaluations = 0;
|
|
double columnComputeMilliseconds = 0.0;
|
|
+ // L118 opt-in, one-in-128 sampled timings. Nanoseconds are accumulated only for
|
|
+ // sampled calls; report estimates multiply by 128.
|
|
+ std::uint64_t sampledPointNanoseconds = 0;
|
|
+ std::uint64_t sampledPointCalls = 0;
|
|
+ std::uint64_t sampledCachedColumnNanoseconds = 0;
|
|
+ std::uint64_t sampledCachedColumnCalls = 0;
|
|
+ std::uint64_t sampledSharedInitialReadNanoseconds = 0;
|
|
+ std::uint64_t sampledSharedInitialReads = 0;
|
|
+ std::uint64_t sampledSharedLockedReadNanoseconds = 0;
|
|
+ std::uint64_t sampledSharedLockedReads = 0;
|
|
bool captureCoordinates = false;
|
|
bool coordinateCaptureComplete = true;
|
|
std::vector<HeightfieldColumnEvaluation> evaluatedCoordinates;
|
|
diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h
|
|
index c266bd8..4782980 100644
|
|
--- a/Standalone/Core/src/ShapeWorldInternal.h
|
|
+++ b/Standalone/Core/src/ShapeWorldInternal.h
|
|
@@ -72,6 +72,14 @@ static bool VF_IsL98PassagePrepareTraceEnabled()
|
|
return bEnabled;
|
|
}
|
|
|
|
+static bool VF_IsL118TagTimingEnabled()
|
|
+{
|
|
+ static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled(
|
|
+ "VOXELFORGE_L118_TAG_TIMING");
|
|
+ return bEnabled;
|
|
+}
|
|
+static thread_local std::uint64_t VF_L118SmoothMinTimingSequence=0;
|
|
+
|
|
static double VF_L93PassageCarveReachMetres()
|
|
{
|
|
// The generated world uses a fixed bounded tube influence. An explicit diagnostic
|
|
@@ -289,6 +297,8 @@ public:
|
|
std::vector<FPassageSamples> passages;
|
|
std::atomic<std::uint64_t> lookups{0},cacheHits{0},baseInfluenceHits{0},
|
|
outOfInfluenceHits{0},missingSamples{0};
|
|
+ std::atomic<std::uint64_t> smoothMinCalls{0},smoothMinSampledNanoseconds{0},
|
|
+ smoothMinSamples{0};
|
|
std::array<std::atomic<std::uint64_t>,PassageDensityQueryCallerCount>
|
|
missingLookupsByCaller{},exactRouteFallbacksByCaller{},
|
|
featureBoundSkipsByCaller{},tileFreeSkipsByCaller{},
|
|
@@ -376,6 +386,17 @@ public:
|
|
const FPassageSweepSample* sweptSample(std::size_t passageIndex,
|
|
double qx,double qy,double qz) const;
|
|
bool hasSweptTubeBase() const noexcept{return passageSweepData_!=nullptr;}
|
|
+ void recordSmoothMinTiming(std::uint64_t nanoseconds,bool sampled) const noexcept
|
|
+ {
|
|
+ if(!profileStats_||!passageSweepData_)return;
|
|
+ passageSweepData_->smoothMinCalls.fetch_add(1,std::memory_order_relaxed);
|
|
+ if(sampled)
|
|
+ {
|
|
+ passageSweepData_->smoothMinSampledNanoseconds.fetch_add(
|
|
+ nanoseconds,std::memory_order_relaxed);
|
|
+ passageSweepData_->smoothMinSamples.fetch_add(1,std::memory_order_relaxed);
|
|
+ }
|
|
+ }
|
|
void copySweptTubeRuntimeCounts() const noexcept
|
|
{
|
|
if(!ownsPassageSweepDiagnostics_||!profileStats_||!passageSweepData_)return;
|
|
@@ -389,6 +410,13 @@ public:
|
|
passageSweepData_->outOfInfluenceHits.load(std::memory_order_relaxed);
|
|
profileStats_->passageSweepMissingSamples=passageSweepData_->missingSamples.load(
|
|
std::memory_order_relaxed);
|
|
+ profileStats_->passageSmoothMinCalls=passageSweepData_->smoothMinCalls.load(
|
|
+ std::memory_order_relaxed);
|
|
+ profileStats_->passageSmoothMinSampledNanoseconds=
|
|
+ passageSweepData_->smoothMinSampledNanoseconds.load(
|
|
+ std::memory_order_relaxed);
|
|
+ profileStats_->passageSmoothMinSamples=passageSweepData_->smoothMinSamples.load(
|
|
+ std::memory_order_relaxed);
|
|
for(std::size_t caller=0;caller<PassageDensityQueryCallerCount;++caller)
|
|
{
|
|
profileStats_->passageSweepMissingLookupsByCaller[caller]=
|
|
@@ -1298,12 +1326,19 @@ public:
|
|
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
|
|
const double roundDistance=DeterministicSqrt(
|
|
ex*ex+ey*ey+ez*ez)-radius;
|
|
+ const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
|
|
+ const bool floorWins=floorCenterDistanceSq<bestFloorCenterDistanceSq
|
|
+ ||(floorCenterDistanceSq==bestFloorCenterDistanceSq
|
|
+ &&segmentIndex<nearest.floorSegmentIndex);
|
|
+ const bool roundWins=roundDistance<bestDistance
|
|
+ ||(roundDistance==bestDistance
|
|
+ &&(!nearest.valid||segmentIndex<nearest.segmentIndex));
|
|
+ if(!floorWins&&!roundWins)return;
|
|
const double floorT=segment.horizontalLengthSq>1.0e-12
|
|
?(((qx-segment.a[0])*segment.delta[0]
|
|
+(qy-segment.a[1])*segment.delta[1])
|
|
/segment.horizontalLengthSq):t;
|
|
const double floorTClamped=std::clamp(floorT,0.0,1.0);
|
|
- const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
|
|
const double floorRadius=segment.radiusA
|
|
+floorTClamped*segment.radiusDelta;
|
|
const double floorDepth=PassageFlatFloorDepthSubvoxels(
|
|
@@ -1311,13 +1346,9 @@ public:
|
|
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
|
|
constants,segments,segmentIndex,floorT,segment,
|
|
qx,qy,floorDepth,qPerMetre);
|
|
- const double flatDistance=PassageFlatBottomDistance(
|
|
- static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
|
|
// The profile uses the closest route projection's arc length. Tube
|
|
// radius selects the bore shape only; it never selects floor height.
|
|
- if(floorCenterDistanceSq<bestFloorCenterDistanceSq
|
|
- ||(floorCenterDistanceSq==bestFloorCenterDistanceSq
|
|
- &&segmentIndex<nearest.floorSegmentIndex))
|
|
+ if(floorWins)
|
|
{
|
|
bestFloorCenterDistanceSq=floorCenterDistanceSq;
|
|
nearest.floorCenterZ=floorCenterZ;
|
|
@@ -1329,10 +1360,11 @@ public:
|
|
// minimum of each segment's flattened distance lets a lower, downhill
|
|
// floor plane extend backward through the overlapping tube and makes
|
|
// the floor jump at the prepared-segment spacing.
|
|
- if(roundDistance<bestDistance
|
|
- ||(roundDistance==bestDistance
|
|
- &&(!nearest.valid||segmentIndex<nearest.segmentIndex)))
|
|
+ if(roundWins)
|
|
{
|
|
+ const double flatDistance=PassageFlatBottomDistance(
|
|
+ static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,
|
|
+ qPerMetre);
|
|
bestDistance=roundDistance;
|
|
nearest.signedDistance=flatDistance;
|
|
nearest.roundSignedDistance=roundDistance;
|
|
@@ -6929,12 +6961,31 @@ private:
|
|
static float SmoothMinDistance(float A,float B,float Width)
|
|
{
|
|
FScopedLegacySmoothMinTimer SmoothMinTimer;
|
|
+ const auto* tagScope=VF_IsL118TagTimingEnabled()
|
|
+ ?ActivePassageTileQueries:nullptr;
|
|
+ const bool tag=tagScope&&tagScope->profileStats()
|
|
+ &&tagScope->hasSweptTubeBase();
|
|
+ const bool sampled=tag&&((++VF_L118SmoothMinTimingSequence&127u)==0);
|
|
+ const auto tagStart=sampled?std::chrono::steady_clock::now()
|
|
+ :std::chrono::steady_clock::time_point{};
|
|
+ const auto recordTag=[&]()
|
|
+ {
|
|
+ if(!tag)return;
|
|
+ const std::uint64_t elapsed=sampled?static_cast<std::uint64_t>(std::max<std::int64_t>(0,
|
|
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
+ std::chrono::steady_clock::now()-tagStart).count())):0;
|
|
+ tagScope->recordSmoothMinTiming(elapsed,sampled);
|
|
+ };
|
|
if(Width<=0.0f)
|
|
{
|
|
- return std::min(A,B);
|
|
+ const float result=std::min(A,B);
|
|
+ recordTag();
|
|
+ return result;
|
|
}
|
|
const float H=std::max(Width-std::abs(A-B),0.0f)/Width;
|
|
- return std::min(A,B)-(H*H*H)*(Width/6.0f);
|
|
+ const float result=std::min(A,B)-(H*H*H)*(Width/6.0f);
|
|
+ recordTag();
|
|
+ return result;
|
|
}
|
|
static float SmoothMaxDistance(float A,float B,float Width)
|
|
{return -SmoothMinDistance(-A,-B,Width);}
|
|
@@ -11464,6 +11515,9 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
|
const double wallReliefShell=0.41*qPerMetre;
|
|
const double floorDetailShell=1.0*qPerMetre;
|
|
const bool profile=profileStats_!=nullptr;
|
|
+ const bool tagTiming=profile&&VF_IsL118TagTimingEnabled();
|
|
+ const auto tagStart=tagTiming?std::chrono::steady_clock::now()
|
|
+ :std::chrono::steady_clock::time_point{};
|
|
if(profile)
|
|
{
|
|
profileStats_->passageSweepRouteSegmentCandidates=routeSegmentCandidateCount_;
|
|
@@ -11527,6 +11581,31 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
|
||qy>segment.sweepMaximumSubvoxels[1]
|
|
||qz<segment.sweepMinimumSubvoxels[2]
|
|
||qz>segment.sweepMaximumSubvoxels[2])continue;
|
|
+ const std::size_t sampleIndex=(static_cast<std::size_t>(gz)
|
|
+ *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
|
|
+ *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
|
|
+ auto& sample=passageSamples.samples[sampleIndex];
|
|
+ const double boundDx=std::max({std::min(segment.a[0],segment.b[0])-qx,
|
|
+ 0.0,qx-std::max(segment.a[0],segment.b[0])});
|
|
+ const double boundDy=std::max({std::min(segment.a[1],segment.b[1])-qy,
|
|
+ 0.0,qy-std::max(segment.a[1],segment.b[1])});
|
|
+ const double boundDz=std::max({std::min(segment.a[2],segment.b[2])-qz,
|
|
+ 0.0,qz-std::max(segment.a[2],segment.b[2])});
|
|
+ const double centerDistanceLowerBoundSq=boundDx*boundDx
|
|
+ +boundDy*boundDy+boundDz*boundDz;
|
|
+ const double roundDistanceLowerBound=FStandaloneShapeWorld::DeterministicSqrt(
|
|
+ centerDistanceLowerBoundSq)-std::max(segment.radiusA,segment.radiusB);
|
|
+ const bool couldImproveRound=!sample.valid
|
|
+ ||roundDistanceLowerBound<=sample.roundSignedDistance;
|
|
+ const bool couldImproveFloor=centerDistanceLowerBoundSq
|
|
+ <=sample.floorCenterDistanceSq;
|
|
+ const bool couldContributeShell=roundDistanceLowerBound<=shellThickness;
|
|
+ if(!couldImproveRound&&!couldImproveFloor&&!couldContributeShell)
|
|
+ {
|
|
+ if(profile)++profileStats_->passageSweepBoundPrunedPairs;
|
|
+ continue;
|
|
+ }
|
|
+ if(profile)++profileStats_->passageSweepSegmentSamplePairs;
|
|
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
|
|
+(qy-segment.a[1])*segment.delta[1];
|
|
const double t=std::clamp((projectionXY
|
|
@@ -11552,15 +11631,10 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
|
qx,qy,floorDepth,qPerMetre);
|
|
const double flatDistance=FStandaloneShapeWorld::PassageFlatBottomDistance(
|
|
static_cast<float>(signedDistance),floorCenterZ,qz,floorDepth,qPerMetre);
|
|
- if(profile)++profileStats_->passageSweepSegmentSamplePairs;
|
|
if(signedDistance>sweep->influenceSubvoxels)
|
|
{
|
|
if(profile)++profileStats_->passageSweepOutsideInfluencePairs;
|
|
}
|
|
- const std::size_t sampleIndex=(static_cast<std::size_t>(gz)
|
|
- *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
|
|
- *static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
|
|
- auto& sample=passageSamples.samples[sampleIndex];
|
|
if(floorCenterDistanceSq<sample.floorCenterDistanceSq
|
|
||(floorCenterDistanceSq==sample.floorCenterDistanceSq
|
|
&&segmentIndex<sample.floorSegmentIndex))
|
|
@@ -11687,6 +11761,10 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
|
static_cast<std::uint64_t>(std::count(anyPassageInfluence.begin(),
|
|
anyPassageInfluence.end(),static_cast<std::uint8_t>(0)));
|
|
}
|
|
+ if(tagTiming)
|
|
+ profileStats_->passageSweepPrepareMilliseconds=
|
|
+ std::chrono::duration<double,std::milli>(
|
|
+ std::chrono::steady_clock::now()-tagStart).count();
|
|
passageSweepData_=std::move(sweep);
|
|
}
|
|
|
|
diff --git a/Standalone/Core/src/VoxelForgeCore.cpp b/Standalone/Core/src/VoxelForgeCore.cpp
|
|
index b139ec4..a25049d 100644
|
|
--- a/Standalone/Core/src/VoxelForgeCore.cpp
|
|
+++ b/Standalone/Core/src/VoxelForgeCore.cpp
|
|
@@ -2444,6 +2444,28 @@ namespace VoxelForgeCore
|
|
FStandaloneShapeWorld::SetThreadDensityQueryProfileEnabled(options.instrumentTileDrain);
|
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(
|
|
options.captureColumnCoordinates);
|
|
+ const auto copyL118FieldTags=[&]()
|
|
+ {
|
|
+ if(!options.profilePassageComposer
|
|
+ ||!world_assembly_detail::L118FieldTagTimingEnabled())return;
|
|
+ const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
|
|
+ out.diagnostics.l118FieldTagsCaptured=true;
|
|
+ out.diagnostics.heightfieldPointSampledNanoseconds=
|
|
+ Columns.sampledPointNanoseconds;
|
|
+ out.diagnostics.heightfieldPointSamples=Columns.sampledPointCalls;
|
|
+ out.diagnostics.heightfieldCachedColumnSampledNanoseconds=
|
|
+ Columns.sampledCachedColumnNanoseconds;
|
|
+ out.diagnostics.heightfieldCachedColumnSamples=
|
|
+ Columns.sampledCachedColumnCalls;
|
|
+ out.diagnostics.heightfieldSharedInitialReadSampledNanoseconds=
|
|
+ Columns.sampledSharedInitialReadNanoseconds;
|
|
+ out.diagnostics.heightfieldSharedInitialReadSamples=
|
|
+ Columns.sampledSharedInitialReads;
|
|
+ out.diagnostics.heightfieldSharedLockedReadSampledNanoseconds=
|
|
+ Columns.sampledSharedLockedReadNanoseconds;
|
|
+ out.diagnostics.heightfieldSharedLockedReadSamples=
|
|
+ Columns.sampledSharedLockedReads;
|
|
+ };
|
|
if(options.captureColumnCoordinates)
|
|
{
|
|
constexpr std::int64_t SubvoxelsPerVoxel=WorldAssemblySubvoxelsPerVoxel;
|
|
@@ -2530,6 +2552,7 @@ namespace VoxelForgeCore
|
|
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
|
|
Counts.Milliseconds-classificationColumnComputeMilliseconds);
|
|
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
|
+ copyL118FieldTags();
|
|
#if defined(VF_L30_ENABLE_COST_COUNTERS)
|
|
PrintL30CostCounters(Counts);
|
|
#endif
|
|
@@ -3128,6 +3151,7 @@ namespace VoxelForgeCore
|
|
out.diagnostics.columnEvaluations=Columns.columnEvaluations;
|
|
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
|
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
|
|
+ copyL118FieldTags();
|
|
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
|
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
|
|
Counts.Milliseconds-classificationColumnComputeMilliseconds);
|
|
diff --git a/Standalone/Core/src/WorldAssemblerInternal.h b/Standalone/Core/src/WorldAssemblerInternal.h
|
|
index 07b3c9b..dd7c1d6 100644
|
|
--- a/Standalone/Core/src/WorldAssemblerInternal.h
|
|
+++ b/Standalone/Core/src/WorldAssemblerInternal.h
|
|
@@ -10,6 +10,7 @@
|
|
#include <charconv>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
+#include <cstdlib>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <sstream>
|
|
@@ -1798,11 +1799,49 @@ namespace world_assembly_detail
|
|
inline thread_local VoxelForgeCoreThreadLocal::FLazy<
|
|
std::array<std::uint8_t, HeightfieldColumnCacheSetCount>> HeightfieldColumnCacheNextWay;
|
|
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
|
|
+ inline thread_local VoxelForgeCoreShared::FColumnCacheMetrics
|
|
+ HeightfieldSharedColumnCacheMetrics{};
|
|
+ inline thread_local std::uint64_t HeightfieldPointTimingSequence=0;
|
|
+ inline thread_local std::uint64_t HeightfieldCachedColumnTimingSequence=0;
|
|
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
|
|
inline thread_local std::array<std::int64_t,4> HeightfieldColumnCaptureBounds{};
|
|
inline thread_local bool HeightfieldColumnCaptureBoundsValid=false;
|
|
inline std::atomic<std::uint64_t> HeightfieldColumnCaptureSequence{0};
|
|
|
|
+ inline bool L118FieldTagTimingEnabled() noexcept
|
|
+ {
|
|
+ static const bool enabled=[]
|
|
+ {
|
|
+ const char* value=std::getenv("VOXELFORGE_L118_TAG_TIMING");
|
|
+ return value&&value[0]=='1'&&value[1]=='\0';
|
|
+ }();
|
|
+ return enabled;
|
|
+ }
|
|
+
|
|
+ struct FScopedHeightfieldSampledTimer
|
|
+ {
|
|
+ FScopedHeightfieldSampledTimer(std::uint64_t& nanoseconds,
|
|
+ std::uint64_t& samples,std::uint64_t& sequence) noexcept
|
|
+ :Nanoseconds(nanoseconds),Samples(samples)
|
|
+ {
|
|
+ if(!L118FieldTagTimingEnabled()||((++sequence&127u)!=0))return;
|
|
+ Start=std::chrono::steady_clock::now();
|
|
+ bTimed=true;
|
|
+ }
|
|
+ ~FScopedHeightfieldSampledTimer()
|
|
+ {
|
|
+ if(!bTimed)return;
|
|
+ Nanoseconds+=static_cast<std::uint64_t>(std::max<std::int64_t>(0,
|
|
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
+ std::chrono::steady_clock::now()-Start).count()));
|
|
+ ++Samples;
|
|
+ }
|
|
+ std::uint64_t& Nanoseconds;
|
|
+ std::uint64_t& Samples;
|
|
+ std::chrono::steady_clock::time_point Start{};
|
|
+ bool bTimed=false;
|
|
+ };
|
|
+
|
|
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
|
|
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y) noexcept
|
|
{
|
|
@@ -1989,6 +2028,10 @@ namespace world_assembly_detail
|
|
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
|
|
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
|
|
{
|
|
+ FScopedHeightfieldSampledTimer CachedColumnTimer(
|
|
+ HeightfieldColumnStats.sampledCachedColumnNanoseconds,
|
|
+ HeightfieldColumnStats.sampledCachedColumnCalls,
|
|
+ HeightfieldCachedColumnTimingSequence);
|
|
const std::int64_t InterpolationStride=HeightfieldColumnInterpolationStrideSubvoxels;
|
|
if(InterpolationStride>0)
|
|
{
|
|
@@ -2105,7 +2148,10 @@ namespace world_assembly_detail
|
|
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),
|
|
plan.contentHash,x,y};
|
|
bool computed=false;
|
|
- column=shared->getOrCompute(key,compute,computed);
|
|
+ auto* metrics=L118FieldTagTimingEnabled()
|
|
+ ?&HeightfieldSharedColumnCacheMetrics:nullptr;
|
|
+ if(metrics)metrics->captureNanoseconds=true;
|
|
+ column=shared->getOrCompute(key,compute,computed,metrics);
|
|
if(!computed)++HeightfieldColumnStats.sharedColumnHits;
|
|
}
|
|
else column=compute();
|
|
@@ -2576,6 +2622,10 @@ WorldAssemblyFeatureTileListV1 Heightfield2D::buildFeatureTileList(
|
|
HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
|
std::int64_t z) const noexcept
|
|
{
|
|
+ world_assembly_detail::FScopedHeightfieldSampledTimer PointTimer(
|
|
+ world_assembly_detail::HeightfieldColumnStats.sampledPointNanoseconds,
|
|
+ world_assembly_detail::HeightfieldColumnStats.sampledPointCalls,
|
|
+ world_assembly_detail::HeightfieldPointTimingSequence);
|
|
HeightfieldPointSample out;
|
|
if (!isValid() || world_assembly_detail::OutsideRange(x)
|
|
|| world_assembly_detail::OutsideRange(y)
|
|
@@ -2635,6 +2685,17 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
|
|
Stats.sharedColumnHits=0;
|
|
Stats.columnEvaluations=0;
|
|
Stats.columnComputeMilliseconds=0.0;
|
|
+ Stats.sampledPointNanoseconds=0;
|
|
+ Stats.sampledPointCalls=0;
|
|
+ Stats.sampledCachedColumnNanoseconds=0;
|
|
+ Stats.sampledCachedColumnCalls=0;
|
|
+ Stats.sampledSharedInitialReadNanoseconds=0;
|
|
+ Stats.sampledSharedInitialReads=0;
|
|
+ Stats.sampledSharedLockedReadNanoseconds=0;
|
|
+ Stats.sampledSharedLockedReads=0;
|
|
+ world_assembly_detail::HeightfieldSharedColumnCacheMetrics={};
|
|
+ world_assembly_detail::HeightfieldPointTimingSequence=0;
|
|
+ world_assembly_detail::HeightfieldCachedColumnTimingSequence=0;
|
|
Stats.captureCoordinates=captureCoordinates;
|
|
Stats.coordinateCaptureComplete=true;
|
|
Stats.evaluatedCoordinates.clear();
|
|
@@ -2670,7 +2731,15 @@ std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept
|
|
|
|
|
|
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
|
|
-{ return world_assembly_detail::HeightfieldColumnStats; }
|
|
+{
|
|
+ auto& Stats=world_assembly_detail::HeightfieldColumnStats;
|
|
+ const auto& Shared=world_assembly_detail::HeightfieldSharedColumnCacheMetrics;
|
|
+ Stats.sampledSharedInitialReadNanoseconds=Shared.sampledInitialReadNanoseconds;
|
|
+ Stats.sampledSharedInitialReads=Shared.sampledInitialReads;
|
|
+ Stats.sampledSharedLockedReadNanoseconds=Shared.sampledLockedReadNanoseconds;
|
|
+ Stats.sampledSharedLockedReads=Shared.sampledLockedReads;
|
|
+ return Stats;
|
|
+}
|
|
|
|
HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX,
|
|
std::int64_t minimumY,std::int64_t minimumZ,std::int64_t maximumX,
|
|
diff --git a/Standalone/Source/StreamingWorld.h b/Standalone/Source/StreamingWorld.h
|
|
index 2964e86..231362b 100644
|
|
--- a/Standalone/Source/StreamingWorld.h
|
|
+++ b/Standalone/Source/StreamingWorld.h
|
|
@@ -1580,7 +1580,7 @@ private:
|
|
static_cast<unsigned long long>(D.islandSharedCacheConflictReplacements),
|
|
static_cast<double>(D.islandSharedCacheLockWaitNanoseconds)/1000.0);
|
|
if(D.passageComposerProfiled)
|
|
- std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu carve_segment_visits=%llu relief_field_calls=%llu sweep_segments=%llu sweep_pairs=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
|
|
+ std::printf("[PassageComposerTile] key=(%d,%d,%d,lod%d) worker_ms=%.3f samples=%llu native_field_calls=%llu features=%llu route_candidates=%llu boundary_band_queries=%llu queries=%llu passage_iterations=%llu carve_segment_visits=%llu relief_field_calls=%llu sweep_segments=%llu sweep_pairs=%llu sweep_bound_pruned=%llu sweep_outside_pairs=%llu sweep_min_updates=%llu sweep_base_samples=%llu sweep_detail_samples=%llu sweep_grid_samples=%llu sweep_no_influence_samples=%llu sweep_lookups=%llu sweep_cache_hits=%llu sweep_base_hits=%llu sweep_outside_hits=%llu sweep_missing=%llu\n",
|
|
Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
|
Event.WorkerMilliseconds,static_cast<unsigned long long>(Event.DensitySamples),
|
|
static_cast<unsigned long long>(D.fieldCalls),
|
|
@@ -1593,6 +1593,7 @@ private:
|
|
static_cast<unsigned long long>(D.passageReliefFieldCalls),
|
|
static_cast<unsigned long long>(D.passageSweepRouteSegmentCandidates),
|
|
static_cast<unsigned long long>(D.passageSweepSegmentSamplePairs),
|
|
+ static_cast<unsigned long long>(D.passageSweepBoundPrunedPairs),
|
|
static_cast<unsigned long long>(D.passageSweepOutsideInfluencePairs),
|
|
static_cast<unsigned long long>(D.passageSweepMinimumUpdates),
|
|
static_cast<unsigned long long>(D.passageSweepBaseInfluenceSamples),
|
|
@@ -1604,6 +1605,24 @@ private:
|
|
static_cast<unsigned long long>(D.passageSweepBaseInfluenceHits),
|
|
static_cast<unsigned long long>(D.passageSweepOutOfInfluenceHits),
|
|
static_cast<unsigned long long>(D.passageSweepMissingSamples));
|
|
+ if(D.l118FieldTagsCaptured)
|
|
+ std::printf("[L118FieldTags] key=(%d,%d,%d,lod%d) sweep_prepare_ms=%.3f smoothmin_calls=%llu smoothmin_samples=%llu smoothmin_ms_est=%.3f point_samples=%llu point_ms_est=%.3f cached_column_samples=%llu cached_column_ms_est=%.3f shared_initial_read_samples=%llu shared_initial_read_ms_est=%.3f shared_locked_read_samples=%llu shared_locked_read_ms_est=%.3f sample_period=128 overlap=(point_contains_cached_column,cached_column_contains_shared_reads)\n",
|
|
+ Event.Key.Coord.X,Event.Key.Coord.Y,Event.Key.Coord.Z,Event.Key.Level,
|
|
+ D.passageSweepPrepareMilliseconds,
|
|
+ static_cast<unsigned long long>(D.passageSmoothMinCalls),
|
|
+ static_cast<unsigned long long>(D.passageSmoothMinSamples),
|
|
+ D.passageSmoothMinSamples
|
|
+ ?static_cast<double>(D.passageSmoothMinSampledNanoseconds)*128.0/1.0e6:0.0,
|
|
+ static_cast<unsigned long long>(D.heightfieldPointSamples),
|
|
+ static_cast<double>(D.heightfieldPointSampledNanoseconds)*128.0/1.0e6,
|
|
+ static_cast<unsigned long long>(D.heightfieldCachedColumnSamples),
|
|
+ static_cast<double>(D.heightfieldCachedColumnSampledNanoseconds)*128.0/1.0e6,
|
|
+ static_cast<unsigned long long>(D.heightfieldSharedInitialReadSamples),
|
|
+ static_cast<double>(D.heightfieldSharedInitialReadSampledNanoseconds)
|
|
+ *128.0/1.0e6,
|
|
+ static_cast<unsigned long long>(D.heightfieldSharedLockedReadSamples),
|
|
+ static_cast<double>(D.heightfieldSharedLockedReadSampledNanoseconds)
|
|
+ *128.0/1.0e6);
|
|
}
|
|
void RecordTimelineEvent(FStandaloneCpuTileTimelineEvent::EType Type,
|
|
const FVoxelTileKey& Key,int32 WorkerId,size_t QueueDepth,size_t ActiveWorkers,
|