wip(standalone): GPU step 3 -- GPU decides far rock/air, sparse CPU exceptions, hybrid viewer (H)

1,114 of 1,266 far tiles meshed on the GPU; vs CPU and vs WARP: 0 sign/topology differences,
including the protected/thin exception audit; max symmetric vertex distance 0.125 cm; repeat RX runs
identical; GPU field+mesh 0.029 ms/tile average. Strata survival 7,200/7,200, but connector survival
fails (GPU lacks the connector repair panels). Hybrid cold clipmap 5.74 s vs CPU 2.23 s: dispatch and
readback overhead, not compute (~33 ms of GPU work in total).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-06 09:14:22 +02:00
1 parent b61ccff549
commit 782f195bdb
15 files changed
+1018 -173

No files matched your search

@@ -215,12 +215,18 @@ namespace VoxelForgeCore
// Shape IDs preserve the CPU spatial-bin iteration order.
std::vector<FarFieldSampleRange> sampleShapeRanges;
std::vector<std::uint32_t> sampleShapeIds;
// Bit 1 means air, matching the CPU density sign rule (density >= 0).
std::vector<std::uint32_t> rockAirSigns;
// Critical route air is a subset of rockAirSigns and is always authoritative.
// Sparse exception bitset: bit 1 forces route-protected air. Ordinary rock/air
// classification is computed from the GPU field and is never uploaded by the CPU.
std::vector<std::uint32_t> protectedAir;
// Sparse exception bitset for exact solid overlays/thin route-adjacent features.
std::vector<std::uint32_t> protectedSolid;
std::vector<std::uint32_t> protectedThin;
std::vector<FarFieldStratumBoundary> stratumBoundaries;
std::uint64_t protectedSampleCount = 0;
std::uint64_t protectedSolidSampleCount = 0;
std::uint64_t protectedSealSampleCount = 0;
std::uint64_t protectedThinSampleCount = 0;
std::uint64_t protectedSolidOverlaySampleCount = 0;
};
struct EditBrush
@@ -312,6 +318,7 @@ namespace VoxelForgeCore
std::vector<std::uint32_t> farConnectorCandidates(Vec3i minVoxel, Vec3i maxVoxel) const;
std::optional<FarFieldTileInput> exportFarFieldTile(const TileKey& key,
std::string& error) const;
bool isFarFieldTileEligible(const TileKey& key) const;
bool auditDetailPassing() const;
bool auditGridStairIntegrity() const;
+78 -3
View File
@@ -501,8 +501,9 @@ public:
{return std::abs(A.z-B.z)<0.01f;}),Out.stratumBoundaries.end());
}
Out.sampleShapeRanges.resize(SampleCount);
Out.rockAirSigns.assign((SampleCount+31u)/32u,0u);
Out.protectedAir.assign((SampleCount+31u)/32u,0u);
Out.protectedSolid.assign((SampleCount+31u)/32u,0u);
Out.protectedThin.assign((SampleCount+31u)/32u,0u);
size_t SampleIndex=0;
for(int32 Z=-1;Z<=Cells+1;++Z)
@@ -512,13 +513,53 @@ public:
const int32 WX=Origin.X+X*Step,WY=Origin.Y+Y*Step,WZ=Origin.Z+Z*Step;
if(WX>=Min[0]&&WX<=Max[0]&&WY>=Min[1]&&WY<=Max[1]&&WZ>=Min[2]&&WZ<=Max[2])
{Error="tile lattice intersects the near analytic/detail region; this export is far-field only";return false;}
const float Density=DensityAtLattice(WX,WY,WZ);
if(Density>=0.0f)Out.rockAirSigns[SampleIndex>>5]|=1u<<(SampleIndex&31u);
if(IsInsideCriticalAir(WX,WY,WZ))
{
Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u);
++Out.protectedSampleCount;
}
else if(IsNearCriticalAirBoundary(WX,WY,WZ,4.0))
{
// Preserve exact signs within roughly one far-field lattice step of
// route air. This catches thin walls/floors at the route boundary
// without evaluating the ordinary far field on the CPU.
const float CriticalDensity=AnalyticDensityAt(static_cast<float>(WX),
static_cast<float>(WY),static_cast<float>(WZ));
Out.protectedThin[SampleIndex>>5]|=1u<<(SampleIndex&31u);
if(CriticalDensity>=0.0f)Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u);
else
{
Out.protectedSolid[SampleIndex>>5]|=1u<<(SampleIndex&31u);
++Out.protectedSolidSampleCount;
}
++Out.protectedThinSampleCount;
}
else
{
bool bSealSample=false;
for(const VoxelForgeCore::FarFieldStratumBoundary& Boundary:Out.stratumBoundaries)
{
if(std::abs(static_cast<double>(WZ)-Boundary.z)>0.5)continue;
// Only the exact lattice samples that lie on a seal height need a
// CPU sign override. The surrounding far field remains GPU-decided.
const float SealDensity=AnalyticDensityAt(static_cast<float>(WX),
static_cast<float>(WY),static_cast<float>(WZ));
if(SealDensity>=0.0f)
Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u);
else
Out.protectedSolid[SampleIndex>>5]|=1u<<(SampleIndex&31u);
++Out.protectedSealSampleCount;
if(SealDensity<0.0f)++Out.protectedSolidSampleCount;
bSealSample=true;
break;
}
if(!bSealSample&&IsInsideSolidOverlay(WX,WY,WZ))
{
Out.protectedSolid[SampleIndex>>5]|=1u<<(SampleIndex&31u);
++Out.protectedSolidSampleCount;
++Out.protectedSolidOverlaySampleCount;
}
}
const auto Found=SpatialBins.find(PointBin(WX,WY,WZ));
if(Found==SpatialBins.end())continue;
auto& Ids=PerSample[SampleIndex];
@@ -586,6 +627,19 @@ public:
return true;
}
bool IsFarFieldTileOutsideNearRegion(const FIntVector& Origin,int32 Cells,int32 Step) const
{
if(!FarBuilt||Cells<=0||Cells>32||Step<=0)return false;
const int32 Origins[3]={Origin.X,Origin.Y,Origin.Z};
for(int32 Axis=0;Axis<3;++Axis)
{
const double First=std::ceil((static_cast<double>(Min[Axis])-Origins[Axis])/Step);
const double Last=std::floor((static_cast<double>(Max[Axis])-Origins[Axis])/Step);
if(First>Cells+1||Last< -1)return true;
}
return false;
}
bool DebugCriticalAirAt(double X,double Y,double Z) const { return IsInsideCriticalAir(X,Y,Z); }
bool DebugSolidOverlayAt(double X,double Y,double Z) const { return IsInsideSolidOverlay(X,Y,Z); }
@@ -3336,6 +3390,27 @@ private:
return false;
}
bool IsNearCriticalAirBoundary(int32 X,int32 Y,int32 Z,double Margin) const
{
const auto It=CriticalAirBins.find(PointBin(X,Y,Z));
if(It==CriticalAirBins.end())return false;
for(uint32 Id:It->second)
{
const FPrimitive& P=Primitives[Id];if(!P.bCarve||!P.bCritical)continue;
if(X<P.Min[0]-Margin||X>P.Max[0]+Margin
||Y<P.Min[1]-Margin||Y>P.Max[1]+Margin
||Z<P.Min[2]-Margin||Z>P.Max[2]+Margin)continue;
float Distance=ShapeDistance(P,X,Y,Z);
if(P.bHasFloorClip)
{
const float FloorDistance=static_cast<float>(FloorAt(P,X,Y)-Z);
Distance=SmoothMaxDistance(Distance,FloorDistance,static_cast<float>(P.FloorBlendRadius));
}
if(std::abs(Distance)<=Margin)return true;
}
return false;
}
bool IsInsideSolidOverlay(double X,double Y,double Z) const
{
const auto It=SpatialBins.find(PointBin(X,Y,Z));
+11
View File
@@ -751,6 +751,17 @@ namespace VoxelForgeCore
return result;
}
bool World::isFarFieldTileEligible(const TileKey& key) const
{
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt()
||!impl_->shape->HasFarWorld()||key.lod<impl_->configuration.fullResolutionLodCount
||key.lod>8)return false;
const int32 extent=CHUNK_SIZE<<key.lod;
const int32 cells=std::max(1,impl_->owner->CoarseTileCells);
const int32 step=std::max(1,extent/cells);
return impl_->shape->IsFarFieldTileOutsideNearRegion(ToNative(key).OriginVoxels(),cells,step);
}
bool World::auditDetailPassing() const { return impl_&&impl_->shape&&impl_->shape->IsDetailAuditPassing(); }
bool World::auditGridStairIntegrity() const
{ return impl_&&impl_->shape&&impl_->shape->AuditGridStairIntegrity(); }