perf(opstack): fused evaluator -- 1.05s to 0.56s, byte-identical, and the ~70% early-out is back
WORK-NEXT item 2. The authored operator graph is lowered at chunk-cache build time into a fused scalar evaluator for canonical TunnelNetwork/Underwater stacks. Operators are untouched; this is a lowering, not a de-composition. Custom recipes, composer/lateral stacks and other archetypes stay on the interpreted block path, selected by a build-time EVoxelOpFusedEvaluator marker with `-voxel.UseFusedEvaluator=0` to A/B it. ⭐ MY OWN A/B, same build, 3 runs each: fused 0.545 / 0.563 / 0.561 s mean 0.556 interpreted 1.066 / 1.036 / 1.033 s mean 1.045 -46.8%, 1.88x geometry SHA-256 b3e5f4c3..9b377 IDENTICAL across fused, interpreted, and081cc5e. ⚠️ Codex reported this as 5.88% (2.121 vs 2.253 s). Its figures come from its staged harness with diagnostics enabled, which inflates both sides and compresses the ratio. Measured clean it is 1.88x. This is the fourth time a profiled number has misled us -- the instrument is the instrument, not the thing. THE MECHANISM IS RESTORED, which is the part that matters The audit's finding was that the old path short-circuited the cave detail suite per sample when bNearCaveSurface was false -- historically ~70% of deep-solid samples -- and that we had traded that for per-block pruning. fused export skipped detail on 1,130,230 / 2,490,664 samples = 45.38% capability walk skipped 69.26% 69.26% matches the historical ~70% almost exactly. The early-out is genuinely back. Capability exact: player-fit 20,830, walk-reachable 10,909, Connected. WHERE THIS LEAVES THE 8x pre-op-stack34f06df0.130 s now 0.556 s 4.3x, down from ~8x So roughly half the regression is recovered, and the remaining 4.3x is NOT operator interpretation -- that hypothesis is now tested and spent. Codex's own read: the rest sits in "common structural/passage work and meshing". Three of us predicted the interpretation overhead was the whole story; it was about half. Build 40 actions, 0 errors, loaded-module verified. Six plugin source files. Cumulative on this region: 342.2 s -> 0.556 s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
0388c7dede
commit
35de59bac5
6 files changed
+200
-21
No files matched your search
@@ -9539,6 +9539,13 @@ namespace VoxelDensityOps
|
|||||||
// disturbances. Keeping these pre-disturbance graph scans here duplicated both the
|
// disturbances. Keeping these pre-disturbance graph scans here duplicated both the
|
||||||
// support and air queries for every voxel; the feature remains in that shared post.
|
// support and air queries for every voxel; the feature remains in that shared post.
|
||||||
OutStack.Add(MakeUnique<FXYEdgeSealOp>(P.BaseDensity));
|
OutStack.Add(MakeUnique<FXYEdgeSealOp>(P.BaseDensity));
|
||||||
|
|
||||||
|
// Lower only the canonical native graph. The old scalar TunnelNetwork evaluator is
|
||||||
|
// the fused form of exactly these authored stages: it keeps density/SDF/room state in
|
||||||
|
// locals and gates the complete 4b-4h detail suite per sample. Marking the plan after
|
||||||
|
// the final op is important: recipes and composer stacks keep the interpreted/block
|
||||||
|
// fallback, even when they happen to contain a similar-looking subset of operators.
|
||||||
|
OutStack.SetFusedEvaluator(EVoxelOpFusedEvaluator::TunnelNetwork);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -483,6 +483,9 @@ namespace VoxelDensityProfile
|
|||||||
case ECounter::TileVerdictCacheHits: return TEXT("TileVerdictCacheHits");
|
case ECounter::TileVerdictCacheHits: return TEXT("TileVerdictCacheHits");
|
||||||
case ECounter::TileVerdictRegionHits: return TEXT("TileVerdictRegionHits");
|
case ECounter::TileVerdictRegionHits: return TEXT("TileVerdictRegionHits");
|
||||||
case ECounter::TileVerdictCacheStores: return TEXT("TileVerdictCacheStores");
|
case ECounter::TileVerdictCacheStores: return TEXT("TileVerdictCacheStores");
|
||||||
|
case ECounter::FusedTunnelSamples: return TEXT("FusedTunnelSamples");
|
||||||
|
case ECounter::FusedTunnelDetailSamples: return TEXT("FusedTunnelDetailSamples");
|
||||||
|
case ECounter::FusedTunnelDetailSkipped: return TEXT("FusedTunnelDetailSkipped");
|
||||||
case ECounter::OpBlockBuilds: return TEXT("OpBlockBuilds");
|
case ECounter::OpBlockBuilds: return TEXT("OpBlockBuilds");
|
||||||
case ECounter::OpBlockSamples: return TEXT("OpBlockSamples");
|
case ECounter::OpBlockSamples: return TEXT("OpBlockSamples");
|
||||||
case ECounter::OpBlockOperators: return TEXT("OpBlockOperators");
|
case ECounter::OpBlockOperators: return TEXT("OpBlockOperators");
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
#include "VoxelDensityProfile.h"
|
#include "VoxelDensityProfile.h"
|
||||||
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
#include "ProfilingDebugging/CpuProfilerTrace.h"
|
||||||
#include "HAL/PlatformTime.h"
|
#include "HAL/PlatformTime.h"
|
||||||
|
#include "HAL/IConsoleManager.h"
|
||||||
|
#include "Misc/CommandLine.h"
|
||||||
#include "Misc/ScopeLock.h"
|
#include "Misc/ScopeLock.h"
|
||||||
|
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
@@ -28,6 +30,34 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// The canonical native TunnelNetwork/Underwater graph has a hand-lowered evaluator. Keep a
|
||||||
|
// command-line switch so every change can be A/B'd against the existing interpreted/block
|
||||||
|
// path without changing the authored graph or rebuilding a different world definition.
|
||||||
|
int32 GVoxelForgeUseFusedEvaluator = 1;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeUseFusedEvaluator(
|
||||||
|
TEXT("voxel.UseFusedEvaluator"),
|
||||||
|
GVoxelForgeUseFusedEvaluator,
|
||||||
|
TEXT("Use the lowered fused evaluator for canonical TunnelNetwork operator stacks."));
|
||||||
|
bool GVoxelForgeFusedEvaluatorSwitchParsed = false;
|
||||||
|
|
||||||
|
void VF_ParseFusedEvaluatorSwitch()
|
||||||
|
{
|
||||||
|
if (GVoxelForgeFusedEvaluatorSwitchParsed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GVoxelForgeFusedEvaluatorSwitchParsed = true;
|
||||||
|
int32 CommandLineValue = GVoxelForgeUseFusedEvaluator;
|
||||||
|
if (FParse::Value(
|
||||||
|
FCommandLine::Get(), TEXT("voxel.UseFusedEvaluator="), CommandLineValue))
|
||||||
|
{
|
||||||
|
GVoxelForgeUseFusedEvaluator = CommandLineValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// L'ADAPTATEUR DE CHAMP DE BIOMES / THE BIOME FIELD ADAPTER
|
// L'ADAPTATEUR DE CHAMP DE BIOMES / THE BIOME FIELD ADAPTER
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
@@ -1690,6 +1720,7 @@ void UVoxelGenerator::EndDensityBlock() const
|
|||||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) const
|
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) const
|
||||||
{
|
{
|
||||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
||||||
|
VF_ParseFusedEvaluatorSwitch();
|
||||||
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::GetDensityAt);
|
VoxelDensityProfile::EBucket::GetDensityAt);
|
||||||
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
||||||
@@ -2156,14 +2187,43 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
VoxelDensityProfile::FScopedTimer DensityCoreTimer(
|
VoxelDensityProfile::FScopedTimer DensityCoreTimer(
|
||||||
VoxelDensityProfile::EBucket::DensityCore);
|
VoxelDensityProfile::EBucket::DensityCore);
|
||||||
|
|
||||||
FVoxelOpSample BlockCoreSample;
|
|
||||||
bool bUsedOpBlockSample = false;
|
bool bUsedOpBlockSample = false;
|
||||||
|
float BlockDensity = 0.f;
|
||||||
|
bool bBlockHasTunnelCore = false;
|
||||||
|
float BlockTunnelCoreSDF = FLT_MAX;
|
||||||
|
bool bBlockTunnelCoreSupportFloor = false;
|
||||||
|
bool bBlockTunnelCoreRoomFloor = false;
|
||||||
|
bool bUsedFusedEvaluator = false;
|
||||||
|
FTunnelCoreWorldEvaluation FusedTunnelCore;
|
||||||
|
|
||||||
// Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention
|
// Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention
|
||||||
// MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche
|
// MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche
|
||||||
// de diff qui suivent ne voient aucune différence.
|
// de diff qui suivent ne voient aucune différence.
|
||||||
if (CP_UseOpStack)
|
if (CP_UseOpStack)
|
||||||
{
|
{
|
||||||
|
const bool bCanUseFusedEvaluator =
|
||||||
|
GVoxelForgeUseFusedEvaluator != 0
|
||||||
|
&& !CP_UseCustomRecipe
|
||||||
|
&& (CP_GenType == ECaveGeneratorType::TunnelNetwork
|
||||||
|
|| CP_GenType == ECaveGeneratorType::Underwater)
|
||||||
|
#if WITH_EDITOR
|
||||||
|
&& !CP_UseComposerRegions
|
||||||
|
#endif
|
||||||
|
&& ActiveOpStack->GetFusedEvaluator()
|
||||||
|
== EVoxelOpFusedEvaluator::TunnelNetwork;
|
||||||
|
if (bCanUseFusedEvaluator)
|
||||||
|
{
|
||||||
|
// This is the lowered canonical graph. It returns the same MC core as the
|
||||||
|
// interpreted stack, plus the source's structural hand-off, without allocating
|
||||||
|
// FVoxelOpSample or traversing the graph one operator at a time for this voxel.
|
||||||
|
bUsedFusedEvaluator = true;
|
||||||
|
Result = GetDensityWithParams(
|
||||||
|
WorldX, WorldY, WorldZ, CP_Tunnel, CP_TunnelFP, LayoutVersion,
|
||||||
|
/*bApplyLegacyStructuralPosts=*/false,
|
||||||
|
&FusedTunnelCore,
|
||||||
|
/*bCollectFusedDiagnostics=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
const bool bIntegerLatticePoint =
|
const bool bIntegerLatticePoint =
|
||||||
WorldX == FMath::FloorToFloat(WorldX)
|
WorldX == FMath::FloorToFloat(WorldX)
|
||||||
&& WorldY == FMath::FloorToFloat(WorldY)
|
&& WorldY == FMath::FloorToFloat(WorldY)
|
||||||
@@ -2171,9 +2231,13 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
const bool bBlockSafeGenerator =
|
const bool bBlockSafeGenerator =
|
||||||
CP_GenType == ECaveGeneratorType::TunnelNetwork
|
CP_GenType == ECaveGeneratorType::TunnelNetwork
|
||||||
|| CP_GenType == ECaveGeneratorType::Underwater;
|
|| CP_GenType == ECaveGeneratorType::Underwater;
|
||||||
if (bIntegerLatticePoint && bBlockSafeGenerator
|
if (!bUsedFusedEvaluator && bIntegerLatticePoint && bBlockSafeGenerator
|
||||||
&& GVoxelDensityBlockSession.bActive)
|
&& GVoxelDensityBlockSession.bActive)
|
||||||
{
|
{
|
||||||
|
// Keep the rich hand-off object in the fallback branch. The lowered evaluator
|
||||||
|
// must not construct a per-sample FVoxelOpSample merely because this compatibility
|
||||||
|
// path still needs one when the block session is enabled.
|
||||||
|
FVoxelOpSample BlockCoreSample;
|
||||||
bUsedOpBlockSample = GVoxelDensityBlockSession.FillChunk(
|
bUsedOpBlockSample = GVoxelDensityBlockSession.FillChunk(
|
||||||
*ActiveOpStack,
|
*ActiveOpStack,
|
||||||
ChunkCoord,
|
ChunkCoord,
|
||||||
@@ -2182,11 +2246,22 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
FMath::RoundToInt(WorldY),
|
FMath::RoundToInt(WorldY),
|
||||||
FMath::RoundToInt(WorldZ)),
|
FMath::RoundToInt(WorldZ)),
|
||||||
BlockCoreSample);
|
BlockCoreSample);
|
||||||
|
if (bUsedOpBlockSample)
|
||||||
|
{
|
||||||
|
BlockDensity = BlockCoreSample.Density;
|
||||||
|
bBlockHasTunnelCore = BlockCoreSample.bHasTunnelCoreWorldEvaluation;
|
||||||
|
BlockTunnelCoreSDF = BlockCoreSample.TunnelCoreWorldSDF;
|
||||||
|
bBlockTunnelCoreSupportFloor = BlockCoreSample.bTunnelCoreSupportFloor;
|
||||||
|
bBlockTunnelCoreRoomFloor = BlockCoreSample.bTunnelCoreRoomFloor;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!bUsedFusedEvaluator)
|
||||||
|
{
|
||||||
Result = bUsedOpBlockSample
|
Result = bUsedOpBlockSample
|
||||||
? -BlockCoreSample.Density
|
? -BlockDensity
|
||||||
: ActiveOpStack->EvalMC(WorldX, WorldY, WorldZ);
|
: ActiveOpStack->EvalMC(WorldX, WorldY, WorldZ);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else switch (CP_GenType)
|
else switch (CP_GenType)
|
||||||
{
|
{
|
||||||
case ECaveGeneratorType::FlatPlain:
|
case ECaveGeneratorType::FlatPlain:
|
||||||
@@ -2250,13 +2325,16 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
bool bHavePreDisturbanceTunnelCore = false;
|
bool bHavePreDisturbanceTunnelCore = false;
|
||||||
if (CP_UseOpStack)
|
if (CP_UseOpStack)
|
||||||
{
|
{
|
||||||
if (bUsedOpBlockSample && BlockCoreSample.bHasTunnelCoreWorldEvaluation)
|
if (bUsedFusedEvaluator)
|
||||||
{
|
{
|
||||||
PreDisturbanceTunnelCore.SDF = BlockCoreSample.TunnelCoreWorldSDF;
|
PreDisturbanceTunnelCore = FusedTunnelCore;
|
||||||
PreDisturbanceTunnelCore.bSupportFloor =
|
bHavePreDisturbanceTunnelCore = true;
|
||||||
BlockCoreSample.bTunnelCoreSupportFloor;
|
}
|
||||||
PreDisturbanceTunnelCore.bRoomFloor =
|
else if (bUsedOpBlockSample && bBlockHasTunnelCore)
|
||||||
BlockCoreSample.bTunnelCoreRoomFloor;
|
{
|
||||||
|
PreDisturbanceTunnelCore.SDF = BlockTunnelCoreSDF;
|
||||||
|
PreDisturbanceTunnelCore.bSupportFloor = bBlockTunnelCoreSupportFloor;
|
||||||
|
PreDisturbanceTunnelCore.bRoomFloor = bBlockTunnelCoreRoomFloor;
|
||||||
bHavePreDisturbanceTunnelCore = true;
|
bHavePreDisturbanceTunnelCore = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -2576,7 +2654,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
||||||
const FStrateGenerationParams& Params,
|
const FStrateGenerationParams& Params,
|
||||||
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||||
bool bApplyLegacyStructuralPosts) const
|
bool bApplyLegacyStructuralPosts,
|
||||||
|
FTunnelCoreWorldEvaluation* OutTunnelCore,
|
||||||
|
bool bCollectFusedDiagnostics) const
|
||||||
{
|
{
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// STRATE DENSITY FUNCTION (Morphology Pipeline)
|
// STRATE DENSITY FUNCTION (Morphology Pipeline)
|
||||||
@@ -2603,6 +2683,16 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// At the end, we negate for the MC table (negative = solid there).
|
// At the end, we negate for the MC table (negative = solid there).
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
||||||
|
if (OutTunnelCore != nullptr)
|
||||||
|
{
|
||||||
|
*OutTunnelCore = FTunnelCoreWorldEvaluation();
|
||||||
|
}
|
||||||
|
if (bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled())
|
||||||
|
{
|
||||||
|
VoxelDensityProfile::AddCounter(
|
||||||
|
VoxelDensityProfile::ECounter::FusedTunnelSamples);
|
||||||
|
}
|
||||||
|
|
||||||
const uint32 SeedU = (uint32)Seed;
|
const uint32 SeedU = (uint32)Seed;
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
@@ -2708,6 +2798,9 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// transition, ce qui est le nombre de reconstructions que ce cache aurait toujours dû faire.
|
// transition, ce qui est le nombre de reconstructions que ce cache aurait toujours dû faire.
|
||||||
thread_local uint32 CachedFingerprint = 0xFFFFFFFFu;
|
thread_local uint32 CachedFingerprint = 0xFFFFFFFFu;
|
||||||
thread_local uint32 CachedLayout = 0xFFFFFFFFu;
|
thread_local uint32 CachedLayout = 0xFFFFFFFFu;
|
||||||
|
thread_local bool CachedUsesFusedCacheWindow = false;
|
||||||
|
|
||||||
|
const bool bUseFusedCacheWindow = OutTunnelCore != nullptr;
|
||||||
|
|
||||||
// Index of the room with the smallest (most-inside) SDF for this voxel.
|
// Index of the room with the smallest (most-inside) SDF for this voxel.
|
||||||
// Written by EvaluateSDFCached, read by the terrain ops block to pick the
|
// Written by EvaluateSDFCached, read by the terrain ops block to pick the
|
||||||
@@ -2747,6 +2840,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
const bool bNeedRebuild =
|
const bool bNeedRebuild =
|
||||||
StrateIdx != CachedStrate || (uint32)Seed != CachedSeed ||
|
StrateIdx != CachedStrate || (uint32)Seed != CachedSeed ||
|
||||||
ParamsFingerprint != CachedFingerprint || LayoutVersion != CachedLayout ||
|
ParamsFingerprint != CachedFingerprint || LayoutVersion != CachedLayout ||
|
||||||
|
bUseFusedCacheWindow != CachedUsesFusedCacheWindow ||
|
||||||
WarpedX < CachedSMinX || WarpedX > CachedSMaxX ||
|
WarpedX < CachedSMinX || WarpedX > CachedSMaxX ||
|
||||||
WarpedY < CachedSMinY || WarpedY > CachedSMaxY;
|
WarpedY < CachedSMinY || WarpedY > CachedSMaxY;
|
||||||
|
|
||||||
@@ -2757,15 +2851,24 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
VoxelDensityProfile::AddCounter(
|
VoxelDensityProfile::AddCounter(
|
||||||
VoxelDensityProfile::ECounter::SdfCacheBuild);
|
VoxelDensityProfile::ECounter::SdfCacheBuild);
|
||||||
}
|
}
|
||||||
// Center the search box on this query's chunk. Search area = chunk XY extent
|
// The lowered evaluator uses the same window-invariance contract as the prepared
|
||||||
// + CaveWarpStrength margin (covers warp displacement) + gradient sampling.
|
// room-graph source, but keeps a four-chunk XY window per worker. A tile worker can
|
||||||
// MaxInfluence (room/tunnel reach) is added internally by BuildChunkCache.
|
// visit neighbouring chunk keys in an arbitrary order; the larger deterministic
|
||||||
const int32 CacheChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
// window prevents those jumps from rebuilding the graph for every small tile while
|
||||||
const int32 CacheChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
|
// preserving the pointwise SDF. The public/reference call keeps its historical
|
||||||
const float ChunkMinX = CacheChunkX * (float)CHUNK_SIZE;
|
// one-chunk window.
|
||||||
const float ChunkMinY = CacheChunkY * (float)CHUNK_SIZE;
|
const int32 QueryChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
||||||
const float ChunkMaxX = ChunkMinX + (float)CHUNK_SIZE;
|
const int32 QueryChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
|
||||||
const float ChunkMaxY = ChunkMinY + (float)CHUNK_SIZE;
|
const int32 CacheRegionChunks = bUseFusedCacheWindow ? 4 : 1;
|
||||||
|
const int32 CacheRegionSize = CHUNK_SIZE * CacheRegionChunks;
|
||||||
|
const int32 CacheRegionX = FMath::FloorToInt(
|
||||||
|
WorldX / static_cast<float>(CacheRegionSize));
|
||||||
|
const int32 CacheRegionY = FMath::FloorToInt(
|
||||||
|
WorldY / static_cast<float>(CacheRegionSize));
|
||||||
|
const float ChunkMinX = CacheRegionX * (float)CacheRegionSize;
|
||||||
|
const float ChunkMinY = CacheRegionY * (float)CacheRegionSize;
|
||||||
|
const float ChunkMaxX = ChunkMinX + (float)CacheRegionSize;
|
||||||
|
const float ChunkMaxY = ChunkMinY + (float)CacheRegionSize;
|
||||||
const float Expansion = FMath::Abs(Params.CaveWarpStrength)
|
const float Expansion = FMath::Abs(Params.CaveWarpStrength)
|
||||||
* VOXEL_NOISE_SCALE * 1.5f + 2.0f;
|
* VOXEL_NOISE_SCALE * 1.5f + 2.0f;
|
||||||
|
|
||||||
@@ -2781,7 +2884,10 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
{
|
{
|
||||||
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||||
UVoxelStrateDefinition* Def = StrateManager->GetStrateForChunk(
|
UVoxelStrateDefinition* Def = StrateManager->GetStrateForChunk(
|
||||||
FIntVector(CacheChunkX, CacheChunkY, ChunkZ));
|
FIntVector(
|
||||||
|
bUseFusedCacheWindow ? QueryChunkX : CacheRegionX,
|
||||||
|
bUseFusedCacheWindow ? QueryChunkY : CacheRegionY,
|
||||||
|
ChunkZ));
|
||||||
if (Def) TerrainOps = &Def->TerrainOperations;
|
if (Def) TerrainOps = &Def->TerrainOperations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2798,6 +2904,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
CachedSeed = (uint32)Seed;
|
CachedSeed = (uint32)Seed;
|
||||||
CachedFingerprint = ParamsFingerprint;
|
CachedFingerprint = ParamsFingerprint;
|
||||||
CachedLayout = LayoutVersion;
|
CachedLayout = LayoutVersion;
|
||||||
|
CachedUsesFusedCacheWindow = bUseFusedCacheWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate SDF using cached rooms and tunnels (WARPED coordinates).
|
// Evaluate SDF using cached rooms and tunnels (WARPED coordinates).
|
||||||
@@ -2901,6 +3008,21 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The canonical stack's source publishes this same structural result before its detail
|
||||||
|
// modifiers run. The lowered path asks the scalar evaluator for it directly, so the common
|
||||||
|
// post-disturbance tail can consume the result without allocating an FVoxelOpSample or
|
||||||
|
// invoking the interpreted stack a second time for this sample.
|
||||||
|
if (OutTunnelCore != nullptr
|
||||||
|
&& Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||||
|
{
|
||||||
|
// The no-column form is the exact scalar reference path: it performs the world-bound and
|
||||||
|
// floor projection for the current point without eagerly materialising a whole XY table.
|
||||||
|
// The interpreted source has a lazy worker LRU for block evaluation; building that table
|
||||||
|
// here would turn a cache-window change into hundreds of unnecessary column builds.
|
||||||
|
*OutTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
|
WorldX, WorldY, WorldZ, SDFCache, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// EARLY-OUT: Skip detail work for deep-solid voxels
|
// EARLY-OUT: Skip detail work for deep-solid voxels
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
@@ -2913,6 +3035,13 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// seal / modifiers, so we jump to Step 5 instead of returning early.
|
// seal / modifiers, so we jump to Step 5 instead of returning early.
|
||||||
const float DetailThreshold = Params.SDFBlendRadius * 3.0f;
|
const float DetailThreshold = Params.SDFBlendRadius * 3.0f;
|
||||||
const bool bNearCaveSurface = (CaveSDF < DetailThreshold) && (CaveSDF < FLT_MAX);
|
const bool bNearCaveSurface = (CaveSDF < DetailThreshold) && (CaveSDF < FLT_MAX);
|
||||||
|
if (bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled())
|
||||||
|
{
|
||||||
|
VoxelDensityProfile::AddCounter(
|
||||||
|
bNearCaveSurface
|
||||||
|
? VoxelDensityProfile::ECounter::FusedTunnelDetailSamples
|
||||||
|
: VoxelDensityProfile::ECounter::FusedTunnelDetailSkipped);
|
||||||
|
}
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// STEP 4b: SURFACE ROUGHNESS (volumetric, SDF-based)
|
// STEP 4b: SURFACE ROUGHNESS (volumetric, SDF-based)
|
||||||
|
|||||||
@@ -57,6 +57,19 @@ struct VOXELFORGE_API FVoxelStrateOpContract
|
|||||||
EVoxelOpResourceMask ProvidedResources = VoxelOpResources::None;
|
EVoxelOpResourceMask ProvidedResources = VoxelOpResources::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lowered evaluator selected when a stack has a proven, canonical implementation.
|
||||||
|
*
|
||||||
|
* The authored operator list remains present for classification, diagnostics, tests, and the
|
||||||
|
* fallback evaluator. This is a plan choice made while the stack is assembled, not a runtime
|
||||||
|
* CPU-feature dispatch and not a second semantic graph.
|
||||||
|
*/
|
||||||
|
enum class EVoxelOpFusedEvaluator : uint8
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
TunnelNetwork,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FVoxelOpStack — une liste ordonnée d'opérateurs + le pliage de verdict de boîte.
|
* FVoxelOpStack — une liste ordonnée d'opérateurs + le pliage de verdict de boîte.
|
||||||
*
|
*
|
||||||
@@ -114,6 +127,9 @@ public:
|
|||||||
|
|
||||||
void Add(TUniquePtr<IVoxelDensityOp> Op)
|
void Add(TUniquePtr<IVoxelDensityOp> Op)
|
||||||
{
|
{
|
||||||
|
// Adding an op invalidates any previously selected lowered plan. Factories mark the plan
|
||||||
|
// only after the complete authored graph has been assembled.
|
||||||
|
FusedEvaluator = EVoxelOpFusedEvaluator::None;
|
||||||
FOpEntry Entry;
|
FOpEntry Entry;
|
||||||
Entry.Op = MoveTemp(Op);
|
Entry.Op = MoveTemp(Op);
|
||||||
if (Entry.Op.Get() != nullptr)
|
if (Entry.Op.Get() != nullptr)
|
||||||
@@ -133,6 +149,18 @@ public:
|
|||||||
|
|
||||||
int32 Num() const { return Ops.Num(); }
|
int32 Num() const { return Ops.Num(); }
|
||||||
|
|
||||||
|
/** Select the lowered evaluator for this complete authored graph. */
|
||||||
|
void SetFusedEvaluator(EVoxelOpFusedEvaluator InEvaluator)
|
||||||
|
{
|
||||||
|
FusedEvaluator = InEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The lowered plan, or None when the general interpreted/block path must be used. */
|
||||||
|
EVoxelOpFusedEvaluator GetFusedEvaluator() const
|
||||||
|
{
|
||||||
|
return FusedEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
/** True when this stack publishes the requested immutable geometry resource. */
|
/** True when this stack publishes the requested immutable geometry resource. */
|
||||||
bool ProvidesResource(EVoxelOpResourceMask Resource) const
|
bool ProvidesResource(EVoxelOpResourceMask Resource) const
|
||||||
{
|
{
|
||||||
@@ -475,6 +503,7 @@ private:
|
|||||||
TArray<FOpEntry> Ops;
|
TArray<FOpEntry> Ops;
|
||||||
FVoxelOpContext PreparedContext;
|
FVoxelOpContext PreparedContext;
|
||||||
bool bHasPreparedContext = false;
|
bool bHasPreparedContext = false;
|
||||||
|
EVoxelOpFusedEvaluator FusedEvaluator = EVoxelOpFusedEvaluator::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ namespace VoxelDensityProfile
|
|||||||
TileVerdictCacheHits,
|
TileVerdictCacheHits,
|
||||||
TileVerdictRegionHits,
|
TileVerdictRegionHits,
|
||||||
TileVerdictCacheStores,
|
TileVerdictCacheStores,
|
||||||
|
FusedTunnelSamples,
|
||||||
|
FusedTunnelDetailSamples,
|
||||||
|
FusedTunnelDetailSkipped,
|
||||||
OpBlockBuilds,
|
OpBlockBuilds,
|
||||||
OpBlockSamples,
|
OpBlockSamples,
|
||||||
OpBlockOperators,
|
OpBlockOperators,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class UVoxelSettings;
|
|||||||
class UVoxelStrateManager;
|
class UVoxelStrateManager;
|
||||||
class UVoxelDiffLayer;
|
class UVoxelDiffLayer;
|
||||||
class UVoxelBiomeDefinition;
|
class UVoxelBiomeDefinition;
|
||||||
|
struct FTunnelCoreWorldEvaluation;
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// LOD-AWARE OCTAVE REDUCTION (T2.b)
|
// LOD-AWARE OCTAVE REDUCTION (T2.b)
|
||||||
@@ -189,7 +190,14 @@ public:
|
|||||||
float GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
float GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
||||||
const FStrateGenerationParams& Params,
|
const FStrateGenerationParams& Params,
|
||||||
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||||
bool bApplyLegacyStructuralPosts = true) const;
|
bool bApplyLegacyStructuralPosts = true,
|
||||||
|
// Optional hand-off used by a lowered canonical evaluator. When
|
||||||
|
// non-null, this receives the pre-disturbance TunnelNetwork core;
|
||||||
|
// it does not alter the scalar field result.
|
||||||
|
FTunnelCoreWorldEvaluation* OutTunnelCore = nullptr,
|
||||||
|
// Diagnostics are opt-in so ordinary/reference calls do not pay
|
||||||
|
// for fused-path counters.
|
||||||
|
bool bCollectFusedDiagnostics = false) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Densité pour une strate Slab (FlatPlain / CrystalChamber).
|
* Densité pour une strate Slab (FlatPlain / CrystalChamber).
|
||||||
|
|||||||
Reference in new issue
Block a user