wip(standalone): GPU step 6 -- near field on GPU, exact; cold start still slower than CPU
Near LOD0/1 (detail band, relief, root refinement, normals) now evaluated and meshed on the GPU. 960 near tiles: zero sign/topology differences vs CPU and WARP, vertex max 0.0049 mm (was 15 cm); far survival audit passes; CPU hashes 44/44 unchanged. Far connector-panel outlier (64.56 cm, LOD8) remains a tessellation difference, and remote captures show missing far geometry on GPU. Cold readiness (viewer clock, my interleaved runs): CPU 2.51/2.49/2.40 s, GPU 5.05/2.68/2.80 s. The ~2.1 s Present stall hit only the first GPU launch. Viewer spends ~1.13 s before shape compile starts (startup checks), which is the gap between the 0.95 s benchmark and ~2.4 s on-screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
0e730cdbba
commit
b0a14262b1
50 files changed
+1681
-236
No files matched your search
@@ -526,9 +526,20 @@ public:
|
||||
auto Data=std::make_shared<VoxelForgeCore::FarFieldWorldInput>();
|
||||
Data->spatialBinSize=static_cast<uint32>(ActiveSpatialBinSize);
|
||||
Data->spatialBinMask=static_cast<uint32>(TableCapacity-1);
|
||||
Data->fieldSeed=static_cast<uint32>(Owner?Owner->Seed:0);
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{Data->detailMinVoxel[Axis]=Min[Axis];Data->detailMaxVoxel[Axis]=Max[Axis];}
|
||||
Data->shapes.reserve(SharedFieldPrimitives.size());
|
||||
for(const VF_FieldPrimitive& Shape:SharedFieldPrimitives)
|
||||
Data->shapes.push_back(ToFarFieldGpuPrimitive(Shape));
|
||||
Data->detailLayers.reserve(LayerFields.size());
|
||||
for(const FLayerField& Field:LayerFields)
|
||||
{
|
||||
const FStrateGenerationParams& Params=Field.Params;
|
||||
Data->detailLayers.push_back({Field.Lo,Field.Hi,Params.SurfaceRoughness,
|
||||
Params.RoughnessFrequency,Params.VerticalScale,Params.DomainWarpStrength,
|
||||
Params.DomainWarpFrequency,static_cast<uint32>(Params.RoughnessNoiseType)});
|
||||
}
|
||||
Data->spatialBins.resize(TableCapacity);
|
||||
size_t ReferenceCount=0;
|
||||
for(const auto& Bin:SpatialBins)ReferenceCount+=Bin.second.size();
|
||||
@@ -551,20 +562,22 @@ public:
|
||||
const double UploadMiB=(G.shapes.size()*sizeof(VoxelForgeCore::FarFieldPrimitiveGpu)
|
||||
+G.spatialBins.size()*sizeof(VoxelForgeCore::FarFieldSpatialBin)
|
||||
+G.spatialBinShapeIds.size()*sizeof(uint32))/1048576.0;
|
||||
std::printf("[FarGpuWorldData] shapes=%zu spatial_bins=%zu hash_slots=%zu bin_shape_ids=%zu bin_size=%d bytes=%.3fMiB upload_scope=once_per_world\n",
|
||||
std::printf("[FarGpuWorldData] shapes=%zu spatial_bins=%zu hash_slots=%zu bin_shape_ids=%zu bin_size=%d detail_min=(%d,%d,%d) detail_max=(%d,%d,%d) bytes=%.3fMiB upload_scope=once_per_world\n",
|
||||
G.shapes.size(),SpatialBins.size(),G.spatialBins.size(),G.spatialBinShapeIds.size(),
|
||||
ActiveSpatialBinSize,UploadMiB);
|
||||
ActiveSpatialBinSize,G.detailMinVoxel[0],G.detailMinVoxel[1],G.detailMinVoxel[2],
|
||||
G.detailMaxVoxel[0],G.detailMaxVoxel[1],G.detailMaxVoxel[2],UploadMiB);
|
||||
return CachedFarFieldGpuInput;
|
||||
}
|
||||
|
||||
bool ExportFarFieldTileInput(const FIntVector& Origin,int32 Cells,int32 Step,
|
||||
VoxelForgeCore::FarFieldTileInput& Out,std::string& Error) const
|
||||
bool bNearDetail,VoxelForgeCore::FarFieldTileInput& Out,std::string& Error) const
|
||||
{
|
||||
if(!FarBuilt||Cells<=0||Cells>32||Step<=0)
|
||||
{Error="far field export requires a built far world and a bounded coarse tile";return false;}
|
||||
const int32 Dim=Cells+3;
|
||||
const size_t SampleCount=static_cast<size_t>(Dim)*Dim*Dim;
|
||||
Out={};Error.clear();Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim;
|
||||
Out.nearDetailEnabled=bNearDetail;
|
||||
Out.worldInput=GetFarFieldWorldInput(Error);
|
||||
if(!Out.worldInput)return false;
|
||||
if(LayerRanges.empty())
|
||||
@@ -596,6 +609,8 @@ public:
|
||||
Out.protectedAir.assign((SampleCount+31u)/32u,0u);
|
||||
Out.protectedSolid.assign((SampleCount+31u)/32u,0u);
|
||||
Out.protectedThin.assign((SampleCount+31u)/32u,0u);
|
||||
if(bNearDetail)
|
||||
Out.nearPackedProxy.assign(SampleCount,std::numeric_limits<uint32>::max());
|
||||
|
||||
const int32 LatticeMin[3]={Origin.X-Step,Origin.Y-Step,Origin.Z-Step};
|
||||
const int32 LatticeMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,
|
||||
@@ -723,7 +738,34 @@ public:
|
||||
for(int32 X=-1;X<=Cells+1;++X,++SampleIndex)
|
||||
{
|
||||
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])
|
||||
if(bNearDetail&&!bAnalyticOnly&&Contains(WX,WY,WZ))
|
||||
{
|
||||
const size_t Column=static_cast<size_t>(WY-Min[1])*Size[0]
|
||||
+static_cast<size_t>(WX-Min[0]);
|
||||
const size_t Zi=static_cast<size_t>(WZ-Min[2]);
|
||||
const size_t Word=Column*WordsPerZ+(Zi>>6);
|
||||
const uint64 Mask=uint64{1}<<(Zi&63u);
|
||||
if((CandidateWords[Word]&Mask)==0)
|
||||
Out.nearPackedProxy[SampleIndex]=0u;
|
||||
else
|
||||
{
|
||||
const size_t Node=Zi*static_cast<size_t>(Size[1])*Size[0]+Column;
|
||||
const int32 Q=std::abs(static_cast<int32>(SurfaceDistanceQ[Node]));
|
||||
const uint32 Magnitude=static_cast<uint32>(std::clamp(Q,1,32767));
|
||||
if(Magnitude==1u)
|
||||
{
|
||||
// At the minimum quantization step a one-ULP GPU sign flip
|
||||
// can change an entire marching-cubes cell. Keep this sparse
|
||||
// ambiguous boundary decision aligned with the packed cache.
|
||||
const bool bRock=(RockWords[Word]&Mask)!=0;
|
||||
Out.nearPackedProxy[SampleIndex]=0x40000000u
|
||||
|(bRock?0x80000000u:0u)|Magnitude;
|
||||
}
|
||||
else Out.nearPackedProxy[SampleIndex]=Magnitude;
|
||||
}
|
||||
}
|
||||
if(!bNearDetail&&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;}
|
||||
if(InsideCritical(WX,WY,WZ))
|
||||
{
|
||||
@@ -1746,6 +1788,7 @@ private:
|
||||
if(P.bCurvedFloorClip)Out.flags|=VF_FIELD_PRIMITIVE_CURVED_FLOOR;
|
||||
if(P.bCarve)Out.flags|=VF_FIELD_PRIMITIVE_CARVE;
|
||||
if(P.bCritical)Out.flags|=VF_FIELD_PRIMITIVE_CRITICAL;
|
||||
if(P.bPassageSurface)Out.flags|=VF_FIELD_PRIMITIVE_PASSAGE;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
Out.minVoxel[A]=P.Min[A];Out.maxVoxel[A]=P.Max[A];
|
||||
@@ -4081,68 +4124,12 @@ private:
|
||||
float OwnerCaveRoughnessCore(double X,double Y,double Z,
|
||||
const FStrateGenerationParams& Params) const
|
||||
{
|
||||
const uint32 Seed=static_cast<uint32>(Owner->Seed);
|
||||
const float RF=Params.RoughnessFrequency;
|
||||
const float NoiseX=static_cast<float>(X);
|
||||
const float NoiseY=static_cast<float>(Y);
|
||||
const float EffectiveZ=(Params.VerticalScale>0.0f&&Params.VerticalScale!=1.0f)
|
||||
?static_cast<float>(Z)/Params.VerticalScale:static_cast<float>(Z);
|
||||
FVector3f Main(NoiseX*RF+VoxelHash::SeedOffset(Seed,11.3f),
|
||||
NoiseY*RF+VoxelHash::SeedOffset(Seed,13.7f),
|
||||
EffectiveZ*RF+VoxelHash::SeedOffset(Seed,17.1f));
|
||||
FVector3f Fine(NoiseX*RF*3.0f+VoxelHash::SeedOffset(Seed,19.1f)+2000.0f,
|
||||
NoiseY*RF*3.0f+VoxelHash::SeedOffset(Seed,23.7f)+2500.0f,
|
||||
EffectiveZ*RF*3.0f+VoxelHash::SeedOffset(Seed,29.3f)+3000.0f);
|
||||
if(Params.DomainWarpStrength>0.0f)
|
||||
{
|
||||
const float WF=Params.DomainWarpFrequency,WS=Params.DomainWarpStrength;
|
||||
float WX=0.0f,WY=0.0f,WZ=0.0f;
|
||||
VoxelNoise::Perlin3D_x3(
|
||||
NoiseX*WF+VoxelHash::SeedOffset(Seed,5.2f),
|
||||
NoiseY*WF+VoxelHash::SeedOffset(Seed,1.3f),
|
||||
EffectiveZ*WF+VoxelHash::SeedOffset(Seed,9.7f),
|
||||
NoiseX*WF+100.0f+VoxelHash::SeedOffset(Seed,7.7f),
|
||||
NoiseY*WF+200.0f+VoxelHash::SeedOffset(Seed,3.1f),
|
||||
EffectiveZ*WF+300.0f,
|
||||
NoiseX*WF+400.0f,
|
||||
NoiseY*WF+500.0f+VoxelHash::SeedOffset(Seed,11.9f),
|
||||
EffectiveZ*WF+600.0f+VoxelHash::SeedOffset(Seed,13.3f),WX,WY,WZ);
|
||||
WX*=VOXEL_NOISE_SCALE*WS;WY*=VOXEL_NOISE_SCALE*WS;WZ*=VOXEL_NOISE_SCALE*WS;
|
||||
Main+=FVector3f(WX,WY,WZ);Fine+=FVector3f(WX,WY,WZ);
|
||||
}
|
||||
float Rough=0.0f,FineValue=0.0f;
|
||||
switch(Params.RoughnessNoiseType)
|
||||
{
|
||||
case EVoxelNoiseType::Ridged:
|
||||
{
|
||||
float MainFBM=0.0f,MainRidged=0.0f,FineFBM=0.0f,FineRidged=0.0f;
|
||||
VoxelNoise::FractalPair<false,true>(Main.X,Main.Y,Main.Z,3,
|
||||
Fine.X,Fine.Y,Fine.Z,2,MainFBM,MainRidged,FineFBM,FineRidged);
|
||||
Rough=MainRidged;FineValue=FineRidged;
|
||||
break;
|
||||
}
|
||||
case EVoxelNoiseType::Mixed:
|
||||
{
|
||||
float MainFBM=0.0f,MainRidged=0.0f,FineFBM=0.0f,FineRidged=0.0f;
|
||||
VoxelNoise::FractalPair<true,true>(Main.X,Main.Y,Main.Z,3,
|
||||
Fine.X,Fine.Y,Fine.Z,2,MainFBM,MainRidged,FineFBM,FineRidged);
|
||||
Rough=MainFBM*0.5f+MainRidged*0.5f;
|
||||
FineValue=FineFBM*0.5f+FineRidged*0.5f;
|
||||
break;
|
||||
}
|
||||
case EVoxelNoiseType::Cellular:
|
||||
Rough=VoxelNoise::Cellular3D(Main);FineValue=VoxelNoise::Cellular3D(Fine);break;
|
||||
case EVoxelNoiseType::FBM:
|
||||
default:
|
||||
{
|
||||
float MainFBM=0.0f,MainRidged=0.0f,FineFBM=0.0f,FineRidged=0.0f;
|
||||
VoxelNoise::FractalPair<true,false>(Main.X,Main.Y,Main.Z,3,
|
||||
Fine.X,Fine.Y,Fine.Z,2,MainFBM,MainRidged,FineFBM,FineRidged);
|
||||
Rough=MainFBM;FineValue=FineFBM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (Rough+FineValue*0.4f)*VOXEL_NOISE_SCALE*Params.SurfaceRoughness;
|
||||
// Keep CPU prediction and the near-field HLSL on one scalar source. The helper
|
||||
// preserves the legacy seed offsets, octave order, and roughness families.
|
||||
return VF_FieldOwnerRoughnessCore(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),static_cast<uint32>(Owner->Seed),Params.SurfaceRoughness,
|
||||
Params.RoughnessFrequency,Params.VerticalScale,Params.DomainWarpStrength,
|
||||
Params.DomainWarpFrequency,static_cast<uint32>(Params.RoughnessNoiseType));
|
||||
}
|
||||
|
||||
float OwnerCaveRoughness(double X,double Y,double Z,float SurfaceSDF,
|
||||
|
||||
Reference in new issue
Block a user