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

@@ -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),