wip(assembler): round S13 -- tile cost counted per LOD (diagnostics only, no output change)
Exclusive per-stage tile timers and probes. Findings: LOD0 is MC-exclusive (10.4 of 14 ms p95, 243 ns/sample); LOD4's 3x cost is column-cache misses (8,224 vs ~1,600-1,900 at LOD3/5), cap refine secondary; per-tile feature lists cost ~0.001 ms; cross-worker column repeats common (277k at LOD0). Global octave drop and stride-2 interpolation probes fail the 2 cm gate. Worker cap 1 kept (cap 0 worse 40 m/s tick tail). Report: Standalone/Research/WorldAssemblerM3/Round17. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
212814a75c
commit
87b0e5d8cd
9 files changed
+763
-76
No files matched your search
@@ -1756,6 +1756,28 @@ near and `BackgroundNormal` for horizon tasks. The task-pickup changes do not al
|
|||||||
triangulation, collision, or Core output, so no recipe version changes.
|
triangulation, collision, or Core output, so no recipe version changes.
|
||||||
See [`Standalone/Research/WorldAssemblerM3/Round16/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round16/REPORT.md).
|
See [`Standalone/Research/WorldAssemblerM3/Round16/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round16/REPORT.md).
|
||||||
|
|
||||||
|
### 8.34 Tile cost and column reuse audit — World Assembler S13 / M3 Round 17
|
||||||
|
|
||||||
|
The owner SurfaceWorld uses 32 marching-cubes cells only at LOD0–1; LOD2 and farther use 16
|
||||||
|
coarse cells. Including the normal halo, those are 35³ and 19³ density samples, respectively.
|
||||||
|
Round 17 added exclusive timing for classification, column evaluations during MC/ground/cap,
|
||||||
|
non-column MC/refinement work, and mesh assembly. Percentiles are independent. The measured
|
||||||
|
owner route is a planar `Heightfield2D` surface: its recipe noise is evaluated while computing XY
|
||||||
|
columns, with no volumetric cave, tunnel, room, or route density term. The two-tile interpolation
|
||||||
|
probe reduced measured column-compute time by about 71–74% at stride two, but exceeded the 2 cm error gate at
|
||||||
|
LOD1 and every tested coarse LOD; stride four also exceeded it on LOD0. It had no near-zero fallback
|
||||||
|
and was not enabled in generation.
|
||||||
|
|
||||||
|
LOD4 is the first far-cap refinement level: its cap grid is 16 voxels (4 m), while LOD5–7 use the
|
||||||
|
128-voxel boundary grid (32 m); ground refinement begins at LOD5. LOD4's cap pass costs about
|
||||||
|
4.87 ms of column evaluation plus 1.26 ms exclusive at the eight-tile p95, but its largest measured
|
||||||
|
cost is 29.36 ms of column evaluation during MC. LOD4 also has 8,224 column-cache misses per sampled
|
||||||
|
tile p95 versus 1,634 at LOD3 and 1,916 at LOD5. The cache remains thread-local. The all-LOD overlap
|
||||||
|
probe found substantial cross-worker duplicate XY evaluations, which is evidence for a future
|
||||||
|
shared-page A/B test, not proof that a shared cache will pay after synchronization and lifetime
|
||||||
|
costs. Round 17 changed no density, mesh, collision, or recipe output; recipe versions remain
|
||||||
|
unchanged. See [`Standalone/Research/WorldAssemblerM3/Round17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round17/REPORT.md).
|
||||||
|
|
||||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+12
@@ -1083,6 +1083,18 @@ submit/start/end times and the OS worker thread ID. No tile generation, mesh, de
|
|||||||
Core recipe output changes; recipe versions remain unchanged. See ARCHITECTURE §8.33 and
|
Core recipe output changes; recipe versions remain unchanged. See ARCHITECTURE §8.33 and
|
||||||
[`Standalone/Research/WorldAssemblerM3/Round16/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round16/REPORT.md).
|
[`Standalone/Research/WorldAssemblerM3/Round16/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round16/REPORT.md).
|
||||||
|
|
||||||
|
### 3.19 Exclusive tile cost and all-LOD column overlap — World Assembler S13 / M3 Round 17
|
||||||
|
|
||||||
|
`VoxelForgeCore::TileGenerationStats` now exposes classification-exclusive time and the column
|
||||||
|
evaluation performed during marching cubes, ground refinement, and cap refinement. The BuildHost
|
||||||
|
shared-feature audit reports these p95 stages and per-density-sample costs for owner SurfaceWorld
|
||||||
|
tiles at LOD0, 1, 3, 4, 5, and 8. Standalone `--profile-column-overlap` opts into XY miss-coordinate
|
||||||
|
capture for all LODs and reports repeated evaluations across same-LOD tiles and workers; the older
|
||||||
|
`--profile-far-column-overlap` spelling remains an alias. `--profile-column-overlap` changes no
|
||||||
|
generation output. Measurement-only stride and octave probes are confined to the shared-feature
|
||||||
|
audit and are not runtime controls. The result and limits are recorded in
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Round17/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round17/REPORT.md).
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -15940,13 +15940,282 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
const std::vector<VoxelForgeCore::TileKey>& Keys,const TCHAR* Label,
|
const std::vector<VoxelForgeCore::TileKey>& Keys,const TCHAR* Label,
|
||||||
const VoxelForgeCore::WorldAssemblyManifest* Assembly,FString& OutError)
|
const VoxelForgeCore::WorldAssemblyManifest* Assembly,FString& OutError)
|
||||||
{
|
{
|
||||||
for (const int32 LOD : {0,1,7,8})
|
if(Assembly!=nullptr&&FCString::Stricmp(Label,TEXT("surface"))==0)
|
||||||
|
{
|
||||||
|
struct FProbeColumn
|
||||||
|
{
|
||||||
|
std::int64_t Ground=0;
|
||||||
|
std::int64_t Cap=0;
|
||||||
|
};
|
||||||
|
const auto P95=[](TArray<double> Values)
|
||||||
|
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
|
||||||
|
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
|
||||||
|
const auto MaxValue=[](const TArray<double>& Values)
|
||||||
|
{ return Values.IsEmpty() ? 0.0 : *std::max_element(Values.GetData(),Values.GetData()+Values.Num()); };
|
||||||
|
const std::int32_t TileVoxelCount=static_cast<std::int32_t>(
|
||||||
|
World.configuration().tileVoxelCount);
|
||||||
|
constexpr std::int64_t Subvoxels=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
constexpr double CoreVoxelSizeCm=25.0;
|
||||||
|
for(const std::int32_t LOD:{0,1,3,4,5,8})
|
||||||
|
{
|
||||||
|
for(const std::int32_t Stride:{2,4})
|
||||||
|
{
|
||||||
|
const std::int32_t Cells=LOD<World.configuration().fullResolutionLodCount
|
||||||
|
?TileVoxelCount:std::max(1,World.configuration().coarseTileCells);
|
||||||
|
TArray<double> GroundErrorsCm,CapErrorsCm,BaselineColumnMs,CandidateColumnMs;
|
||||||
|
std::uint64_t BaselineColumns=0,CandidateColumns=0,FiniteFeatureTiles=0;
|
||||||
|
std::int32_t Tiles=0;
|
||||||
|
for(const auto& Key:Keys)
|
||||||
|
{
|
||||||
|
if(Key.lod!=LOD||Tiles>=2)continue;
|
||||||
|
const std::int64_t TileExtent=static_cast<std::int64_t>(TileVoxelCount)<<LOD;
|
||||||
|
const std::int64_t TileStep=TileExtent/Cells;
|
||||||
|
const std::int64_t OriginX=static_cast<std::int64_t>(Key.coordinate.x)*TileExtent;
|
||||||
|
const std::int64_t OriginY=static_cast<std::int64_t>(Key.coordinate.y)*TileExtent;
|
||||||
|
const std::int64_t OriginZ=static_cast<std::int64_t>(Key.coordinate.z)*TileExtent;
|
||||||
|
const std::int64_t TileMinimumZ=OriginZ;
|
||||||
|
const std::int64_t TileMaximumZ=OriginZ+TileExtent;
|
||||||
|
const auto PlanIt=std::find_if(Assembly->plan.strates.begin(),
|
||||||
|
Assembly->plan.strates.end(),[TileMinimumZ,TileMaximumZ,TileVoxelCount](const auto& Plan)
|
||||||
|
{
|
||||||
|
const std::int64_t PlanMinimumZ=static_cast<std::int64_t>(
|
||||||
|
std::min(Plan.bottomChunkZ,Plan.topChunkZ))*TileVoxelCount;
|
||||||
|
const std::int64_t PlanMaximumZ=(static_cast<std::int64_t>(
|
||||||
|
std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*TileVoxelCount;
|
||||||
|
return TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ;
|
||||||
|
});
|
||||||
|
if(PlanIt==Assembly->plan.strates.end())continue;
|
||||||
|
|
||||||
|
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,*PlanIt);
|
||||||
|
if(!Field.isValid())continue;
|
||||||
|
const std::array<std::int64_t,3> Minimum={
|
||||||
|
(OriginX-TileStep)*Subvoxels,(OriginY-TileStep)*Subvoxels,
|
||||||
|
(OriginZ-TileStep)*Subvoxels};
|
||||||
|
const std::array<std::int64_t,3> Maximum={
|
||||||
|
(OriginX+TileExtent+TileStep)*Subvoxels,
|
||||||
|
(OriginY+TileExtent+TileStep)*Subvoxels,
|
||||||
|
(OriginZ+TileExtent+TileStep)*Subvoxels};
|
||||||
|
std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> Queries;
|
||||||
|
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Minimum,Maximum)});
|
||||||
|
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 QueryScope{
|
||||||
|
std::span<const VoxelForgeCore::WorldAssemblyFeatureQueryV1>(Queries)};
|
||||||
|
const bool bHasFiniteFeature=std::any_of(PlanIt->features.begin(),
|
||||||
|
PlanIt->features.end(),[&](const auto& Feature)
|
||||||
|
{
|
||||||
|
return (Feature.type==2||Feature.type==4)
|
||||||
|
&&Feature.minimumSubvoxels[0]<=Maximum[0]
|
||||||
|
&&Feature.maximumSubvoxels[0]>=Minimum[0]
|
||||||
|
&&Feature.minimumSubvoxels[1]<=Maximum[1]
|
||||||
|
&&Feature.maximumSubvoxels[1]>=Minimum[1]
|
||||||
|
&&Feature.minimumSubvoxels[2]<=Maximum[2]
|
||||||
|
&&Feature.maximumSubvoxels[2]>=Minimum[2];
|
||||||
|
});
|
||||||
|
if(bHasFiniteFeature)++FiniteFeatureTiles;
|
||||||
|
|
||||||
|
std::vector<FProbeColumn> Exact(static_cast<std::size_t>(Cells+1)*(Cells+1));
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
|
||||||
|
for(std::int32_t Y=0;Y<=Cells;++Y)
|
||||||
|
for(std::int32_t X=0;X<=Cells;++X)
|
||||||
|
{
|
||||||
|
const auto Sample=Field.point((OriginX+X*TileStep)*Subvoxels,
|
||||||
|
(OriginY+Y*TileStep)*Subvoxels,0);
|
||||||
|
Exact[static_cast<std::size_t>(Y)*(Cells+1)+X]={
|
||||||
|
Sample.groundHeightSubvoxels,Sample.capHeightSubvoxels};
|
||||||
|
}
|
||||||
|
const auto& BaselineStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
|
||||||
|
BaselineColumnMs.Add(BaselineStats.columnComputeMilliseconds);
|
||||||
|
BaselineColumns+=BaselineStats.columnMisses;
|
||||||
|
|
||||||
|
std::vector<std::int32_t> Axis;
|
||||||
|
for(std::int32_t Index=0;Index<=Cells;Index+=Stride)Axis.push_back(Index);
|
||||||
|
if(Axis.empty()||Axis.back()!=Cells)Axis.push_back(Cells);
|
||||||
|
std::vector<FProbeColumn> Coarse(static_cast<std::size_t>(Axis.size())*Axis.size());
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
|
||||||
|
for(std::size_t Y=0;Y<Axis.size();++Y)
|
||||||
|
for(std::size_t X=0;X<Axis.size();++X)
|
||||||
|
{
|
||||||
|
const auto Sample=Field.point((OriginX+Axis[X]*TileStep)*Subvoxels,
|
||||||
|
(OriginY+Axis[Y]*TileStep)*Subvoxels,0);
|
||||||
|
Coarse[Y*Axis.size()+X]={Sample.groundHeightSubvoxels,
|
||||||
|
Sample.capHeightSubvoxels};
|
||||||
|
}
|
||||||
|
const auto& CandidateStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
|
||||||
|
CandidateColumnMs.Add(CandidateStats.columnComputeMilliseconds);
|
||||||
|
CandidateColumns+=CandidateStats.columnMisses;
|
||||||
|
const auto Bracket=[&Axis](std::int32_t Value)
|
||||||
|
{
|
||||||
|
const auto Upper=std::upper_bound(Axis.begin(),Axis.end(),Value);
|
||||||
|
const std::size_t High=Upper==Axis.end()?Axis.size()-1
|
||||||
|
:static_cast<std::size_t>(Upper-Axis.begin());
|
||||||
|
const std::size_t Low=High==0?0:High-1;
|
||||||
|
return std::pair<std::size_t,std::size_t>{Low,High};
|
||||||
|
};
|
||||||
|
const auto Lerp=[](double A,double B,double T){return A+(B-A)*T;};
|
||||||
|
for(std::int32_t Y=0;Y<=Cells;++Y)
|
||||||
|
for(std::int32_t X=0;X<=Cells;++X)
|
||||||
|
{
|
||||||
|
const auto [X0,X1]=Bracket(X);
|
||||||
|
const auto [Y0,Y1]=Bracket(Y);
|
||||||
|
const double TX=Axis[X1]==Axis[X0]?0.0
|
||||||
|
:static_cast<double>(X-Axis[X0])/(Axis[X1]-Axis[X0]);
|
||||||
|
const double TY=Axis[Y1]==Axis[Y0]?0.0
|
||||||
|
:static_cast<double>(Y-Axis[Y0])/(Axis[Y1]-Axis[Y0]);
|
||||||
|
const auto& C00=Coarse[Y0*Axis.size()+X0];
|
||||||
|
const auto& C10=Coarse[Y0*Axis.size()+X1];
|
||||||
|
const auto& C01=Coarse[Y1*Axis.size()+X0];
|
||||||
|
const auto& C11=Coarse[Y1*Axis.size()+X1];
|
||||||
|
const double Ground=Lerp(Lerp(static_cast<double>(C00.Ground),
|
||||||
|
static_cast<double>(C10.Ground),TX),Lerp(static_cast<double>(C01.Ground),
|
||||||
|
static_cast<double>(C11.Ground),TX),TY);
|
||||||
|
const double Cap=Lerp(Lerp(static_cast<double>(C00.Cap),
|
||||||
|
static_cast<double>(C10.Cap),TX),Lerp(static_cast<double>(C01.Cap),
|
||||||
|
static_cast<double>(C11.Cap),TX),TY);
|
||||||
|
const auto& Reference=Exact[static_cast<std::size_t>(Y)*(Cells+1)+X];
|
||||||
|
GroundErrorsCm.Add(std::abs(Ground-Reference.Ground)*CoreVoxelSizeCm/Subvoxels);
|
||||||
|
CapErrorsCm.Add(std::abs(Cap-Reference.Cap)*CoreVoxelSizeCm/Subvoxels);
|
||||||
|
}
|
||||||
|
++Tiles;
|
||||||
|
}
|
||||||
|
std::uint64_t GroundOver2Cm=0,CapOver2Cm=0;
|
||||||
|
for(const double Error:GroundErrorsCm)if(Error>2.0)++GroundOver2Cm;
|
||||||
|
for(const double Error:CapErrorsCm)if(Error>2.0)++CapOver2Cm;
|
||||||
|
double BaselineAverageMs=0.0,CandidateAverageMs=0.0;
|
||||||
|
for(const double Value:BaselineColumnMs)BaselineAverageMs+=Value;
|
||||||
|
for(const double Value:CandidateColumnMs)CandidateAverageMs+=Value;
|
||||||
|
if(!BaselineColumnMs.IsEmpty())BaselineAverageMs/=BaselineColumnMs.Num();
|
||||||
|
if(!CandidateColumnMs.IsEmpty())CandidateAverageMs/=CandidateColumnMs.Num();
|
||||||
|
UE_LOG(LogTemp,Display,
|
||||||
|
TEXT("[WorldAssemblerInterpolationProbe] label=%s lod=%d tiles=%d stride_samples=%d exact_column_evals=%llu coarse_column_evals=%llu column_eval_saving_pct=%.2f baseline_column_avg_ms=%.5f coarse_column_avg_ms=%.5f ground_error_p95_cm=%.4f ground_error_max_cm=%.4f ground_over_2cm=%llu/%d cap_error_p95_cm=%.4f cap_error_max_cm=%.4f cap_over_2cm=%llu/%d finite_feature_tiles=%llu fallback=none"),
|
||||||
|
TEXT("surface"),LOD,Tiles,Stride,
|
||||||
|
static_cast<unsigned long long>(BaselineColumns),
|
||||||
|
static_cast<unsigned long long>(CandidateColumns),
|
||||||
|
BaselineAverageMs>0.0?100.0*(1.0-CandidateAverageMs/BaselineAverageMs):0.0,
|
||||||
|
BaselineAverageMs,CandidateAverageMs,
|
||||||
|
P95(GroundErrorsCm),MaxValue(GroundErrorsCm),
|
||||||
|
static_cast<unsigned long long>(GroundOver2Cm),GroundErrorsCm.Num(),
|
||||||
|
P95(CapErrorsCm),MaxValue(CapErrorsCm),
|
||||||
|
static_cast<unsigned long long>(CapOver2Cm),CapErrorsCm.Num(),
|
||||||
|
static_cast<unsigned long long>(FiniteFeatureTiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(const std::int32_t LOD:{0,1,3,4,5,8})
|
||||||
|
for(const std::uint32_t Drop:{1u,2u})
|
||||||
|
{
|
||||||
|
const std::int32_t Cells=LOD<World.configuration().fullResolutionLodCount
|
||||||
|
?TileVoxelCount:std::max(1,World.configuration().coarseTileCells);
|
||||||
|
TArray<double> GroundErrorsCm,CapErrorsCm,BaselineColumnMs,CandidateColumnMs;
|
||||||
|
std::uint64_t BaselineColumns=0,CandidateColumns=0,FiniteFeatureTiles=0;
|
||||||
|
std::int32_t Tiles=0;
|
||||||
|
for(const auto& Key:Keys)
|
||||||
|
{
|
||||||
|
if(Key.lod!=LOD||Tiles>=2)continue;
|
||||||
|
const std::int64_t TileExtent=static_cast<std::int64_t>(TileVoxelCount)<<LOD;
|
||||||
|
const std::int64_t TileStep=TileExtent/Cells;
|
||||||
|
const std::int64_t OriginX=static_cast<std::int64_t>(Key.coordinate.x)*TileExtent;
|
||||||
|
const std::int64_t OriginY=static_cast<std::int64_t>(Key.coordinate.y)*TileExtent;
|
||||||
|
const std::int64_t OriginZ=static_cast<std::int64_t>(Key.coordinate.z)*TileExtent;
|
||||||
|
const std::int64_t TileMinimumZ=OriginZ,TileMaximumZ=OriginZ+TileExtent;
|
||||||
|
const auto PlanIt=std::find_if(Assembly->plan.strates.begin(),
|
||||||
|
Assembly->plan.strates.end(),[TileMinimumZ,TileMaximumZ,TileVoxelCount](const auto& Plan)
|
||||||
|
{
|
||||||
|
const std::int64_t PlanMinimumZ=static_cast<std::int64_t>(
|
||||||
|
std::min(Plan.bottomChunkZ,Plan.topChunkZ))*TileVoxelCount;
|
||||||
|
const std::int64_t PlanMaximumZ=(static_cast<std::int64_t>(
|
||||||
|
std::max(Plan.bottomChunkZ,Plan.topChunkZ))+1)*TileVoxelCount;
|
||||||
|
return TileMinimumZ<=PlanMaximumZ&&TileMaximumZ>=PlanMinimumZ;
|
||||||
|
});
|
||||||
|
if(PlanIt==Assembly->plan.strates.end())continue;
|
||||||
|
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,*PlanIt);
|
||||||
|
if(!Field.isValid())continue;
|
||||||
|
const std::array<std::int64_t,3> Minimum={
|
||||||
|
(OriginX-TileStep)*Subvoxels,(OriginY-TileStep)*Subvoxels,
|
||||||
|
(OriginZ-TileStep)*Subvoxels};
|
||||||
|
const std::array<std::int64_t,3> Maximum={
|
||||||
|
(OriginX+TileExtent+TileStep)*Subvoxels,
|
||||||
|
(OriginY+TileExtent+TileStep)*Subvoxels,
|
||||||
|
(OriginZ+TileExtent+TileStep)*Subvoxels};
|
||||||
|
std::vector<VoxelForgeCore::WorldAssemblyFeatureQueryV1> Queries;
|
||||||
|
Queries.push_back({Field.recipe(),Field.plan(),Field.buildFeatureTileList(Minimum,Maximum)});
|
||||||
|
const VoxelForgeCore::ScopedWorldAssemblyFeatureQueriesV1 QueryScope{
|
||||||
|
std::span<const VoxelForgeCore::WorldAssemblyFeatureQueryV1>(Queries)};
|
||||||
|
const bool bHasFiniteFeature=std::any_of(PlanIt->features.begin(),
|
||||||
|
PlanIt->features.end(),[&](const auto& Feature)
|
||||||
|
{
|
||||||
|
return (Feature.type==2||Feature.type==4)
|
||||||
|
&&Feature.minimumSubvoxels[0]<=Maximum[0]
|
||||||
|
&&Feature.maximumSubvoxels[0]>=Minimum[0]
|
||||||
|
&&Feature.minimumSubvoxels[1]<=Maximum[1]
|
||||||
|
&&Feature.maximumSubvoxels[1]>=Minimum[1]
|
||||||
|
&&Feature.minimumSubvoxels[2]<=Maximum[2]
|
||||||
|
&&Feature.maximumSubvoxels[2]>=Minimum[2];
|
||||||
|
});
|
||||||
|
if(bHasFiniteFeature)++FiniteFeatureTiles;
|
||||||
|
std::vector<FProbeColumn> Exact(static_cast<std::size_t>(Cells+1)*(Cells+1));
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
|
||||||
|
for(std::int32_t Y=0;Y<=Cells;++Y)
|
||||||
|
for(std::int32_t X=0;X<=Cells;++X)
|
||||||
|
{
|
||||||
|
const auto Sample=Field.point((OriginX+X*TileStep)*Subvoxels,
|
||||||
|
(OriginY+Y*TileStep)*Subvoxels,0);
|
||||||
|
Exact[static_cast<std::size_t>(Y)*(Cells+1)+X]={
|
||||||
|
Sample.groundHeightSubvoxels,Sample.capHeightSubvoxels};
|
||||||
|
}
|
||||||
|
const auto& BaselineStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
|
||||||
|
BaselineColumnMs.Add(BaselineStats.columnComputeMilliseconds);
|
||||||
|
BaselineColumns+=BaselineStats.columnMisses;
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,Drop);
|
||||||
|
for(std::int32_t Y=0;Y<=Cells;++Y)
|
||||||
|
for(std::int32_t X=0;X<=Cells;++X)
|
||||||
|
{
|
||||||
|
const auto Sample=Field.point((OriginX+X*TileStep)*Subvoxels,
|
||||||
|
(OriginY+Y*TileStep)*Subvoxels,0);
|
||||||
|
GroundErrorsCm.Add(std::abs(static_cast<double>(Sample.groundHeightSubvoxels)
|
||||||
|
-Exact[static_cast<std::size_t>(Y)*(Cells+1)+X].Ground)
|
||||||
|
*CoreVoxelSizeCm/Subvoxels);
|
||||||
|
CapErrorsCm.Add(std::abs(static_cast<double>(Sample.capHeightSubvoxels)
|
||||||
|
-Exact[static_cast<std::size_t>(Y)*(Cells+1)+X].Cap)
|
||||||
|
*CoreVoxelSizeCm/Subvoxels);
|
||||||
|
}
|
||||||
|
const auto& CandidateStats=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
|
||||||
|
CandidateColumnMs.Add(CandidateStats.columnComputeMilliseconds);
|
||||||
|
CandidateColumns+=CandidateStats.columnMisses;
|
||||||
|
++Tiles;
|
||||||
|
}
|
||||||
|
std::uint64_t GroundOver2Cm=0,CapOver2Cm=0;
|
||||||
|
for(const double Error:GroundErrorsCm)if(Error>2.0)++GroundOver2Cm;
|
||||||
|
for(const double Error:CapErrorsCm)if(Error>2.0)++CapOver2Cm;
|
||||||
|
double BaselineAverageMs=0.0,CandidateAverageMs=0.0;
|
||||||
|
for(const double Value:BaselineColumnMs)BaselineAverageMs+=Value;
|
||||||
|
for(const double Value:CandidateColumnMs)CandidateAverageMs+=Value;
|
||||||
|
if(!BaselineColumnMs.IsEmpty())BaselineAverageMs/=BaselineColumnMs.Num();
|
||||||
|
if(!CandidateColumnMs.IsEmpty())CandidateAverageMs/=CandidateColumnMs.Num();
|
||||||
|
UE_LOG(LogTemp,Display,
|
||||||
|
TEXT("[WorldAssemblerFrequencyProbe] label=surface lod=%d tiles=%d drop_highest_octaves=%d exact_column_evals=%llu reduced_column_evals=%llu column_eval_saving_pct=%.2f baseline_column_avg_ms=%.5f reduced_column_avg_ms=%.5f ground_error_p95_cm=%.4f ground_error_max_cm=%.4f ground_over_2cm=%llu/%d cap_error_p95_cm=%.4f cap_error_max_cm=%.4f cap_over_2cm=%llu/%d finite_feature_tiles=%llu"),
|
||||||
|
LOD,Tiles,static_cast<std::int32_t>(Drop),
|
||||||
|
static_cast<unsigned long long>(BaselineColumns),
|
||||||
|
static_cast<unsigned long long>(CandidateColumns),
|
||||||
|
BaselineAverageMs>0.0?100.0*(1.0-CandidateAverageMs/BaselineAverageMs):0.0,
|
||||||
|
BaselineAverageMs,CandidateAverageMs,
|
||||||
|
P95(GroundErrorsCm),MaxValue(GroundErrorsCm),
|
||||||
|
static_cast<unsigned long long>(GroundOver2Cm),GroundErrorsCm.Num(),
|
||||||
|
P95(CapErrorsCm),MaxValue(CapErrorsCm),
|
||||||
|
static_cast<unsigned long long>(CapOver2Cm),CapErrorsCm.Num(),
|
||||||
|
static_cast<unsigned long long>(FiniteFeatureTiles));
|
||||||
|
}
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
|
||||||
|
}
|
||||||
|
for (const int32 LOD : {0,1,3,4,5,8})
|
||||||
{
|
{
|
||||||
TArray<double> FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes;
|
TArray<double> FullTimes,CandidateTimes,FullShellTimes,CandidateShellTimes,ListTimes;
|
||||||
TArray<double> UnknownBoxes,FieldCalls,ColumnMisses,StratumShellTimes,ConnectorShellTimes;
|
TArray<double> UnknownBoxes,FieldCalls,ColumnMisses,StratumShellTimes,ConnectorShellTimes;
|
||||||
TArray<double> ClassificationTimes,ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped;
|
TArray<double> DensitySamples,ClassificationTimes,ClassificationExclusiveTimes;
|
||||||
|
TArray<double> ColumnComputeTimes,MeshingTimes,HorizonTimes,RepairSkipped;
|
||||||
TArray<double> ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes;
|
TArray<double> ColumnEvalExclusiveTimes,MarchingExclusiveTimes,GroundRefineExclusiveTimes;
|
||||||
TArray<double> CapRefineExclusiveTimes,MeshAssemblyTimes;
|
TArray<double> CapRefineExclusiveTimes,MeshAssemblyTimes;
|
||||||
|
TArray<double> MarchingColumnTimes,GroundColumnTimes,CapColumnTimes;
|
||||||
|
TArray<double> ClassificationNsPerSample,ColumnNsPerSample,MarchingNsPerSample;
|
||||||
|
TArray<double> GroundNsPerSample,CapNsPerSample,AssemblyNsPerSample,ListNsPerSample;
|
||||||
|
TArray<double> MarchingColumnNsPerSample,GroundColumnNsPerSample,CapColumnNsPerSample;
|
||||||
TArray<double> StratumFieldCalls,ConnectorFieldCalls,ShellTriangles,GroundTriangles,GroundRemoved,CapTriangles;
|
TArray<double> StratumFieldCalls,ConnectorFieldCalls,ShellTriangles,GroundTriangles,GroundRemoved,CapTriangles;
|
||||||
for (const auto& Key : Keys)
|
for (const auto& Key : Keys)
|
||||||
{
|
{
|
||||||
@@ -15955,57 +16224,98 @@ bool RunCoreSharedFeatureCostAudit(VoxelForgeCore::World& World,
|
|||||||
Options.hashDensity=true;
|
Options.hashDensity=true;
|
||||||
Options.useAssemblyFeatureQueries=false;
|
Options.useAssemblyFeatureQueries=false;
|
||||||
std::string Error;
|
std::string Error;
|
||||||
// Warm both routes before measuring: column/page caches must not favor the second route.
|
// Check density and mesh parity with hashes, then time uncached runtime generation.
|
||||||
// Chauffer les deux parcours : les caches ne doivent pas favoriser le second.
|
// Verifier la parite avec hashes, puis mesurer la generation sans cache de colonnes.
|
||||||
if (!World.generateTile(Key,Options,Error)) return false;
|
const auto FullParity=World.generateTile(Key,Options,Error);
|
||||||
|
if (!FullParity) return false;
|
||||||
Options.useAssemblyFeatureQueries=true;
|
Options.useAssemblyFeatureQueries=true;
|
||||||
if (!World.generateTile(Key,Options,Error)) return false;
|
const auto CandidateParity=World.generateTile(Key,Options,Error);
|
||||||
|
if (!CandidateParity||FullParity->densityHash!=CandidateParity->densityHash
|
||||||
|
||FullParity->meshHash!=CandidateParity->meshHash
|
||||||
|
||FullParity->payloadHash!=CandidateParity->payloadHash
|
||||||
|
||FullParity->topologyHash!=CandidateParity->topologyHash)
|
||||||
|
{ OutError=TEXT("Shared feature full/candidate tile hashes differ."); return false; }
|
||||||
|
Options.hashDensity=false;
|
||||||
Options.useAssemblyFeatureQueries=false;
|
Options.useAssemblyFeatureQueries=false;
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
|
||||||
const double FullStart=FPlatformTime::Seconds();
|
const double FullStart=FPlatformTime::Seconds();
|
||||||
auto Full=World.generateTile(Key,Options,Error);
|
auto Full=World.generateTile(Key,Options,Error);
|
||||||
FullTimes.Add((FPlatformTime::Seconds()-FullStart)*1000.0);
|
FullTimes.Add((FPlatformTime::Seconds()-FullStart)*1000.0);
|
||||||
Options.useAssemblyFeatureQueries=true;
|
Options.useAssemblyFeatureQueries=true;
|
||||||
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true);
|
||||||
const double CandidateStart=FPlatformTime::Seconds();
|
const double CandidateStart=FPlatformTime::Seconds();
|
||||||
auto Candidate=World.generateTile(Key,Options,Error);
|
auto Candidate=World.generateTile(Key,Options,Error);
|
||||||
CandidateTimes.Add((FPlatformTime::Seconds()-CandidateStart)*1000.0);
|
CandidateTimes.Add((FPlatformTime::Seconds()-CandidateStart)*1000.0);
|
||||||
if (!Full||!Candidate||Full->densityHash!=Candidate->densityHash
|
if (!Full||!Candidate)
|
||||||
||Full->meshHash!=Candidate->meshHash||Full->payloadHash!=Candidate->payloadHash
|
{ OutError=TEXT("Shared feature full/candidate measured generation failed."); return false; }
|
||||||
||Full->topologyHash!=Candidate->topologyHash)
|
|
||||||
{ OutError=TEXT("Shared feature full/candidate tile hashes differ."); return false; }
|
|
||||||
FullShellTimes.Add(Full->diagnostics.farShellMilliseconds);
|
FullShellTimes.Add(Full->diagnostics.farShellMilliseconds);
|
||||||
CandidateShellTimes.Add(Candidate->diagnostics.farShellMilliseconds);
|
CandidateShellTimes.Add(Candidate->diagnostics.farShellMilliseconds);
|
||||||
ListTimes.Add(Candidate->diagnostics.featureListMilliseconds);
|
ListTimes.Add(Candidate->diagnostics.featureListMilliseconds);
|
||||||
UnknownBoxes.Add(static_cast<double>(Full->diagnostics.unknownBoxes));
|
const auto& Diagnostics=Candidate->diagnostics;
|
||||||
FieldCalls.Add(static_cast<double>(Full->diagnostics.fieldCalls));
|
const double SampleCount=static_cast<double>(Candidate->densitySampleCount);
|
||||||
ColumnMisses.Add(static_cast<double>(Full->diagnostics.columnCacheMisses));
|
DensitySamples.Add(static_cast<double>(Candidate->densitySampleCount));
|
||||||
ClassificationTimes.Add(Full->diagnostics.classificationMilliseconds);
|
UnknownBoxes.Add(static_cast<double>(Diagnostics.unknownBoxes));
|
||||||
ColumnComputeTimes.Add(Full->diagnostics.columnComputeMilliseconds);
|
FieldCalls.Add(static_cast<double>(Diagnostics.fieldCalls));
|
||||||
ColumnEvalExclusiveTimes.Add(Full->diagnostics.columnEvaluationExclusiveMilliseconds);
|
ColumnMisses.Add(static_cast<double>(Diagnostics.columnCacheMisses));
|
||||||
MarchingExclusiveTimes.Add(Full->diagnostics.marchingCubesExclusiveMilliseconds);
|
ClassificationTimes.Add(Diagnostics.classificationMilliseconds);
|
||||||
GroundRefineExclusiveTimes.Add(Full->diagnostics.groundRefinementExclusiveMilliseconds);
|
ClassificationExclusiveTimes.Add(Diagnostics.classificationExclusiveMilliseconds);
|
||||||
CapRefineExclusiveTimes.Add(Full->diagnostics.capRefinementExclusiveMilliseconds);
|
ColumnComputeTimes.Add(Diagnostics.columnComputeMilliseconds);
|
||||||
MeshAssemblyTimes.Add(Full->diagnostics.meshAssemblyMilliseconds);
|
ColumnEvalExclusiveTimes.Add(Diagnostics.columnEvaluationExclusiveMilliseconds);
|
||||||
MeshingTimes.Add(Full->diagnostics.meshingMilliseconds);
|
MarchingExclusiveTimes.Add(Diagnostics.marchingCubesExclusiveMilliseconds);
|
||||||
HorizonTimes.Add(Full->diagnostics.farHorizonMilliseconds);
|
MarchingColumnTimes.Add(Diagnostics.marchingCubesColumnComputeMilliseconds);
|
||||||
RepairSkipped.Add(Full->diagnostics.farShellRepairSkipped?1.0:0.0);
|
GroundRefineExclusiveTimes.Add(Diagnostics.groundRefinementExclusiveMilliseconds);
|
||||||
StratumShellTimes.Add(Full->diagnostics.farStratumShellMilliseconds);
|
GroundColumnTimes.Add(Diagnostics.groundRefinementColumnComputeMilliseconds);
|
||||||
ConnectorShellTimes.Add(Full->diagnostics.farConnectorShellMilliseconds);
|
CapRefineExclusiveTimes.Add(Diagnostics.capRefinementExclusiveMilliseconds);
|
||||||
StratumFieldCalls.Add(static_cast<double>(Full->diagnostics.farStratumFieldCalls));
|
CapColumnTimes.Add(Diagnostics.capRefinementColumnComputeMilliseconds);
|
||||||
ConnectorFieldCalls.Add(static_cast<double>(Full->diagnostics.farConnectorFieldCalls));
|
MeshAssemblyTimes.Add(Diagnostics.meshAssemblyMilliseconds);
|
||||||
ShellTriangles.Add(static_cast<double>(Full->diagnostics.farShellTrianglesAdded));
|
MeshingTimes.Add(Diagnostics.meshingMilliseconds);
|
||||||
GroundTriangles.Add(static_cast<double>(Full->diagnostics.horizonGroundTriangles));
|
HorizonTimes.Add(Diagnostics.farHorizonMilliseconds);
|
||||||
GroundRemoved.Add(static_cast<double>(Full->diagnostics.horizonGroundTrianglesRemoved));
|
RepairSkipped.Add(Diagnostics.farShellRepairSkipped?1.0:0.0);
|
||||||
CapTriangles.Add(static_cast<double>(Full->diagnostics.horizonCapTriangles));
|
StratumShellTimes.Add(Diagnostics.farStratumShellMilliseconds);
|
||||||
|
ConnectorShellTimes.Add(Diagnostics.farConnectorShellMilliseconds);
|
||||||
|
StratumFieldCalls.Add(static_cast<double>(Diagnostics.farStratumFieldCalls));
|
||||||
|
ConnectorFieldCalls.Add(static_cast<double>(Diagnostics.farConnectorFieldCalls));
|
||||||
|
ShellTriangles.Add(static_cast<double>(Diagnostics.farShellTrianglesAdded));
|
||||||
|
GroundTriangles.Add(static_cast<double>(Diagnostics.horizonGroundTriangles));
|
||||||
|
GroundRemoved.Add(static_cast<double>(Diagnostics.horizonGroundTrianglesRemoved));
|
||||||
|
CapTriangles.Add(static_cast<double>(Diagnostics.horizonCapTriangles));
|
||||||
|
if(SampleCount>0.0)
|
||||||
|
{
|
||||||
|
ClassificationNsPerSample.Add(Diagnostics.classificationExclusiveMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
ColumnNsPerSample.Add(Diagnostics.columnEvaluationExclusiveMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
MarchingColumnNsPerSample.Add(Diagnostics.marchingCubesColumnComputeMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
MarchingNsPerSample.Add(Diagnostics.marchingCubesExclusiveMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
GroundColumnNsPerSample.Add(Diagnostics.groundRefinementColumnComputeMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
GroundNsPerSample.Add(Diagnostics.groundRefinementExclusiveMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
CapColumnNsPerSample.Add(Diagnostics.capRefinementColumnComputeMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
CapNsPerSample.Add(Diagnostics.capRefinementExclusiveMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
AssemblyNsPerSample.Add(Diagnostics.meshAssemblyMilliseconds
|
||||||
|
*1000000.0/SampleCount);
|
||||||
|
ListNsPerSample.Add(Diagnostics.featureListMilliseconds*1000000.0/SampleCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const auto P95=[](TArray<double> Values)
|
const auto P95=[](TArray<double> Values)
|
||||||
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
|
{ Values.Sort(); return Values.IsEmpty() ? 0.0 : Values[FMath::Clamp(
|
||||||
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
|
FMath::CeilToInt(Values.Num()*0.95f)-1,0,Values.Num()-1)]; };
|
||||||
UE_LOG(LogTemp,Display,
|
UE_LOG(LogTemp,Display,
|
||||||
TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_p95_ms=%.5f column_compute_p95_ms=%.5f column_eval_exclusive_p95_ms=%.5f mc_exclusive_p95_ms=%.5f ground_refine_exclusive_p95_ms=%.5f cap_refine_exclusive_p95_ms=%.5f mesh_assembly_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f column_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"),
|
TEXT("[VoxelForgeSharedFeatureCost] label=%s lod=%d tiles=%d hashes_identical=1 full_p95_ms=%.5f candidate_p95_ms=%.5f list_p95_ms=%.5f classification_wall_p95_ms=%.5f classification_exclusive_p95_ms=%.5f column_compute_p95_ms=%.5f column_eval_exclusive_p95_ms=%.5f mc_column_compute_p95_ms=%.5f mc_exclusive_p95_ms=%.5f ground_column_compute_p95_ms=%.5f ground_refine_exclusive_p95_ms=%.5f cap_column_compute_p95_ms=%.5f cap_refine_exclusive_p95_ms=%.5f mesh_assembly_p95_ms=%.5f meshing_p95_ms=%.5f horizon_p95_ms=%.5f samples_p95=%.0f classification_ns_per_sample_p95=%.2f column_ns_per_sample_p95=%.2f mc_column_ns_per_sample_p95=%.2f mc_ns_per_sample_p95=%.2f ground_column_ns_per_sample_p95=%.2f ground_ns_per_sample_p95=%.2f cap_column_ns_per_sample_p95=%.2f cap_ns_per_sample_p95=%.2f assembly_ns_per_sample_p95=%.2f feature_list_ns_per_sample_p95=%.2f full_shell_p95_ms=%.5f candidate_shell_p95_ms=%.5f repair_skipped_p95=%.0f unknown_boxes_p95=%.0f field_calls_p95=%.0f column_misses_p95=%.0f stratum_shell_p95_ms=%.5f stratum_field_calls_p95=%.0f connector_shell_p95_ms=%.5f connector_field_calls_p95=%.0f shell_triangles_p95=%.0f horizon_ground_triangles_p95=%.0f horizon_ground_removed_p95=%.0f horizon_cap_triangles_p95=%.0f"),
|
||||||
Label,LOD,FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes),
|
Label,LOD,FullTimes.Num(),P95(FullTimes),P95(CandidateTimes),P95(ListTimes),
|
||||||
P95(ClassificationTimes),P95(ColumnComputeTimes),P95(ColumnEvalExclusiveTimes),
|
P95(ClassificationTimes),P95(ClassificationExclusiveTimes),P95(ColumnComputeTimes),
|
||||||
P95(MarchingExclusiveTimes),P95(GroundRefineExclusiveTimes),P95(CapRefineExclusiveTimes),
|
P95(ColumnEvalExclusiveTimes),P95(MarchingColumnTimes),P95(MarchingExclusiveTimes),
|
||||||
P95(MeshAssemblyTimes),P95(MeshingTimes),P95(HorizonTimes),
|
P95(GroundColumnTimes),P95(GroundRefineExclusiveTimes),P95(CapColumnTimes),
|
||||||
|
P95(CapRefineExclusiveTimes),
|
||||||
|
P95(MeshAssemblyTimes),P95(MeshingTimes),P95(HorizonTimes),P95(DensitySamples),
|
||||||
|
P95(ClassificationNsPerSample),P95(ColumnNsPerSample),P95(MarchingColumnNsPerSample),
|
||||||
|
P95(MarchingNsPerSample),P95(GroundColumnNsPerSample),P95(GroundNsPerSample),
|
||||||
|
P95(CapColumnNsPerSample),P95(CapNsPerSample),P95(AssemblyNsPerSample),P95(ListNsPerSample),
|
||||||
P95(FullShellTimes),P95(CandidateShellTimes),P95(RepairSkipped),P95(UnknownBoxes),P95(FieldCalls),
|
P95(FullShellTimes),P95(CandidateShellTimes),P95(RepairSkipped),P95(UnknownBoxes),P95(FieldCalls),
|
||||||
P95(ColumnMisses),P95(StratumShellTimes),P95(StratumFieldCalls),
|
P95(ColumnMisses),P95(StratumShellTimes),P95(StratumFieldCalls),
|
||||||
P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles),
|
P95(ConnectorShellTimes),P95(ConnectorFieldCalls),P95(ShellTriangles),
|
||||||
|
|||||||
@@ -126,6 +126,10 @@ namespace VoxelForgeCore
|
|||||||
double classificationMilliseconds = 0.0;
|
double classificationMilliseconds = 0.0;
|
||||||
double columnComputeMilliseconds = 0.0;
|
double columnComputeMilliseconds = 0.0;
|
||||||
double columnEvaluationExclusiveMilliseconds = 0.0;
|
double columnEvaluationExclusiveMilliseconds = 0.0;
|
||||||
|
double marchingCubesColumnComputeMilliseconds = 0.0;
|
||||||
|
double groundRefinementColumnComputeMilliseconds = 0.0;
|
||||||
|
double capRefinementColumnComputeMilliseconds = 0.0;
|
||||||
|
double classificationExclusiveMilliseconds = 0.0;
|
||||||
double marchingCubesExclusiveMilliseconds = 0.0;
|
double marchingCubesExclusiveMilliseconds = 0.0;
|
||||||
double groundRefinementExclusiveMilliseconds = 0.0;
|
double groundRefinementExclusiveMilliseconds = 0.0;
|
||||||
double capRefinementExclusiveMilliseconds = 0.0;
|
double capRefinementExclusiveMilliseconds = 0.0;
|
||||||
@@ -687,7 +691,8 @@ namespace VoxelForgeCore
|
|||||||
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
|
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
|
||||||
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
|
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
|
||||||
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
|
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
|
||||||
static void resetThreadColumnCacheStats(bool captureCoordinates = false) noexcept;
|
static void resetThreadColumnCacheStats(bool captureCoordinates = false,
|
||||||
|
bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept;
|
||||||
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
|
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
|
||||||
FieldInterval interval(std::int64_t minimumXSubvoxels,
|
FieldInterval interval(std::int64_t minimumXSubvoxels,
|
||||||
std::int64_t minimumYSubvoxels,
|
std::int64_t minimumYSubvoxels,
|
||||||
|
|||||||
@@ -1479,12 +1479,18 @@ namespace VoxelForgeCore
|
|||||||
const bool bSkipFarShellRepair=key.lod>=7&&bUseNativeSurfaceHorizon;
|
const bool bSkipFarShellRepair=key.lod>=7&&bUseNativeSurfaceHorizon;
|
||||||
FStandaloneShapeWorld::ResetThreadClassificationStats();
|
FStandaloneShapeWorld::ResetThreadClassificationStats();
|
||||||
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(
|
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(
|
||||||
options.captureColumnCoordinates&&key.lod==8);
|
options.captureColumnCoordinates);
|
||||||
bool bNativeSurfaceTile=false;
|
bool bNativeSurfaceTile=false;
|
||||||
bool bNativeSurfaceBandSafe=false;
|
bool bNativeSurfaceBandSafe=false;
|
||||||
|
double classificationColumnComputeMilliseconds=0.0;
|
||||||
if(bHasWorldAssembly)
|
if(bHasWorldAssembly)
|
||||||
{
|
{
|
||||||
|
const double columnMillisecondsBeforeClassification=
|
||||||
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
|
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
|
||||||
|
classificationColumnComputeMilliseconds=std::max(0.0,
|
||||||
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds
|
||||||
|
-columnMillisecondsBeforeClassification);
|
||||||
bNativeSurfaceTile=Classification.bNativeSurface;
|
bNativeSurfaceTile=Classification.bNativeSurface;
|
||||||
// NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its
|
// NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its
|
||||||
// bounded type-2/type-4 features. Unknown tile occupancy does not make that Z support
|
// bounded type-2/type-4 features. Unknown tile occupancy does not make that Z support
|
||||||
@@ -1518,7 +1524,16 @@ namespace VoxelForgeCore
|
|||||||
out.diagnostics.columnCacheHits=Columns.columnHits;
|
out.diagnostics.columnCacheHits=Columns.columnHits;
|
||||||
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
out.diagnostics.columnCacheMisses=Columns.columnMisses;
|
||||||
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
||||||
|
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
|
||||||
|
Counts.Milliseconds-classificationColumnComputeMilliseconds);
|
||||||
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
||||||
|
if(options.captureColumnCoordinates)
|
||||||
|
{
|
||||||
|
out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>(
|
||||||
|
std::hash<std::thread::id>{}(std::this_thread::get_id()))+1u;
|
||||||
|
out.diagnostics.columnCoordinateCaptureComplete=Columns.coordinateCaptureComplete;
|
||||||
|
out.diagnostics.evaluatedColumnCoordinates=Columns.evaluatedCoordinates;
|
||||||
|
}
|
||||||
out.densitySampleCount=0;
|
out.densitySampleCount=0;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -1662,6 +1677,8 @@ namespace VoxelForgeCore
|
|||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0,
|
out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0,
|
||||||
MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing));
|
MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing));
|
||||||
|
out.diagnostics.marchingCubesColumnComputeMilliseconds=std::max(0.0,
|
||||||
|
ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing);
|
||||||
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-MeshingStart).count();
|
std::chrono::steady_clock::now()-MeshingStart).count();
|
||||||
out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
|
out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
|
||||||
@@ -1712,6 +1729,8 @@ namespace VoxelForgeCore
|
|||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
out.diagnostics.groundRefinementExclusiveMilliseconds=std::max(0.0,
|
out.diagnostics.groundRefinementExclusiveMilliseconds=std::max(0.0,
|
||||||
GroundWallMilliseconds-(ColumnMillisecondsAfterGround-ColumnMillisecondsBeforeGround));
|
GroundWallMilliseconds-(ColumnMillisecondsAfterGround-ColumnMillisecondsBeforeGround));
|
||||||
|
out.diagnostics.groundRefinementColumnComputeMilliseconds=std::max(0.0,
|
||||||
|
ColumnMillisecondsAfterGround-ColumnMillisecondsBeforeGround);
|
||||||
const double ColumnMillisecondsBeforeCap=
|
const double ColumnMillisecondsBeforeCap=
|
||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
const auto CapStart=std::chrono::steady_clock::now();
|
const auto CapStart=std::chrono::steady_clock::now();
|
||||||
@@ -1723,6 +1742,8 @@ namespace VoxelForgeCore
|
|||||||
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
|
||||||
out.diagnostics.capRefinementExclusiveMilliseconds=std::max(0.0,
|
out.diagnostics.capRefinementExclusiveMilliseconds=std::max(0.0,
|
||||||
CapWallMilliseconds-(ColumnMillisecondsAfterCap-ColumnMillisecondsBeforeCap));
|
CapWallMilliseconds-(ColumnMillisecondsAfterCap-ColumnMillisecondsBeforeCap));
|
||||||
|
out.diagnostics.capRefinementColumnComputeMilliseconds=std::max(0.0,
|
||||||
|
ColumnMillisecondsAfterCap-ColumnMillisecondsBeforeCap);
|
||||||
out.diagnostics.farHorizonMilliseconds=std::chrono::duration<double,std::milli>(
|
out.diagnostics.farHorizonMilliseconds=std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-HorizonStart).count();
|
std::chrono::steady_clock::now()-HorizonStart).count();
|
||||||
out.diagnostics.capRefined=CapStats.Refined;
|
out.diagnostics.capRefined=CapStats.Refined;
|
||||||
@@ -1747,7 +1768,9 @@ namespace VoxelForgeCore
|
|||||||
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
|
||||||
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
|
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
|
||||||
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
|
||||||
if(options.captureColumnCoordinates&&key.lod==8)
|
out.diagnostics.classificationExclusiveMilliseconds=std::max(0.0,
|
||||||
|
Counts.Milliseconds-classificationColumnComputeMilliseconds);
|
||||||
|
if(options.captureColumnCoordinates)
|
||||||
{
|
{
|
||||||
out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>(
|
out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>(
|
||||||
std::hash<std::thread::id>{}(std::this_thread::get_id()))+1u;
|
std::hash<std::thread::id>{}(std::this_thread::get_id()))+1u;
|
||||||
|
|||||||
@@ -1027,11 +1027,18 @@ namespace world_assembly_detail
|
|||||||
std::int64_t wavelengthSubvoxels)
|
std::int64_t wavelengthSubvoxels)
|
||||||
{ return FloorDiv(positionSubvoxels * Q16, wavelengthSubvoxels); }
|
{ return FloorDiv(positionSubvoxels * Q16, wavelengthSubvoxels); }
|
||||||
|
|
||||||
|
// Audit-only override; normal tile generation resets this to zero before evaluating a recipe.
|
||||||
|
// Surcharge reservee aux mesures; la generation remet zero avant d'evaluer une recette.
|
||||||
|
inline thread_local std::uint32_t HeightfieldColumnOctaveDrop = 0;
|
||||||
|
|
||||||
inline std::int32_t FractalNoiseQ16(std::int64_t xQ16, std::int64_t yQ16,
|
inline std::int32_t FractalNoiseQ16(std::int64_t xQ16, std::int64_t yQ16,
|
||||||
std::int64_t zQ16, std::uint32_t octaves, bool ridged = false)
|
std::int64_t zQ16, std::uint32_t octaves, bool ridged = false)
|
||||||
{
|
{
|
||||||
std::int64_t total = 0, weightSum = 0, weight = Q16, ridgeWeight = Q16;
|
std::int64_t total = 0, weightSum = 0, weight = Q16, ridgeWeight = Q16;
|
||||||
for (std::uint32_t octave = 0; octave < octaves; ++octave)
|
std::uint32_t effectiveOctaves=octaves;
|
||||||
|
if(HeightfieldColumnOctaveDrop!=0&&octaves!=0)
|
||||||
|
effectiveOctaves=std::max(1u,octaves-std::min(octaves-1,HeightfieldColumnOctaveDrop));
|
||||||
|
for (std::uint32_t octave = 0; octave < effectiveOctaves; ++octave)
|
||||||
{
|
{
|
||||||
const std::int64_t scale = 1ll << octave;
|
const std::int64_t scale = 1ll << octave;
|
||||||
const std::int64_t noise = Perlin3DQ16(xQ16 * scale,yQ16 * scale,zQ16 * scale);
|
const std::int64_t noise = Perlin3DQ16(xQ16 * scale,yQ16 * scale,zQ16 * scale);
|
||||||
@@ -1965,9 +1972,15 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates) noexcept
|
void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool clearCache,
|
||||||
|
std::uint32_t octaveDrop) noexcept
|
||||||
{
|
{
|
||||||
|
// Diagnostic probes may clear old tile entries before comparing isolated sample grids.
|
||||||
|
// Les sondes de mesure peuvent vider les entrees d'une tuile avant de comparer deux grilles.
|
||||||
|
if(clearCache)world_assembly_detail::HeightfieldColumnCache.fill(
|
||||||
|
world_assembly_detail::HeightfieldColumnCacheEntry{});
|
||||||
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
|
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
|
||||||
|
world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;
|
||||||
Stats.pointCalls=0;
|
Stats.pointCalls=0;
|
||||||
Stats.columnHits=0;
|
Stats.columnHits=0;
|
||||||
Stats.columnMisses=0;
|
Stats.columnMisses=0;
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# World Assembler S13 — Round 17
|
||||||
|
|
||||||
|
**2026-10-19 · What does a tile actually cost?**
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
No generation optimization shipped. The measurements show that owner-plan tile time is mostly
|
||||||
|
column evaluation plus MC, that LOD4 is dominated by column-cache misses, and that cross-worker XY
|
||||||
|
reuse is common. The interpolation and global octave-drop probes fail the 2 cm quality bar. A shared
|
||||||
|
column page still needs a measured candidate with bounded memory, synchronization, and lifecycle
|
||||||
|
proof, so I did not add one. Density, mesh, collision, and recipe output are unchanged; no recipe
|
||||||
|
version bump is needed.
|
||||||
|
|
||||||
|
The current BuildHost 40 m/s trace passed the support gate **75/75**. It reported a 4.825 s
|
||||||
|
initial-set ready time, 28.760 worker-seconds, and a 0.510 s stationary ring. The last initial LOD0
|
||||||
|
tile applied at 1.146 s. At 40 m/s, `UWorld::Tick` was 3.133/5.426 ms p95/max at spawn and
|
||||||
|
30.314/43.142 ms while walking. The worker-second and full-set targets passed in this trace; the
|
||||||
|
LOD0 and strict S12 tick-tail targets did not. Startup-clock measurements and worker-task intervals
|
||||||
|
use different origins from the S12 actor report, so I do not treat 4.825 s as an apples-to-apples
|
||||||
|
replacement for S12's 4.911 s.
|
||||||
|
|
||||||
|
## 1. Exclusive tile cost on the owner plan
|
||||||
|
|
||||||
|
The BuildHost audit measures eight cold, no-hash tiles per requested LOD, with a separate hashed
|
||||||
|
full-feature-list versus per-tile-list parity check (`hashes_identical=1`). The table gives p95
|
||||||
|
stage time and the p95 of each tile's `stage time / density samples`, in `ms / ns per sample`.
|
||||||
|
Percentiles are computed independently, so stage p95 values do not add to the full-tile p95. The
|
||||||
|
sample denominator includes the normal halo: LOD0–1 are 32 MC cells / 35³ = 42,875 samples;
|
||||||
|
LOD3–8 are 16 coarse cells / 19³ = 6,859 samples. The owner configuration is not a 32³ lattice at
|
||||||
|
every LOD.
|
||||||
|
|
||||||
|
| LOD | Full scan → per-tile list p95 (ms) | Classify excl. | MC column eval. | MC excl. | Ground column eval. | Ground excl. | Cap column eval. | Cap excl. | Mesh assembly |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 13.806 → 14.160 | 0.0064 / 0.15 | 3.708 / 86.48 | 10.436 / 243.40 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0.0251 / 0.59 |
|
||||||
|
| 1 | 9.616 → 9.584 | 0.0821 / 0.02 | 4.096 / 95.52 | 7.191 / 167.72 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0.0314 / 0.73 |
|
||||||
|
| 3 | 6.952 → 6.878 | 0.0286 / 9.90 | 3.989 / 1,090.54 | 2.879 / 419.80 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0.0075 / 2.97 |
|
||||||
|
| 4 | 36.445 → 35.790 | 0.0015 / 0.22 | 29.361 / 4,280.71 | 2.948 / 429.84 | 0 / 0 | 0.0001 / 0.01 | 4.865 / 709.35 | 1.259 / 183.61 | 0.0231 / 3.37 |
|
||||||
|
| 5 | 10.586 → 10.420 | 0.0010 / 0.15 | 2.958 / 431.27 | 1.195 / 174.25 | 4.894 / 713.57 | 2.234 / 325.63 | 0.405 / 59.06 | 0.241 / 35.08 | 0.0054 / 0.79 |
|
||||||
|
| 8 | 14.778 → 14.507 | 0.0046 / 0.67 | 5.671 / 826.84 | 2.366 / 345.01 | 1.775 / 258.74 | 1.652 / 240.87 | 1.823 / 265.78 | 1.348 / 196.50 | 0.0252 / 3.67 |
|
||||||
|
|
||||||
|
The paired full/candidate timings are the feature-query negative control: the full plan scans feature
|
||||||
|
bounds; the candidate builds the per-tile feature lists used by Core. All tested hashes matched.
|
||||||
|
Their timing differences are small and mixed in sign. Feature-list p95 itself was only 0.0009–
|
||||||
|
0.0015 ms on this owner sample.
|
||||||
|
|
||||||
|
There is no separate volumetric 3D density-noise stage in native SurfaceWorld. Its surface recipe
|
||||||
|
does call `FractalNoiseQ16`/Perlin while evaluating 2D XY columns, so that work is included in
|
||||||
|
column evaluation. Material/color assignment is not a separate timer; it is included in meshing and
|
||||||
|
the final mesh/payload path. The current route has no tunnel, room, shaft, or route-density stage.
|
||||||
|
Classification-exclusive time subtracts column work done while classifying; MC/ground/cap column
|
||||||
|
time is reported beside the corresponding non-column exclusive time. The `horizon_p95_ms` counter
|
||||||
|
wraps ground and cap refinement and is inclusive, so it is not another exclusive stage. Far-shell
|
||||||
|
repair was negligible in this sample (at most 0.0003 ms p95).
|
||||||
|
|
||||||
|
### Why LOD4 is expensive
|
||||||
|
|
||||||
|
LOD4 full tile p95 is 35.790 ms, versus 6.878 ms at LOD3 and 10.420 ms at LOD5. LOD4 is the first
|
||||||
|
cap-refined level: it samples cap at 16 voxels (4 m); LOD3 has no cap refinement, and LOD5 cap uses
|
||||||
|
the 128-voxel (32 m) boundary grid. Ground refinement starts at LOD5. LOD4's cap work costs
|
||||||
|
4.865 ms of column evaluation and 1.259 ms exclusive at p95.
|
||||||
|
|
||||||
|
That cap difference does not explain most of the spike. LOD4 MC performs 29.361 ms p95 of column
|
||||||
|
evaluation, and its sampled tile has 8,224 column misses versus 1,634 at LOD3 and 1,916 at LOD5.
|
||||||
|
Thus the measured anomaly is primarily poor column reuse at LOD4, with cap refinement adding a
|
||||||
|
smaller separate cost. The data does not isolate why the thread-local direct-mapped cache misses
|
||||||
|
rise at LOD4; tile extent, halo/normal probes, and XY spread all differ with LOD.
|
||||||
|
|
||||||
|
## 2. Do far tiles evaluate detail they cannot show?
|
||||||
|
|
||||||
|
The LOD8 tile p95 spends 9.203 ms in column evaluation out of 14.507 ms. There is no volumetric
|
||||||
|
cave-noise loop in this path, but the 2D heightfield recipe still evaluates its full configured
|
||||||
|
noise octaves at every requested XY column. A measurement-only probe dropped the highest one or two
|
||||||
|
octaves globally on two LOD8 tiles. It reported 26.50% / 40.31% less column time, but ground-height
|
||||||
|
p95 errors of 7,545 / 7,581 cm and cap-height errors of 2,427 / 7,859 cm; all 578 cap samples were
|
||||||
|
over 2 cm. Near LOD0–1 the same probe was slower than baseline, so its timing is not a reliable
|
||||||
|
frequency cost estimate. It also renormalizes the remaining octaves and does not implement a
|
||||||
|
per-recipe wavelength cutoff at half the MC cell size.
|
||||||
|
|
||||||
|
This rejects global octave removal. A frequency-aware cutoff could change appearance while keeping
|
||||||
|
determinism, but this probe does not justify a cutoff or quantify its screen-space result. It was
|
||||||
|
measurement-only and did not affect tile hashes.
|
||||||
|
|
||||||
|
## 3. Per-tile feature lists and 2D cache reuse
|
||||||
|
|
||||||
|
Core's `World::generateTile` builds bounded per-tile query lists for overlapping
|
||||||
|
`Heightfield2D` strata and `FloatingIslandFieldV1` plans, including the normal halo. Those two
|
||||||
|
WorldAssembler families share the scoped feature-list route. The legacy Unreal `UVoxelGenerator`
|
||||||
|
room/tunnel/shaft families use their own room, tunnel, and generator caches; Round 1's Core tile list
|
||||||
|
is not one shared list for all legacy generator families. On the owner SurfaceWorld tiles, list-build
|
||||||
|
time is under 0.002 ms p95.
|
||||||
|
|
||||||
|
`Heightfield2D::point(x,y,z)` may be called at many Z/normal samples, but the thread-local XY cache
|
||||||
|
normally evaluates a column once per worker-cache miss. The cold BuildHost sample shows:
|
||||||
|
|
||||||
|
| LOD | Field-point calls p95 | Column misses p95 | Approx. hit rate |
|
||||||
|
|---:|---:|---:|---:|
|
||||||
|
| 0 | 44,100 | 2,244 | 94.9% |
|
||||||
|
| 1 | 44,100 | 2,289 | 94.8% |
|
||||||
|
| 3 | 4,191 | 1,634 | 61.0% |
|
||||||
|
| 4 | 9,405 | 8,224 | 12.6% |
|
||||||
|
| 5 | 8,197 | 1,916 | 76.6% |
|
||||||
|
| 8 | 4,441 | 2,708 | 39.0% |
|
||||||
|
|
||||||
|
The Standalone W8 capture recorded all tiles and all LODs. `repeated` means an XY coordinate was
|
||||||
|
re-evaluated by a different same-LOD tile; the neighbor count includes any prior adjacent tile,
|
||||||
|
and the cross-worker column counts a neighbor repeat observed on a different worker.
|
||||||
|
|
||||||
|
| LOD | Tiles | Unique XY | Repeated tile evaluations | Neighbor repeats | Cross-worker neighbor repeats |
|
||||||
|
|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 512 | 128,240 | 347,280 | 280,233 | 276,658 |
|
||||||
|
| 1 | 448 | 103,302 | 300,242 | 249,825 | 244,941 |
|
||||||
|
| 2 | 448 | 32,274 | 102,713 | 84,318 | 79,402 |
|
||||||
|
| 3 | 384 | 33,279 | 87,158 | 81,680 | 78,272 |
|
||||||
|
| 4 | 320 | 91,325 | 230,492 | 229,811 | 202,684 |
|
||||||
|
| 5 | 144 | 71,950 | 118,630 | 118,580 | 112,191 |
|
||||||
|
| 6 | 96 | 280,313 | 63,523 | 63,523 | 63,435 |
|
||||||
|
| 7 | 48 | 71,529 | 7,780 | 7,780 | 7,138 |
|
||||||
|
| 8 | 153 | 106,219 | 32,879 | 32,879 | 30,956 |
|
||||||
|
|
||||||
|
This makes immutable shared column pages worth a candidate A/B, especially for LOD0–5. The counts
|
||||||
|
do not measure synchronization, page compilation, eviction, memory, or resulting wall-time savings.
|
||||||
|
No page cache was added on counts alone. The column capture is opt-in, worker-local, and read back
|
||||||
|
after generation.
|
||||||
|
|
||||||
|
## 4. Coarse-lattice interpolation probe
|
||||||
|
|
||||||
|
The measurement-only probe evaluated exact ground/cap columns at every MC XY sample, then sampled
|
||||||
|
every second MC sample and bilinearly interpolated the rest. No near-zero or thin-feature fallback
|
||||||
|
was included, so this is a lower-cost bound and a failure screen, not a production candidate. The
|
||||||
|
table is two tiles per LOD. Errors are p95 over sampled columns; counts over 2 cm are explicit.
|
||||||
|
|
||||||
|
| LOD | Measured column-compute time saved at stride 2 | Ground p95 / >2 cm | Cap p95 / >2 cm |
|
||||||
|
|---:|---:|---:|---:|
|
||||||
|
| 0 | 73.48% | 0.78 cm / 0 of 2,178 | 1.32 cm / 25 of 2,178 |
|
||||||
|
| 1 | 73.96% | 2.54 cm / 236 of 2,178 | 3.00 cm / 375 of 2,178 |
|
||||||
|
| 3 | 71.99% | 388.62 cm / 405 of 578 | 37.70 cm / 397 of 578 |
|
||||||
|
| 4 | 71.39% | 702.29 cm / 403 of 578 | 68.92 cm / 401 of 578 |
|
||||||
|
| 5 | 71.27% | 1,248.85 cm / 411 of 578 | 87.74 cm / 395 of 578 |
|
||||||
|
| 8 | 72.81% | 4,369.95 cm / 414 of 578 | 2,201.17 cm / 415 of 578 |
|
||||||
|
|
||||||
|
At stride two, exact column evaluations fell from 2,178 to 578 at LOD0–1 and from 578 to 162 at
|
||||||
|
LOD3–8. The measured column-compute time reduction is shown above; it was 71–74%. Stride four
|
||||||
|
reduced exact evaluations from 2,178 to 162 at LOD0–1 and from 578 to 50 at LOD3–8, roughly
|
||||||
|
90–92%. It already gives LOD0 ground/cap p95 errors of 2.72/2.09 cm (293/2,178 and 123/2,178
|
||||||
|
samples over 2 cm). At LOD1, stride two fails the
|
||||||
|
2 cm threshold in both fields; coarse LOD errors are much larger. A near-zero exact fallback could
|
||||||
|
recover surface crossings, but it was not simulated here and could erase much of the nominal saving.
|
||||||
|
This probe does not exercise volumetric tunnel/wall thickness or the arrival-to-departure route
|
||||||
|
law. No interpolation was enabled; exact sampled outputs, walkability, and route behavior are
|
||||||
|
unchanged.
|
||||||
|
|
||||||
|
## 5. S12 worker-cap follow-up and current trace
|
||||||
|
|
||||||
|
The matched cap comparison kept `voxel.StreamingReadyQueueDepth=8` and changed only
|
||||||
|
`voxel.StreamingBackgroundWorkerCap`. Both controls were recorded in the JSONL trace, as were the
|
||||||
|
effective slots and the eight actual background worker count.
|
||||||
|
|
||||||
|
| Speed | Background cap | Effective slots / actual tile workers | Initial set ready | Spawn tick p95/max | Walk tick p95/max | Ring |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 6 m/s | 0 | 10 / 8 | 4.669 s | 3.483 / 5.184 ms | 2.708 / 28.581 ms | — |
|
||||||
|
| 6 m/s | 1 | 8 / 8 | 4.745 s | 3.283 / 14.945 ms | 2.696 / 28.535 ms | — |
|
||||||
|
| 40 m/s | 0 | 10 / 8 | 4.633 s | 3.455 / 5.468 ms | 32.345 / 55.115 ms | 75/75 |
|
||||||
|
| 40 m/s | 1, matched | 8 / 8 | 4.712 s | 3.031 / 5.364 ms | 30.541 / 41.598 ms | 75/75 |
|
||||||
|
| 40 m/s | 1, final build | 8 / 8 | 4.825 s | 3.133 / 5.426 ms | 30.314 / 43.142 ms | 75/75 |
|
||||||
|
|
||||||
|
Cap 0 gained only 76 ms at 6 m/s and about 79 ms in the matched 40 m/s pair, while its 40 m/s
|
||||||
|
walk tick tail was substantially worse. The final cap-1 run confirms the ring at 75/75 gates. Keep
|
||||||
|
cap 1 as the default; the latest tick p95/max is still 0.752/3.915 ms above Round 16's 29.562 /
|
||||||
|
39.227 ms 40 m/s result, so the strict tick-tail target is not claimed.
|
||||||
|
|
||||||
|
On the final cap-1 40 m/s trace, the initial set contains 2,578 tiles and totals 28.760 worker-
|
||||||
|
seconds. The worker-seconds by LOD were: LOD0 3.81, LOD1 3.94, LOD2 0.81, LOD3 2.36, LOD4 7.73,
|
||||||
|
LOD5 1.68, LOD6 2.17, LOD7 1.97, LOD8 4.31. The ring was ready at 0.510 s; full initial set
|
||||||
|
ready at 4.825 s. LOD0's last worker ended at 1.131 s and its last apply was at 1.146 s.
|
||||||
|
|
||||||
|
## Builds, quality, determinism, and files
|
||||||
|
|
||||||
|
Build commands:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\BuildVoxelForgeCoreHost.ps1 -MaxParallelActions 12 -TimeoutSeconds 900
|
||||||
|
.\Standalone\Build.ps1 -Target All -CompileWorkers 12 -BuildPriority Normal
|
||||||
|
```
|
||||||
|
|
||||||
|
Measurement commands (each was a fresh process; BuildHost runs were serial, Normal priority, and
|
||||||
|
limited to the 12-CPU affinity):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -SharedFeatureAuditOnly -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 0 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 0 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
The final Standalone commands used the same options for each worker count; W8 also enabled the
|
||||||
|
all-LOD overlap capture:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=1 --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=8 --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --profile-column-overlap --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=12 --world-assembler-prototype --stream-benchmark --stream-pipeline --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60
|
||||||
|
```
|
||||||
|
|
||||||
|
Current-build compatibility gates:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreOffTest -CoreVoxelSizeCm 12.5 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV2 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -FloatingIslandsOnly -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
The current BuildHost SurfaceWorld actor run passed and reported stable W1/W8/W16 hashes:
|
||||||
|
density/mesh/payload/topology `0cd2a1a4152ee4aa / a06243aee16b65d5 / c700162935c262d7 /
|
||||||
|
0af901aa42b452bc`. The current BuildHost cap audit passed S4 agreement (128 points, max 0.0504 cm
|
||||||
|
against 2 cm) and 1,584/1,584 cap rays (100%). The BuildHost actor run also passed S3 far-horizon
|
||||||
|
agreement at max 0.0427 cm against 2 cm. Current Standalone W1/W8/W12 each matched full-view hashes:
|
||||||
|
density/mesh/payload/topology `c10697a8f48380c7 / e75c9119a1498cd5 / 49a9a8d1835a6c8c /
|
||||||
|
edcb23288b5e8724`. W8's final full-view pipeline was 3.518 s for 2,553/2,553 tiles.
|
||||||
|
|
||||||
|
The current-build Core-off gate passed `legacy_unchanged=PASS` at 12.5 cm. M2 Hybrid V1 and V2
|
||||||
|
each passed all five analytic fixtures and the border suite at 25 cm. FloatingIslands passed at
|
||||||
|
25 cm with stable W1/W8/W16 hashes `4dcfb37585d7d986 / f14f76fbf065c6ed / 53035c45fd1a082e /
|
||||||
|
23550100654a4ec3`; its island and span capsule contacts, content anchors, and carve/fill checks
|
||||||
|
passed. These runs used the current BuildHost binaries. No density output changed, so paired
|
||||||
|
near/far captures and a recipe bump were not required. No runtime CVar or behavior change was
|
||||||
|
introduced; the cap-0 negative controls above were trace-read back.
|
||||||
|
|
||||||
|
Full BuildHost logs are under
|
||||||
|
`Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreWorldValidation/Logs/` with stems
|
||||||
|
`CoreWorldValidation-core_off_voxel_12.5_applyboost_default_legacy_order_nearreserve_auto`,
|
||||||
|
`CoreWorldValidation-owner_surface_applyboost_default_legacy_order_nearreserve_auto_worldassemblerhybridv1`,
|
||||||
|
`CoreWorldValidation-owner_surface_applyboost_default_legacy_order_nearreserve_auto_worldassemblerhybridv2`,
|
||||||
|
and `CoreWorldValidation-floating_islands_applyboost_default_legacy_order_nearreserve_auto`.
|
||||||
|
|
||||||
|
Current binary SHA-256:
|
||||||
|
|
||||||
|
| Binary | SHA-256 |
|
||||||
|
|---|---|
|
||||||
|
| BuildHost `UnrealEditor-VoxelForge.dll` | `811F83D43EDE47EE870DD608F25C9F902422E1B264901917726CDBECE0EC0A37` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForgeCore.dll` | `AA74A8BC8E1C9C59FB7EA94D5A6BA71341EF8208EDE2458A7B5FAC0045FF2520` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForgeEditor.dll` | `A851836ED435E8795AF860EBFB49AD84B603FF0D94600637D9B8CE52E0710EE3` |
|
||||||
|
| Standalone `VoxelForgeCoreStandalone.exe` | `704F433791AF0D483D082CCBB2043DB4194323F40A986DA72F338FC424BF82F3` |
|
||||||
|
|
||||||
|
Round 17 changed these existing source files; no new source file was added:
|
||||||
|
|
||||||
|
| File | Likely compile spot |
|
||||||
|
|---|---|
|
||||||
|
| `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp`, `Standalone/Core/src/WorldAssemblerInternal.h` | BuildHost VoxelForgeCore and Standalone Core |
|
||||||
|
| `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | BuildHost VoxelForgeEditor commandlet |
|
||||||
|
| `Standalone/Source/StreamingWorld.h`, `Standalone/Source/StandaloneViewer.cpp` | Standalone viewer executable |
|
||||||
|
|
||||||
|
Async-safety review: no actor queues or worker mutation code changed. `Epoch`, MPSC queue modes,
|
||||||
|
`EndPlay` draining, worker `bShuttingDown` checks, `ModsLock`, and shared region-page lifetime are
|
||||||
|
unchanged. Coordinate capture is opt-in thread-local diagnostic data copied into each tile result;
|
||||||
|
UObject and RMC mutation remains on the game thread. RealtimeMeshComponent was read-only. No
|
||||||
|
`.uproject`, owner editor, `.uasset`, generated header, `Binaries/`, or `Intermediate/` source was
|
||||||
|
edited. Files over 300 KB are under `Saved/BuildHost/`. No `0xC0000142` occurred and no new
|
||||||
|
`UECC-*` crash folder appeared.
|
||||||
|
|
||||||
|
The first BuildHost compile failed because two probe lambdas omitted the configured tile-size
|
||||||
|
capture; that was fixed and the next build passed. The Standalone build passed with the existing
|
||||||
|
`vswhere.exe` lookup warning and `VoxelStrateManager.cpp` C4838 warnings. A final source check and
|
||||||
|
both current targets passed. No commit, push, or stash.
|
||||||
|
|
||||||
|
### Dialogue
|
||||||
|
|
||||||
|
**User:** Shouldn't a tile be way cheaper? Measure the stages and explain LOD4 before choosing a fix.
|
||||||
|
|
||||||
|
**Codex:** The largest measured cost is XY column evaluation, and LOD4 has a sharp spike in cache
|
||||||
|
misses plus its first fine cap pass. Two-step interpolation reduced measured column-compute time by
|
||||||
|
roughly 72%, but moved far ground by tens of meters; global octave dropping also fails badly.
|
||||||
|
Cross-worker column
|
||||||
|
reuse is a strong candidate for a future measured page-cache experiment, so Round 17 keeps output
|
||||||
|
unchanged and records the floor instead of shipping an unproven approximation.
|
||||||
@@ -6976,7 +6976,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
|
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
|
||||||
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
|
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
|
||||||
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
|
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
|
||||||
std::chrono::steady_clock::time_point ColdStart,bool bProfileFarColumnOverlap,
|
std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap,
|
||||||
int32 FarHorizonLOD8InteriorStepVoxels)
|
int32 FarHorizonLOD8InteriorStepVoxels)
|
||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
@@ -7045,7 +7045,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
const auto ShapeReady=Clock::now();
|
const auto ShapeReady=Clock::now();
|
||||||
const double ShapeSeconds=std::chrono::duration<double>(ShapeReady-ShapeBuildStart).count();
|
const double ShapeSeconds=std::chrono::duration<double>(ShapeReady-ShapeBuildStart).count();
|
||||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileFarColumnOverlap,
|
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||||
FarHorizonLOD8InteriorStepVoxels);
|
FarHorizonLOD8InteriorStepVoxels);
|
||||||
const auto StreamStart=Clock::now();
|
const auto StreamStart=Clock::now();
|
||||||
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
|
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
|
||||||
@@ -7078,19 +7078,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
FVoxelTileKey Tile;
|
FVoxelTileKey Tile;
|
||||||
std::uint64_t WorkerHash=0;
|
std::uint64_t WorkerHash=0;
|
||||||
};
|
};
|
||||||
std::unordered_map<FColumnXY,FFirstColumnUse,FColumnXYHash> LOD8ColumnFirstUse;
|
std::array<std::unordered_map<FColumnXY,std::vector<FFirstColumnUse>,FColumnXYHash>,9> ColumnUsesByLOD;
|
||||||
std::uint64_t LOD8ColumnDuplicateEvents=0,LOD8NeighbourDuplicateEvents=0;
|
std::array<std::uint64_t,9> ColumnDuplicateEventsByLOD{},NeighbourDuplicateEventsByLOD{};
|
||||||
std::uint64_t LOD8CrossWorkerNeighbourDuplicateEvents=0;
|
std::array<std::uint64_t,9> CrossWorkerNeighbourDuplicateEventsByLOD{};
|
||||||
std::size_t LOD8ColumnCaptureTiles=0;
|
std::array<std::uint64_t,9> ColumnCaptureTilesByLOD{},ExpectedTilesByLOD{};
|
||||||
bool bLOD8ColumnCaptureComplete=true;
|
std::array<bool,9> ColumnCaptureCompleteByLOD{};
|
||||||
|
ColumnCaptureCompleteByLOD.fill(true);
|
||||||
std::array<double,9> WorkerSecondsByLOD{};
|
std::array<double,9> WorkerSecondsByLOD{};
|
||||||
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
|
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
|
||||||
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
|
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
|
||||||
std::array<double,9> MeshAssemblyMsByLOD{};
|
std::array<double,9> MeshAssemblyMsByLOD{};
|
||||||
std::array<std::uint64_t,9> TileCountByLOD{};
|
std::array<std::uint64_t,9> TileCountByLOD{};
|
||||||
const auto AreLOD8Neighbours=[](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||||
|
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
|
||||||
|
++ExpectedTilesByLOD[static_cast<std::size_t>(Tile.Level)];
|
||||||
|
const auto AreSameLODNeighbours=[](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||||
{
|
{
|
||||||
if(A.Level!=8||B.Level!=8)return false;
|
if(A.Level!=B.Level)return false;
|
||||||
const std::int64_t DX=std::abs(static_cast<std::int64_t>(A.Coord.X)-B.Coord.X);
|
const std::int64_t DX=std::abs(static_cast<std::int64_t>(A.Coord.X)-B.Coord.X);
|
||||||
const std::int64_t DY=std::abs(static_cast<std::int64_t>(A.Coord.Y)-B.Coord.Y);
|
const std::int64_t DY=std::abs(static_cast<std::int64_t>(A.Coord.Y)-B.Coord.Y);
|
||||||
const std::int64_t DZ=std::abs(static_cast<std::int64_t>(A.Coord.Z)-B.Coord.Z);
|
const std::int64_t DZ=std::abs(static_cast<std::int64_t>(A.Coord.Z)-B.Coord.Z);
|
||||||
@@ -7122,10 +7126,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
|
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
|
||||||
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
|
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
|
||||||
}
|
}
|
||||||
if(bProfileFarColumnOverlap&&Tile.Key.Level==8)
|
if(bProfileColumnOverlap&&Tile.Key.Level>=0
|
||||||
|
&&Tile.Key.Level<static_cast<int32>(ColumnCaptureTilesByLOD.size()))
|
||||||
{
|
{
|
||||||
++LOD8ColumnCaptureTiles;
|
const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level);
|
||||||
bLOD8ColumnCaptureComplete=bLOD8ColumnCaptureComplete
|
++ColumnCaptureTilesByLOD[LOD];
|
||||||
|
ColumnCaptureCompleteByLOD[LOD]=ColumnCaptureCompleteByLOD[LOD]
|
||||||
&&Tile.CoreDiagnostics.columnCoordinateCaptureComplete;
|
&&Tile.CoreDiagnostics.columnCoordinateCaptureComplete;
|
||||||
std::vector<std::array<std::int64_t,2>> Coordinates=
|
std::vector<std::array<std::int64_t,2>> Coordinates=
|
||||||
Tile.CoreDiagnostics.evaluatedColumnCoordinates;
|
Tile.CoreDiagnostics.evaluatedColumnCoordinates;
|
||||||
@@ -7134,16 +7140,26 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
for(const auto& Coordinate:Coordinates)
|
for(const auto& Coordinate:Coordinates)
|
||||||
{
|
{
|
||||||
const FColumnXY XY{Coordinate[0],Coordinate[1]};
|
const FColumnXY XY{Coordinate[0],Coordinate[1]};
|
||||||
const auto [First,Inserted]=LOD8ColumnFirstUse.try_emplace(XY,
|
auto [Uses,Inserted]=ColumnUsesByLOD[LOD].try_emplace(XY);
|
||||||
FFirstColumnUse{Tile.Key,Tile.CoreDiagnostics.columnEvaluationWorkerHash});
|
std::vector<FFirstColumnUse>& PriorUses=Uses->second;
|
||||||
if(Inserted)continue;
|
if(Inserted)
|
||||||
++LOD8ColumnDuplicateEvents;
|
|
||||||
if(AreLOD8Neighbours(First->second.Tile,Tile.Key))
|
|
||||||
{
|
{
|
||||||
++LOD8NeighbourDuplicateEvents;
|
PriorUses.push_back({Tile.Key,Tile.CoreDiagnostics.columnEvaluationWorkerHash});
|
||||||
if(First->second.WorkerHash!=Tile.CoreDiagnostics.columnEvaluationWorkerHash)
|
continue;
|
||||||
++LOD8CrossWorkerNeighbourDuplicateEvents;
|
|
||||||
}
|
}
|
||||||
|
++ColumnDuplicateEventsByLOD[LOD];
|
||||||
|
bool bHasNeighbourDuplicate=false,bHasCrossWorkerNeighbourDuplicate=false;
|
||||||
|
for(const FFirstColumnUse& Prior:PriorUses)
|
||||||
|
{
|
||||||
|
if(!AreSameLODNeighbours(Prior.Tile,Tile.Key))continue;
|
||||||
|
bHasNeighbourDuplicate=true;
|
||||||
|
bHasCrossWorkerNeighbourDuplicate=bHasCrossWorkerNeighbourDuplicate
|
||||||
|
||Prior.WorkerHash!=Tile.CoreDiagnostics.columnEvaluationWorkerHash;
|
||||||
|
}
|
||||||
|
if(bHasNeighbourDuplicate)++NeighbourDuplicateEventsByLOD[LOD];
|
||||||
|
if(bHasCrossWorkerNeighbourDuplicate)
|
||||||
|
++CrossWorkerNeighbourDuplicateEventsByLOD[LOD];
|
||||||
|
PriorUses.push_back({Tile.Key,Tile.CoreDiagnostics.columnEvaluationWorkerHash});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WholeViewReady=std::max(WholeViewReady,Tile.CompletedAt);
|
WholeViewReady=std::max(WholeViewReady,Tile.CompletedAt);
|
||||||
@@ -7191,14 +7207,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Streamer.Stop();
|
Streamer.Stop();
|
||||||
if(bProfileFarColumnOverlap)
|
bool bColumnOverlapCapturePassing=true;
|
||||||
|
if(bProfileColumnOverlap)
|
||||||
{
|
{
|
||||||
std::printf("[WorldAssemblerLOD8ColumnOverlap] tiles=%zu unique_xy_columns=%zu repeated_tile_evaluations=%llu neighbouring_duplicate_evaluations=%llu cross_worker_neighbouring_duplicates=%llu capture_complete=%d\n",
|
for(std::size_t LOD=0;LOD<ColumnCaptureTilesByLOD.size();++LOD)
|
||||||
LOD8ColumnCaptureTiles,LOD8ColumnFirstUse.size(),
|
{
|
||||||
static_cast<unsigned long long>(LOD8ColumnDuplicateEvents),
|
const bool bLODComplete=ColumnCaptureCompleteByLOD[LOD]
|
||||||
static_cast<unsigned long long>(LOD8NeighbourDuplicateEvents),
|
&&ColumnCaptureTilesByLOD[LOD]==ExpectedTilesByLOD[LOD];
|
||||||
static_cast<unsigned long long>(LOD8CrossWorkerNeighbourDuplicateEvents),
|
bColumnOverlapCapturePassing=bColumnOverlapCapturePassing&&bLODComplete;
|
||||||
bLOD8ColumnCaptureComplete?1:0);
|
std::printf("[WorldAssemblerLODColumnOverlap] lod=%zu tiles=%llu expected_tiles=%llu unique_xy_columns=%zu repeated_tile_evaluations=%llu neighbouring_duplicate_evaluations=%llu cross_worker_neighbouring_duplicates=%llu capture_complete=%d\n",
|
||||||
|
LOD,static_cast<unsigned long long>(ColumnCaptureTilesByLOD[LOD]),
|
||||||
|
static_cast<unsigned long long>(ExpectedTilesByLOD[LOD]),
|
||||||
|
ColumnUsesByLOD[LOD].size(),
|
||||||
|
static_cast<unsigned long long>(ColumnDuplicateEventsByLOD[LOD]),
|
||||||
|
static_cast<unsigned long long>(NeighbourDuplicateEventsByLOD[LOD]),
|
||||||
|
static_cast<unsigned long long>(CrossWorkerNeighbourDuplicateEventsByLOD[LOD]),
|
||||||
|
bLODComplete?1:0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
|
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
|
||||||
{
|
{
|
||||||
@@ -7253,7 +7278,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
}
|
}
|
||||||
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
|
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
|
||||||
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing
|
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing
|
||||||
&&(!bProfileFarColumnOverlap||(bLOD8ColumnCaptureComplete&&LOD8ColumnCaptureTiles>0))?0:6;
|
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)?0:6;
|
||||||
}
|
}
|
||||||
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,VoxelForgeCore::World* CoreWorld,
|
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,VoxelForgeCore::World* CoreWorld,
|
||||||
const FVector& CameraPositionVoxels,
|
const FVector& CameraPositionVoxels,
|
||||||
@@ -9601,7 +9626,7 @@ int main(int Argc,char** Argv)
|
|||||||
std::string WorldAssemblyManifestOutputPath;
|
std::string WorldAssemblyManifestOutputPath;
|
||||||
bool bStreamBenchmark=false;
|
bool bStreamBenchmark=false;
|
||||||
bool bStreamPipeline=false;
|
bool bStreamPipeline=false;
|
||||||
bool bProfileFarColumnOverlap=false;
|
bool bProfileColumnOverlap=false;
|
||||||
bool bCoarseFirstStreamOrder=false;
|
bool bCoarseFirstStreamOrder=false;
|
||||||
bool bProfileDetailBand=false;
|
bool bProfileDetailBand=false;
|
||||||
bool bPassageDetailAuditOnly=false;
|
bool bPassageDetailAuditOnly=false;
|
||||||
@@ -9739,8 +9764,8 @@ int main(int Argc,char** Argv)
|
|||||||
else if(Arg=="--coarse-first-stream-order")bCoarseFirstStreamOrder=true;
|
else if(Arg=="--coarse-first-stream-order")bCoarseFirstStreamOrder=true;
|
||||||
else if(Arg=="--stream-pipeline")
|
else if(Arg=="--stream-pipeline")
|
||||||
{bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;}
|
{bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;}
|
||||||
else if(Arg=="--profile-far-column-overlap")
|
else if(Arg=="--profile-column-overlap"||Arg=="--profile-far-column-overlap")
|
||||||
{bProfileFarColumnOverlap=true;bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;}
|
{bProfileColumnOverlap=true;bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;}
|
||||||
else if(Arg=="--edit-benchmark")
|
else if(Arg=="--edit-benchmark")
|
||||||
{bEditBenchmark=true;bStreamBenchmark=true;App.bShapeWorld=true;}
|
{bEditBenchmark=true;bStreamBenchmark=true;App.bShapeWorld=true;}
|
||||||
else if(Arg=="--stream-hash")bStreamHash=true;
|
else if(Arg=="--stream-hash")bStreamHash=true;
|
||||||
@@ -9957,7 +9982,7 @@ int main(int Argc,char** Argv)
|
|||||||
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
||||||
App.WorkerCount,StreamBenchmarkMaxLevel,App.ShapeDetailVariant,App.CoreScheduler,
|
App.WorkerCount,StreamBenchmarkMaxLevel,App.ShapeDetailVariant,App.CoreScheduler,
|
||||||
bStreamHash||!BakeMeshCachePath.empty(),bSkipUvAndColors,bSkipMaterialGroups,
|
bStreamHash||!BakeMeshCachePath.empty(),bSkipUvAndColors,bSkipMaterialGroups,
|
||||||
bSkipUV,bSkipColors,ColdProcessStart,bProfileFarColumnOverlap,
|
bSkipUV,bSkipColors,ColdProcessStart,bProfileColumnOverlap,
|
||||||
App.FarHorizonLOD8InteriorStepVoxels);
|
App.FarHorizonLOD8InteriorStepVoxels);
|
||||||
if(bEditBenchmark)
|
if(bEditBenchmark)
|
||||||
return RunEditTransactionBenchmark(*App.World,*App.ShapeWorld,
|
return RunEditTransactionBenchmark(*App.World,*App.ShapeWorld,
|
||||||
|
|||||||
@@ -424,13 +424,13 @@ public:
|
|||||||
bool bInSkipUV=false,
|
bool bInSkipUV=false,
|
||||||
bool bInSkipColors=false,
|
bool bInSkipColors=false,
|
||||||
VoxelForgeCore::World* InCoreWorld=nullptr,
|
VoxelForgeCore::World* InCoreWorld=nullptr,
|
||||||
bool bInCaptureFarColumnCoordinates=false,
|
bool bInCaptureColumnCoordinates=false,
|
||||||
int32 InLod8HorizonInteriorStepVoxels=0)
|
int32 InLod8HorizonInteriorStepVoxels=0)
|
||||||
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
|
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
|
||||||
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
|
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
|
||||||
CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
|
CoreWorld(InCoreWorld),bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups),
|
||||||
bSkipUV(bInSkipUV),bSkipColors(bInSkipColors),
|
bSkipUV(bInSkipUV),bSkipColors(bInSkipColors),
|
||||||
bCaptureFarColumnCoordinates(bInCaptureFarColumnCoordinates),
|
bCaptureColumnCoordinates(bInCaptureColumnCoordinates),
|
||||||
Lod8HorizonInteriorStepVoxels(std::max(0,InLod8HorizonInteriorStepVoxels))
|
Lod8HorizonInteriorStepVoxels(std::max(0,InLod8HorizonInteriorStepVoxels))
|
||||||
{
|
{
|
||||||
VoxelGenLOD::InitializeConsoleSwitches();
|
VoxelGenLOD::InitializeConsoleSwitches();
|
||||||
@@ -799,7 +799,7 @@ private:
|
|||||||
Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax;
|
Options.bandMinZVoxel=Job.BandZMin;Options.bandMaxZVoxel=Job.BandZMax;
|
||||||
Options.hashDensity=bHashDensity;Options.skipUvAndColors=bSkipUvAndColors;
|
Options.hashDensity=bHashDensity;Options.skipUvAndColors=bSkipUvAndColors;
|
||||||
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
||||||
Options.captureColumnCoordinates=bCaptureFarColumnCoordinates&&Level==8;
|
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||||
std::string Error;
|
std::string Error;
|
||||||
auto Result=CoreWorld->generateTile(
|
auto Result=CoreWorld->generateTile(
|
||||||
@@ -971,7 +971,7 @@ private:
|
|||||||
bool bSkipMaterialGroups=false;
|
bool bSkipMaterialGroups=false;
|
||||||
bool bSkipUV=false;
|
bool bSkipUV=false;
|
||||||
bool bSkipColors=false;
|
bool bSkipColors=false;
|
||||||
bool bCaptureFarColumnCoordinates=false;
|
bool bCaptureColumnCoordinates=false;
|
||||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||||
std::atomic<uint64> CacheHitCount{0};
|
std::atomic<uint64> CacheHitCount{0};
|
||||||
mutable std::mutex Mutex;
|
mutable std::mutex Mutex;
|
||||||
|
|||||||
Reference in new issue
Block a user