Files
VoxelForge/Standalone/Research/WorldAssemblerM3/Linux118/l118-designs.patch
T
Fr0zkaandClaude Opus 5.5 6e52fb48b2 research: L118 passage-tile designs (hash-exact, no readiness gain; kept as patch)
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
2026-10-30 04:39:40 +01:00

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,