wip(L5, side branch): Linux offscreen captures + sampled far-LOD gate + no-op octave cutoff

Not for assembler/world: no octave qualifies under the cutoff's supremum criterion (world
unchanged), and the build is +4.6% slower than L4 with the flag off (reviewer interleaved W12).
Keeps the working EGL/GLES capture path and a gate whose metric is vertical height error, which
overstates screen error on steep slopes; its "exact far meshes exceed 1 px / 126 px pop" claims are
unconfirmed. See Linux5/REPORT.md "Orchestrator review".

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 20:39:41 +02:00
1 parent 7770074503
commit b21a19d561
33 files changed
+1977 -26

No files matched your search

+7
View File
@@ -33,6 +33,8 @@ if [[ "$FP_CONTRACT" != "default" && "$FP_CONTRACT" != "off" && "$FP_CONTRACT" !
fi
command -v "$COMPILER" >/dev/null || { printf 'Compiler not found: %s\n' "$COMPILER" >&2; exit 2; }
command -v ar >/dev/null || { printf 'ar not found\n' >&2; exit 2; }
command -v pkg-config >/dev/null || { printf 'pkg-config not found\n' >&2; exit 2; }
pkg-config --exists egl glesv2 libpng || { printf 'EGL, GLESv2, and libpng development files are required\n' >&2; exit 2; }
BUILD_DIR="$ROOT/Build/linux-$VARIANT"
BIN_DIR="$ROOT/bin/linux"
@@ -117,5 +119,10 @@ CORE_LIBRARY="$BUILD_DIR/libVoxelForgeCore.a"
"$(command -v ar)" rcs "$CORE_LIBRARY" "${CORE_OBJECTS[@]}"
OUTPUT="$BIN_DIR/VoxelForgeCoreHeadless-$VARIANT"
"$COMPILER" -pthread "${LEGACY_OBJECTS[@]}" "$CORE_LIBRARY" -o "$OUTPUT"
CAPTURE_OUTPUT="$BIN_DIR/VoxelForgeOffscreenCapture-$VARIANT"
"$COMPILER" -std=c++20 -O2 -fno-fast-math -pthread \
"$ROOT/Source/LinuxOffscreenCapture.cpp" -o "$CAPTURE_OUTPUT" \
$(pkg-config --cflags --libs egl glesv2 libpng)
printf 'Built Core library: %s\nBuilt headless benchmark: %s\n' "$CORE_LIBRARY" "$OUTPUT"
printf 'Built EGL offscreen capture: %s\n' "$CAPTURE_OUTPUT"
printf 'Compiler: %s | variant: %s | fp-contract: %s | jobs: %s\n' "$COMPILER" "$VARIANT" "$FP_CONTRACT" "$JOBS"
@@ -146,6 +146,11 @@ namespace VoxelForgeCore
double meshAssemblyMilliseconds = 0.0;
double meshingMilliseconds = 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 farStratumShellMilliseconds = 0.0;
double farConnectorShellMilliseconds = 0.0;
@@ -580,6 +585,8 @@ namespace VoxelForgeCore
struct FieldInterval;
struct FieldDetailEnvelope;
struct FieldBlockContract;
struct VisualDetailRecipeContractV1;
struct VisualDetailCutoffV1;
class VFCORE_API FloatingIslandFieldV1
{
@@ -602,6 +609,7 @@ namespace VoxelForgeCore
std::int64_t maximumZSubvoxels) const noexcept;
FloatingIslandInfluenceBounds influenceBounds() const noexcept;
FieldDetailEnvelope detailEnvelope() const noexcept;
VisualDetailRecipeContractV1 visualDetailContract() const;
FieldBlockContract contract() const noexcept;
const FloatingIslandRecipeV1* recipe() const noexcept { return recipe_; }
const FloatingIslandPlanV1* plan() const noexcept { return plan_; }
@@ -651,6 +659,12 @@ namespace VoxelForgeCore
std::uint64_t columnMisses = 0;
std::uint64_t columnConflictMisses = 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 coordinateCaptureComplete = true;
std::vector<std::array<std::int64_t, 2>> evaluatedCoordinates;
@@ -670,6 +684,44 @@ namespace VoxelForgeCore
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
{
std::uint32_t readsChannels = 0;
@@ -714,6 +766,15 @@ namespace VoxelForgeCore
std::int64_t maximumZSubvoxels) const noexcept;
FieldInfluenceBounds influenceBounds() 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;
const WorldAssemblyRecipeV1* recipe() const noexcept { return recipe_; }
const WorldAssemblyStratumPlanV1* plan() const noexcept { return plan_; }
@@ -1050,6 +1111,18 @@ namespace VoxelForgeCore
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
// densite et hashes LOD0-3 restent inchanges.
std::uint32_t farHorizonVersion = 3;
// Display mesh only. Zero preserves the exact field. Collision, queries and edits
// 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 diagnosticCellStride = 1;
std::uint32_t diagnosticColumnInterpolationStride = 1;
};
class VFCORE_API World
@@ -1905,6 +1905,13 @@ FieldDetailEnvelope FloatingIslandFieldV1::detailEnvelope() const noexcept
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 result;
+69 -1
View File
@@ -220,6 +220,13 @@ namespace VoxelForgeCore
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)
{
switch(value)
@@ -1330,6 +1337,25 @@ namespace VoxelForgeCore
std::optional<TileMesh> World::generateTile(const TileKey& key,const TileGenerationOptions& options,
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
// 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.
@@ -1446,6 +1472,14 @@ namespace VoxelForgeCore
}
}
}
int32 MeshCells=cells,MeshStep=step;
if(options.diagnosticCellStride>1&&level>=3)
{
const int32 Stride=static_cast<int32>(std::min<std::uint32_t>(
options.diagnosticCellStride,static_cast<std::uint32_t>(std::max(1,cells))));
MeshCells=FMath::Max(1,cells/Stride);
MeshStep=FMath::Max(1,extent/MeshCells);
}
int64 sampleCount=0;std::vector<float> densityValues;
FVoxelMarchingCubesStageTimings McStageTimings;
TileMesh out;out.key=key;
@@ -1678,10 +1712,23 @@ namespace VoxelForgeCore
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-FeatureListStart).count();
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
VisualDetailCutoffV1 DisplayCutoff;
DisplayCutoff.pixelBudgetSubvoxels=options.visualDetailPixelBudgetSubvoxels;
DisplayCutoff.sampleStepSubvoxels=DisplayCutoff.pixelBudgetSubvoxels>0
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel:0;
DisplayCutoff.callBudgetDivisor=std::max<std::uint32_t>(1,
options.visualDetailCallBudgetDivisor);
DisplayCutoff.apply=options.visualDetailCutoffEnabled;
Heightfield2D::setThreadVisualDetailCutoff(DisplayCutoff);
Heightfield2D::setThreadColumnOctaveDrop(options.diagnosticGlobalOctaveDrop);
Heightfield2D::setThreadColumnInterpolationStrideSubvoxels(
options.diagnosticColumnInterpolationStride>1&&level>=3
?static_cast<std::int64_t>(MeshStep)*WorldAssemblySubvoxelsPerVoxel
*options.diagnosticColumnInterpolationStride:0);
const double ColumnMillisecondsBeforeMeshing=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto MarchingCubesStart=std::chrono::steady_clock::now();
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
FVoxelMeshData mesh=mesher.GenerateMesh(origin,MeshStep,MeshCells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr,
options.profileMesherStages?&McStageTimings:nullptr);
@@ -1798,6 +1845,12 @@ namespace VoxelForgeCore
out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses;
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.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
@@ -1867,6 +1920,21 @@ namespace VoxelForgeCore
out.payloadHash=SaltFarHorizonHash(out.payloadHash,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.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
+322 -18
View File
@@ -1031,18 +1031,91 @@ namespace world_assembly_detail
// Audit-only override; normal tile generation resets this to zero before evaluating a recipe.
// Surcharge reservee aux mesures; la generation remet zero avant d'evaluer une recette.
inline thread_local std::uint32_t HeightfieldColumnOctaveDrop = 0;
inline thread_local VisualDetailCutoffV1 HeightfieldVisualDetailCutoff{};
inline thread_local bool HeightfieldVisualDetailProfileEnabled = false;
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,
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::uint32_t effectiveOctaves=octaves;
if(HeightfieldColumnOctaveDrop!=0&&octaves!=0)
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)
{
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);
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;
if (ridged)
{
@@ -1056,17 +1129,24 @@ namespace world_assembly_detail
weight /= 2;
}
if (weightSum == 0) return 0;
const std::int64_t average = (total * Q16) / weightSum;
// The cutoff retains the original denominator: omitted zero-mean terms do not
// 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);
}
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(
NoiseCoordinateQ16(x,wavelength)+SeedOffsetQ16(seed,730),
NoiseCoordinateQ16(y,wavelength)+SeedOffsetQ16(seed,210),
SeedOffsetQ16(seed,50),2);
SeedOffsetQ16(seed,50),2,false,wavelength,maximumDisplacementSubvoxels);
const std::int64_t raw = Q16/2 + noise/2;
const std::int64_t contrasted = Q16/2 + MulQ16(raw-Q16/2,contrastQ16);
return static_cast<std::int32_t>(SmoothQ16(static_cast<std::uint32_t>(
@@ -1102,15 +1182,22 @@ namespace world_assembly_detail
qx+=WarpDisplacement(wx,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,
biome.reliefContrastQ16);
biome.reliefContrastQ16,bDetailContractSafe?reliefInfluence:0);
const std::int32_t mountainous=static_cast<std::int32_t>(Q16
+MulQ16(relief-Q16,biome.reliefStrengthQ16));
if(outRelief)*outRelief=mountainous;
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 contZ=SeedOffsetQ16(seed,70);
const std::int32_t continent=FractalNoiseQ16(contX,contY,contZ,4);
const std::int64_t continentInfluence=(std::abs(biome.continentAmplitudeSubvoxels)*775+999)/1000;
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);
std::int64_t height=biome.baseHeightSubvoxels
+MulQ16(MulQ16(continent,biome.continentAmplitudeSubvoxels),continentScale)/2;
@@ -1119,14 +1206,19 @@ namespace world_assembly_detail
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 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);
height+=MulQ16(MulQ16(ridgeHeight,biome.mountainAmplitudeSubvoxels),mountainous);
}
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 detailZ=SeedOffsetQ16(seed,130);
height+=MulQ16(FractalNoiseQ16(detailX,detailY,detailZ,3),biome.detailAmplitudeSubvoxels);
const std::int32_t detailNoise=FractalNoiseQ16(detailX,detailY,detailZ,3,false,
biome.detailWavelengthSubvoxels,
bDetailContractSafe?std::abs(biome.detailAmplitudeSubvoxels):0);
height+=MulQ16(detailNoise,biome.detailAmplitudeSubvoxels);
return height;
}
@@ -1323,21 +1415,24 @@ namespace world_assembly_detail
{
const std::int64_t nx=NoiseCoordinateQ16(qx,swellWave)+SeedOffsetQ16(recipe.seed,190);
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);
}
if(detail>0)
{
const std::int64_t nx=NoiseCoordinateQ16(x,detailWave)+5*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);
}
if(ridge>0)
{
const std::int64_t nx=NoiseCoordinateQ16(qx,ridgeWave)+31*Q16;
const std::int64_t ny=NoiseCoordinateQ16(qy,ridgeWave)+47*Q16;
const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4,true);
const std::int32_t ridgeNoise=FractalNoiseQ16(nx,ny,SeedOffsetQ16(recipe.seed,110),4,
true,ridgeWave,std::abs(ridge)*2);
const std::int64_t hang=std::clamp<std::int64_t>((ridgeNoise+Q16)/2,0,Q16);
cap-=MulQ16(hang,ridge);
}
@@ -1360,6 +1455,10 @@ namespace world_assembly_detail
const WorldAssemblyStratumPlanV1* plan = nullptr;
std::uint64_t recipeHash = 0, planHash = 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{};
};
// Keep 65,536 entries in each worker cache; four ways tolerate coordinate families that
@@ -1372,7 +1471,6 @@ namespace world_assembly_detail
std::array<HeightfieldColumnCacheEntry, 65536>> HeightfieldColumnCache;
inline thread_local VoxelForgeCoreThreadLocal::FLazy<
std::array<std::uint8_t, HeightfieldColumnCacheSetCount>> HeightfieldColumnCacheNextWay;
inline thread_local HeightfieldColumnCacheStats HeightfieldColumnStats{};
inline thread_local bool HeightfieldColumnCacheLegacyDirectMapped=false;
inline std::size_t HeightfieldColumnCacheSet(const WorldAssemblyRecipeV1& recipe,
@@ -1382,9 +1480,19 @@ namespace world_assembly_detail
// 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
// 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::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*=0xBF58476D1CE4E5B9ull;
mixed^=mixed>>27;
@@ -1430,7 +1538,8 @@ namespace world_assembly_detail
const std::int64_t nx=NoiseCoordinateQ16(x,wave)+SeedOffsetQ16(featureSeed,1700);
const std::int64_t ny=NoiseCoordinateQ16(y,wave)+SeedOffsetQ16(featureSeed,2900);
const std::int32_t noise=FractalNoiseQ16(nx,ny,
SeedOffsetQ16(featureSeed,4100),3);
SeedOffsetQ16(featureSeed,4100),3,false,wave,
std::abs(feature.parametersSubvoxels[0]));
cap+=MulQ16(MulQ16(noise,feature.parametersSubvoxels[0]),fade);
}
}
@@ -1506,6 +1615,49 @@ namespace world_assembly_detail
inline HeightfieldPointSample CachedColumn(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan,std::int64_t x,std::int64_t y)
{
const std::int64_t InterpolationStride=HeightfieldColumnInterpolationStrideSubvoxels;
if(InterpolationStride>0)
{
const std::int64_t X0=FloorDiv(x,InterpolationStride)*InterpolationStride;
const std::int64_t Y0=FloorDiv(y,InterpolationStride)*InterpolationStride;
const std::int64_t X1=X0+InterpolationStride,Y1=Y0+InterpolationStride;
const std::int64_t Fx=x-X0,Fy=y-Y0;
struct FScopedInterpolationSampling
{
std::int64_t Previous=HeightfieldColumnInterpolationStrideSubvoxels;
~FScopedInterpolationSampling()
{HeightfieldColumnInterpolationStrideSubvoxels=Previous;}
} SamplingScope;
HeightfieldColumnInterpolationStrideSubvoxels=0;
const HeightfieldPointSample C00=CachedColumn(recipe,plan,X0,Y0);
const HeightfieldPointSample C10=CachedColumn(recipe,plan,X1,Y0);
const HeightfieldPointSample C01=CachedColumn(recipe,plan,X0,Y1);
const HeightfieldPointSample C11=CachedColumn(recipe,plan,X1,Y1);
const auto Lerp=[&](std::int64_t A,std::int64_t B,std::int64_t T)
{return A+((B-A)*T)/InterpolationStride;};
const auto Bilinear=[&](std::int64_t A,std::int64_t B,
std::int64_t C,std::int64_t D)
{
const std::int64_t Top=Lerp(A,B,Fx),Bottom=Lerp(C,D,Fx);
return Lerp(Top,Bottom,Fy);
};
HeightfieldPointSample Out=C00;
Out.groundHeightSubvoxels=Bilinear(C00.groundHeightSubvoxels,
C10.groundHeightSubvoxels,C01.groundHeightSubvoxels,
C11.groundHeightSubvoxels);
Out.capHeightSubvoxels=Bilinear(C00.capHeightSubvoxels,
C10.capHeightSubvoxels,C01.capHeightSubvoxels,
C11.capHeightSubvoxels);
return Out;
}
const std::int64_t DetailBudget=HeightfieldVisualDetailCutoff.pixelBudgetSubvoxels;
const bool bVisualContextActive=DetailBudget>0;
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& nextWay=HeightfieldColumnCacheNextWay.Get();
HeightfieldColumnCacheEntry* replacement=nullptr;
@@ -1516,11 +1668,23 @@ namespace world_assembly_detail
const std::uint64_t mixed=(static_cast<std::uint64_t>(x)*0x9E3779B185EBCA87ull)
^std::rotl(static_cast<std::uint64_t>(y)*0xC2B2AE3D27D4EB4Full,23)
^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)];
if(replacement->recipe==&recipe&&replacement->plan==&plan
&&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;}
if(replacement->recipe!=nullptr)++HeightfieldColumnStats.columnConflictMisses;
}
@@ -1534,7 +1698,14 @@ namespace world_assembly_detail
auto& candidate=cache[first+way];
if(candidate.recipe==&recipe&&candidate.plan==&plan
&&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;}
if(candidate.recipe==nullptr&&replacementWay==HeightfieldColumnCacheWays)
replacementWay=way;
@@ -1565,7 +1736,8 @@ namespace world_assembly_detail
HeightfieldColumnStats.coordinateCaptureComplete=false;
}
}
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column};
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,
DetailBudget,DetailStep,DetailDivisor,DetailApply,OctaveDrop,column};
return column;
}
@@ -1655,6 +1827,48 @@ 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::string_view json, std::string& error)
{
@@ -2050,6 +2264,11 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
Stats.columnMisses=0;
Stats.columnConflictMisses=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.coordinateCaptureComplete=true;
Stats.evaluatedCoordinates.clear();
@@ -2058,6 +2277,33 @@ void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates, bool cl
void Heightfield2D::setThreadColumnCacheLegacyDirectMapped(bool enabled) noexcept
{ 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
{world_assembly_detail::HeightfieldColumnOctaveDrop=octaveDrop;}
std::uint32_t Heightfield2D::threadColumnOctaveDrop() noexcept
{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
{world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels=std::max<std::int64_t>(0,stride);}
std::int64_t Heightfield2D::threadColumnInterpolationStrideSubvoxels() noexcept
{return world_assembly_detail::HeightfieldColumnInterpolationStrideSubvoxels;}
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
{ return world_assembly_detail::HeightfieldColumnStats; }
@@ -2254,6 +2500,64 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
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
{
return {ChannelBit(FieldChannel::biome),
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
import os
import signal
import subprocess
import sys
import time
if len(sys.argv) < 4 or sys.argv[2] != "--":
raise SystemExit("usage: MeasureProcess.py LOG -- COMMAND [ARGS...]")
log_path = sys.argv[1]
command = sys.argv[3:]
os.makedirs(os.path.dirname(log_path) or ".", exist_ok=True)
start = time.monotonic()
with open(log_path, "wb") as log_file:
process = subprocess.Popen(command, stdout=log_file, stderr=subprocess.STDOUT,
start_new_session=True)
max_hwm_kib = 0
max_rss_kib = 0
max_pid = process.pid
max_name = ""
deadline = start + 1800.0
while process.poll() is None:
if time.monotonic() >= deadline:
os.killpg(process.pid, signal.SIGTERM)
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
print("MONITOR_TIMEOUT_SECONDS=1800", file=log_file, flush=True)
break
stat_by_pid = {}
for entry in os.scandir("/proc"):
if not entry.name.isdigit():
continue
pid = int(entry.name)
try:
with open(f"/proc/{pid}/stat", "r", encoding="ascii") as stat_file:
fields = stat_file.read().rsplit(")", 1)[1].split()
ppid = int(fields[1])
stat_by_pid[pid] = ppid
except (OSError, ValueError, IndexError):
continue
descendants = {process.pid}
changed = True
while changed:
changed = False
for pid, ppid in stat_by_pid.items():
if ppid in descendants and pid not in descendants:
descendants.add(pid)
changed = True
for pid in descendants:
try:
with open(f"/proc/{pid}/status", "r", encoding="ascii") as status_file:
status = status_file.read().splitlines()
values = {line.split(":", 1)[0]: line.split(":", 1)[1].strip()
for line in status if ":" in line}
hwm = int(values.get("VmHWM", "0 kB").split()[0])
rss = int(values.get("VmRSS", "0 kB").split()[0])
name = values.get("Name", "")
except (OSError, ValueError, IndexError):
continue
if hwm > max_hwm_kib:
max_hwm_kib, max_pid, max_name = hwm, pid, name
max_rss_kib = max(max_rss_kib, rss)
time.sleep(0.05)
result = process.wait()
elapsed = time.monotonic() - start
for line in (
f"MONITOR_EXIT_CODE={result}",
f"MONITOR_WALL_SECONDS={elapsed:.3f}",
f"MONITOR_PEAK_PROCESS_HWM_KIB={max_hwm_kib}",
f"MONITOR_PEAK_PROCESS_PID={max_pid}",
f"MONITOR_PEAK_PROCESS_NAME={max_name}",
f"MONITOR_MAX_SAMPLED_PROCESS_RSS_KIB={max_rss_kib}",
):
log_file.write((line + "\n").encode("utf-8"))
raise SystemExit(result)
@@ -0,0 +1,165 @@
# World Assembler M3 — Linux L5
**Result:** no octave tail is safely removable under the declared bounds. The exact far meshes already exceed the 1 px field-error and LOD-swap budgets, and the cutoff flag is 2.2% slower in the W12 view. The candidate therefore generated the exact same meshes; recipe version stays 1.
## Build and machine
Built `VoxelForgeCoreHeadless-linux5-l5-final7` and its capture target on `assembler/world` at `7770074` with Clang 19 `-ffp-contract=off`, then GCC 14 default contraction, both with 12 jobs. W1/W12 hashes match across both compilers; the L3 FP-contract preamble and runtime self-test remain active. Runs used CPUs 0–11, normal priority, and the 6 GiB process limit. Maximum process HWM was 898,692 KiB (GCC compile); the largest runtime process was 454,828 KiB. CPU governor stayed `powersave`. `/dev/dri/renderD128` was absent, so EGL used Mesa llvmpipe with `LP_NUM_THREADS=12`. No packages or system settings were changed.
## Capture and gate
`LinuxOffscreenCapture.cpp` reads the `.vfmesh` export from the headless streaming pipeline, creates a surfaceless EGL/GLES3 pbuffer, and writes 2560×1440 PNGs with libpng. `ffmpeg` made 1280×720 review JPEGs; the largest is 73,923 bytes. The shader combines surface normals and 80 m height bands. Only the small JPEGs were opened for review.
The gate samples a 65×65 XY grid on the closest native-surface tile at each LOD, excluding skirts. It reports two comparisons: displayed mesh against exact `Heightfield2D` ground/cap roots, and against a regenerated exact same-LOD mesh. The second is the zero-delta regression control; the first measures the actual field error. Swap error compares the coarse tile with its fine children at those same field roots. Full missing-surface, 2 m floating-surface, and missing fine-ring injections were rejected at every LOD3–8; so were both S13 controls.
This is a sampled heightfield gate, not a full camera-ray rasterizer. It can miss cracks narrower than one grid cell (1 m at LOD3, growing to 32 m at LOD8), and its direct-field row does not model tunnel walls or other non-heightfield faces. The same-LOD mesh comparison covers extra and missing mesh intersections at sampled points; a denser or full-screen geometry pass would be needed to certify sub-cell cracks and non-heightfield surfaces.
## Closest display distances and budgets
The code sweeps clipmap desired sets across X-boundary phases and measures tile AABB distance. The result matches the clipmap ring formula `3 × 32 chunks × 2^(LOD−1) × 0.25 m`; LOD0 is the player tile. The pixel angle is `90° / 2560 = 0.000613592 rad`.
| LOD | Closest display distance | 1 px at that distance | Active rule |
|---:|---:|---:|---:|
| 0 | 0 m | 0 cm | exact, 2 cm |
| 1 | 24.001 m | 1.473 cm | exact, 2 cm |
| 2 | 48.001 m | 2.945 cm | exact, 2 cm |
| 3 | 96.001 m | 5.891 cm | 1 px |
| 4 | 192.001 m | 11.781 cm | 1 px |
| 5 | 384.001 m | 23.562 cm | 1 px |
| 6 | 768.001 m | 47.124 cm | 1 px |
| 7 | 1,536.001 m | 94.248 cm | 1 px |
| 8 | 3,072.001 m | 188.496 cm | 1 px |
## Exact-field error and swap pop
Baseline and cutoff values are identical. The exact same-LOD regression control is 0.0000 px p50/p99/max at all LOD3–8, with no missing or floating samples. Against the continuous `Heightfield2D` roots, however, the current exact meshes already exceed 1 px at every LOD. The owner’s expected near-zero control holds for exact-versus-exact mesh output, not for the true-field comparison.
| LOD | Exact field p50 / p99 / max px (baseline → cutoff) | Swap p50 / p99 / max px | Swap cracks / floating |
|---:|---:|---:|---:|
| 3 → 2 | 0.9035 / 4.5839 / 7.3277 → same | 0.6714 / 4.1861 / 7.3277 | 0 / 0 |
| 4 → 3 | 1.1170 / 5.6056 / 8.3307 → same | 0.8269 / 5.1958 / 8.2105 | 0 / 0 |
| 5 → 4 | 1.5667 / 45.3010 / 126.2667 → same | 0.0000 / 20.7227 / 63.2266 | 0 / 0 |
| 6 → 5 | 0.5870 / 21.2445 / 82.3615 → same | 0.0000 / 21.2445 / 82.3615 | 0 / 0 |
| 7 → 6 | 0.3393 / 16.4341 / 47.9760 → same | 0.0000 / 16.4341 / 47.9760 | 0 / 0 |
| 8 → 7 | 0.9616 / 24.9479 / 50.0934 → same | 0.9616 / 24.9479 / 50.0934 | 0 / 0 |
The baseline gate rejects on the field row and every swap row. The cutoff run returns the same rejection. Fault injections also rejected: missing-surface samples were 4,225–13,642; floating-surface samples 4,225–13,641; missing fine-ring cracks 4,225–8,450.
| LOD | S13 octave-drop p99 / max px | Stride-2 column interpolation p99 / max px |
|---:|---:|---:|
| 3 | 960.2333 / 998.8654 | 9.3917 / 13.9093 |
| 4 | 3.7405 / 5.1103 | 9.2115 / 15.3435 |
| 5 | 105.7114 / 129.3089 | 125.1435 / 191.6424 |
| 6 | 218.7777 / 749.9535 | 82.1098 / 148.9485 |
| 7 | 95.1867 / 129.1796 | 40.6881 / 80.6012 |
| 8 | 38.1461 / 282.3288 | 28.8473 / 278.6236 |
Both controls were rejected at every LOD3–8. The octave-drop case produced missing samples at LOD3, 5–8 and floating samples at LOD5–8; stride-2 interpolation produced missing/floating samples at LOD5, 7 and 8.
## Cutoff contract and detail cost
The version-2 shared visual-detail contract declares stable octave terms with wavelength, octave count, displacement bound, and noise bound. A suffix is eligible only when its wavelengths are undersampled and its full displacement bound fits within one sixty-fourth of the tile’s pixel budget. Skipped zero-mean terms retain the original normalization denominator. Recipes with no terms keep the exact path; `FloatingIslandFieldV1` declares none, and its baseline/cutoff LOD hashes match. This is separate from content recipe version 1, which stays unchanged because output did not change.
SurfaceWorld declares 17 terms per LOD. No term or octave tail qualifies at LOD3–8. Sample steps are 16, 32, 64, 128, 256, and 512 voxels respectively. The bounds explain the result: even the smallest declared maximum displacement is 256 voxels, against a 0.2356-voxel 1 px budget at LOD3. The cutoff skips zero octaves, so eligible octave time is 0 ms.
| Octave | Terms | Wavelength range (voxels) | Max displacement range (voxels) |
|---:|---:|---:|---:|
| 0 | 17 | 25–10,000 | 1,024–640,000 |
| 1 | 17 | 12.5–5,000 | 512–320,000 |
| 2 | 15 | 6.25–1,250 | 256–160,000 |
| 3 | 4 | 41.664–250 | 3,968–24,800 |
W12 profile from the exact baseline; octave milliseconds are summed across evaluated calls/workers. They are all measured detail work, not removable work under the bound above.
| LOD | Worker sum s | Octave 0 ms | Octave 1 ms | Octave 2 ms | Octave 3 ms | Detail total ms (% worker sum) | Eligible ms |
|---:|---:|---:|---:|---:|---:|---:|---:|
| 3 | 1.660 | 71.585 | 69.911 | 68.535 | 15.407 | 225.437 (13.58%) | 0 |
| 4 | 3.321 | 135.512 | 131.224 | 128.645 | 26.506 | 421.887 (12.70%) | 0 |
| 5 | 2.283 | 82.131 | 80.450 | 77.545 | 16.262 | 256.388 (11.23%) | 0 |
| 6 | 3.587 | 153.409 | 148.854 | 144.175 | 29.984 | 476.422 (13.28%) | 0 |
| 7 | 1.447 | 35.301 | 34.493 | 33.138 | 7.069 | 110.001 (7.60%) | 0 |
| 8 | 2.685 | 58.763 | 57.751 | 56.277 | 11.848 | 184.638 (6.88%) | 0 |
The cutoff is recipe-generic, display-mesh-only, and activated only for LOD3+. Exact density, detail, collision, edit, and walkability paths remain outside it. LOD0–2 keep the 2 cm path. ARCHITECTURE §8.10 invariants remain: lazy thread-local caches, two-pass MC, SSE noise, and clipmap streaming. The cutoff state is scoped to mesh generation under the async-safety rules.
## W12 speed, interleaved with L4
Three L4/candidate pairs, all W12, measured on the L1 camera. The candidate is the final build with `--visual-detail-cutoff`.
| Measure (median) | L4 binary | Candidate | Change |
|---|---:|---:|---:|
| Whole-view ready | 2.183 s | 2.232 s | +2.2% |
| Stream wall | 1.895 s | 1.963 s | +3.6% |
| Far worker sum, LOD3–8 | 12.247 s | 12.458 s | +1.7% |
| LOD | L4 worker sum s | Candidate worker sum s |
|---:|---:|---:|
| 0 | 3.799 | 4.094 |
| 1 | 5.304 | 5.573 |
| 2 | 1.281 | 1.340 |
| 3 | 1.102 | 1.138 |
| 4 | 2.723 | 2.719 |
| 5 | 1.898 | 1.976 |
| 6 | 2.875 | 2.944 |
| 7 | 1.243 | 1.282 |
| 8 | 2.355 | 2.400 |
There is no speed benefit because nothing qualifies for skipping; the small regression is cutoff setup and bookkeeping.
## Hashes and captures
Baseline and cutoff hashes match for all LOD0–8. The old L4 binary’s aggregate hashes also match: whole view `c10697a8f48380c7 / e75c9119a1498cd5 / 49a9a8d1835a6c8c / edcb23288b5e8724` and full detail `0ee0579bd15cbbe4 / 32e5d4964d610fee / 1932b96ee4b1d910 / 69743c6bad3447dc`. The recipe stays version 1 because output did not change.
| LOD | Density | Mesh | Payload | Topology |
|---:|---|---|---|---|
| 0 | 769f2a706d585a72 | 33eeeb39fa443816 | 645621a1d968983f | c2457ed905036c9d |
| 1 | 69cc5a5c0a2c60af | b48335f5410c57d5 | d15c5480162ab8ba | feca1bd025823604 |
| 2 | 85deed5683a77202 | 53469895987aeea2 | e55f8ae7e1532033 | d5feb12a0e85c09b |
| 3 | 6518caefb3be7e7f | ec513c90b853a614 | f62fa1137ff17f82 | ccf1dcf681965816 |
| 4 | 62d4be96021992f4 | f1e4c6efcc2615a9 | 9dc5f9c88be02f33 | fe6f55125a07c3bf |
| 5 | 5e4a8c38b8cea6fc | 08ee56998aaf21e5 | 541eea8558f26cc8 | 6539374a8b3336d8 |
| 6 | e036e95b9006945b | 624a33f941a99b02 | 81b73e14f68bdf86 | 4de4c63ce1f80a56 |
| 7 | 8ee358af56a30d8d | 46ab3c6184048b7c | 0a6164b7e68e80c5 | 0c26b85bb03bc831 |
| 8 | c3458e1cbdb9c471 | 46c03d0994d75192 | 8f89fc5bde12d230 | d16dc0cf62031898 |
Nine direct-Core query probes also match baseline/cutoff and W1/W12 Clang/GCC: density and unclamped `e4d4a8eb95b35933`, detail `efcba20c0c70c415`, collision `e604823a249029bf`.
| Before / after pair | View |
|---|---|
| `before_l1.jpg` / `after_l1.jpg` | Exact L1 camera `0,-35,-487.94,0,-22` |
| `before_horizon.jpg` / `after_horizon.jpg` | Same position, pitch −2° |
| `before_boundary_lod2_3.jpg` / `after_boundary_lod2_3.jpg` | 20 m above local heightfield, aimed at the 96 m LOD3 entry band |
The boundary camera is `0,-35,-480.77246,0,-12`; the local heightfield is at −500.77246 m, placing the ground intersection about 96 m away.
The six review JPEGs are byte-identical within each pair. PNGs are in `Saved/Linux/captures/`; JPEGs are in `Linux5/captures/`.
## Failed and retried runs
- The first headless invocation omitted required streaming flags; the next used the default binary-relative data path and could not open the owner JSON. Both were retried with all flags and the explicit `Standalone/data/owner_world.json` path.
- The first process-monitor wrapper errored after its Clang build had completed; the run was repeated with `MeasureProcess.py` to record process HWM and duration.
- The first capture named a nonexistent mesh path and exited 2; the exported `.vfmesh` path was corrected.
- The first gate build failed on a duplicate `Distances` local; the corrected build passed. A first stride-2 control changed the MC lattice instead of reproducing S13’s every-second-column interpolation; the final control uses bilinear column interpolation.
- Initial comparison against only the continuous field mixed surface interpolation with unrelated tunnel intersections. The final instrument reports exact heightfield roots separately from the exact-mesh regression and ring checks.
- Early W12 cutoff candidates took 2.70–2.91 s. The zero-eligible path and distance setup were moved off the expensive profiling/phase-sweep path; the final interleaved candidate median is 2.232 s. It still does not improve L4.
- One final GCC W12 command omitted `--stream-pipeline` and exited 2; the corrected rerun passed. The initial −50° boundary capture mostly showed near ground; the final pair uses the camera listed above.
- The final baseline and cutoff gate commands exit 6 by design: both correctly reject the exact-field error and swap pops. No process exceeded 30 minutes or stalled for 15 minutes.
Core/API changes are in `VoxelForgeCore.h`, `WorldAssemblerInternal.h`, `FloatingIslandsInternal.h`, and `VoxelForgeCore.cpp`. Standalone changes are in `StreamingWorld.h`, `StreamBenchmarkPipeline.h`, `StreamBenchmarkHeadless.cpp`, `LinuxScreenSpaceGate.h`, `LinuxOffscreenCapture.cpp`, and `BuildLinux.sh`; process measurements are in `MeasureProcess.py`.
**For the owner:** Far terrain will look exactly as it does now because no octave tail met the bound. Existing far LOD swaps exceed the 1 px target, so the mesh sampling and transition error need attention before octave skipping can be used.
## Orchestrator review (Claude)
- **Not merged into `assembler/world`:** kept on side branch `wip/l5-far-cutoff`. With the cutoff flag OFF,
the L5 build is slower than L4. My interleaved W12 runs: L4 2.054/2.097/2.179 s vs L5 `final7`
2.127/2.193/2.208 s (medians 2.097 → 2.193, +4.6%), same world. Cost and no benefit.
- **Instrument caveat:** the gate prints `metric=vertical_height_error`. On steep slopes and cliffs, a
small horizontal offset becomes a large vertical error, so vertical error overstates screen
displacement. p50 is ~1 px but p99/max reach 20–126 px, the pattern such an artifact produces. The
claims "today's far meshes exceed 1 px" and "LOD swaps pop by up to 126 px" are therefore NOT yet
established. They need a screen-space measure: error along the surface normal or along camera rays,
projected at the display distance.
- The cutoff criterion (full displacement supremum ≤ 1/64 px) cannot pass for any SurfaceWorld octave
(smallest bound 256 voxels vs 0.24), so the null result was decided by the criterion, not measured.
- The capture path (EGL/GLES pbuffer; llvmpipe, since the sandbox hides `/dev/dri`) works and is worth keeping.
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+387
View File
@@ -0,0 +1,387 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <png.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <string_view>
#include <vector>
namespace
{
struct FVertex
{
float Position[3];
float Normal[3];
float Height;
};
struct FScene
{
std::uint32_t VoxelSizeMicrons=0;
std::array<double,3> CameraMeters{};
double YawDegrees=0.0,PitchDegrees=0.0;
std::vector<FVertex> Vertices;
std::vector<std::uint32_t> Indices;
std::uint64_t Triangles=0,SkirtTriangles=0;
std::uint32_t TileCount=0;
};
template<typename T> bool Read(std::ifstream& File,T& Value)
{
File.read(reinterpret_cast<char*>(&Value),sizeof(Value));
return static_cast<bool>(File);
}
bool LoadScene(const std::filesystem::path& Path,FScene& Scene,std::string& Error)
{
std::ifstream File(Path,std::ios::binary);
if(!File){Error="cannot open mesh scene";return false;}
char Magic[8]{};
File.read(Magic,sizeof(Magic));
if(!File||std::memcmp(Magic,"VFMSH001",8)!=0)
{Error="mesh scene magic is invalid";return false;}
std::uint32_t Version=0,ExpectedTiles=0;
if(!Read(File,Version)||!Read(File,Scene.VoxelSizeMicrons)||!Read(File,ExpectedTiles)
||Version!=1||Scene.VoxelSizeMicrons==0||ExpectedTiles>1000000)
{Error="mesh scene header is malformed";return false;}
for(double& Value:Scene.CameraMeters)if(!Read(File,Value))
{Error="mesh scene camera is truncated";return false;}
if(!Read(File,Scene.YawDegrees)||!Read(File,Scene.PitchDegrees))
{Error="mesh scene camera orientation is truncated";return false;}
Scene.TileCount=ExpectedTiles;
std::uint64_t TotalVertices=0,TotalIndices=0;
for(std::uint32_t Tile=0;Tile<ExpectedTiles;++Tile)
{
std::int32_t Key[4]{};
std::uint32_t SurfaceVertices=0,SkirtCount=0,VertexCount=0,IndexCount=0,MaskCount=0;
for(auto& Value:Key)if(!Read(File,Value)){Error="tile key is truncated";return false;}
if(!Read(File,SurfaceVertices)||!Read(File,SkirtCount)||!Read(File,VertexCount)
||!Read(File,IndexCount)||!Read(File,MaskCount)
||VertexCount>10000000||IndexCount>60000000||MaskCount>20000000
||IndexCount%3!=0||(MaskCount!=0&&MaskCount!=IndexCount/3))
{Error="tile mesh header is malformed";return false;}
if(TotalVertices+VertexCount>std::numeric_limits<std::uint32_t>::max()
||TotalIndices+IndexCount>std::numeric_limits<std::size_t>::max())
{Error="mesh scene exceeds supported index space";return false;}
const std::size_t Base=Scene.Vertices.size();
Scene.Vertices.resize(Base+VertexCount);
Scene.Indices.reserve(Scene.Indices.size()+IndexCount);
for(std::size_t I=Base;I<Base+VertexCount;++I)
for(float& Value:Scene.Vertices[I].Position)if(!Read(File,Value))
{Error="tile positions are truncated";return false;}
for(std::size_t I=Base;I<Base+VertexCount;++I)
{
for(float& Value:Scene.Vertices[I].Normal)if(!Read(File,Value))
{Error="tile normals are truncated";return false;}
Scene.Vertices[I].Height=Scene.Vertices[I].Position[2];
}
const std::size_t IndexOffset=Scene.Indices.size();
Scene.Indices.resize(IndexOffset+IndexCount);
if(IndexCount)File.read(reinterpret_cast<char*>(Scene.Indices.data()+IndexOffset),
static_cast<std::streamsize>(sizeof(std::uint32_t)*IndexCount));
std::vector<std::uint8_t> Masks(MaskCount);
if(MaskCount)File.read(reinterpret_cast<char*>(Masks.data()),MaskCount);
if(!File){Error="tile index or skirt data is truncated";return false;}
for(std::size_t Index=IndexOffset;Index<Scene.Indices.size();++Index)
{
if(Scene.Indices[Index]>=VertexCount)
{Error="tile index points outside its vertex array";return false;}
Scene.Indices[Index]+=static_cast<std::uint32_t>(Base);
}
Scene.Triangles+=IndexCount/3;
Scene.SkirtTriangles+=SkirtCount;
TotalVertices+=VertexCount;TotalIndices+=IndexCount;
}
if(File.peek()!=std::char_traits<char>::eof())
{Error="mesh scene has trailing bytes";return false;}
if(Scene.Indices.empty()){Error="mesh scene contains no triangles";return false;}
return true;
}
EGLDisplay MakeDisplay()
{
using TGetPlatformDisplay=EGLDisplay(*)(EGLenum,void*,const EGLint*);
auto GetPlatformDisplay=reinterpret_cast<TGetPlatformDisplay>(
eglGetProcAddress("eglGetPlatformDisplayEXT"));
#ifdef EGL_PLATFORM_SURFACELESS_MESA
if(GetPlatformDisplay)
{
const EGLDisplay Display=GetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA,
EGL_DEFAULT_DISPLAY,nullptr);
if(Display!=EGL_NO_DISPLAY)return Display;
}
#endif
return eglGetDisplay(EGL_DEFAULT_DISPLAY);
}
GLuint CompileShader(GLenum Type,const char* Source)
{
const GLuint Shader=glCreateShader(Type);
glShaderSource(Shader,1,&Source,nullptr);
glCompileShader(Shader);
GLint Success=GL_FALSE;
glGetShaderiv(Shader,GL_COMPILE_STATUS,&Success);
if(Success==GL_FALSE)
{
GLint Length=0;glGetShaderiv(Shader,GL_INFO_LOG_LENGTH,&Length);
std::string Log(static_cast<std::size_t>(std::max(Length,1)),0);
glGetShaderInfoLog(Shader,Length,nullptr,Log.data());
std::fprintf(stderr,"[OffscreenGL] shader compile failed: %s\n",Log.c_str());
glDeleteShader(Shader);return 0;
}
return Shader;
}
GLuint MakeProgram()
{
static constexpr char VertexSource[]=R"GLSL(#version 300 es
precision highp float;
layout(location=0) in vec3 inCameraPosition;
layout(location=1) in vec3 inNormal;
layout(location=2) in float inHeight;
out vec3 vNormal;
out float vHeight;
uniform float uTanHalfHorizontal;
uniform float uTanHalfVertical;
uniform float uNear;
uniform float uFar;
void main()
{
float z=max(inCameraPosition.z,0.0001);
float clipZ=((uFar+uNear)/(uFar-uNear))*z-(2.0*uFar*uNear)/(uFar-uNear);
gl_Position=vec4(inCameraPosition.x/uTanHalfHorizontal,
inCameraPosition.y/uTanHalfVertical,clipZ,z);
vNormal=normalize(inNormal);
vHeight=inHeight;
})GLSL";
static constexpr char FragmentSource[]=R"GLSL(#version 300 es
precision highp float;
in vec3 vNormal;
in float vHeight;
out vec4 outColor;
void main()
{
vec3 n=normalize(vNormal);
vec3 normalColor=abs(n)*0.78+vec3(0.12);
float band=mod(floor((vHeight+10000.0)/80.0),2.0);
vec3 bandColor=mix(vec3(0.24,0.31,0.37),vec3(0.39,0.42,0.35),band);
outColor=vec4(mix(bandColor,normalColor,0.68),1.0);
})GLSL";
const GLuint VS=CompileShader(GL_VERTEX_SHADER,VertexSource);
const GLuint FS=CompileShader(GL_FRAGMENT_SHADER,FragmentSource);
if(!VS||!FS)return 0;
const GLuint Program=glCreateProgram();
glAttachShader(Program,VS);glAttachShader(Program,FS);glLinkProgram(Program);
glDeleteShader(VS);glDeleteShader(FS);
GLint Success=GL_FALSE;glGetProgramiv(Program,GL_LINK_STATUS,&Success);
if(Success==GL_FALSE)
{
GLint Length=0;glGetProgramiv(Program,GL_INFO_LOG_LENGTH,&Length);
std::string Log(static_cast<std::size_t>(std::max(Length,1)),0);
glGetProgramInfoLog(Program,Length,nullptr,Log.data());
std::fprintf(stderr,"[OffscreenGL] program link failed: %s\n",Log.c_str());
glDeleteProgram(Program);return 0;
}
return Program;
}
bool WritePng(const std::filesystem::path& Path,int Width,int Height,
const std::vector<std::uint8_t>& Pixels)
{
std::error_code Error;
if(!Path.parent_path().empty())std::filesystem::create_directories(Path.parent_path(),Error);
if(Error)return false;
FILE* File=std::fopen(Path.c_str(),"wb");
if(!File)return false;
png_structp Png=png_create_write_struct(PNG_LIBPNG_VER_STRING,nullptr,nullptr,nullptr);
png_infop Info=Png?png_create_info_struct(Png):nullptr;
if(!Png||!Info){if(Png)png_destroy_write_struct(&Png,nullptr);std::fclose(File);return false;}
if(setjmp(png_jmpbuf(Png)))
{png_destroy_write_struct(&Png,&Info);std::fclose(File);return false;}
png_init_io(Png,File);
png_set_IHDR(Png,Info,static_cast<png_uint_32>(Width),static_cast<png_uint_32>(Height),
8,PNG_COLOR_TYPE_RGBA,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
png_write_info(Png,Info);
const std::size_t Stride=static_cast<std::size_t>(Width)*4;
for(int Y=Height-1;Y>=0;--Y)
png_write_row(Png,const_cast<png_bytep>(Pixels.data()+static_cast<std::size_t>(Y)*Stride));
png_write_end(Png,nullptr);
png_destroy_write_struct(&Png,&Info);
const bool Good=std::fclose(File)==0;
return Good;
}
bool Render(FScene& Scene,const std::filesystem::path& Output,int Width,int Height,
double HorizontalFovDegrees)
{
EGLDisplay Display=MakeDisplay();
EGLint Major=0,Minor=0;
if(Display==EGL_NO_DISPLAY||eglInitialize(Display,&Major,&Minor)!=EGL_TRUE)
{std::fprintf(stderr,"[OffscreenEGL] eglInitialize failed (0x%x)\n",eglGetError());return false;}
if(eglBindAPI(EGL_OPENGL_ES_API)!=EGL_TRUE)
{std::fprintf(stderr,"[OffscreenEGL] eglBindAPI failed (0x%x)\n",eglGetError());eglTerminate(Display);return false;}
const EGLint ConfigAttributes[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE,EGL_OPENGL_ES3_BIT,EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,
EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_DEPTH_SIZE,24,EGL_NONE};
EGLConfig Config{};EGLint ConfigCount=0;
if(eglChooseConfig(Display,ConfigAttributes,&Config,1,&ConfigCount)!=EGL_TRUE||ConfigCount==0)
{std::fprintf(stderr,"[OffscreenEGL] no GLES3 pbuffer config (0x%x)\n",eglGetError());eglTerminate(Display);return false;}
const EGLint SurfaceAttributes[]={EGL_WIDTH,Width,EGL_HEIGHT,Height,EGL_NONE};
EGLSurface Surface=eglCreatePbufferSurface(Display,Config,SurfaceAttributes);
const EGLint ContextAttributes[]={EGL_CONTEXT_CLIENT_VERSION,3,EGL_NONE};
EGLContext Context=eglCreateContext(Display,Config,EGL_NO_CONTEXT,ContextAttributes);
if(Surface==EGL_NO_SURFACE||Context==EGL_NO_CONTEXT
||eglMakeCurrent(Display,Surface,Surface,Context)!=EGL_TRUE)
{std::fprintf(stderr,"[OffscreenEGL] context/surface creation failed (0x%x)\n",eglGetError());
if(Context!=EGL_NO_CONTEXT)eglDestroyContext(Display,Context);
if(Surface!=EGL_NO_SURFACE)eglDestroySurface(Display,Surface);
eglTerminate(Display);return false;}
const GLubyte* Renderer=glGetString(GL_RENDERER);
const GLubyte* GlVersion=glGetString(GL_VERSION);
std::printf("[OffscreenEGL] renderer=%s version=%s pbuffer=%dx%d\n",
Renderer?reinterpret_cast<const char*>(Renderer):"unknown",
GlVersion?reinterpret_cast<const char*>(GlVersion):"unknown",Width,Height);
GLuint ColorTexture=0,DepthBuffer=0,Framebuffer=0;
glGenTextures(1,&ColorTexture);glBindTexture(GL_TEXTURE_2D,ColorTexture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,Width,Height,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glGenRenderbuffers(1,&DepthBuffer);glBindRenderbuffer(GL_RENDERBUFFER,DepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT24,Width,Height);
glGenFramebuffers(1,&Framebuffer);glBindFramebuffer(GL_FRAMEBUFFER,Framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,ColorTexture,0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,DepthBuffer);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
{std::fprintf(stderr,"[OffscreenGL] framebuffer is incomplete\n");return false;}
constexpr double Pi=3.14159265358979323846;
const double Yaw=Scene.YawDegrees*Pi/180.0,Pitch=Scene.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)};
const std::array<double,3> Right={std::cos(Yaw),-std::sin(Yaw),0.0};
const std::array<double,3> Up={Right[1]*Forward[2]-Right[2]*Forward[1],
Right[2]*Forward[0]-Right[0]*Forward[2],Right[0]*Forward[1]-Right[1]*Forward[0]};
const double TanHalfHorizontal=std::tan(HorizontalFovDegrees*Pi/360.0);
const double Aspect=static_cast<double>(Width)/Height;
const double TanHalfVertical=TanHalfHorizontal/Aspect;
const double Near=0.1,Far=20000.0;
for(FVertex& Vertex:Scene.Vertices)
{
const double DX=Vertex.Position[0]-Scene.CameraMeters[0];
const double DY=Vertex.Position[1]-Scene.CameraMeters[1];
const double DZ=Vertex.Position[2]-Scene.CameraMeters[2];
const float CameraX=static_cast<float>(DX*Right[0]+DY*Right[1]+DZ*Right[2]);
const float CameraY=static_cast<float>(DX*Up[0]+DY*Up[1]+DZ*Up[2]);
const float CameraZ=static_cast<float>(DX*Forward[0]+DY*Forward[1]+DZ*Forward[2]);
Vertex.Position[0]=CameraX;Vertex.Position[1]=CameraY;Vertex.Position[2]=CameraZ;
}
const GLuint Program=MakeProgram();
if(!Program)return false;
GLuint Vao=0,Vbo=0,Ebo=0;
glGenVertexArrays(1,&Vao);glBindVertexArray(Vao);
glGenBuffers(1,&Vbo);glBindBuffer(GL_ARRAY_BUFFER,Vbo);
glBufferData(GL_ARRAY_BUFFER,static_cast<GLsizeiptr>(Scene.Vertices.size()*sizeof(FVertex)),
Scene.Vertices.data(),GL_STATIC_DRAW);
glGenBuffers(1,&Ebo);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,Ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,static_cast<GLsizeiptr>(Scene.Indices.size()*sizeof(std::uint32_t)),
Scene.Indices.data(),GL_STATIC_DRAW);
glEnableVertexAttribArray(0);glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Position)));
glEnableVertexAttribArray(1);glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Normal)));
glEnableVertexAttribArray(2);glVertexAttribPointer(2,1,GL_FLOAT,GL_FALSE,sizeof(FVertex),
reinterpret_cast<void*>(offsetof(FVertex,Height)));
glViewport(0,0,Width,Height);glDisable(GL_CULL_FACE);glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);glClearColor(0.035f,0.055f,0.075f,1.0f);
glClearDepthf(1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glUseProgram(Program);
glUniform1f(glGetUniformLocation(Program,"uTanHalfHorizontal"),static_cast<float>(TanHalfHorizontal));
glUniform1f(glGetUniformLocation(Program,"uTanHalfVertical"),static_cast<float>(TanHalfVertical));
glUniform1f(glGetUniformLocation(Program,"uNear"),static_cast<float>(Near));
glUniform1f(glGetUniformLocation(Program,"uFar"),static_cast<float>(Far));
glDrawElements(GL_TRIANGLES,static_cast<GLsizei>(Scene.Indices.size()),GL_UNSIGNED_INT,nullptr);
glFinish();
std::vector<std::uint8_t> Pixels(static_cast<std::size_t>(Width)*Height*4);
glReadPixels(0,0,Width,Height,GL_RGBA,GL_UNSIGNED_BYTE,Pixels.data());
const GLenum GlError=glGetError();
const bool PngGood=GlError==GL_NO_ERROR&&WritePng(Output,Width,Height,Pixels);
if(!PngGood)std::fprintf(stderr,"[OffscreenGL] render or PNG write failed (GL=0x%x)\n",GlError);
else
{
std::error_code SizeError;
const auto Bytes=std::filesystem::file_size(Output,SizeError);
std::printf("[OffscreenCapture] triangles=%llu tiles=%u skirts=%llu png_bytes=%llu status=PASS path=%s\n",
static_cast<unsigned long long>(Scene.Triangles),Scene.TileCount,
static_cast<unsigned long long>(Scene.SkirtTriangles),
static_cast<unsigned long long>(SizeError?0:Bytes),Output.string().c_str());
}
glDeleteProgram(Program);glDeleteBuffers(1,&Ebo);glDeleteBuffers(1,&Vbo);
glDeleteVertexArrays(1,&Vao);glDeleteFramebuffers(1,&Framebuffer);
glDeleteRenderbuffers(1,&DepthBuffer);glDeleteTextures(1,&ColorTexture);
eglMakeCurrent(Display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT);
eglDestroyContext(Display,Context);eglDestroySurface(Display,Surface);eglTerminate(Display);
return PngGood;
}
}
int main(int argc,char** argv)
{
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]);
return 2;
}
int Width=2560,Height=1440;double Fov=90.0;
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
for(int I=3;I<argc;++I)
{
const std::string_view Arg(argv[I]);
if(Arg.starts_with("--size="))
{
const std::string Value(Arg.substr(7));
if(std::sscanf(Value.c_str(),"%dx%d",&Width,&Height)!=2||Width<=0||Height<=0)
{std::fprintf(stderr,"Invalid image size\n");return 2;}
}
else if(Arg.starts_with("--fov-deg="))
{
char* End=nullptr;const std::string Value(Arg.substr(10));Fov=std::strtod(Value.c_str(),&End);
if(End==Value.c_str()||*End!='\0'||Fov<=1.0||Fov>=179.0)
{std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;}
}
else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg="))
{
const bool IsYaw=Arg.starts_with("--yaw-deg=");
const std::size_t Prefix=IsYaw?10:12;
const std::string Value(Arg.substr(Prefix));char* End=nullptr;
const double Parsed=std::strtod(Value.c_str(),&End);
if(End==Value.c_str()||*End!='\0'||!std::isfinite(Parsed))
{std::fprintf(stderr,"Invalid camera angle\n");return 2;}
if(IsYaw){Yaw=Parsed;HasYaw=true;}else{Pitch=Parsed;HasPitch=true;}
}
else {std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(Arg.size()),Arg.data());return 2;}
}
FScene Scene;std::string Error;
if(!LoadScene(argv[1],Scene,Error))
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
if(HasYaw)Scene.YawDegrees=Yaw;
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",
1000000.0/Scene.VoxelSizeMicrons,Scene.CameraMeters[0],Scene.CameraMeters[1],Scene.CameraMeters[2],
Scene.YawDegrees,Scene.PitchDegrees,Scene.Vertices.size(),
static_cast<unsigned long long>(Scene.Triangles));
return Render(Scene,argv[2],Width,Height,Fov)?0:3;
}
+512
View File
@@ -0,0 +1,512 @@
#pragma once
#include "StreamingWorld.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <limits>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace StandaloneScreenSpaceGate
{
inline constexpr double OnePixelRadians=1.57079632679489661923/2560.0;
struct FErrorRow
{
int Lod=0;
std::uint64_t Samples=0,Missing=0,Floating=0;
double P50=0.0,P99=0.0,Maximum=0.0;
bool Valid=false;
};
struct FPopRow
{
int CoarseLod=0;
std::uint64_t Samples=0,Cracks=0,Floating=0;
double P50=0.0,P99=0.0,Maximum=0.0;
bool Valid=false;
};
struct FHeightGrid
{
int Side=65;
double MinimumX=0.0,MaximumX=0.0,MinimumY=0.0,MaximumY=0.0;
std::vector<std::vector<double>> Hits;
FHeightGrid()=default;
FHeightGrid(int InSide,double MinX,double MaxX,double MinY,double MaxY)
:Side(InSide),MinimumX(MinX),MaximumX(MaxX),MinimumY(MinY),MaximumY(MaxY),
Hits(static_cast<std::size_t>(InSide)*InSide)
{}
double X(int Ix) const
{return MinimumX+(MaximumX-MinimumX)*Ix/(Side-1);}
double Y(int Iy) const
{return MinimumY+(MaximumY-MinimumY)*Iy/(Side-1);}
void Add(int Ix,int Iy,double Z)
{
auto& Values=Hits[static_cast<std::size_t>(Iy)*Side+Ix];
Values.push_back(Z);
}
const std::vector<double>& At(int Ix,int Iy) const
{return Hits[static_cast<std::size_t>(Iy)*Side+Ix];}
};
inline double Percentile(std::vector<double>& Values,double P)
{
if(Values.empty())return 0.0;
std::sort(Values.begin(),Values.end());
const std::size_t Index=std::min(Values.size()-1,
static_cast<std::size_t>(std::ceil(P*Values.size())-1));
return Values[Index];
}
inline double TileAabbDistanceMeters(const FVoxelTileKey& Key,
const FVector& CameraVoxels,double VoxelMeters)
{
const double Extent=static_cast<double>(Key.ExtentVoxels());
const double Min[3]={static_cast<double>(Key.OriginVoxels().X),
static_cast<double>(Key.OriginVoxels().Y),static_cast<double>(Key.OriginVoxels().Z)};
const double Cam[3]={CameraVoxels.X,CameraVoxels.Y,CameraVoxels.Z};
double Squared=0.0;
for(int Axis=0;Axis<3;++Axis)
{
const double Delta=Cam[Axis]<Min[Axis]?Min[Axis]-Cam[Axis]
:Cam[Axis]>Min[Axis]+Extent?Cam[Axis]-(Min[Axis]+Extent):0.0;
Squared+=Delta*Delta;
}
return std::sqrt(Squared)*VoxelMeters;
}
inline bool TileHasNativeSurface(const FVoxelTileKey& Key,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
double VoxelMeters)
{
const double Extent=static_cast<double>(Key.ExtentVoxels());
const double X=(static_cast<double>(Key.OriginVoxels().X)+Extent*0.5)*256.0;
const double Y=(static_cast<double>(Key.OriginVoxels().Y)+Extent*0.5)*256.0;
const double MinZ=static_cast<double>(Key.OriginVoxels().Z);
const double MaxZ=MinZ+Extent;
for(const auto& Plan:Assembly.plan.strates)
{
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue;
const auto Column=Field.point(static_cast<std::int64_t>(std::llround(X)),
static_cast<std::int64_t>(std::llround(Y)),0);
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/256.0;
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/256.0;
const double Margin=std::max(2.0,0.25/VoxelMeters);
if(Ground<Cap&&((Ground>=MinZ-Margin&&Ground<=MaxZ+Margin)
||(Cap>=MinZ-Margin&&Cap<=MaxZ+Margin)))return true;
}
return false;
}
inline FHeightGrid Rasterize(std::span<const FStandaloneCpuTile* const> Tiles,
const FVoxelTileKey& Patch,double VoxelMeters,int Side=65)
{
const double Extent=static_cast<double>(Patch.ExtentVoxels())*VoxelMeters;
const double MinX=static_cast<double>(Patch.OriginVoxels().X)*VoxelMeters;
const double MinY=static_cast<double>(Patch.OriginVoxels().Y)*VoxelMeters;
FHeightGrid Grid(Side,MinX,MinX+Extent,MinY,MinY+Extent);
for(const FStandaloneCpuTile* Tile:Tiles)
{
if(!Tile||Tile->Indices.size()<3)continue;
const std::size_t TriangleCount=Tile->Indices.size()/3;
for(std::size_t Triangle=0;Triangle<TriangleCount;++Triangle)
{
if(Triangle<Tile->TriangleSkirtFaceMasks.size()
&&Tile->TriangleSkirtFaceMasks[Triangle]!=0)continue;
const std::uint32_t I0=Tile->Indices[Triangle*3];
const std::uint32_t I1=Tile->Indices[Triangle*3+1];
const std::uint32_t I2=Tile->Indices[Triangle*3+2];
if(I0>=Tile->Vertices.size()||I1>=Tile->Vertices.size()||I2>=Tile->Vertices.size())continue;
const auto& A=Tile->Vertices[I0].Position;
const auto& B=Tile->Vertices[I1].Position;
const auto& C=Tile->Vertices[I2].Position;
const double Denominator=(B[1]-C[1])*(A[0]-C[0])+(C[0]-B[0])*(A[1]-C[1]);
if(std::abs(Denominator)<1e-10)continue;
const double MinTriX=std::min({A[0],B[0],C[0]}),MaxTriX=std::max({A[0],B[0],C[0]});
const double MinTriY=std::min({A[1],B[1],C[1]}),MaxTriY=std::max({A[1],B[1],C[1]});
const double StepX=Extent/(Side-1),StepY=Extent/(Side-1);
const int Ix0=std::clamp(static_cast<int>(std::ceil((MinTriX-MinX)/StepX)),0,Side-1);
const int Ix1=std::clamp(static_cast<int>(std::floor((MaxTriX-MinX)/StepX)),0,Side-1);
const int Iy0=std::clamp(static_cast<int>(std::ceil((MinTriY-MinY)/StepY)),0,Side-1);
const int Iy1=std::clamp(static_cast<int>(std::floor((MaxTriY-MinY)/StepY)),0,Side-1);
for(int Iy=Iy0;Iy<=Iy1;++Iy)
for(int Ix=Ix0;Ix<=Ix1;++Ix)
{
const double X=Grid.X(Ix),Y=Grid.Y(Iy);
const double W0=((B[1]-C[1])*(X-C[0])+(C[0]-B[0])*(Y-C[1]))/Denominator;
const double W1=((C[1]-A[1])*(X-C[0])+(A[0]-C[0])*(Y-C[1]))/Denominator;
const double W2=1.0-W0-W1;
if(W0<-1e-6||W1<-1e-6||W2<-1e-6)continue;
Grid.Add(Ix,Iy,W0*A[2]+W1*B[2]+W2*C[2]);
}
}
}
for(auto& Values:Grid.Hits)
{
std::sort(Values.begin(),Values.end());
Values.erase(std::unique(Values.begin(),Values.end(),[](double A,double B)
{return std::abs(A-B)<0.02;}),Values.end());
}
return Grid;
}
inline std::vector<double> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
const FVoxelTileKey& Patch,double VoxelMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
{
std::vector<double> Roots;
const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0;
const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0;
const double MinZ=static_cast<double>(Patch.OriginVoxels().Z);
const double MaxZ=MinZ+Patch.ExtentVoxels();
for(const auto& Plan:Assembly.plan.strates)
{
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
if(!Field.isValid())continue;
const auto Column=Field.point(static_cast<std::int64_t>(std::llround(XSubvoxels)),
static_cast<std::int64_t>(std::llround(YSubvoxels)),0);
if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue;
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 PlanMaxZ=static_cast<double>(Plan.topChunkZ+1)*32.0*VoxelMeters;
if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters
&&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters
&&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters)
Roots.push_back(Ground);
if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters
&&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters
&&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters)
Roots.push_back(Cap);
}
std::sort(Roots.begin(),Roots.end());
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](double A,double B)
{return std::abs(A-B)<0.01;}),Roots.end());
return Roots;
}
inline bool MatchNearest(const std::vector<double>& Hits,const std::vector<bool>& Used,
double Root,std::size_t& OutIndex,double& OutError)
{
double Best=std::numeric_limits<double>::infinity();std::size_t BestIndex=Hits.size();
for(std::size_t I=0;I<Hits.size();++I)
if(I>=Used.size()||!Used[I])
{
const double Error=std::abs(Hits[I]-Root);
if(Error<Best){Best=Error;BestIndex=I;}
}
if(BestIndex==Hits.size())return false;
OutIndex=BestIndex;OutError=Best;return true;
}
inline FErrorRow CompareToExact(const FHeightGrid& MeshGrid,const FVoxelTileKey& Patch,
double VoxelMeters,double PixelBudgetMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
{
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
std::vector<double> Errors;
for(int Iy=0;Iy<MeshGrid.Side;++Iy)
for(int Ix=0;Ix<MeshGrid.Side;++Ix)
{
const auto Roots=ExactHeightRoots(MeshGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
if(Roots.empty()&&MeshGrid.At(Ix,Iy).empty())continue;
std::vector<bool> Used(MeshGrid.At(Ix,Iy).size(),false);
for(double Root:Roots)
{
std::size_t HitIndex=0;double ErrorMeters=0.0;
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root,HitIndex,ErrorMeters))
{
++Row.Missing;
Errors.push_back(2.0);
continue;
}
Used[HitIndex]=true;
Errors.push_back(ErrorMeters/PixelBudgetMeters);
++Row.Samples;
}
// Heightfield2D declares ground and cap roots only. Other mesh intersections may
// be valid tunnel geometry; the exact-mesh comparison checks extra pieces.
}
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
Row.Samples=Errors.size();
return Row;
}
inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid,
const FHeightGrid& ReferenceGrid,int Lod,double PixelBudgetMeters)
{
FErrorRow Row;Row.Lod=Lod;Row.Valid=true;
std::vector<double> Errors;
for(int Iy=0;Iy<ReferenceGrid.Side;++Iy)
for(int Ix=0;Ix<ReferenceGrid.Side;++Ix)
{
const auto& Reference=ReferenceGrid.At(Ix,Iy);
const auto& Mesh=MeshGrid.At(Ix,Iy);
if(Reference.empty()&&Mesh.empty())continue;
std::vector<bool> Used(Mesh.size(),false);
for(double ReferenceZ:Reference)
{
std::size_t HitIndex=0;double ErrorMeters=0.0;
if(!MatchNearest(Mesh,Used,ReferenceZ,HitIndex,ErrorMeters))
{
++Row.Missing;Errors.push_back(2.0);continue;
}
Used[HitIndex]=true;
Errors.push_back(ErrorMeters/PixelBudgetMeters);
}
for(std::size_t I=0;I<Mesh.size();++I)
if(!Used[I]){++Row.Floating;Errors.push_back(2.0);}
}
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
Row.Samples=Errors.size();
return Row;
}
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
{
FPopRow Row;Row.CoarseLod=Patch.Level;Row.Valid=true;
std::vector<double> Errors;
for(int Iy=0;Iy<Coarse.Side;++Iy)
for(int Ix=0;Ix<Coarse.Side;++Ix)
{
const auto& CoarseHits=Coarse.At(Ix,Iy);
const auto& FineHits=Fine.At(Ix,Iy);
const auto Roots=ExactHeightRoots(Coarse,Ix,Iy,Patch,VoxelMeters,Assembly);
if(Roots.empty()&&CoarseHits.empty()&&FineHits.empty())continue;
std::vector<bool> CoarseUsed(CoarseHits.size(),false);
std::vector<bool> FineUsed(FineHits.size(),false);
for(double Root:Roots)
{
std::size_t CoarseIndex=0,FineIndex=0;
double CoarseError=0.0,FineError=0.0;
if(!MatchNearest(CoarseHits,CoarseUsed,Root,CoarseIndex,CoarseError)
||!MatchNearest(FineHits,FineUsed,Root,FineIndex,FineError))
{
++Row.Cracks;Errors.push_back(2.0);continue;
}
CoarseUsed[CoarseIndex]=true;
FineUsed[FineIndex]=true;
Errors.push_back(std::abs(CoarseHits[CoarseIndex]-FineHits[FineIndex])
/PixelBudgetMeters);
++Row.Samples;
}
}
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
Row.Samples=Errors.size();
return Row;
}
inline std::array<double,9> ClosestDisplayedDistancesMeters(
const FStandaloneOwnerWorld& World,const FVector& CameraVoxels,
std::uint32_t VoxelSizeMicrons)
{
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
std::array<double,9> Minimums{};
Minimums.fill(std::numeric_limits<double>::infinity());
for(int Lod=0;Lod<=8;++Lod)
{
const int PhaseCount=1<<Lod;
for(int Phase=0;Phase<PhaseCount;++Phase)
{
FVector Probe=CameraVoxels;
Probe.X=static_cast<float>(Phase*32.0+32.0-1.0/256.0);
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,Probe);
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
if(Tile.Level!=Lod)continue;
Minimums[Lod]=std::min(Minimums[Lod],
TileAabbDistanceMeters(Tile,Probe,VoxelMeters));
}
}
}
for(double& Distance:Minimums)
if(!std::isfinite(Distance))Distance=0.0;
return Minimums;
}
inline std::array<double,9> ClipmapClosestDisplayedDistancesMeters(
const FStandaloneOwnerWorld& World,std::uint32_t VoxelSizeMicrons)
{
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
std::array<double,9> Distances{};
for(int Lod=1;Lod<=8;++Lod)
Distances[Lod]=static_cast<double>(World.ClipRadius)
*static_cast<double>(CHUNK_SIZE)*(1ull<<(Lod-1))*VoxelMeters;
return Distances;
}
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
FStandaloneStreamingGenerator& Streamer,
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod,
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels)
{
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
std::array<double,9> Budgets{};
std::array<FErrorRow,9> ErrorRows{};
std::array<FPopRow,9> PopRows{};
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",
OnePixelRadians);
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
{
Budgets[Lod]=Distances[Lod]*OnePixelRadians;
std::printf("[ScreenSpaceDistance] lod=%d closest_m=%.3f pixel_budget_m=%.6f pixel_budget_cm=%.3f pixel_budget_subvoxels=%lld rule=%s\n",
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px");
}
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod)
{
if(!SampleTiles[Lod]||SampleTiles[Lod]->Vertices.empty())
{
std::printf("[ScreenSpaceGateLOD] lod=%d status=NO_SURFACE_TILE\n",Lod);
GatePass=false;continue;
}
const FStandaloneCpuTile& CoarseTile=*SampleTiles[Lod];
const FStandaloneCpuTile* CoarsePtr=&CoarseTile;
const std::span<const FStandaloneCpuTile* const> CoarseSpan(&CoarsePtr,1);
const FHeightGrid CoarseGrid=Rasterize(CoarseSpan,CoarseTile.Key,VoxelMeters);
const FStandaloneCpuTile ExactTile=Streamer.GenerateDiagnostic(
CoarseTile.Key,0,1,true);
const FStandaloneCpuTile* ExactPointer=&ExactTile;
const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1);
const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters);
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,Lod,Budgets[Lod]);
const FErrorRow& Error=ErrorRows[Lod];
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
&&Error.Samples>=16&&Error.Maximum<=1.0;
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",
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.Missing),static_cast<unsigned long long>(Error.Floating),
LodPass?"PASS":"REJECT");
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly);
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
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",
Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50,
FieldError.P99,FieldError.Maximum,static_cast<unsigned long long>(FieldError.Missing),
static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT");
bool FaultControlsPass=true;
const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX,
ExactGrid.MinimumY,ExactGrid.MaximumY);
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,Lod,
Budgets[Lod]);
const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&MissingRejected;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n",
Lod,static_cast<unsigned long long>(MissingError.Missing),
MissingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
FStandaloneCpuTile FloatingTile=CoarseTile;
const std::uint32_t FloatingVertexBase=static_cast<std::uint32_t>(FloatingTile.Vertices.size());
for(FStandaloneCpuVertex Vertex:CoarseTile.Vertices)
{
Vertex.Position[2]+=2.0f;
FloatingTile.Vertices.push_back(Vertex);
}
for(const std::uint32_t Index:CoarseTile.Indices)
FloatingTile.Indices.push_back(FloatingVertexBase+Index);
FloatingTile.TriangleSkirtFaceMasks.insert(FloatingTile.TriangleSkirtFaceMasks.end(),
CoarseTile.TriangleSkirtFaceMasks.begin(),CoarseTile.TriangleSkirtFaceMasks.end());
const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters);
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,Lod,
Budgets[Lod]);
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&FloatingRejected;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
Lod,static_cast<unsigned long long>(FloatingError.Floating),FloatingError.Maximum,
FloatingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
std::vector<FStandaloneCpuTile> FineTiles;
FineTiles.reserve(8);
for(int Dz=0;Dz<=1;++Dz)
for(int Dy=0;Dy<=1;++Dy)
for(int Dx=0;Dx<=1;++Dx)
{
const FVoxelTileKey FineKey(FIntVector(2*CoarseTile.Key.Coord.X+Dx,
2*CoarseTile.Key.Coord.Y+Dy,2*CoarseTile.Key.Coord.Z+Dz),Lod-1);
FStandaloneCpuTile FineTile=Streamer.GenerateDiagnostic(FineKey);
if(!FineTile.Vertices.empty())FineTiles.push_back(std::move(FineTile));
}
std::vector<const FStandaloneCpuTile*> FinePointers;
FinePointers.reserve(FineTiles.size());
for(const auto& Tile:FineTiles)FinePointers.push_back(&Tile);
const auto FineSpan=std::span<const FStandaloneCpuTile* const>(
FinePointers.data(),FinePointers.size());
const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters);
PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters,
Budgets[Lod],Assembly);
const FPopRow& Pop=PopRows[Lod];
const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0;
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",
Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples),
Pop.P50,Pop.P99,Pop.Maximum,static_cast<unsigned long long>(Pop.Cracks),
static_cast<unsigned long long>(Pop.Floating),
PopPass?"PASS":"REJECT");
const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX,
CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY);
const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid,
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly);
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
FaultControlsPass=FaultControlsPass&&CrackRejected;
GatePass=GatePass&&FaultControlsPass;
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-fine-ring-surface cracks=%llu max_px=%.4f status=%s\n",
Lod,static_cast<unsigned long long>(MissingFineError.Cracks),
MissingFineError.Maximum,CrackRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
bool NegativeControlsPass=true;
const auto CheckNegative=[&](const char* Name,std::uint32_t Drop,std::uint32_t Stride)
{
FStandaloneCpuTile Bad=Streamer.GenerateDiagnostic(
CoarseTile.Key,Drop,Stride,true);
const FStandaloneCpuTile* BadPointer=&Bad;
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,Lod,
Budgets[Lod]);
const bool Rejected=BadError.Missing>0||BadError.Floating>0
||BadError.Samples<16||BadError.Maximum>1.0;
NegativeControlsPass=NegativeControlsPass&&Rejected;
std::printf("[ScreenSpaceNegative] lod=%d case=%s p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
Lod,Name,BadError.P50,BadError.P99,BadError.Maximum,
static_cast<unsigned long long>(BadError.Missing),
static_cast<unsigned long long>(BadError.Floating),
Rejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE");
};
CheckNegative("s13-global-octave-drop",1,1);
CheckNegative("stride-2-column-interpolation",0,2);
GatePass=GatePass&&NegativeControlsPass;
}
std::printf("[ScreenSpaceGateResult] status=%s\n",GatePass?"PASS":"REJECT");
return GatePass;
}
}
+23 -3
View File
@@ -37,6 +37,8 @@ namespace
bool ProfileColumnOverlap=false;
bool ForceGenerate=false;
bool MeshCoverage=false;
bool ScreenSpaceGate=false;
bool VisualDetailCutoff=false;
bool HasSeedOverride=false;
int SeedOverride=0;
bool HasRenderDistanceOverride=false;
@@ -48,6 +50,9 @@ namespace
bool HasCamera=false;
bool AutoSurfaceCamera=false;
FVector CameraMeters=FVector::ZeroVector;
float CameraYawDegrees=0.0f;
float CameraPitchDegrees=0.0f;
std::filesystem::path MeshExportPath;
};
bool ParseInteger(std::string_view text,int& output)
@@ -61,13 +66,15 @@ namespace
return true;
}
bool ParseCamera(std::string_view text,FVector& meters)
bool ParseCamera(std::string_view text,FVector& meters,float& yawDegrees,float& pitchDegrees)
{
const std::string values(text);
float x=0.0f,y=0.0f,z=0.0f,yaw=0.0f,pitch=0.0f;
if(std::sscanf(values.c_str(),"%f,%f,%f,%f,%f",&x,&y,&z,&yaw,&pitch)!=5)
return false;
meters={x,y,z};
yawDegrees=yaw;
pitchDegrees=pitch;
return true;
}
@@ -85,10 +92,13 @@ namespace
std::printf("Usage: %s [owner_world.json] --world-assembler-prototype --stream-benchmark --stream-pipeline [options]\n"
" --workers=N --clip-radius=N --max-clip-level=N --max-level=N\n"
" --camera=X,Y,Z,YAW,PITCH (position in metres) --stream-hash\n"
" --mesh-export=FILE (write the exact generated tile meshes for offline rendering)\n"
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
" --seed=N --recipe=surface-world|floating-islands\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"
" --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"
" --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n"
" --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n"
@@ -103,6 +113,8 @@ namespace
else if(arg=="--profile-column-overlap")options.ProfileColumnOverlap=true;
else if(arg=="--force-generate")options.ForceGenerate=true;
else if(arg=="--mesh-coverage")options.MeshCoverage=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.starts_with("--recipe="))
{
@@ -135,10 +147,17 @@ namespace
}
else if(arg.starts_with("--camera="))
{
if(!ParseCamera(arg.substr(9),options.CameraMeters))
if(!ParseCamera(arg.substr(9),options.CameraMeters,
options.CameraYawDegrees,options.CameraPitchDegrees))
{std::fprintf(stderr,"Invalid --camera value: %.*s\n",static_cast<int>(arg.size()),arg.data());return false;}
options.HasCamera=true;
}
else if(arg.starts_with("--mesh-export="))
{
options.MeshExportPath=std::filesystem::path(arg.substr(14));
if(options.MeshExportPath.empty())
{std::fprintf(stderr,"--mesh-export needs a file path\n");return false;}
}
else if(arg.starts_with("--workers="))
{if(!readInt("--workers=",options.WorkerCount))return false;}
else if(arg.starts_with("--clip-radius="))
@@ -297,5 +316,6 @@ int main(int argc,char** argv)
cameraVoxels,options.WorkerCount,options.MaxLevel,VoxelForgeCore::DetailVariant::coherent,
scheduler,options.StreamHash,false,false,false,false,coldStart,
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels,
options.MeshCoverage);
options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees,
options.CameraPitchDegrees,options.ScreenSpaceGate,options.VisualDetailCutoff);
}
+296 -2
View File
@@ -2,6 +2,7 @@
#include "OwnerWorldLoader.h"
#include "StreamingWorld.h"
#include "LinuxScreenSpaceGate.h"
#include <algorithm>
#include <array>
@@ -9,6 +10,8 @@
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <limits>
#include <thread>
#include <unordered_map>
@@ -17,6 +20,76 @@
namespace StandaloneStreamBenchmark
{
struct FSceneExportWriter
{
std::ofstream File;
bool bGood=true;
std::uint32_t TileCount=0;
template<typename T> void Write(const T& Value)
{
File.write(reinterpret_cast<const char*>(&Value),sizeof(Value));
bGood=bGood&&static_cast<bool>(File);
}
bool Open(const std::filesystem::path& Path,std::uint32_t VoxelSizeMicrons,
const FVector& CameraVoxels,float YawDegrees,float PitchDegrees,
std::uint32_t ExpectedTiles)
{
std::error_code Error;
if(!Path.parent_path().empty())
std::filesystem::create_directories(Path.parent_path(),Error);
if(Error)return false;
File.open(Path,std::ios::binary|std::ios::trunc);
if(!File)return false;
constexpr char Magic[8]={'V','F','M','S','H','0','0','1'};
File.write(Magic,sizeof(Magic));
const std::uint32_t Version=1;
Write(Version);Write(VoxelSizeMicrons);Write(ExpectedTiles);
const double MicronsToMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
const double Camera[3]={CameraVoxels.X*MicronsToMeters,
CameraVoxels.Y*MicronsToMeters,CameraVoxels.Z*MicronsToMeters};
for(const double Value:Camera)Write(Value);
const double Yaw=YawDegrees,Pitch=PitchDegrees;
Write(Yaw);Write(Pitch);
return bGood;
}
void Add(const FStandaloneCpuTile& Tile)
{
const std::int32_t Key[4]={Tile.Key.Coord.X,Tile.Key.Coord.Y,
Tile.Key.Coord.Z,Tile.Key.Level};
for(const auto Value:Key)Write(Value);
const std::uint32_t SurfaceVertexCount=Tile.SurfaceVertexCount;
const std::uint32_t SkirtTriangleCount=Tile.SkirtTriangleCount;
const std::uint32_t VertexCount=static_cast<std::uint32_t>(Tile.Vertices.size());
const std::uint32_t IndexCount=static_cast<std::uint32_t>(Tile.Indices.size());
const std::uint32_t MaskCount=static_cast<std::uint32_t>(Tile.TriangleSkirtFaceMasks.size());
Write(SurfaceVertexCount);Write(SkirtTriangleCount);
Write(VertexCount);Write(IndexCount);Write(MaskCount);
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Position)Write(Value);
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
for(const float Value:Vertex.Normal)Write(Value);
if(IndexCount)
File.write(reinterpret_cast<const char*>(Tile.Indices.data()),
static_cast<std::streamsize>(sizeof(std::uint32_t)*Tile.Indices.size()));
if(MaskCount)
File.write(reinterpret_cast<const char*>(Tile.TriangleSkirtFaceMasks.data()),
static_cast<std::streamsize>(Tile.TriangleSkirtFaceMasks.size()));
bGood=bGood&&static_cast<bool>(File);
++TileCount;
}
bool Close(std::uint32_t ExpectedTiles)
{
File.flush();
const bool bComplete=bGood&&static_cast<bool>(File)&&TileCount==ExpectedTiles;
File.close();
return bComplete;
}
};
inline void HashWord(uint64& Hash,uint64 Word,int32 Bytes=8)
{
for(int32 I=0;I<Bytes;++I)
@@ -62,10 +135,92 @@ namespace StandaloneStreamBenchmark
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
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,
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false,
bool bApplyVisualDetailCutoff=false)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly();
std::array<double,9> ClosestDisplayDistances{};
std::array<std::int64_t,9> PixelBudgetsSubvoxels{};
std::array<std::int64_t,9> AppliedPixelBudgetsSubvoxels{};
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
{
ClosestDisplayDistances=bScreenSpaceGate
?StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons)
:StandaloneScreenSpaceGate::ClipmapClosestDisplayedDistancesMeters(
World,World.CoreVoxelSizeMicrons);
const double VoxelMeters=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=static_cast<std::int64_t>(std::floor(
ClosestDisplayDistances[static_cast<std::size_t>(Lod)]
*StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters
*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 bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
VoxelForgeCore::World Shape(CoreSnapshot);
@@ -154,6 +309,12 @@ namespace StandaloneStreamBenchmark
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels);
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
{
Streamer.SetVisualDetailCutoffBudgets(AppliedPixelBudgetsSubvoxels,
bApplyVisualDetailCutoff);
Streamer.SetVisualDetailProfilingEnabled(bScreenSpaceGate);
}
const auto StreamStart=Clock::now();
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
@@ -162,6 +323,22 @@ namespace StandaloneStreamBenchmark
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles but queued %d.\n",
Desired.Tiles.size(),Update.DesiredTiles);return 7;
}
FSceneExportWriter SceneExport;
const bool bExportRequested=!MeshExportPath.empty();
bool bSceneExportPassing=true;
if(bExportRequested)
{
bSceneExportPassing=SceneExport.Open(MeshExportPath,World.CoreVoxelSizeMicrons,
CameraPositionVoxels,CameraYawDegrees,CameraPitchDegrees,
static_cast<std::uint32_t>(Update.DesiredTiles));
if(!bSceneExportPassing)
{
Streamer.Stop();
std::fprintf(stderr,"[MeshSceneExport] FAIL: could not open %s\n",
MeshExportPath.string().c_str());
return 8;
}
}
std::vector<FHashedTile> DensityRows,MeshRows,PayloadRows,TopologyRows;
int32 Finished=0,NeighborhoodFinished=0,FullDetailFinished=0;
@@ -193,6 +370,11 @@ namespace StandaloneStreamBenchmark
std::array<bool,9> ColumnCaptureCompleteByLOD{};
ColumnCaptureCompleteByLOD.fill(true);
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> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
std::array<double,9> MeshAssemblyMsByLOD{};
@@ -214,6 +396,9 @@ namespace StandaloneStreamBenchmark
double NativeMeshingMilliseconds=0.0;
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level<World.FullResClipLevels)++FullDetailExpected;
std::array<std::optional<FStandaloneCpuTile>,9> GateSampleTiles{};
std::array<double,9> GateSampleDistances{};
GateSampleDistances.fill(std::numeric_limits<double>::infinity());
Clock::time_point NeighborhoodReady{},FullDetailReady{},WholeViewReady{};
bool bNeighborhoodReported=false,bFullDetailReported=false;
while(Finished<Update.DesiredTiles)
@@ -222,6 +407,7 @@ namespace StandaloneStreamBenchmark
if(Ready.empty()){std::this_thread::sleep_for(std::chrono::milliseconds(1));continue;}
for(const FStandaloneCpuTile& Tile:Ready)
{
if(bExportRequested)SceneExport.Add(Tile);
++Finished;
if(bReportMeshCoverage)
{
@@ -239,6 +425,34 @@ namespace StandaloneStreamBenchmark
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
for(std::size_t Octave=0;Octave<8;++Octave)
{
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
&&!Tile.Vertices.empty()
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0))
{
const double Distance=StandaloneScreenSpaceGate::TileAabbDistanceMeters(
Tile.Key,CameraPositionVoxels,
static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0);
const bool bCloser=Distance<GateSampleDistances[LOD]-1e-9;
const bool bTied=std::abs(Distance-GateSampleDistances[LOD])<=1e-9;
const bool bKeyLess=!GateSampleTiles[LOD]
||TileKeyLessForHash(Tile.Key,GateSampleTiles[LOD]->Key);
if(bCloser||(bTied&&bKeyLess))
{GateSampleDistances[LOD]=Distance;GateSampleTiles[LOD]=Tile;}
}
}
if(bProfileColumnOverlap&&Tile.Key.Level>=0
&&Tile.Key.Level<static_cast<int32>(ColumnCaptureTilesByLOD.size()))
@@ -321,6 +535,27 @@ namespace StandaloneStreamBenchmark
}
}
Streamer.Stop();
bool bScreenSpaceGatePassing=true;
if(bScreenSpaceGate)
{
if(Assembly)
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
World.MaxClipLevel,PixelBudgetsSubvoxels);
else
{std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;}
}
if(bExportRequested)
{
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Update.DesiredTiles));
std::error_code FileSizeError;
const std::uintmax_t ExportBytes=std::filesystem::file_size(MeshExportPath,FileSizeError);
std::printf("[MeshSceneExport] tiles=%u expected=%d bytes=%llu status=%s path=%s\n",
SceneExport.TileCount,Update.DesiredTiles,
static_cast<unsigned long long>(FileSizeError?0:ExportBytes),
bSceneExportPassing?"PASS":"FAIL",MeshExportPath.string().c_str());
bSceneExportPassing=bSceneExportPassing&&!FileSizeError;
}
bool bColumnOverlapCapturePassing=true;
if(bProfileColumnOverlap)
{
@@ -345,6 +580,15 @@ namespace StandaloneStreamBenchmark
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD],
ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD],
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],MeshAssemblyMsByLOD[LOD]);
if(bScreenSpaceGate)
for(std::size_t Octave=0;Octave<8;++Octave)
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 NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
@@ -393,9 +637,59 @@ namespace StandaloneStreamBenchmark
static_cast<unsigned long long>(HashTileRows(MeshRows)),
static_cast<unsigned long long>(HashTileRows(PayloadRows)),
static_cast<unsigned long long>(HashTileRows(TopologyRows)));
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
{
const auto FilterLOD=[LOD](const std::vector<FHashedTile>& Rows)
{
std::vector<FHashedTile> Selected;
for(const FHashedTile& Row:Rows)
if(Row.Key.Level==static_cast<int32>(LOD))Selected.push_back(Row);
return Selected;
};
auto LodDensity=FilterLOD(DensityRows),LodMesh=FilterLOD(MeshRows);
auto LodPayload=FilterLOD(PayloadRows),LodTopology=FilterLOD(TopologyRows);
std::printf("[PipelineLODHash] lod=%zu tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx\n",
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),
static_cast<unsigned long long>(HashTileRows(LodDensity)),
static_cast<unsigned long long>(HashTileRows(LodMesh)),
static_cast<unsigned long long>(HashTileRows(LodPayload)),
static_cast<unsigned long long>(HashTileRows(LodTopology)));
}
const std::array<VoxelForgeCore::Vec3d,9> QueryPoints={{
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
{CameraPositionVoxels.X+8.0,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
{CameraPositionVoxels.X-8.0,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
{CameraPositionVoxels.X,CameraPositionVoxels.Y+8.0,CameraPositionVoxels.Z},
{CameraPositionVoxels.X,CameraPositionVoxels.Y-8.0,CameraPositionVoxels.Z},
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z+8.0},
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z-8.0},
{CameraPositionVoxels.X+64.0,CameraPositionVoxels.Y-32.0,CameraPositionVoxels.Z+16.0},
{CameraPositionVoxels.X-128.0,CameraPositionVoxels.Y+96.0,CameraPositionVoxels.Z-64.0}}};
uint64 ExactDensityHash=14695981039346656037ull;
uint64 ExactUnclampedHash=14695981039346656037ull;
uint64 ExactDetailHash=14695981039346656037ull;
uint64 ExactCollisionHash=14695981039346656037ull;
for(const auto& Point:QueryPoints)
{
const float Density=Shape.densityAt(Point);
const float Unclamped=Shape.unclampedDensityAt(Point);
const auto Detail=Shape.detailSampleAt(Point);
HashWord(ExactDensityHash,std::bit_cast<uint32>(Density),4);
HashWord(ExactUnclampedHash,std::bit_cast<uint32>(Unclamped),4);
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.displacement),4);
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.caveInfluence),4);
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.floorInfluence),4);
HashWord(ExactCollisionHash,Shape.isSolidAt(Point)?1u:0u,1);
}
std::printf("[PipelineExactQueryHash] probes=%zu density=0x%016llx unclamped=0x%016llx detail=0x%016llx collision=0x%016llx path=direct-Core-queries\n",
QueryPoints.size(),static_cast<unsigned long long>(ExactDensityHash),
static_cast<unsigned long long>(ExactUnclampedHash),
static_cast<unsigned long long>(ExactDetailHash),
static_cast<unsigned long long>(ExactCollisionHash));
}
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)?0:6;
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)
&&(!bExportRequested||bSceneExportPassing)&&bScreenSpaceGatePassing?0:6;
}
} // namespace StandaloneStreamBenchmark
+36 -2
View File
@@ -646,6 +646,18 @@ public:
int32 GetWorkerCount()const{return WorkerCount;}
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
void SetVisualDetailCutoffBudgets(const std::array<std::int64_t,9>& Budgets,bool Apply)
{VisualDetailBudgetSubvoxels=Budgets;bEnableVisualDetailCutoff=true;bApplyVisualDetailCutoff=Apply;}
void SetVisualDetailProfilingEnabled(bool Enabled)
{bProfileVisualDetailOctaves=Enabled;}
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
bool ForceExactVisualDetail=false)
{
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
return Generate(Job,GlobalOctaveDrop,CellStride,0,ForceExactVisualDetail);
}
private:
FStandaloneStreamingUpdate UpdateDesiredSet(FStandaloneDesiredTileSet NewDesired,
const std::vector<FVoxelTileKey>& ResidentKeys,uint64 CrossingIndex)
@@ -761,12 +773,15 @@ private:
Wake.notify_all();
return Stats;
}
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
std::int64_t DiagnosticVisualBudgetSubvoxels=0,
bool ForceExactVisualDetail=false)
{
const auto StartTime=Job.StartedAt;
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
if(MeshCache)
if(MeshCache&&!ForceExactVisualDetail)
{
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
{
@@ -805,6 +820,21 @@ private:
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
Options.visualDetailPixelBudgetSubvoxels=DiagnosticVisualBudgetSubvoxels;
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.diagnosticColumnInterpolationStride=
std::max<std::uint32_t>(1,DiagnosticCellStride);
std::string Error;
auto Result=CoreWorld->generateTile(
{{Job.Key.Coord.X,Job.Key.Coord.Y,Job.Key.Coord.Z},Job.Key.Level},Options,Error);
@@ -977,6 +1007,10 @@ private:
bool bSkipColors=false;
bool bCaptureColumnCoordinates=false;
int32 Lod8HorizonInteriorStepVoxels=0;
std::array<std::int64_t,9> VisualDetailBudgetSubvoxels{};
bool bEnableVisualDetailCutoff=false;
bool bApplyVisualDetailCutoff=false;
bool bProfileVisualDetailOctaves=false;
std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex;
std::condition_variable Wake;