wip(L6, side branch): screen-space far gate; no-op cutoff removed; cost within noise vs L4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ED33VnqepD4YY6gDAAA832
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 21:10:08 +02:00
1 parent b21a19d561
commit aa67bd31f5
13 files changed
+469 -610

No files matched your search

@@ -137,6 +137,7 @@ namespace VoxelForgeCore
double marchingCubesGradientNormalsMilliseconds = 0.0; double marchingCubesGradientNormalsMilliseconds = 0.0;
double marchingCubesEdgeInterpolationMilliseconds = 0.0; double marchingCubesEdgeInterpolationMilliseconds = 0.0;
double marchingCubesVertexDedupStreamMilliseconds = 0.0; double marchingCubesVertexDedupStreamMilliseconds = 0.0;
double marchingCubesSkirtMilliseconds = 0.0;
double marchingCubesOtherMilliseconds = 0.0; double marchingCubesOtherMilliseconds = 0.0;
std::uint64_t marchingCubesGradientFallbackDensityCalls = 0; std::uint64_t marchingCubesGradientFallbackDensityCalls = 0;
std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0; std::uint64_t marchingCubesEdgeMidpointDensityCalls = 0;
@@ -146,11 +147,6 @@ namespace VoxelForgeCore
double meshAssemblyMilliseconds = 0.0; double meshAssemblyMilliseconds = 0.0;
double meshingMilliseconds = 0.0; double meshingMilliseconds = 0.0;
double featureListMilliseconds = 0.0; double featureListMilliseconds = 0.0;
std::array<std::uint64_t,8> visualDetailOctaveEvaluations{};
std::array<std::uint64_t,8> visualDetailOctaveBudgetEligible{};
std::array<std::uint64_t,8> visualDetailOctaveSkips{};
std::array<double,8> visualDetailOctaveMilliseconds{};
std::array<double,8> visualDetailOctaveEligibleMilliseconds{};
double farShellMilliseconds = 0.0; double farShellMilliseconds = 0.0;
double farStratumShellMilliseconds = 0.0; double farStratumShellMilliseconds = 0.0;
double farConnectorShellMilliseconds = 0.0; double farConnectorShellMilliseconds = 0.0;
@@ -585,8 +581,6 @@ namespace VoxelForgeCore
struct FieldInterval; struct FieldInterval;
struct FieldDetailEnvelope; struct FieldDetailEnvelope;
struct FieldBlockContract; struct FieldBlockContract;
struct VisualDetailRecipeContractV1;
struct VisualDetailCutoffV1;
class VFCORE_API FloatingIslandFieldV1 class VFCORE_API FloatingIslandFieldV1
{ {
@@ -609,7 +603,6 @@ namespace VoxelForgeCore
std::int64_t maximumZSubvoxels) const noexcept; std::int64_t maximumZSubvoxels) const noexcept;
FloatingIslandInfluenceBounds influenceBounds() const noexcept; FloatingIslandInfluenceBounds influenceBounds() const noexcept;
FieldDetailEnvelope detailEnvelope() const noexcept; FieldDetailEnvelope detailEnvelope() const noexcept;
VisualDetailRecipeContractV1 visualDetailContract() const;
FieldBlockContract contract() const noexcept; FieldBlockContract contract() const noexcept;
const FloatingIslandRecipeV1* recipe() const noexcept { return recipe_; } const FloatingIslandRecipeV1* recipe() const noexcept { return recipe_; }
const FloatingIslandPlanV1* plan() const noexcept { return plan_; } const FloatingIslandPlanV1* plan() const noexcept { return plan_; }
@@ -659,12 +652,6 @@ namespace VoxelForgeCore
std::uint64_t columnMisses = 0; std::uint64_t columnMisses = 0;
std::uint64_t columnConflictMisses = 0; std::uint64_t columnConflictMisses = 0;
double columnComputeMilliseconds = 0.0; double columnComputeMilliseconds = 0.0;
std::array<std::uint64_t,8> visualDetailOctaveEvaluations{};
std::array<std::uint64_t,8> visualDetailOctaveBudgetEligible{};
std::array<std::uint64_t,8> visualDetailOctaveSkips{};
std::array<double,8> visualDetailOctaveMilliseconds{};
std::array<double,8> visualDetailOctaveEligibleMilliseconds{};
bool profileVisualDetailOctaves = false;
bool captureCoordinates = false; bool captureCoordinates = false;
bool coordinateCaptureComplete = true; bool coordinateCaptureComplete = true;
std::vector<std::array<std::int64_t, 2>> evaluatedCoordinates; std::vector<std::array<std::int64_t, 2>> evaluatedCoordinates;
@@ -684,44 +671,6 @@ namespace VoxelForgeCore
std::int64_t minimumFeatureSizeSubvoxels = 1; std::int64_t minimumFeatureSizeSubvoxels = 1;
}; };
// A recipe may opt its display-only noise terms into this shared octave contract.
// A missing declaration keeps the exact evaluator. The per-call amplitude includes
// every downstream scale, so wavelength alone can never authorize dropping a term.
struct VisualDetailOctaveTermV1
{
std::uint64_t stableId = 0;
std::int64_t baseWavelengthSubvoxels = 0;
std::int64_t maximumDisplacementSubvoxels = 0;
std::uint32_t octaveCount = 0;
std::uint32_t maximumNoiseAbsQ16 = 2u * 65536u;
};
inline constexpr std::uint32_t CurrentVisualDetailCutoffContractVersion = 2;
struct VisualDetailRecipeContractV1
{
std::uint32_t version = CurrentVisualDetailCutoffContractVersion;
std::vector<VisualDetailOctaveTermV1> terms;
};
struct VisualDetailCutoffV1
{
std::int64_t pixelBudgetSubvoxels = 0;
std::int64_t sampleStepSubvoxels = 0;
std::uint32_t callBudgetDivisor = 64;
bool apply = false;
};
struct VisualDetailCutoffResultV1
{
std::uint32_t firstSkippedOctave = 0;
std::int64_t maximumErrorSubvoxels = 0;
};
VFCORE_API VisualDetailCutoffResultV1 FindVisualDetailTailCutoffV1(
const VisualDetailOctaveTermV1& term,
const VisualDetailCutoffV1& cutoff) noexcept;
struct FieldBlockContract struct FieldBlockContract
{ {
std::uint32_t readsChannels = 0; std::uint32_t readsChannels = 0;
@@ -756,6 +705,10 @@ namespace VoxelForgeCore
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, static void resetThreadColumnCacheStats(bool captureCoordinates = false,
bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept; bool clearCache = false, std::uint32_t octaveDrop = 0) noexcept;
static void setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept;
static std::uint32_t threadColumnOctaveDrop() noexcept;
static void setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept;
static std::int64_t threadColumnInterpolationStrideSubvoxels() noexcept;
static void setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept; static void setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept;
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept; static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
FieldInterval interval(std::int64_t minimumXSubvoxels, FieldInterval interval(std::int64_t minimumXSubvoxels,
@@ -766,15 +719,6 @@ namespace VoxelForgeCore
std::int64_t maximumZSubvoxels) const noexcept; std::int64_t maximumZSubvoxels) const noexcept;
FieldInfluenceBounds influenceBounds() const noexcept; FieldInfluenceBounds influenceBounds() const noexcept;
FieldDetailEnvelope detailEnvelope() const noexcept; FieldDetailEnvelope detailEnvelope() const noexcept;
VisualDetailRecipeContractV1 visualDetailContract() const;
static void setThreadVisualDetailCutoff(VisualDetailCutoffV1 cutoff) noexcept;
static VisualDetailCutoffV1 threadVisualDetailCutoff() noexcept;
static void setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept;
static std::uint32_t threadColumnOctaveDrop() noexcept;
static void setThreadVisualDetailProfileEnabled(bool enabled) noexcept;
static bool threadVisualDetailProfileEnabled() noexcept;
static void setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept;
static std::int64_t threadColumnInterpolationStrideSubvoxels() noexcept;
FieldBlockContract contract() const noexcept; FieldBlockContract contract() const noexcept;
const WorldAssemblyRecipeV1* recipe() const noexcept { return recipe_; } const WorldAssemblyRecipeV1* recipe() const noexcept { return recipe_; }
const WorldAssemblyStratumPlanV1* plan() const noexcept { return plan_; } const WorldAssemblyStratumPlanV1* plan() const noexcept { return plan_; }
@@ -1111,15 +1055,7 @@ namespace VoxelForgeCore
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version; // Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
// densite et hashes LOD0-3 restent inchanges. // densite et hashes LOD0-3 restent inchanges.
std::uint32_t farHorizonVersion = 3; std::uint32_t farHorizonVersion = 3;
// Display mesh only. Zero preserves the exact field. Collision, queries and edits // Linux research controls for reproducing L5 negative controls. Defaults preserve output.
// never set this option.
std::int64_t visualDetailPixelBudgetSubvoxels = 0;
std::int32_t visualDetailSampleStepVoxels = 0;
std::uint32_t visualDetailCallBudgetDivisor = 64;
bool profileVisualDetailOctaves = false;
bool visualDetailCutoffEnabled = false;
std::uint32_t visualDetailContractVersion = 0;
// Diagnostic controls used only by the Linux gate's historical negative controls.
std::uint32_t diagnosticGlobalOctaveDrop = 0; std::uint32_t diagnosticGlobalOctaveDrop = 0;
std::uint32_t diagnosticCellStride = 1; std::uint32_t diagnosticCellStride = 1;
std::uint32_t diagnosticColumnInterpolationStride = 1; std::uint32_t diagnosticColumnInterpolationStride = 1;
@@ -1905,13 +1905,6 @@ FieldDetailEnvelope FloatingIslandFieldV1::detailEnvelope() const noexcept
return result; return result;
} }
VisualDetailRecipeContractV1 FloatingIslandFieldV1::visualDetailContract() const
{
// Current island geometry uses shape and connector terms without an octave declaration.
// Returning an empty shared contract deliberately selects exact sampling.
return {};
}
FieldBlockContract FloatingIslandFieldV1::contract() const noexcept FieldBlockContract FloatingIslandFieldV1::contract() const noexcept
{ {
FieldBlockContract result; FieldBlockContract result;
+18 -55
View File
@@ -220,13 +220,6 @@ namespace VoxelForgeCore
return Hash; return Hash;
} }
std::uint64_t SaltVisualDetailHash(std::uint64_t Hash,std::uint32_t Version)
{
AddWord(Hash,0x5649534445544149ull,8);
AddWord(Hash,Version,4);
return Hash;
}
FStandaloneShapeWorld::EDetailVariant ToNative(DetailVariant value) FStandaloneShapeWorld::EDetailVariant ToNative(DetailVariant value)
{ {
switch(value) switch(value)
@@ -1337,25 +1330,6 @@ namespace VoxelForgeCore
std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options, std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options,
std::string& error) const std::string& error) const
{ {
struct FScopedHeightfieldVisualDetailState
{
VisualDetailCutoffV1 Cutoff=Heightfield2D::threadVisualDetailCutoff();
std::uint32_t OctaveDrop=Heightfield2D::threadColumnOctaveDrop();
bool bProfile=Heightfield2D::threadVisualDetailProfileEnabled();
std::int64_t InterpolationStride=
Heightfield2D::threadColumnInterpolationStrideSubvoxels();
~FScopedHeightfieldVisualDetailState()
{
Heightfield2D::setThreadVisualDetailCutoff(Cutoff);
Heightfield2D::setThreadColumnOctaveDrop(OctaveDrop);
Heightfield2D::setThreadVisualDetailProfileEnabled(bProfile);
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(InterpolationStride);
}
} VisualDetailState;
Heightfield2D::setThreadVisualDetailProfileEnabled(options.profileVisualDetailOctaves);
if(options.visualDetailCutoffEnabled
&&options.visualDetailContractVersion!=CurrentVisualDetailCutoffContractVersion)
{error="visual-detail recipe contract version is unsupported";return std::nullopt;}
// Fine owner tiles had zero direct-map conflict evictions in the cold audit. Keep their // Fine owner tiles had zero direct-map conflict evictions in the cold audit. Keep their
// single-probe path and use associativity for the coarse coordinate families that collide. // single-probe path and use associativity for the coarse coordinate families that collide.
// Les tuiles proches n'avaient aucun conflit; garder leur acces simple et associer les LOD grossiers. // Les tuiles proches n'avaient aucun conflit; garder leur acces simple et associer les LOD grossiers.
@@ -1712,19 +1686,20 @@ namespace VoxelForgeCore
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>( out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-FeatureListStart).count(); std::chrono::steady_clock::now()-FeatureListStart).count();
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries); const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
VisualDetailCutoffV1 DisplayCutoff; const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
DisplayCutoff.pixelBudgetSubvoxels=options.visualDetailPixelBudgetSubvoxels; ||options.diagnosticColumnInterpolationStride>1;
DisplayCutoff.sampleStepSubvoxels=DisplayCutoff.pixelBudgetSubvoxels>0 const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel:0; ?Heightfield2D::threadColumnOctaveDrop():0;
DisplayCutoff.callBudgetDivisor=std::max<std::uint32_t>(1, const std::int64_t PreviousInterpolationStride=bDiagnosticColumnSampling
options.visualDetailCallBudgetDivisor); ?Heightfield2D::threadColumnInterpolationStrideSubvoxels():0;
DisplayCutoff.apply=options.visualDetailCutoffEnabled; if(bDiagnosticColumnSampling)
Heightfield2D::setThreadVisualDetailCutoff(DisplayCutoff); {
Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop); Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop);
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels( Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(
options.diagnosticColumnInterpolationStride>1&&level>=3 options.diagnosticColumnInterpolationStride>1&&level>=3
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel ?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel
*options.diagnosticColumnInterpolationStride:0); *options.diagnosticColumnInterpolationStride:0);
}
const double ColumnMillisecondsBeforeMeshing= const double ColumnMillisecondsBeforeMeshing=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds; VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto MarchingCubesStart=std::chrono::steady_clock::now(); const auto MarchingCubesStart=std::chrono::steady_clock::now();
@@ -1732,6 +1707,11 @@ namespace VoxelForgeCore
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr, MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr, (options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
options.profileMesherStages?&McStageTimings:nullptr); options.profileMesherStages?&McStageTimings:nullptr);
if(bDiagnosticColumnSampling)
{
Heightfield2D::setThreadColumnOctaveDrop(PreviousOctaveDrop);
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(PreviousInterpolationStride);
}
const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>( const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MarchingCubesStart).count(); std::chrono::steady_clock::now()-MarchingCubesStart).count();
const double ColumnMillisecondsAfterMeshing= const double ColumnMillisecondsAfterMeshing=
@@ -1742,6 +1722,10 @@ namespace VoxelForgeCore
ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing); ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing);
if(options.profileMesherStages) if(options.profileMesherStages)
{ {
out.diagnostics.marchingCubesSkirtMilliseconds=McStageTimings.SkirtMilliseconds;
out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0,
out.diagnostics.marchingCubesExclusiveMilliseconds
-out.diagnostics.marchingCubesSkirtMilliseconds);
out.diagnostics.marchingCubesDensityGridMilliseconds=McStageTimings.DensityGridMilliseconds; out.diagnostics.marchingCubesDensityGridMilliseconds=McStageTimings.DensityGridMilliseconds;
out.diagnostics.marchingCubesCellClassificationMilliseconds= out.diagnostics.marchingCubesCellClassificationMilliseconds=
McStageTimings.CellClassificationMilliseconds; McStageTimings.CellClassificationMilliseconds;
@@ -1845,12 +1829,6 @@ namespace VoxelForgeCore
out.diagnostics.columnCacheHits=Columns.columnHits; out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses; out.diagnostics.columnCacheMisses=Columns.columnMisses;
out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses; out.diagnostics.columnCacheConflictMisses=Columns.columnConflictMisses;
out.diagnostics.visualDetailOctaveEvaluations=Columns.visualDetailOctaveEvaluations;
out.diagnostics.visualDetailOctaveBudgetEligible=Columns.visualDetailOctaveBudgetEligible;
out.diagnostics.visualDetailOctaveSkips=Columns.visualDetailOctaveSkips;
out.diagnostics.visualDetailOctaveMilliseconds=Columns.visualDetailOctaveMilliseconds;
out.diagnostics.visualDetailOctaveEligibleMilliseconds=
Columns.visualDetailOctaveEligibleMilliseconds;
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;
@@ -1920,21 +1898,6 @@ namespace VoxelForgeCore
out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion); out.payloadHash=SaltFarHorizonHash(out.payloadHash,options.farHorizonVersion);
out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion); out.topologyHash=SaltFarHorizonHash(out.topologyHash,options.farHorizonVersion);
} }
if(key.lod>=3&&options.visualDetailCutoffEnabled
&&options.visualDetailPixelBudgetSubvoxels>0)
{
const auto& DetailStats=Heightfield2D::threadColumnCacheStats();
const bool bVisualDetailChanged=std::any_of(
DetailStats.visualDetailOctaveSkips.begin(),
DetailStats.visualDetailOctaveSkips.end(),
[](std::uint64_t Skips){return Skips!=0;});
if(bVisualDetailChanged)
{
out.meshHash=SaltVisualDetailHash(out.meshHash,options.visualDetailContractVersion);
out.payloadHash=SaltVisualDetailHash(out.payloadHash,options.visualDetailContractVersion);
out.topologyHash=SaltVisualDetailHash(out.topologyHash,options.visualDetailContractVersion);
}
}
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true; out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
} }
out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>( out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
+25 -278
View File
@@ -1031,91 +1031,19 @@ namespace world_assembly_detail
// Audit-only override; normal tile generation resets this to zero before evaluating a recipe. // 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. // Surcharge reservee aux mesures; la generation remet zero avant d'evaluer une recette.
inline thread_local std::uint32_t HeightfieldColumnOctaveDrop = 0; inline thread_local std::uint32_t HeightfieldColumnOctaveDrop = 0;
inline thread_local VisualDetailCutoffV1 HeightfieldVisualDetailCutoff{};
inline thread_local bool HeightfieldVisualDetailProfileEnabled = false;
inline thread_local std::int64_t HeightfieldColumnInterpolationStrideSubvoxels = 0; inline thread_local std::int64_t HeightfieldColumnInterpolationStrideSubvoxels = 0;
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
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 baseWavelengthSubvoxels = 0,
std::int64_t maximumDisplacementSubvoxels = 0)
{ {
if(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels<=0
&&HeightfieldColumnOctaveDrop==0&&!HeightfieldVisualDetailProfileEnabled)
{
std::int64_t ExactTotal=0,ExactWeightSum=0,ExactWeight=Q16,ExactRidgeWeight=Q16;
for(std::uint32_t Octave=0;Octave<octaves;++Octave)
{
const std::int64_t Scale=1ll<<Octave;
const std::int64_t Noise=Perlin3DQ16(
xQ16*Scale,yQ16*Scale,zQ16*Scale);
std::int64_t Value=Noise;
if(ridged)
{
const std::int64_t Scaled=Noise*5/4;
const std::int64_t Folded=Q16-std::abs(Scaled);
Value=MulQ16(MulQ16(Folded,Folded),ExactRidgeWeight);
ExactRidgeWeight=std::clamp<std::int64_t>(Value*2,0,Q16);
}
ExactTotal+=MulQ16(Value,ExactWeight);
ExactWeightSum+=ExactWeight;
ExactWeight/=2;
}
if(ExactWeightSum==0)return 0;
const std::int64_t ExactAverage=(ExactTotal*Q16)/ExactWeightSum;
return static_cast<std::int32_t>(ridged?ExactAverage*2-Q16:ExactAverage);
}
std::int64_t total = 0, weightSum = 0, weight = Q16, ridgeWeight = Q16; std::int64_t total = 0, weightSum = 0, weight = Q16, ridgeWeight = Q16;
std::uint32_t effectiveOctaves=octaves; std::uint32_t effectiveOctaves=octaves;
if(HeightfieldColumnOctaveDrop!=0&&octaves!=0) if(HeightfieldColumnOctaveDrop!=0&&octaves!=0)
effectiveOctaves=std::max(1u,octaves-std::min(octaves-1,HeightfieldColumnOctaveDrop)); effectiveOctaves=std::max(1u,octaves-std::min(octaves-1,HeightfieldColumnOctaveDrop));
VisualDetailCutoffResultV1 DetailCutoff;
const bool bDeclaredVisualTerm=baseWavelengthSubvoxels>0
&&maximumDisplacementSubvoxels>0;
if(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels>0
&&HeightfieldColumnOctaveDrop==0&&baseWavelengthSubvoxels>0
&&maximumDisplacementSubvoxels>0)
{
const VisualDetailOctaveTermV1 Term{0,baseWavelengthSubvoxels,
maximumDisplacementSubvoxels,octaves,2u*static_cast<std::uint32_t>(Q16)};
DetailCutoff=FindVisualDetailTailCutoffV1(Term,HeightfieldVisualDetailCutoff);
if(DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply)
effectiveOctaves=std::min(effectiveOctaves,DetailCutoff.firstSkippedOctave);
}
if(HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm
&&DetailCutoff.firstSkippedOctave>0)
for(std::uint32_t octave=DetailCutoff.firstSkippedOctave;
octave<octaves&&octave<HeightfieldColumnStats.visualDetailOctaveBudgetEligible.size();++octave)
{
++HeightfieldColumnStats.visualDetailOctaveBudgetEligible[octave];
if(HeightfieldVisualDetailCutoff.apply)
++HeightfieldColumnStats.visualDetailOctaveSkips[octave];
}
std::int64_t fullWeightSum=0;
if(DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply)
{
std::int64_t fullWeight=Q16;
for(std::uint32_t octave=0;octave<octaves;++octave)
{fullWeightSum+=fullWeight;fullWeight/=2;}
}
for (std::uint32_t octave = 0; octave < effectiveOctaves; ++octave) for (std::uint32_t octave = 0; octave < effectiveOctaves; ++octave)
{ {
const std::int64_t scale = 1ll << octave; const std::int64_t scale = 1ll << octave;
const auto OctaveStart=HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm
?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{};
const std::int64_t noise = Perlin3DQ16(xQ16 * scale,yQ16 * scale,zQ16 * scale); const std::int64_t noise = Perlin3DQ16(xQ16 * scale,yQ16 * scale,zQ16 * scale);
if(HeightfieldVisualDetailProfileEnabled&&bDeclaredVisualTerm
&&octave<HeightfieldColumnStats.visualDetailOctaveEvaluations.size())
{
const double OctaveMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-OctaveStart).count();
++HeightfieldColumnStats.visualDetailOctaveEvaluations[octave];
HeightfieldColumnStats.visualDetailOctaveMilliseconds[octave]+=OctaveMilliseconds;
if(DetailCutoff.firstSkippedOctave>0&&octave>=DetailCutoff.firstSkippedOctave)
HeightfieldColumnStats.visualDetailOctaveEligibleMilliseconds[octave]
+=OctaveMilliseconds;
}
std::int64_t value = noise; std::int64_t value = noise;
if (ridged) if (ridged)
{ {
@@ -1129,24 +1057,17 @@ namespace world_assembly_detail
weight /= 2; weight /= 2;
} }
if (weightSum == 0) return 0; if (weightSum == 0) return 0;
// The cutoff retains the original denominator: omitted zero-mean terms do not const std::int64_t average = (total * Q16) / weightSum;
// renormalize the surviving terrain. The historical diagnostic drop above keeps
// its old renormalized path so the quality gate can reproduce S13.
const std::int64_t denominator=HeightfieldColumnOctaveDrop==0
&&DetailCutoff.firstSkippedOctave>0&&HeightfieldVisualDetailCutoff.apply
?fullWeightSum:weightSum;
const std::int64_t average = (total * Q16) / denominator;
return static_cast<std::int32_t>(ridged ? average * 2 - Q16 : average); return static_cast<std::int32_t>(ridged ? average * 2 - Q16 : average);
} }
inline std::int32_t ReliefQ16(std::uint32_t seed, std::int64_t x, inline std::int32_t ReliefQ16(std::uint32_t seed, std::int64_t x,
std::int64_t y, std::int64_t wavelength, std::int64_t contrastQ16, std::int64_t y, std::int64_t wavelength, std::int64_t contrastQ16)
std::int64_t maximumDisplacementSubvoxels=0)
{ {
const std::int32_t noise = FractalNoiseQ16( const std::int32_t noise = FractalNoiseQ16(
NoiseCoordinateQ16(x,wavelength)+SeedOffsetQ16(seed,730), NoiseCoordinateQ16(x,wavelength)+SeedOffsetQ16(seed,730),
NoiseCoordinateQ16(y,wavelength)+SeedOffsetQ16(seed,210), NoiseCoordinateQ16(y,wavelength)+SeedOffsetQ16(seed,210),
SeedOffsetQ16(seed,50),2,false,wavelength,maximumDisplacementSubvoxels); SeedOffsetQ16(seed,50),2);
const std::int64_t raw = Q16/2 + noise/2; const std::int64_t raw = Q16/2 + noise/2;
const std::int64_t contrasted = Q16/2 + MulQ16(raw-Q16/2,contrastQ16); const std::int64_t contrasted = Q16/2 + MulQ16(raw-Q16/2,contrastQ16);
return static_cast<std::int32_t>(SmoothQ16(static_cast<std::uint32_t>( return static_cast<std::int32_t>(SmoothQ16(static_cast<std::uint32_t>(
@@ -1182,22 +1103,15 @@ namespace world_assembly_detail
qx+=WarpDisplacement(wx,biome.heightWarpStrengthSubvoxels); qx+=WarpDisplacement(wx,biome.heightWarpStrengthSubvoxels);
qy+=WarpDisplacement(wy,biome.heightWarpStrengthSubvoxels); qy+=WarpDisplacement(wy,biome.heightWarpStrengthSubvoxels);
} }
const bool bDetailContractSafe=biome.cliffStrengthQ16==0;
const std::int64_t reliefInfluence=(std::abs(biome.mountainAmplitudeSubvoxels)
+std::abs(biome.continentAmplitudeSubvoxels)*11/40)
*std::clamp<std::int64_t>(biome.reliefStrengthQ16,0,Q16)
*std::max<std::int64_t>(0,biome.reliefContrastQ16)*3/(4*Q16*Q16);
const std::int32_t relief=ReliefQ16(seed,x,y,biome.reliefWavelengthSubvoxels, const std::int32_t relief=ReliefQ16(seed,x,y,biome.reliefWavelengthSubvoxels,
biome.reliefContrastQ16,bDetailContractSafe?reliefInfluence:0); biome.reliefContrastQ16);
const std::int32_t mountainous=static_cast<std::int32_t>(Q16 const std::int32_t mountainous=static_cast<std::int32_t>(Q16
+MulQ16(relief-Q16,biome.reliefStrengthQ16)); +MulQ16(relief-Q16,biome.reliefStrengthQ16));
if(outRelief)*outRelief=mountainous; if(outRelief)*outRelief=mountainous;
const std::int64_t contX=NoiseCoordinateQ16(qx,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,310); const std::int64_t contX=NoiseCoordinateQ16(qx,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,310);
const std::int64_t contY=NoiseCoordinateQ16(qy,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,570); const std::int64_t contY=NoiseCoordinateQ16(qy,biome.continentWavelengthSubvoxels)+SeedOffsetQ16(seed,570);
const std::int64_t contZ=SeedOffsetQ16(seed,70); const std::int64_t contZ=SeedOffsetQ16(seed,70);
const std::int64_t continentInfluence=(std::abs(biome.continentAmplitudeSubvoxels)*775+999)/1000; const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4);
const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4,false,
biome.continentWavelengthSubvoxels,bDetailContractSafe?continentInfluence:0);
const std::int64_t continentScale=Q16*45/100+MulQ16(mountainous,Q16*55/100); const std::int64_t continentScale=Q16*45/100+MulQ16(mountainous,Q16*55/100);
std::int64_t height=biome.baseHeightSubvoxels std::int64_t height=biome.baseHeightSubvoxels
+MulQ16(MulQ16(continent,biome.continentAmplitudeSubvoxels),continentScale)/2; +MulQ16(MulQ16(continent,biome.continentAmplitudeSubvoxels),continentScale)/2;
@@ -1206,19 +1120,14 @@ namespace world_assembly_detail
const std::int64_t mountainX=NoiseCoordinateQ16(qx,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,990); const std::int64_t mountainX=NoiseCoordinateQ16(qx,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,990);
const std::int64_t mountainY=NoiseCoordinateQ16(qy,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,770); const std::int64_t mountainY=NoiseCoordinateQ16(qy,biome.mountainWavelengthSubvoxels)+SeedOffsetQ16(seed,770);
const std::int64_t mountainZ=SeedOffsetQ16(seed,90); const std::int64_t mountainZ=SeedOffsetQ16(seed,90);
const std::int32_t ridge=FractalNoiseQ16(mountainX,mountainY,mountainZ,4,true, const std::int32_t ridge=FractalNoiseQ16(mountainX,mountainY,mountainZ,4,true);
biome.mountainWavelengthSubvoxels,
bDetailContractSafe?std::abs(biome.mountainAmplitudeSubvoxels)*2:0);
const std::int64_t ridgeHeight=std::clamp<std::int64_t>((ridge+Q16)/2,0,Q16); const std::int64_t ridgeHeight=std::clamp<std::int64_t>((ridge+Q16)/2,0,Q16);
height+=MulQ16(MulQ16(ridgeHeight,biome.mountainAmplitudeSubvoxels),mountainous); height+=MulQ16(MulQ16(ridgeHeight,biome.mountainAmplitudeSubvoxels),mountainous);
} }
const std::int64_t detailX=NoiseCoordinateQ16(x,biome.detailWavelengthSubvoxels)+11*Q16; const std::int64_t detailX=NoiseCoordinateQ16(x,biome.detailWavelengthSubvoxels)+11*Q16;
const std::int64_t detailY=NoiseCoordinateQ16(y,biome.detailWavelengthSubvoxels)+22*Q16; const std::int64_t detailY=NoiseCoordinateQ16(y,biome.detailWavelengthSubvoxels)+22*Q16;
const std::int64_t detailZ=SeedOffsetQ16(seed,130); const std::int64_t detailZ=SeedOffsetQ16(seed,130);
const std::int32_t detailNoise=FractalNoiseQ16(detailX,detailY,detailZ,3,false, height+=MulQ16(FractalNoiseQ16(detailX,detailY,detailZ,3),biome.detailAmplitudeSubvoxels);
biome.detailWavelengthSubvoxels,
bDetailContractSafe?std::abs(biome.detailAmplitudeSubvoxels):0);
height+=MulQ16(detailNoise,biome.detailAmplitudeSubvoxels);
return height; return height;
} }
@@ -1415,24 +1324,21 @@ namespace world_assembly_detail
{ {
const std::int64_t nx=NoiseCoordinateQ16(qx,swellWave)+SeedOffsetQ16(recipe.seed,190); const std::int64_t nx=NoiseCoordinateQ16(qx,swellWave)+SeedOffsetQ16(recipe.seed,190);
const std::int64_t ny=NoiseCoordinateQ16(qy,swellWave)+13*Q16; const std::int64_t ny=NoiseCoordinateQ16(qy,swellWave)+13*Q16;
const std::int32_t swell=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,50),3, const std::int32_t swell=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,50),3);
false,swellWave,(std::abs(undulation)*5+3)/4);
cap+=WarpDisplacement(swell,undulation); cap+=WarpDisplacement(swell,undulation);
} }
if(detail>0) if(detail>0)
{ {
const std::int64_t nx=NoiseCoordinateQ16(x,detailWave)+5*Q16; const std::int64_t nx=NoiseCoordinateQ16(x,detailWave)+5*Q16;
const std::int64_t ny=NoiseCoordinateQ16(y,detailWave)+6*Q16; const std::int64_t ny=NoiseCoordinateQ16(y,detailWave)+6*Q16;
const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,210),3, const std::int32_t noise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,210),3);
false,detailWave,(std::abs(detail)*5+3)/4);
cap-=WarpDisplacement(std::abs(noise),detail); cap-=WarpDisplacement(std::abs(noise),detail);
} }
if(ridge>0) if(ridge>0)
{ {
const std::int64_t nx=NoiseCoordinateQ16(qx,ridgeWave)+31*Q16; const std::int64_t nx=NoiseCoordinateQ16(qx,ridgeWave)+31*Q16;
const std::int64_t ny=NoiseCoordinateQ16(qy,ridgeWave)+47*Q16; const std::int64_t ny=NoiseCoordinateQ16(qy,ridgeWave)+47*Q16;
const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4, const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4,true);
true,ridgeWave,std::abs(ridge)*2);
const std::int64_t hang=std::clamp<std::int64_t>((ridgeNoise+Q16)/2,0,Q16); const std::int64_t hang=std::clamp<std::int64_t>((ridgeNoise+Q16)/2,0,Q16);
cap-=MulQ16(hang,ridge); cap-=MulQ16(hang,ridge);
} }
@@ -1455,10 +1361,6 @@ namespace world_assembly_detail
const WorldAssemblyStratumPlanV1* plan = nullptr; const WorldAssemblyStratumPlanV1* plan = nullptr;
std::uint64_t recipeHash = 0, planHash = 0; std::uint64_t recipeHash = 0, planHash = 0;
std::int64_t x = 0, y = 0; std::int64_t x = 0, y = 0;
std::int64_t detailBudgetSubvoxels = 0,detailStepSubvoxels = 0;
std::uint32_t detailCallBudgetDivisor = 0;
bool detailApply = false;
std::uint32_t octaveDrop = 0;
HeightfieldPointSample column{}; HeightfieldPointSample column{};
}; };
// Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that // Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that
@@ -1471,6 +1373,7 @@ namespace world_assembly_detail
std::array<HeightfieldColumnCacheEntry, 65536>> HeightfieldColumnCache; std::array<HeightfieldColumnCacheEntry, 65536>> HeightfieldColumnCache;
inline thread_local VoxelForgeCoreThreadLocal::FLazy< inline thread_local VoxelForgeCoreThreadLocal::FLazy<
std::array<std::uint8_t, HeightfieldColumnCacheSetCount>> HeightfieldColumnCacheNextWay; std::array<std::uint8_t, HeightfieldColumnCacheSetCount>> HeightfieldColumnCacheNextWay;
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false; inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe, inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
@@ -1480,19 +1383,9 @@ namespace world_assembly_detail
// entropy from widely spaced coarse LOD coordinates in its low 16 bits. // entropy from widely spaced coarse LOD coordinates in its low 16 bits.
// Melanger tous les bits de coordonnee avant le masque; l'ancien hash perdait de // Melanger tous les bits de coordonnee avant le masque; l'ancien hash perdait de
// l'entropie sur les coordonnees espacees des LOD grossiers. // l'entropie sur les coordonnees espacees des LOD grossiers.
const std::uint64_t DetailContext=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels>0
?static_cast<std::uint64_t>(HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels)
^std::rotl(static_cast<std::uint64_t>(
HeightfieldVisualDetailCutoff.sampleStepSubvoxels),11)
^(static_cast<std::uint64_t>(
HeightfieldVisualDetailCutoff.callBudgetDivisor)<<19)
^(static_cast<std::uint64_t>(HeightfieldVisualDetailCutoff.apply)<<53)
:0;
std::uint64_t mixed=static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull std::uint64_t mixed=static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,29) ^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,29)
^recipe.contentHash^std::rotl(plan.contentHash,17) ^recipe.contentHash^std::rotl(plan.contentHash,17);
^DetailContext
^(static_cast<std::uint64_t>(HeightfieldColumnOctaveDrop)<<37);
mixed^=mixed>>30; mixed^=mixed>>30;
mixed*=0xBF58476D1CE4E5B9ull; mixed*=0xBF58476D1CE4E5B9ull;
mixed^=mixed>>27; mixed^=mixed>>27;
@@ -1538,8 +1431,7 @@ namespace world_assembly_detail
const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700); const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700);
const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900); const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900);
const std::int32_t noise=FractalNoiseQ16(nx,ny, const std::int32_t noise=FractalNoiseQ16(nx,ny,
SeedOffsetQ16(featureSeed,4100),3,false,wave, SeedOffsetQ16(featureSeed,4100),3);
std::abs(feature.parametersSubvoxels[0]));
cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade); cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade);
} }
} }
@@ -1650,14 +1542,8 @@ namespace world_assembly_detail
C11.capHeightSubvoxels); C11.capHeightSubvoxels);
return Out; return Out;
} }
const std::int64_t DetailBudget=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels; if(HeightfieldColumnOctaveDrop!=0)
const bool bVisualContextActive=DetailBudget>0; return EvaluateColumn(recipe,plan,x,y);
const std::int64_t DetailStep=DetailBudget>0
?HeightfieldVisualDetailCutoff.sampleStepSubvoxels:0;
const std::uint32_t DetailDivisor=DetailBudget>0
?HeightfieldVisualDetailCutoff.callBudgetDivisor:0;
const bool DetailApply=DetailBudget>0&&HeightfieldVisualDetailCutoff.apply;
const std::uint32_t OctaveDrop=HeightfieldColumnOctaveDrop;
auto& cache=HeightfieldColumnCache.Get(); auto& cache=HeightfieldColumnCache.Get();
auto& nextWay=HeightfieldColumnCacheNextWay.Get(); auto& nextWay=HeightfieldColumnCacheNextWay.Get();
HeightfieldColumnCacheEntry* replacement=nullptr; HeightfieldColumnCacheEntry* replacement=nullptr;
@@ -1668,23 +1554,11 @@ namespace world_assembly_detail
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull) const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23) ^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe)) ^static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&recipe))
^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41) ^std::rotl(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(&plan)),41);
^static_cast<std::uint64_t>(DetailBudget)
^std::rotl(static_cast<std::uint64_t>(DetailStep),11)
^(static_cast<std::uint64_t>(DetailDivisor)<<19)
^(static_cast<std::uint64_t>(DetailApply)<<53)
^(static_cast<std::uint64_t>(OctaveDrop)<<37);
replacement=&cache[static_cast<std::size_t>(mixed)&(cache.size()-1)]; replacement=&cache[static_cast<std::size_t>(mixed)&(cache.size()-1)];
if(replacement->recipe==&recipe&&replacement->plan==&plan if(replacement->recipe==&recipe&&replacement->plan==&plan
&&replacement->recipeHash==recipe.contentHash &&replacement->recipeHash==recipe.contentHash
&&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y &&replacement->planHash==plan.contentHash&&replacement->x==x&&replacement->y==y)
&&replacement->octaveDrop==OctaveDrop
&&(bVisualContextActive
?replacement->detailBudgetSubvoxels==DetailBudget
&&replacement->detailStepSubvoxels==DetailStep
&&replacement->detailCallBudgetDivisor==DetailDivisor
&&replacement->detailApply==DetailApply
:replacement->detailBudgetSubvoxels==0))
{++HeightfieldColumnStats.columnHits;return replacement->column;} {++HeightfieldColumnStats.columnHits;return replacement->column;}
if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses; if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses;
} }
@@ -1698,14 +1572,7 @@ namespace world_assembly_detail
auto& candidate=cache[first+way]; auto& candidate=cache[first+way];
if(candidate.recipe==&recipe&&candidate.plan==&plan if(candidate.recipe==&recipe&&candidate.plan==&plan
&&candidate.recipeHash==recipe.contentHash&&candidate.planHash==plan.contentHash &&candidate.recipeHash==recipe.contentHash&&candidate.planHash==plan.contentHash
&&candidate.x==x&&candidate.y==y &&candidate.x==x&&candidate.y==y)
&&candidate.octaveDrop==OctaveDrop
&&(bVisualContextActive
?candidate.detailBudgetSubvoxels==DetailBudget
&&candidate.detailStepSubvoxels==DetailStep
&&candidate.detailCallBudgetDivisor==DetailDivisor
&&candidate.detailApply==DetailApply
:candidate.detailBudgetSubvoxels==0))
{++HeightfieldColumnStats.columnHits;return candidate.column;} {++HeightfieldColumnStats.columnHits;return candidate.column;}
if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays) if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays)
replacementWay=way; replacementWay=way;
@@ -1736,8 +1603,7 @@ namespace world_assembly_detail
HeightfieldColumnStats.coordinateCaptureComplete=false; HeightfieldColumnStats.coordinateCaptureComplete=false;
} }
} }
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y, entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column};
DetailBudget,DetailStep,DetailDivisor,DetailApply,OctaveDrop,column};
return column; return column;
} }
@@ -1827,48 +1693,6 @@ namespace detail_world_assembly
} }
} }
VisualDetailCutoffResultV1 FindVisualDetailTailCutoffV1(
const VisualDetailOctaveTermV1& Term,const VisualDetailCutoffV1& Cutoff) noexcept
{
VisualDetailCutoffResultV1 Result;
if(Term.octaveCount<2||Term.baseWavelengthSubvoxels<=0
||Term.maximumDisplacementSubvoxels<=0||Cutoff.pixelBudgetSubvoxels<=0
||Cutoff.sampleStepSubvoxels<=0||Cutoff.callBudgetDivisor==0)return Result;
std::int64_t TotalWeight=0,Weight=65536;
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
{TotalWeight+=Weight;Weight/=2;}
if(TotalWeight<=0)return Result;
const std::int64_t PerCallBudget=std::max<std::int64_t>(1,
Cutoff.pixelBudgetSubvoxels/Cutoff.callBudgetDivisor);
for(std::uint32_t First=1;First<Term.octaveCount;++First)
{
bool bSubSampled=true;std::int64_t TailWeight=0;Weight=65536;
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
{
if(Octave>=First)
{
const std::int64_t Wave=Term.baseWavelengthSubvoxels
/static_cast<std::int64_t>(1ull<<std::min<std::uint32_t>(Octave,62));
bSubSampled=bSubSampled&&Wave<=2*Cutoff.sampleStepSubvoxels;
TailWeight+=Weight;
}
Weight/=2;
}
if(!bSubSampled)continue;
const long double Bound=static_cast<long double>(Term.maximumDisplacementSubvoxels)
*Term.maximumNoiseAbsQ16*TailWeight
/(65536.0L*TotalWeight);
const std::int64_t Error=static_cast<std::int64_t>(std::ceil(Bound));
if(Error<=PerCallBudget)
{
Result.firstSkippedOctave=First;
Result.maximumErrorSubvoxels=Error;
break;
}
}
return Result;
}
std::optional<WorldAssemblyManifest> WorldAssemblyManifest::FromJson( std::optional<WorldAssemblyManifest> WorldAssemblyManifest::FromJson(
std::string_view json, std::string& error) std::string_view json, std::string& error)
{ {
@@ -2264,11 +2088,6 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
Stats.columnMisses=0; Stats.columnMisses=0;
Stats.columnConflictMisses=0; Stats.columnConflictMisses=0;
Stats.columnComputeMilliseconds=0.0; Stats.columnComputeMilliseconds=0.0;
Stats.visualDetailOctaveEvaluations.fill(0);
Stats.visualDetailOctaveBudgetEligible.fill(0);
Stats.visualDetailOctaveSkips.fill(0);
Stats.visualDetailOctaveMilliseconds.fill(0.0);
Stats.visualDetailOctaveEligibleMilliseconds.fill(0.0);
Stats.captureCoordinates=captureCoordinates; Stats.captureCoordinates=captureCoordinates;
Stats.coordinateCaptureComplete=true; Stats.coordinateCaptureComplete=true;
Stats.evaluatedCoordinates.clear(); Stats.evaluatedCoordinates.clear();
@@ -2277,32 +2096,18 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept
{ world_assembly_detail::HeightfieldColumnCacheLegacyDirectMapped=enabled; } { world_assembly_detail::HeightfieldColumnCacheLegacyDirectMapped=enabled; }
void Heightfield2D::setThreadVisualDetailCutoff(VisualDetailCutoffV1 cutoff) noexcept
{world_assembly_detail::HeightfieldVisualDetailCutoff=cutoff;}
VisualDetailCutoffV1 Heightfield2D::threadVisualDetailCutoff() noexcept
{return world_assembly_detail::HeightfieldVisualDetailCutoff;}
void Heightfield2D::setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept void Heightfield2D::setThreadColumnOctaveDrop(std::uint32_t octaveDrop) noexcept
{world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;} { world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop; }
std::uint32_t Heightfield2D::threadColumnOctaveDrop() noexcept std::uint32_t Heightfield2D::threadColumnOctaveDrop() noexcept
{return world_assembly_detail::HeightfieldColumnOctaveDrop;} { return world_assembly_detail::HeightfieldColumnOctaveDrop; }
void Heightfield2D::setThreadVisualDetailProfileEnabled(bool enabled) noexcept
{
world_assembly_detail::HeightfieldVisualDetailProfileEnabled=enabled;
world_assembly_detail::HeightfieldColumnStats.profileVisualDetailOctaves=enabled;
}
bool Heightfield2D::threadVisualDetailProfileEnabled() noexcept
{return world_assembly_detail::HeightfieldVisualDetailProfileEnabled;}
void Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept void Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(std::int64_t stride) noexcept
{world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max<std::int64_t>(0,stride);} { world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max<std::int64_t>(0,stride); }
std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept
{return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels;} { return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels; }
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
{ return world_assembly_detail::HeightfieldColumnStats; } { return world_assembly_detail::HeightfieldColumnStats; }
@@ -2500,64 +2305,6 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
return out; return out;
} }
VisualDetailRecipeContractV1 Heightfield2D::visualDetailContract() const
{
VisualDetailRecipeContractV1 Contract;
if(!isValid()||recipe_->evaluatorVersion<2)return Contract;
const auto Add=[&](std::uint64_t Id,std::int64_t Wavelength,
std::int64_t Amplitude,std::uint32_t Octaves)
{
if(Wavelength>0&&Amplitude>0)
Contract.terms.push_back({Id,Wavelength,Amplitude,Octaves,
2u*static_cast<std::uint32_t>(world_assembly_detail::Q16)});
};
for(const auto& Biome:recipe_->biomes)
{
if(Biome.cliffStrengthQ16==0)
{
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|1u,
Biome.continentWavelengthSubvoxels,
(std::abs(Biome.continentAmplitudeSubvoxels)*775+999)/1000,4);
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|2u,
Biome.mountainWavelengthSubvoxels,
std::abs(Biome.mountainAmplitudeSubvoxels)*2,4);
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|3u,
Biome.detailWavelengthSubvoxels,
std::abs(Biome.detailAmplitudeSubvoxels),3);
const std::int64_t ReliefInfluence=(std::abs(Biome.mountainAmplitudeSubvoxels)
+std::abs(Biome.continentAmplitudeSubvoxels)*11/40)
*std::clamp<std::int64_t>(Biome.reliefStrengthQ16,0,world_assembly_detail::Q16)
*std::max<std::int64_t>(0,Biome.reliefContrastQ16)*3
/(4*world_assembly_detail::Q16*world_assembly_detail::Q16);
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|4u,
Biome.reliefWavelengthSubvoxels,ReliefInfluence,2);
}
const std::int64_t CapUndulation=Biome.hasCapProfile
?Biome.capUndulationSubvoxels:plan_->capUndulationSubvoxels;
const std::int64_t CapWave=Biome.hasCapProfile
?Biome.capWavelengthSubvoxels:plan_->capWavelengthSubvoxels;
const std::int64_t CapDetail=Biome.hasCapProfile
?Biome.capDetailAmplitudeSubvoxels:plan_->capDetailAmplitudeSubvoxels;
const std::int64_t CapDetailWave=Biome.hasCapProfile
?Biome.capDetailWavelengthSubvoxels:plan_->capDetailWavelengthSubvoxels;
const std::int64_t CapRidge=Biome.hasCapProfile
?Biome.capRidgeAmplitudeSubvoxels:plan_->capRidgeAmplitudeSubvoxels;
const std::int64_t CapRidgeWave=Biome.hasCapProfile
?Biome.capRidgeWavelengthSubvoxels:plan_->capRidgeWavelengthSubvoxels;
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|5u,CapWave,
(std::abs(CapUndulation)*5+3)/4,3);
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|6u,CapDetailWave,
(std::abs(CapDetail)*5+3)/4,3);
Add((static_cast<std::uint64_t>(Biome.stableId)<<8)|7u,CapRidgeWave,
std::abs(CapRidge)*2,4);
}
for(const auto& Feature:plan_->features)
if(Feature.type==3)
Add(Feature.stableId,Feature.parametersSubvoxels[1],
std::abs(Feature.parametersSubvoxels[0]),3);
return Contract;
}
FieldBlockContract Heightfield2D::contract() const noexcept FieldBlockContract Heightfield2D::contract() const noexcept
{ {
return {ChannelBit(FieldChannel::biome), return {ChannelBit(FieldChannel::biome),
@@ -0,0 +1,102 @@
# Linux6 report
## Result
The screen-space gate rejects today’s far meshes at every sampled LOD3–8: the largest measured same-field error is 92.0 px, and the largest sampled swap pop is 70.3 px at LOD6→5. Same-LOD regeneration is exact, the reported swaps have no cracks or floaters, and no display mesh changed in this round.
Source base: `wip/l5-far-cutoff` at `b21a19d`; L4 reference: `7770074`.
## Screen-space method
For one surface tile nearest the L1 camera at each LOD, the gate rasterizes triangles on a 65×65 XY grid, excluding skirts. It compares the mesh hit to direct `Heightfield2D` ground/cap roots; slopes use ±1-voxel finite differences. For vertical residual `Δz` and slopes `sx,sy`, it computes normal distance `|Δz|/sqrt(1+sx²+sy²)`, projects that normal onto the camera’s screen plane, then divides by the one-pixel world distance at that LOD’s closest display distance. The view uses 90° horizontal FOV, 2560 px, yaw 0°, pitch −22°; one pixel is 0.000613592 rad. Same-LOD regeneration is the mesh reference for swaps; direct field roots are the reference for true surface error.
The known failure control is a 1 m cell on an 89.427° plane, shifted sideways by 5 mm (0.005 cell): its vertical metric says 8.488 px, while its true normal distance is 5 mm and projected screen error is 0.0849 px. This confirms that vertical height can badly overstate visible displacement.
| LOD | Closest distance (m) | Field error p50 / p99 / max (px) | Max normal distance (m) |
|---:|---:|---:|---:|
| 3 | 96 | 0.81 / 4.16 / 6.00 | 0.423 |
| 4 | 192 | 1.01 / 5.12 / 7.93 | 0.952 |
| 5 | 384 | 0.79 / 16.37 / 92.02 | 25.122 |
| 6 | 768 | 0.51 / 9.75 / 70.34 | 37.644 |
| 7 | 1,536 | 0.29 / 9.04 / 42.36 | 42.714 |
| 8 | 3,072 | 0.83 / 18.03 / 47.18 | 93.175 |
| Swap | Distance (m) | Pop p50 / p99 / max (px) | Max normal distance (m) |
|---|---:|---:|---:|
| 3→2 | 96 | 0.61 / 3.77 / 6.05 | 0.367 |
| 4→3 | 192 | 0.75 / 4.63 / 7.51 | 0.896 |
| 5→4 | 384 | 0.00 / 9.58 / 31.78 | 8.139 |
| 6→5 | 768 | 0.00 / 9.75 / 70.34 | 37.644 |
| 7→6 | 1,536 | 0.00 / 9.04 / 42.36 | 42.714 |
| 8→7 | 3,072 | 0.83 / 18.03 / 47.18 | 93.175 |
The tails are not confined to cliffs. The field maximum slopes at LOD5–8 are 8.34°, 14.09°, 6.76°, and 9.33°; LOD5’s p99 is 19.8 px below 30° and 21.1 px at 45–60°. The sampled LOD6→5 maximum is at `(-472, -1432, -463.151) m`; [the marked offscreen capture](captures/lod6_to_5_max_pop.jpg) shows that location at the 768 m swap distance. This is a sampled-tile result, not a whole-world supremum.
All same-LOD mesh comparisons at LOD3–8 pass with zero error. The S13 octave-drop and corrected stride-2 column-interpolation controls are rejected at every LOD3–8, as are missing displayed surface, 2 m floating surface, and missing fine-ring injections. The measured swap rows have zero cracks and floaters; they reject solely on displacement.
## Far worker time, W12
The L1 view has 2553 tiles. Clean-run phase totals for LOD3–8 are shown below. A separate temporary timer measured complete `FractalNoiseQ16` calls; “column outside” is clean column time minus that subtotal. The timer added 3–6% to worker time, so the split between those two column subphases is approximate; it has been removed from the final tree.
| LOD | Worker (s) | Column outside noise (ms) | Fractal/noise calls (ms) | MC (ms) | Ground refine (ms) | Cap refine (ms) | Skirts (ms) | Assembly (ms) |
|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| 3 | 1.084 | 171 | 276 | 473 | 0 | 0 | 118 | 33 |
| 4 | 2.581 | 1,000 | 534 | 440 | 0 | 304 | 196 | 102 |
| 5 | 1.829 | 658 | 358 | 100 | 464 | 22 | 161 | 70 |
| 6 | 2.761 | 1,608 | 284 | 59 | 385 | 58 | 162 | 70 |
| 7 | 1.192 | 234 | 198 | 32 | 275 | 202 | 153 | 96 |
| 8 | 2.224 | 264 | 469 | 256 | 424 | 299 | 333 | 177 |
| **Total** | **11.671** | **3,934** | **2,119** | **1,360** | **1,548** | **885** | **1,123** | **548** |
Column work outside the fractal calls is the largest single phase (33.7% of far worker time); all column work is 51.9%. Ground and cap refinement total 2.43 worker-s, skirts 1.12 worker-s, MC 1.36 worker-s, and assembly 0.55 worker-s. The earlier 7–13% profile timed individual Perlin calls; the L6 subtotal times complete fractal calls, including their octave accumulation.
The listed subphases total 11.52 worker-s; about 0.15 worker-s remains in timer and per-tile residuals.
The overlap scan counted about 411,000 cross-worker neighboring duplicate XY evaluations at LOD3–8 (about 166,000 at LOD4 alone). Exact column reuse is the largest plausible lever because it targets the largest phase. I did not build it: no candidate has yet shown a screen-gated, greater-than-3% whole-view win. Stride-2 interpolation is not a safe substitute; its control fails at every far LOD.
## Cost with the feature off
Three interleaved W12 runs per binary, same L1 camera and forced generation. `whole_view_ready` is the comparison metric; the six whole-view hashes matched exactly.
| Binary | Runs (s) | Median |
|---|---|---:|
| L4 reference | 2.082, 2.180, 2.183 | 2.180 s |
| L6 cleaned tree | 2.127, 2.185, 2.223 | 2.185 s (+0.2%) |
Run-to-run spread was about 0.10 s, so the 5 ms median difference is within noise. The no-op visual cutoff contract, plan, and CLI flag were removed. The opt-in research gate retains its negative controls; skirt timing uses the existing optional stage-timing pointer, which is null on the default path. Governor stayed `powersave`; runs used CPUs 0–11, W12, 6 GiB address-space limits, and normal priority. Peak runtime RSS was 403 MiB; peak build process RSS was 874 MiB (GCC).
## Determinism and hashes
Clang 19 `-ffp-contract=off` W1/W12 and GCC default W1/W12 produced the same hashes. LOD0–2, exact queries, and all whole-view hashes equal L4; no recipe bump is needed.
- Recipe / plan / manifest: `83b616a3f4fe781d` / `1ca84619c8600ee5` / `e55991385bf0ca29` (recipe version 1).
- Whole view (density / mesh / payload / topology): `c10697a8f48380c7` / `e75c9119a1498cd5` / `49a9a8d1835a6c8c` / `edcb23288b5e8724`.
- Full detail (density / mesh / payload / topology): `0ee0579bd15cbbe4` / `32e5d4964d610fee` / `1932b96ee4b1d910` / `69743c6bad3447dc`.
- Exact queries (density / unclamped / detail / collision): `e4d4a8eb95b35933` / `e4d4a8eb95b35933` / `efcba20c0c70c415` / `e604823a249029bf`.
- SHA-256 L4 binary / Clang L6 binary / GCC L6 binary: `6efb8f19a41ca3cd23aca9294ceee1e9cdd1d3dc8da23b3ee61ef8ae943aa763` / `3f5a4f88b0d911e8e66af9ef72e294926a5507a73d44fcfe868df27c666bf01e` / `de872e8fdaa9f40e8a6c096700fd998bcf35f232b06f2b4e35a374b4b0e9fa07`.
- Capture JPEG SHA-256: `483b434f3ad752dd09b82cd1c78fe81ee8cd31803ecf4dcd3c2918a8ae5d66b1` (156 KiB).
## Failed or retried runs
- First Clang build failed on a missing view argument in `LinuxScreenSpaceGate.h`; fixed and rebuilt successfully.
- First gate pass paired extra mesh hits incorrectly and reported same-LOD floaters at LOD5–8. The reference pairing was corrected; the final gate reports zero same-LOD displacement and no floaters.
- A `/usr/bin/time -v` wrapper attempt exited 127 because that binary is unavailable; the runs were retried with `/proc` RSS monitoring.
- The final quality gates exit 6 by design because measured displacement exceeds the 1 px rule. Builds, captures, determinism runs, and cost runs completed successfully. EGL could not write `~/.cache` in this environment, disabled shader caching, and rendered successfully on llvmpipe.
## Note for the owner
Today’s `assembler/world` remains at L4; this sampled view measures up to 92.0 px from the field and a 70.3 px LOD6→5 pop at the tested display distances, with the largest point marked in the capture. This round changes no default mesh or recipe output, removes the no-op cutoff, and leaves W12 cost within run-to-run noise.
## Orchestrator review (Claude)
- Cost re-run by me (interleaved W12, L1 view): L4 2.098/2.138/2.171 s vs L6 2.116/2.150/2.197 s
(median +0.6%, within noise), density `c10697a8f48380c7`; the L6 binary SHA matches the report.
Merged into `assembler/world` with L5 squashed in.
- The marked LOD6→5 maximum sits on the **rim of a flat-topped hill (mesa edge)**, not on open gentle
ground: the "8–14° slope" is the field slope at the sample, on the plateau top beside the drop. The
coarse mesh rounds the rim by about a cell (16–32 m at LOD5–6), so the pop is a real **silhouette snap
at sharp rims** (tens of px at 768 m), while p50 stays at 0 px. That is ordinary LOD-switch behaviour
without geomorphing or cross-fade, so a fix belongs in rendering (Unreal), not in generation. The
owner decides whether it is visible enough to matter.
- Next speed lever, per the far breakdown: exact cross-tile column reuse (column work outside noise is
34% of far worker time; ~411k duplicate XY evaluations at LOD3–8).
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+11 -1
View File
@@ -341,11 +341,12 @@ int main(int argc,char** argv)
{ {
if(argc<3) if(argc<3)
{ {
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]); std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]);
return 2; return 2;
} }
int Width=2560,Height=1440;double Fov=90.0; int Width=2560,Height=1440;double Fov=90.0;
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0; bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
bool HasCamera=false;std::array<double,3> Camera{};
for(int I=3;I<argc;++I) for(int I=3;I<argc;++I)
{ {
const std::string_view Arg(argv[I]); const std::string_view Arg(argv[I]);
@@ -361,6 +362,14 @@ int main(int argc,char** argv)
if(End==Value.c_str()||*End!='\0'||Fov<=1.0||Fov>=179.0) if(End==Value.c_str()||*End!='\0'||Fov<=1.0||Fov>=179.0)
{std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;} {std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;}
} }
else if(Arg.starts_with("--camera-m="))
{
const std::string Value(Arg.substr(11));
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf",&Camera[0],&Camera[1],&Camera[2])!=3
||!std::isfinite(Camera[0])||!std::isfinite(Camera[1])||!std::isfinite(Camera[2]))
{std::fprintf(stderr,"Invalid camera position\n");return 2;}
HasCamera=true;
}
else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg=")) else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg="))
{ {
const bool IsYaw=Arg.starts_with("--yaw-deg="); const bool IsYaw=Arg.starts_with("--yaw-deg=");
@@ -376,6 +385,7 @@ int main(int argc,char** argv)
FScene Scene;std::string Error; FScene Scene;std::string Error;
if(!LoadScene(argv[1],Scene,Error)) if(!LoadScene(argv[1],Scene,Error))
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;} {std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
if(HasCamera)Scene.CameraMeters=Camera;
if(HasYaw)Scene.YawDegrees=Yaw; if(HasYaw)Scene.YawDegrees=Yaw;
if(HasPitch)Scene.PitchDegrees=Pitch; if(HasPitch)Scene.PitchDegrees=Pitch;
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n", std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n",
+272 -63
View File
@@ -22,6 +22,12 @@ namespace StandaloneScreenSpaceGate
int Lod=0; int Lod=0;
std::uint64_t Samples=0,Missing=0,Floating=0; std::uint64_t Samples=0,Missing=0,Floating=0;
double P50=0.0,P99=0.0,Maximum=0.0; double P50=0.0,P99=0.0,Maximum=0.0;
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
double MaximumSlopeDegrees=0.0;
std::array<std::uint64_t,4> SlopeSamples{};
std::array<double,4> SlopeP99{},SlopeMaximum{};
bool Valid=false; bool Valid=false;
}; };
@@ -30,9 +36,128 @@ namespace StandaloneScreenSpaceGate
int CoarseLod=0; int CoarseLod=0;
std::uint64_t Samples=0,Cracks=0,Floating=0; std::uint64_t Samples=0,Cracks=0,Floating=0;
double P50=0.0,P99=0.0,Maximum=0.0; double P50=0.0,P99=0.0,Maximum=0.0;
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
double MaximumSlopeDegrees=0.0;
std::array<std::uint64_t,4> SlopeSamples{};
std::array<double,4> SlopeP99{},SlopeMaximum{};
bool Valid=false; bool Valid=false;
}; };
struct FSurfaceRoot
{
double ZMeters=0.0;
double SlopeX=0.0;
double SlopeY=0.0;
};
struct FViewBasis
{
std::array<double,3> Right{};
std::array<double,3> Up{};
};
struct FErrorAccumulator
{
std::vector<double> Pixels,VerticalPixels;
std::array<std::vector<double>,4> BySlope;
double Maximum=-1.0,MaximumVertical=0.0,MaximumNormalDistanceMeters=0.0;
double MaximumX=0.0,MaximumY=0.0,MaximumSlopeDegrees=0.0;
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
std::array<std::uint64_t,4> SlopeSamples{};
};
inline double Percentile(std::vector<double>& Values,double P);
inline FViewBasis MakeViewBasis(double YawDegrees,double PitchDegrees)
{
constexpr double Pi=3.14159265358979323846;
const double Yaw=YawDegrees*Pi/180.0,Pitch=PitchDegrees*Pi/180.0;
const std::array<double,3> Forward={std::sin(Yaw)*std::cos(Pitch),
std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)};
FViewBasis Basis;
Basis.Right={std::cos(Yaw),-std::sin(Yaw),0.0};
Basis.Up={Basis.Right[1]*Forward[2]-Basis.Right[2]*Forward[1],
Basis.Right[2]*Forward[0]-Basis.Right[0]*Forward[2],
Basis.Right[0]*Forward[1]-Basis.Right[1]*Forward[0]};
return Basis;
}
inline std::size_t SlopeBin(double Degrees)
{
if(Degrees<30.0)return 0;
if(Degrees<45.0)return 1;
if(Degrees<60.0)return 2;
return 3;
}
inline void AddError(FErrorAccumulator& Accumulator,double DeltaZMeters,
const FSurfaceRoot& Root,double PixelBudgetMeters,const FViewBasis& View,
double X,double Y)
{
constexpr double Pi=3.14159265358979323846;
const double GradientSquared=Root.SlopeX*Root.SlopeX+Root.SlopeY*Root.SlopeY;
const double NormalLength=std::sqrt(1.0+GradientSquared);
const double NormalX=-Root.SlopeX/NormalLength;
const double NormalY=-Root.SlopeY/NormalLength;
const double NormalZ=1.0/NormalLength;
const double NormalDistance=std::abs(DeltaZMeters)/NormalLength;
const double ProjectedNormal=std::hypot(
NormalX*View.Right[0]+NormalY*View.Right[1]+NormalZ*View.Right[2],
NormalX*View.Up[0]+NormalY*View.Up[1]+NormalZ*View.Up[2]);
const double ProjectedVertical=std::hypot(View.Right[2],View.Up[2]);
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
const double VerticalPixels=std::abs(DeltaZMeters)*ProjectedVertical/PixelBudgetMeters;
const double SlopeDegrees=std::atan(std::sqrt(GradientSquared))*180.0/Pi;
Accumulator.Pixels.push_back(NormalPixels);
Accumulator.VerticalPixels.push_back(VerticalPixels);
const std::size_t Bin=SlopeBin(SlopeDegrees);
Accumulator.BySlope[Bin].push_back(NormalPixels);
++Accumulator.SlopeSamples[Bin];
if(NormalPixels>Accumulator.Maximum)
{
Accumulator.Maximum=NormalPixels;
Accumulator.MaximumVertical=VerticalPixels;
Accumulator.MaximumNormalDistanceMeters=NormalDistance;
Accumulator.MaximumX=X;Accumulator.MaximumY=Y;
Accumulator.MaximumSlopeDegrees=SlopeDegrees;
Accumulator.MaximumReferenceZ=Root.ZMeters;
Accumulator.MaximumDisplayedZ=Root.ZMeters+DeltaZMeters;
}
}
inline void AddMissing(FErrorAccumulator& Accumulator)
{
Accumulator.Pixels.push_back(2.0);
Accumulator.VerticalPixels.push_back(2.0);
}
inline void FinalizeError(FErrorRow& Row,FErrorAccumulator& Accumulator)
{
Row.Samples=Accumulator.Pixels.size();
Row.P50=Percentile(Accumulator.Pixels,0.50);
Row.P99=Percentile(Accumulator.Pixels,0.99);
Row.Maximum=Accumulator.Pixels.empty()?0.0
:*std::max_element(Accumulator.Pixels.begin(),Accumulator.Pixels.end());
Row.VerticalP50=Percentile(Accumulator.VerticalPixels,0.50);
Row.VerticalP99=Percentile(Accumulator.VerticalPixels,0.99);
Row.VerticalMaximum=Accumulator.VerticalPixels.empty()?0.0
:*std::max_element(Accumulator.VerticalPixels.begin(),Accumulator.VerticalPixels.end());
Row.MaximumNormalDistanceMeters=Accumulator.MaximumNormalDistanceMeters;
Row.MaximumX=Accumulator.MaximumX;Row.MaximumY=Accumulator.MaximumY;
Row.MaximumReferenceZ=Accumulator.MaximumReferenceZ;
Row.MaximumDisplayedZ=Accumulator.MaximumDisplayedZ;
Row.MaximumSlopeDegrees=Accumulator.MaximumSlopeDegrees;
Row.SlopeSamples=Accumulator.SlopeSamples;
for(std::size_t Bin=0;Bin<Accumulator.BySlope.size();++Bin)
{
Row.SlopeP99[Bin]=Percentile(Accumulator.BySlope[Bin],0.99);
Row.SlopeMaximum[Bin]=Accumulator.BySlope[Bin].empty()?0.0
:*std::max_element(Accumulator.BySlope[Bin].begin(),Accumulator.BySlope[Bin].end());
}
}
struct FHeightGrid struct FHeightGrid
{ {
int Side=65; int Side=65;
@@ -162,38 +287,51 @@ namespace StandaloneScreenSpaceGate
return Grid; return Grid;
} }
inline std::vector<double> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy, inline std::vector<FSurfaceRoot> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
const FVoxelTileKey& Patch,double VoxelMeters, const FVoxelTileKey& Patch,double VoxelMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly) const VoxelForgeCore::WorldAssemblyManifest& Assembly)
{ {
std::vector<double> Roots; std::vector<FSurfaceRoot> Roots;
const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0; const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0;
const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0; const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0;
const auto X=static_cast<std::int64_t>(std::llround(XSubvoxels));
const auto Y=static_cast<std::int64_t>(std::llround(YSubvoxels));
const double MinZ=static_cast<double>(Patch.OriginVoxels().Z); const double MinZ=static_cast<double>(Patch.OriginVoxels().Z);
const double MaxZ=MinZ+Patch.ExtentVoxels(); const double MaxZ=MinZ+Patch.ExtentVoxels();
constexpr std::int64_t NormalProbeSubvoxels=256;
for(const auto& Plan:Assembly.plan.strates) for(const auto& Plan:Assembly.plan.strates)
{ {
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan); VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue; if(!Field.isValid())continue;
const auto Column=Field.point(static_cast<std::int64_t>(std::llround(XSubvoxels)), const auto Column=Field.point(X,Y,0);
static_cast<std::int64_t>(std::llround(YSubvoxels)),0);
if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue; if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue;
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/(256.0)*VoxelMeters; const auto XMinus=Field.point(X-NormalProbeSubvoxels,Y,0);
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/(256.0)*VoxelMeters; const auto XPlus=Field.point(X+NormalProbeSubvoxels,Y,0);
const auto YMinus=Field.point(X,Y-NormalProbeSubvoxels,0);
const auto YPlus=Field.point(X,Y+NormalProbeSubvoxels,0);
const auto Slope=[&](std::int64_t Minus,std::int64_t Plus)
{return static_cast<double>(Plus-Minus)/(2.0*NormalProbeSubvoxels);};
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/256.0*VoxelMeters;
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/256.0*VoxelMeters;
const double PlanMinZ=static_cast<double>(Plan.bottomChunkZ)*32.0*VoxelMeters; const double PlanMinZ=static_cast<double>(Plan.bottomChunkZ)*32.0*VoxelMeters;
const double PlanMaxZ=static_cast<double>(Plan.topChunkZ+1)*32.0*VoxelMeters; const double PlanMaxZ=static_cast<double>(Plan.topChunkZ+1)*32.0*VoxelMeters;
if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters
&&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters &&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters
&&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters) &&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters)
Roots.push_back(Ground); Roots.push_back({Ground,Slope(XMinus.groundHeightSubvoxels,
XPlus.groundHeightSubvoxels),Slope(YMinus.groundHeightSubvoxels,
YPlus.groundHeightSubvoxels)});
if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters
&&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters &&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters
&&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters) &&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters)
Roots.push_back(Cap); Roots.push_back({Cap,Slope(XMinus.capHeightSubvoxels,
XPlus.capHeightSubvoxels),Slope(YMinus.capHeightSubvoxels,
YPlus.capHeightSubvoxels)});
} }
std::sort(Roots.begin(),Roots.end()); std::sort(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,const FSurfaceRoot& B)
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](double A,double B) {return A.ZMeters<B.ZMeters;});
{return std::abs(A-B)<0.01;}),Roots.end()); Roots.erase(std::unique(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,
const FSurfaceRoot& B){return std::abs(A.ZMeters-B.ZMeters)<0.01;}),Roots.end());
return Roots; return Roots;
} }
@@ -213,75 +351,87 @@ namespace StandaloneScreenSpaceGate
inline FErrorRow CompareToExact(const FHeightGrid& MeshGrid,const FVoxelTileKey& Patch, inline FErrorRow CompareToExact(const FHeightGrid& MeshGrid,const FVoxelTileKey& Patch,
double VoxelMeters,double PixelBudgetMeters, double VoxelMeters,double PixelBudgetMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly) const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
{ {
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true; FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
std::vector<double> Errors; FErrorAccumulator Accumulator;
for(int Iy=0;Iy<MeshGrid.Side;++Iy) for(int Iy=0;Iy<MeshGrid.Side;++Iy)
for(int Ix=0;Ix<MeshGrid.Side;++Ix) for(int Ix=0;Ix<MeshGrid.Side;++Ix)
{ {
const auto Roots=ExactHeightRoots(MeshGrid,Ix,Iy,Patch,VoxelMeters,Assembly); const auto Roots=ExactHeightRoots(MeshGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
if(Roots.empty()&&MeshGrid.At(Ix,Iy).empty())continue; if(Roots.empty()&&MeshGrid.At(Ix,Iy).empty())continue;
std::vector<bool> Used(MeshGrid.At(Ix,Iy).size(),false); std::vector<bool> Used(MeshGrid.At(Ix,Iy).size(),false);
for(double Root:Roots) for(const FSurfaceRoot& Root:Roots)
{ {
std::size_t HitIndex=0;double ErrorMeters=0.0; std::size_t HitIndex=0;double ErrorMeters=0.0;
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root,HitIndex,ErrorMeters)) if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root.ZMeters,HitIndex,ErrorMeters))
{ {
++Row.Missing; ++Row.Missing;
Errors.push_back(2.0); AddMissing(Accumulator);
continue; continue;
} }
Used[HitIndex]=true; Used[HitIndex]=true;
Errors.push_back(ErrorMeters/PixelBudgetMeters); AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex]-Root.ZMeters,
Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy));
++Row.Samples; ++Row.Samples;
} }
// Heightfield2D declares ground and cap roots only. Other mesh intersections may // Heightfield2D declares ground and cap roots only. Other mesh intersections may
// be valid tunnel geometry; the exact-mesh comparison checks extra pieces. // be valid tunnel geometry; the exact-mesh comparison checks extra pieces.
} }
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); FinalizeError(Row,Accumulator);
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
Row.Samples=Errors.size();
return Row; return Row;
} }
inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid, inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid,
const FHeightGrid& ReferenceGrid,int Lod,double PixelBudgetMeters) const FHeightGrid& ReferenceGrid,const FVoxelTileKey& Patch,double VoxelMeters,
double PixelBudgetMeters,const VoxelForgeCore::WorldAssemblyManifest& Assembly,
const FViewBasis& View)
{ {
FErrorRow Row;Row.Lod=Lod;Row.Valid=true; FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
std::vector<double> Errors; FErrorAccumulator Accumulator;
for(int Iy=0;Iy<ReferenceGrid.Side;++Iy) for(int Iy=0;Iy<ReferenceGrid.Side;++Iy)
for(int Ix=0;Ix<ReferenceGrid.Side;++Ix) for(int Ix=0;Ix<ReferenceGrid.Side;++Ix)
{ {
const auto& Reference=ReferenceGrid.At(Ix,Iy); const auto& Reference=ReferenceGrid.At(Ix,Iy);
const auto& Mesh=MeshGrid.At(Ix,Iy); const auto& Mesh=MeshGrid.At(Ix,Iy);
if(Reference.empty()&&Mesh.empty())continue; if(Reference.empty()&&Mesh.empty())continue;
std::vector<bool> Used(Mesh.size(),false); const auto Roots=ExactHeightRoots(ReferenceGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
for(double ReferenceZ:Reference) std::vector<bool> UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false);
for(std::size_t ReferenceIndex=0;ReferenceIndex<Reference.size();++ReferenceIndex)
{ {
std::size_t HitIndex=0;double ErrorMeters=0.0; const double ReferenceZ=Reference[ReferenceIndex];
if(!MatchNearest(Mesh,Used,ReferenceZ,HitIndex,ErrorMeters)) UsedReference[ReferenceIndex]=true;
std::size_t MeshIndex=0;double MeshError=0.0;
if(!MatchNearest(Mesh,UsedMesh,ReferenceZ,MeshIndex,MeshError))
{ {
++Row.Missing;Errors.push_back(2.0);continue; ++Row.Missing;AddMissing(Accumulator);continue;
} }
Used[HitIndex]=true; UsedMesh[MeshIndex]=true;
Errors.push_back(ErrorMeters/PixelBudgetMeters); FSurfaceRoot Root{ReferenceZ,0.0,0.0};
if(!Roots.empty())
{
const auto BestRoot=std::min_element(Roots.begin(),Roots.end(),
[ReferenceZ](const FSurfaceRoot& A,const FSurfaceRoot& B)
{return std::abs(A.ZMeters-ReferenceZ)<std::abs(B.ZMeters-ReferenceZ);});
if(BestRoot!=Roots.end()&&std::abs(BestRoot->ZMeters-ReferenceZ)<1.0)
Root=*BestRoot;
}
AddError(Accumulator,Mesh[MeshIndex]-Reference[ReferenceIndex],
Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy));
} }
for(std::size_t I=0;I<Mesh.size();++I) for(std::size_t I=0;I<Mesh.size();++I)
if(!Used[I]){++Row.Floating;Errors.push_back(2.0);} if(!UsedMesh[I]){++Row.Floating;AddMissing(Accumulator);}
} }
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); FinalizeError(Row,Accumulator);
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
Row.Samples=Errors.size();
return Row; return Row;
} }
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine, inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters, const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly) const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
{ {
FPopRow Row;Row.CoarseLod=Patch.Level;Row.Valid=true; FPopRow Row;Row.CoarseLod=Patch.Level;Row.Valid=true;
std::vector<double> Errors; FErrorAccumulator Accumulator;
for(int Iy=0;Iy<Coarse.Side;++Iy) for(int Iy=0;Iy<Coarse.Side;++Iy)
for(int Ix=0;Ix<Coarse.Side;++Ix) for(int Ix=0;Ix<Coarse.Side;++Ix)
{ {
@@ -291,25 +441,34 @@ namespace StandaloneScreenSpaceGate
if(Roots.empty()&&CoarseHits.empty()&&FineHits.empty())continue; if(Roots.empty()&&CoarseHits.empty()&&FineHits.empty())continue;
std::vector<bool> CoarseUsed(CoarseHits.size(),false); std::vector<bool> CoarseUsed(CoarseHits.size(),false);
std::vector<bool> FineUsed(FineHits.size(),false); std::vector<bool> FineUsed(FineHits.size(),false);
for(double Root:Roots) for(const FSurfaceRoot& Root:Roots)
{ {
std::size_t CoarseIndex=0,FineIndex=0; std::size_t CoarseIndex=0,FineIndex=0;
double CoarseError=0.0,FineError=0.0; double CoarseError=0.0,FineError=0.0;
if(!MatchNearest(CoarseHits,CoarseUsed,Root,CoarseIndex,CoarseError) if(!MatchNearest(CoarseHits,CoarseUsed,Root.ZMeters,CoarseIndex,CoarseError)
||!MatchNearest(FineHits,FineUsed,Root,FineIndex,FineError)) ||!MatchNearest(FineHits,FineUsed,Root.ZMeters,FineIndex,FineError))
{ {
++Row.Cracks;Errors.push_back(2.0);continue; ++Row.Cracks;AddMissing(Accumulator);continue;
} }
CoarseUsed[CoarseIndex]=true; CoarseUsed[CoarseIndex]=true;
FineUsed[FineIndex]=true; FineUsed[FineIndex]=true;
Errors.push_back(std::abs(CoarseHits[CoarseIndex]-FineHits[FineIndex]) AddError(Accumulator,CoarseHits[CoarseIndex]-FineHits[FineIndex],
/PixelBudgetMeters); Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy));
++Row.Samples; ++Row.Samples;
} }
} }
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true;
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end()); FinalizeError(Stats,Accumulator);
Row.Samples=Errors.size(); Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum;
Row.VerticalP50=Stats.VerticalP50;Row.VerticalP99=Stats.VerticalP99;
Row.VerticalMaximum=Stats.VerticalMaximum;
Row.MaximumNormalDistanceMeters=Stats.MaximumNormalDistanceMeters;
Row.MaximumX=Stats.MaximumX;Row.MaximumY=Stats.MaximumY;
Row.MaximumReferenceZ=Stats.MaximumReferenceZ;
Row.MaximumDisplayedZ=Stats.MaximumDisplayedZ;
Row.MaximumSlopeDegrees=Stats.MaximumSlopeDegrees;
Row.SlopeSamples=Stats.SlopeSamples;Row.SlopeP99=Stats.SlopeP99;
Row.SlopeMaximum=Stats.SlopeMaximum;
return Row; return Row;
} }
@@ -352,19 +511,52 @@ namespace StandaloneScreenSpaceGate
return Distances; return Distances;
} }
inline void PrintSlopeTail(const char* Kind,int Lod,const std::array<std::uint64_t,4>& Counts,
const std::array<double,4>& P99,const std::array<double,4>& Maximum)
{
constexpr const char* Labels[4]={"lt30","30to45","45to60","ge60"};
for(std::size_t Bin=0;Bin<Counts.size();++Bin)
std::printf("[ScreenSpaceSlopeTail] kind=%s lod=%d slope_deg=%s samples=%llu p99_px=%.4f max_px=%.4f\n",
Kind,Lod,Labels[Bin],static_cast<unsigned long long>(Counts[Bin]),P99[Bin],Maximum[Bin]);
}
inline void PrintKnownFailureControl(double CellMeters,double DisplayDistanceMeters,
const FViewBasis& View)
{
constexpr double Slope=100.0;
const double LateralMeters=CellMeters/200.0;
const double VerticalMeters=Slope*LateralMeters;
const double NormalLength=std::sqrt(1.0+Slope*Slope);
const double NormalDistance=VerticalMeters/NormalLength;
const std::array<double,3> Normal={-Slope/NormalLength,0.0,1.0/NormalLength};
const double ProjectedNormal=std::hypot(
Normal[0]*View.Right[0]+Normal[1]*View.Right[1]+Normal[2]*View.Right[2],
Normal[0]*View.Up[0]+Normal[1]*View.Up[1]+Normal[2]*View.Up[2]);
const double PixelBudgetMeters=DisplayDistanceMeters*OnePixelRadians;
const double VerticalPixels=VerticalMeters/PixelBudgetMeters;
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
const bool Passed=VerticalPixels>4.0&&NormalPixels<1.0;
std::printf("[ScreenSpaceKnownFailureControl] case=steep-plane-lateral-offset slope_deg=%.4f cell_m=%.6f lateral_m=%.6f cell_fraction=%.6f vertical_error_m=%.6f vertical_height_px=%.4f closest_normal_m=%.6f projected_normal_px=%.4f status=%s\n",
std::atan(Slope)*180.0/3.14159265358979323846,CellMeters,LateralMeters,
LateralMeters/CellMeters,VerticalMeters,VerticalPixels,NormalDistance,NormalPixels,
Passed?"EXPECTED_VERTICAL_FALSE_POSITIVE":"CONTROL_FAILED");
}
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles, inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
FStandaloneStreamingGenerator& Streamer, FStandaloneStreamingGenerator& Streamer,
const VoxelForgeCore::WorldAssemblyManifest& Assembly, const VoxelForgeCore::WorldAssemblyManifest& Assembly,
const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod, const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod,
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels) const std::array<std::int64_t,9>& PixelBudgetsSubvoxels,
double CameraYawDegrees,double CameraPitchDegrees)
{ {
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0; const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees);
std::array<double,9> Budgets{}; std::array<double,9> Budgets{};
std::array<FErrorRow,9> ErrorRows{}; std::array<FErrorRow,9> ErrorRows{};
std::array<FPopRow,9> PopRows{}; std::array<FPopRow,9> PopRows{};
bool GatePass=true; bool GatePass=true;
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (90deg/2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=vertical_height_error field_reference=direct-Heightfield2D-roots\n", std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (horizontal_fov=90deg,width=2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=local_surface_normal_projected field_reference=direct-Heightfield2D-roots normal_probe_vox=1 view_yaw_deg=%.3f view_pitch_deg=%.3f\n",
OnePixelRadians); OnePixelRadians,CameraYawDegrees,CameraPitchDegrees);
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod) for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
{ {
Budgets[Lod]=Distances[Lod]*OnePixelRadians; Budgets[Lod]=Distances[Lod]*OnePixelRadians;
@@ -372,6 +564,8 @@ namespace StandaloneScreenSpaceGate
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0, Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px"); static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px");
} }
const double LOD3CellMeters=static_cast<double>(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0;
PrintKnownFailureControl(LOD3CellMeters,Distances[3],View);
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0); VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod) for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod)
{ {
@@ -389,32 +583,43 @@ namespace StandaloneScreenSpaceGate
const FStandaloneCpuTile* ExactPointer=&ExactTile; const FStandaloneCpuTile* ExactPointer=&ExactTile;
const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1); const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1);
const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters); const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters);
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,Lod,Budgets[Lod]); ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly,View);
const FErrorRow& Error=ErrorRows[Lod]; const FErrorRow& Error=ErrorRows[Lod];
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0 const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
&&Error.Samples>=16&&Error.Maximum<=1.0; &&Error.Samples>=16&&Error.Maximum<=1.0;
GatePass=GatePass&&LodPass; GatePass=GatePass&&LodPass;
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xy_m=(%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z, Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z,
static_cast<unsigned long long>(Error.Samples),Error.P50,Error.P99,Error.Maximum, static_cast<unsigned long long>(Error.Samples),Error.P50,Error.P99,Error.Maximum,
Error.VerticalP50,Error.VerticalP99,Error.VerticalMaximum,
Error.MaximumNormalDistanceMeters,Error.MaximumX,Error.MaximumY,
Error.MaximumSlopeDegrees,
static_cast<unsigned long long>(Error.Missing),static_cast<unsigned long long>(Error.Floating), static_cast<unsigned long long>(Error.Missing),static_cast<unsigned long long>(Error.Floating),
LodPass?"PASS":"REJECT"); LodPass?"PASS":"REJECT");
PrintSlopeTail("same_lod",Lod,Error.SlopeSamples,Error.SlopeP99,Error.SlopeMaximum);
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters, const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly); Budgets[Lod],Assembly,View);
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0 const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0; &&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
GatePass=GatePass&&FieldPass; GatePass=GatePass&&FieldPass;
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50, Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50,
FieldError.P99,FieldError.Maximum,static_cast<unsigned long long>(FieldError.Missing), FieldError.P99,FieldError.Maximum,FieldError.VerticalP50,FieldError.VerticalP99,
FieldError.VerticalMaximum,FieldError.MaximumNormalDistanceMeters,
FieldError.MaximumX,FieldError.MaximumY,FieldError.MaximumDisplayedZ,
FieldError.MaximumSlopeDegrees,
static_cast<unsigned long long>(FieldError.Missing),
static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT"); static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT");
PrintSlopeTail("field",Lod,FieldError.SlopeSamples,FieldError.SlopeP99,
FieldError.SlopeMaximum);
bool FaultControlsPass=true; bool FaultControlsPass=true;
const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX, const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX,
ExactGrid.MinimumY,ExactGrid.MaximumY); ExactGrid.MinimumY,ExactGrid.MaximumY);
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,Lod, const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,
Budgets[Lod]); CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0; const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&MissingRejected; FaultControlsPass=FaultControlsPass&&MissingRejected;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n", std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n",
@@ -435,8 +640,8 @@ namespace StandaloneScreenSpaceGate
const FStandaloneCpuTile* FloatingPointer=&FloatingTile; const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1); const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters); const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters);
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,Lod, const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,
Budgets[Lod]); CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0; const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&FloatingRejected; FaultControlsPass=FaultControlsPass&&FloatingRejected;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n", std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
@@ -461,20 +666,24 @@ namespace StandaloneScreenSpaceGate
FinePointers.data(),FinePointers.size()); FinePointers.data(),FinePointers.size());
const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters); const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters);
PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters, PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly); Budgets[Lod],Assembly,View);
const FPopRow& Pop=PopRows[Lod]; const FPopRow& Pop=PopRows[Lod];
const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0; const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0;
GatePass=GatePass&&PopPass; GatePass=GatePass&&PopPass;
std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f cracks=%llu floating=%llu status=%s\n", std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f cracks=%llu floating=%llu status=%s\n",
Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples), Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples),
Pop.P50,Pop.P99,Pop.Maximum,static_cast<unsigned long long>(Pop.Cracks), Pop.P50,Pop.P99,Pop.Maximum,Pop.VerticalP50,Pop.VerticalP99,Pop.VerticalMaximum,
Pop.MaximumNormalDistanceMeters,Pop.MaximumX,Pop.MaximumY,
Pop.MaximumDisplayedZ,
Pop.MaximumSlopeDegrees,static_cast<unsigned long long>(Pop.Cracks),
static_cast<unsigned long long>(Pop.Floating), static_cast<unsigned long long>(Pop.Floating),
PopPass?"PASS":"REJECT"); PopPass?"PASS":"REJECT");
PrintSlopeTail("swap",Lod,Pop.SlopeSamples,Pop.SlopeP99,Pop.SlopeMaximum);
const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX, const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX,
CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY); CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY);
const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid, const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid,
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly); CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0; const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&CrackRejected; FaultControlsPass=FaultControlsPass&&CrackRejected;
GatePass=GatePass&&FaultControlsPass; GatePass=GatePass&&FaultControlsPass;
@@ -490,8 +699,8 @@ namespace StandaloneScreenSpaceGate
const FStandaloneCpuTile* BadPointer=&Bad; const FStandaloneCpuTile* BadPointer=&Bad;
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1); const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters); const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,Lod, const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,
Budgets[Lod]); CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
const bool Rejected=BadError.Missing>0||BadError.Floating>0 const bool Rejected=BadError.Missing>0||BadError.Floating>0
||BadError.Samples<16||BadError.Maximum>1.0; ||BadError.Samples<16||BadError.Maximum>1.0;
NegativeControlsPass=NegativeControlsPass&&Rejected; NegativeControlsPass=NegativeControlsPass&&Rejected;
@@ -38,7 +38,6 @@ namespace
bool ForceGenerate=false; bool ForceGenerate=false;
bool MeshCoverage=false; bool MeshCoverage=false;
bool ScreenSpaceGate=false; bool ScreenSpaceGate=false;
bool VisualDetailCutoff=false;
bool HasSeedOverride=false; bool HasSeedOverride=false;
int SeedOverride=0; int SeedOverride=0;
bool HasRenderDistanceOverride=false; bool HasRenderDistanceOverride=false;
@@ -98,7 +97,6 @@ namespace
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n" " --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
" --mesh-coverage (report non-empty tiles and indexed triangles)\n" " --mesh-coverage (report non-empty tiles and indexed triangles)\n"
" --screen-space-gate (measure display error and historical negative controls)\n" " --screen-space-gate (measure display error and historical negative controls)\n"
" --visual-detail-cutoff (enable the declared far visual octave cutoff)\n"
" --render-distance-chunks=N (0 disables the extra far ring)\n" " --render-distance-chunks=N (0 disables the extra far ring)\n"
" --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n" " --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n"
" --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n" " --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n"
@@ -114,7 +112,6 @@ namespace
else if(arg=="--force-generate")options.ForceGenerate=true; else if(arg=="--force-generate")options.ForceGenerate=true;
else if(arg=="--mesh-coverage")options.MeshCoverage=true; else if(arg=="--mesh-coverage")options.MeshCoverage=true;
else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true; else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true;
else if(arg=="--visual-detail-cutoff")options.VisualDetailCutoff=true;
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true; else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
else if(arg.starts_with("--recipe=")) else if(arg.starts_with("--recipe="))
{ {
@@ -317,5 +314,5 @@ int main(int argc,char** argv)
scheduler,options.StreamHash,false,false,false,false,coldStart, scheduler,options.StreamHash,false,false,false,false,coldStart,
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels, options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels,
options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees, options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees,
options.CameraPitchDegrees,options.ScreenSpaceGate,options.VisualDetailCutoff); options.CameraPitchDegrees,options.ScreenSpaceGate);
} }
+15 -106
View File
@@ -137,22 +137,17 @@ namespace StandaloneStreamBenchmark
std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap, std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap,
int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false, int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false,
const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f, const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f,
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false, float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false)
bool bApplyVisualDetailCutoff=false)
{ {
using Clock=std::chrono::steady_clock; using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld; FStandaloneOwnerWorld& World=*OwnerWorld;
const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly(); const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly();
std::array<double,9> ClosestDisplayDistances{}; std::array<double,9> ClosestDisplayDistances{};
std::array<std::int64_t,9> PixelBudgetsSubvoxels{}; std::array<std::int64_t,9> PixelBudgetsSubvoxels{};
std::array<std::int64_t,9> AppliedPixelBudgetsSubvoxels{}; if(bScreenSpaceGate)
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
{ {
ClosestDisplayDistances=bScreenSpaceGate ClosestDisplayDistances=StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
?StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters( World,CameraPositionVoxels,World.CoreVoxelSizeMicrons);
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons)
:StandaloneScreenSpaceGate::ClipmapClosestDisplayedDistancesMeters(
World,World.CoreVoxelSizeMicrons);
const double VoxelMeters=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0; const double VoxelMeters=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod) for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=static_cast<std::int64_t>(std::floor( PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=static_cast<std::int64_t>(std::floor(
@@ -160,67 +155,6 @@ namespace StandaloneStreamBenchmark
*StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters *StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)); *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
} }
AppliedPixelBudgetsSubvoxels=PixelBudgetsSubvoxels;
if(Assembly&&(bScreenSpaceGate||bApplyVisualDetailCutoff))
{
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
{
const std::int32_t Cells=FMath::Max(1,World.CoarseTileCells);
const std::int32_t Step=FMath::Max(1,(CHUNK_SIZE<<Lod)/Cells);
VoxelForgeCore::VisualDetailCutoffV1 Probe;
Probe.pixelBudgetSubvoxels=PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)];
Probe.sampleStepSubvoxels=static_cast<std::int64_t>(Step)
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
std::uint64_t TermCount=0,EligibleTermCount=0,PotentialSkippedOctaves=0;
for(const auto& Plan:Assembly->plan.strates)
{
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan);
for(const auto& Term:Field.visualDetailContract().terms)
{
++TermCount;
const auto Result=VoxelForgeCore::FindVisualDetailTailCutoffV1(Term,Probe);
if(Result.firstSkippedOctave==0)continue;
++EligibleTermCount;
PotentialSkippedOctaves+=Term.octaveCount-Result.firstSkippedOctave;
}
}
std::printf("[WorldAssemblerVisualDetailCutoffPlan] lod=%d terms=%llu eligible_terms=%llu potential_skipped_octaves=%llu per_call_budget_divisor=%u sample_step_vox=%d status=%s\n",
Lod,static_cast<unsigned long long>(TermCount),
static_cast<unsigned long long>(EligibleTermCount),
static_cast<unsigned long long>(PotentialSkippedOctaves),
Probe.callBudgetDivisor,Step,
EligibleTermCount==0?"exact-no-eligible-tail":"candidate");
if(bApplyVisualDetailCutoff&&EligibleTermCount==0)
AppliedPixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=0;
}
}
if(bScreenSpaceGate&&Assembly)
{
for(const auto& Plan:Assembly->plan.strates)
{
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan);
for(const auto& Term:Field.visualDetailContract().terms)
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
{
const std::int64_t Wavelength=Term.baseWavelengthSubvoxels
/static_cast<std::int64_t>(1ull<<Octave);
const long double Amplitude=static_cast<long double>(Term.maximumDisplacementSubvoxels)
*Term.maximumNoiseAbsQ16/65536.0L
/static_cast<long double>(1ull<<Octave);
std::printf("[WorldAssemblerVisualDetailTerm] stratum=%d id=%llu octave=%u wavelength_vox=%.3f max_abs_surface_vox=%.3f declared=1\n",
Plan.strateIndex,static_cast<unsigned long long>(Term.stableId),Octave,
static_cast<double>(Wavelength)/256.0,static_cast<double>(Amplitude));
}
}
for(const auto& Island:Assembly->floatingIslands)
{
const VoxelForgeCore::FloatingIslandFieldV1 Field(
Island.manifest.recipe,Island.manifest.plan);
const auto Contract=Field.visualDetailContract();
std::printf("[WorldAssemblerVisualDetailContract] recipe=floating-islands strate=%d declared_terms=%zu exact_path=%d\n",
Island.strateIndex,Contract.terms.size(),Contract.terms.empty()?1:0);
}
}
const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels; const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls; const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
VoxelForgeCore::World Shape(CoreSnapshot); VoxelForgeCore::World Shape(CoreSnapshot);
@@ -309,12 +243,7 @@ namespace StandaloneStreamBenchmark
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel, FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap, bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels); FarHorizonLOD8InteriorStepVoxels);
if(bScreenSpaceGate||bApplyVisualDetailCutoff) if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true);
{
Streamer.SetVisualDetailCutoffBudgets(AppliedPixelBudgetsSubvoxels,
bApplyVisualDetailCutoff);
Streamer.SetVisualDetailProfilingEnabled(bScreenSpaceGate);
}
const auto StreamStart=Clock::now(); const auto StreamStart=Clock::now();
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired); const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size())) if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
@@ -370,14 +299,11 @@ namespace StandaloneStreamBenchmark
std::array<bool,9> ColumnCaptureCompleteByLOD{}; std::array<bool,9> ColumnCaptureCompleteByLOD{};
ColumnCaptureCompleteByLOD.fill(true); ColumnCaptureCompleteByLOD.fill(true);
std::array<double,9> WorkerSecondsByLOD{}; std::array<double,9> WorkerSecondsByLOD{};
std::array<std::array<std::uint64_t,8>,9> DetailOctaveEvaluationsByLOD{};
std::array<std::array<std::uint64_t,8>,9> DetailOctaveEligibleByLOD{};
std::array<std::array<std::uint64_t,8>,9> DetailOctaveSkipsByLOD{};
std::array<std::array<double,8>,9> DetailOctaveMillisecondsByLOD{};
std::array<std::array<double,8>,9> DetailOctaveEligibleMillisecondsByLOD{};
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<double,9> SkirtMsByLOD{};
std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
std::array<std::uint64_t,9> TileCountByLOD{}; std::array<std::uint64_t,9> TileCountByLOD{};
for(const FVoxelTileKey& Tile:Desired.Tiles) for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size())) if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
@@ -425,19 +351,8 @@ namespace StandaloneStreamBenchmark
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds; GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds; CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds; MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
for(std::size_t Octave=0;Octave<8;++Octave) SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds;
{ SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount;
DetailOctaveEvaluationsByLOD[LOD][Octave]+=
Tile.CoreDiagnostics.visualDetailOctaveEvaluations[Octave];
DetailOctaveEligibleByLOD[LOD][Octave]+=
Tile.CoreDiagnostics.visualDetailOctaveBudgetEligible[Octave];
DetailOctaveSkipsByLOD[LOD][Octave]+=
Tile.CoreDiagnostics.visualDetailOctaveSkips[Octave];
DetailOctaveMillisecondsByLOD[LOD][Octave]+=
Tile.CoreDiagnostics.visualDetailOctaveMilliseconds[Octave];
DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]+=
Tile.CoreDiagnostics.visualDetailOctaveEligibleMilliseconds[Octave];
}
if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9 if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9
&&!Tile.Vertices.empty() &&!Tile.Vertices.empty()
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly, &&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
@@ -541,7 +456,8 @@ namespace StandaloneStreamBenchmark
if(Assembly) if(Assembly)
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles, bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons, Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
World.MaxClipLevel,PixelBudgetsSubvoxels); World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
CameraPitchDegrees);
else else
{std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;} {std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;}
} }
@@ -576,19 +492,12 @@ namespace StandaloneStreamBenchmark
} }
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD) for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
{ {
std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f mesh_assembly_ms=%.3f\n", std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f skirt_ms=%.3f skirt_triangles=%llu mesh_assembly_ms=%.3f\n",
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD], LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD],
ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD], ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD],
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],MeshAssemblyMsByLOD[LOD]); GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],
if(bScreenSpaceGate) SkirtMsByLOD[LOD],static_cast<unsigned long long>(SkirtTrianglesByLOD[LOD]),
for(std::size_t Octave=0;Octave<8;++Octave) MeshAssemblyMsByLOD[LOD]);
std::printf("[WorldAssemblerLODDetailOctave] lod=%zu octave=%zu evaluated=%llu budget_eligible=%llu skipped=%llu eval_ms=%.6f eligible_ms=%.6f\n",
LOD,Octave,
static_cast<unsigned long long>(DetailOctaveEvaluationsByLOD[LOD][Octave]),
static_cast<unsigned long long>(DetailOctaveEligibleByLOD[LOD][Octave]),
static_cast<unsigned long long>(DetailOctaveSkipsByLOD[LOD][Octave]),
DetailOctaveMillisecondsByLOD[LOD][Octave],
DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]);
} }
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count(); const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count(); const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
+9 -25
View File
@@ -646,16 +646,14 @@ public:
int32 GetWorkerCount()const{return WorkerCount;} int32 GetWorkerCount()const{return WorkerCount;}
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);} uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
void SetVisualDetailCutoffBudgets(const std::array<std::int64_t,9>& Budgets,bool Apply) void SetMesherStageProfilingEnabled(bool Enabled)
{VisualDetailBudgetSubvoxels=Budgets;bEnableVisualDetailCutoff=true;bApplyVisualDetailCutoff=Apply;} {bProfileMesherStages=Enabled;}
void SetVisualDetailProfilingEnabled(bool Enabled)
{bProfileVisualDetailOctaves=Enabled;}
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key, FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1, std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
bool ForceExactVisualDetail=false) bool ForceGenerate=false)
{ {
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt; FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
return Generate(Job,GlobalOctaveDrop,CellStride,0,ForceExactVisualDetail); return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate);
} }
private: private:
@@ -775,13 +773,12 @@ private:
} }
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job, FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1, std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
std::int64_t DiagnosticVisualBudgetSubvoxels=0, bool ForceGenerate=false)
bool ForceExactVisualDetail=false)
{ {
const auto StartTime=Job.StartedAt; const auto StartTime=Job.StartedAt;
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing; FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt; Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
if(MeshCache&&!ForceExactVisualDetail) if(MeshCache&&!ForceGenerate)
{ {
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key)) if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
{ {
@@ -820,19 +817,9 @@ private:
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors; Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
Options.captureColumnCoordinates=bCaptureColumnCoordinates; Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels; Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.visualDetailPixelBudgetSubvoxels=DiagnosticVisualBudgetSubvoxels; Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
if(DiagnosticVisualBudgetSubvoxels==0&&bEnableVisualDetailCutoff
&&Level>=3&&Level<static_cast<int32>(VisualDetailBudgetSubvoxels.size()))
Options.visualDetailPixelBudgetSubvoxels=
VisualDetailBudgetSubvoxels[static_cast<std::size_t>(Level)];
Options.visualDetailSampleStepVoxels=FMath::Max(1,Extent/Cells);
Options.profileVisualDetailOctaves=bProfileVisualDetailOctaves&&!ForceExactVisualDetail;
Options.visualDetailCutoffEnabled=bApplyVisualDetailCutoff
&&!ForceExactVisualDetail&&Level>=3
&&Options.visualDetailPixelBudgetSubvoxels>0;
Options.visualDetailContractVersion=Options.visualDetailCutoffEnabled
?VoxelForgeCore::CurrentVisualDetailCutoffContractVersion:0;
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop; Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
Options.diagnosticColumnInterpolationStride= Options.diagnosticColumnInterpolationStride=
std::max<std::uint32_t>(1,DiagnosticCellStride); std::max<std::uint32_t>(1,DiagnosticCellStride);
std::string Error; std::string Error;
@@ -1007,10 +994,7 @@ private:
bool bSkipColors=false; bool bSkipColors=false;
bool bCaptureColumnCoordinates=false; bool bCaptureColumnCoordinates=false;
int32 Lod8HorizonInteriorStepVoxels=0; int32 Lod8HorizonInteriorStepVoxels=0;
std::array<std::int64_t,9> VisualDetailBudgetSubvoxels{}; bool bProfileMesherStages=false;
bool bEnableVisualDetailCutoff=false;
bool bApplyVisualDetailCutoff=false;
bool bProfileVisualDetailOctaves=false;
std::atomic<uint64> CacheHitCount{0}; std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex; mutable std::mutex Mutex;
std::condition_variable Wake; std::condition_variable Wake;
@@ -1768,6 +1768,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3; const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3;
const int32 CapSurfaceTriangleCount=CapTris.Num()/3; const int32 CapSurfaceTriangleCount=CapTris.Num()/3;
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks; TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
const uint64 SkirtStageStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0) if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
{ {
const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize; const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize;
@@ -2007,6 +2008,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
EmitSkirts(GroundTris,GroundSkirtFaceMasks); EmitSkirts(GroundTris,GroundSkirtFaceMasks);
EmitSkirts(CapTris,CapSkirtFaceMasks); EmitSkirts(CapTris,CapSkirtFaceMasks);
} }
if(OutStageTimings)
OutStageTimings->SkirtMilliseconds=static_cast<double>(
FPlatformTime::Cycles64()-SkirtStageStartCycles)
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
// F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir // F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir
// FVoxelMeshData::NumCeilingTriangles). Mêmes triangles qu'avant, seul l'ordre change. // FVoxelMeshData::NumCeilingTriangles). Mêmes triangles qu'avant, seul l'ordre change.
@@ -2059,6 +2064,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls); AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls);
OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles( OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles(
AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls); AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls);
OutStageTimings->SkirtMilliseconds=std::max(0.0,
OutStageTimings->SkirtMilliseconds);
const double TotalMilliseconds=static_cast<double>( const double TotalMilliseconds=static_cast<double>(
FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs; FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs;
OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds
@@ -2066,7 +2073,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
-OutStageTimings->CellClassificationMilliseconds -OutStageTimings->CellClassificationMilliseconds
-OutStageTimings->GradientNormalsMilliseconds -OutStageTimings->GradientNormalsMilliseconds
-OutStageTimings->EdgeInterpolationMilliseconds -OutStageTimings->EdgeInterpolationMilliseconds
-OutStageTimings->VertexDedupStreamMilliseconds); -OutStageTimings->VertexDedupStreamMilliseconds
-OutStageTimings->SkirtMilliseconds);
} }
return MeshData; return MeshData;
@@ -50,6 +50,7 @@ struct FVoxelMarchingCubesStageTimings
double GradientNormalsMilliseconds = 0.0; double GradientNormalsMilliseconds = 0.0;
double EdgeInterpolationMilliseconds = 0.0; double EdgeInterpolationMilliseconds = 0.0;
double VertexDedupStreamMilliseconds = 0.0; double VertexDedupStreamMilliseconds = 0.0;
double SkirtMilliseconds = 0.0;
double OtherMilliseconds = 0.0; double OtherMilliseconds = 0.0;
uint64 GradientFallbackDensityCalls = 0; uint64 GradientFallbackDensityCalls = 0;
uint64 EdgeMidpointDensityCalls = 0; uint64 EdgeMidpointDensityCalls = 0;