wip(standalone): GPU step 5 -- far GPU work 646 -> 112 ms; hybrid whole view equal to CPU
Whole far field on the RX 7900 XTX: 112 ms total (connector rings 61 ms). Cold hybrid 2.303 s vs all-CPU 2.314 s (within noise): the far field is no longer on the wall-clock critical path. 0 sign/topology differences vs CPU and WARP incl. 127,401 protected and 353 thin-feature cells; repeat GPU runs identical; far survival passes on GPU meshes; CPU hashes unchanged. Open: CPU/GPU vertex distance mean 1.15 mm but max 64.56 cm, 247 tiles over the quantisation bound. 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
247682a8e0
commit
0e730cdbba
9 files changed
+697
-260
No files matched your search
@@ -464,6 +464,99 @@ public:
|
||||
OutIndices.erase(std::unique(OutIndices.begin(),OutIndices.end()),OutIndices.end());
|
||||
}
|
||||
|
||||
static uint32 FarFieldBinHash(int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
uint32 Hash=2166136261u;
|
||||
Hash=(Hash^static_cast<uint32>(X))*16777619u;
|
||||
Hash=(Hash^static_cast<uint32>(Y))*16777619u;
|
||||
Hash=(Hash^static_cast<uint32>(Z))*16777619u;
|
||||
Hash^=Hash>>16;Hash*=0x7FEB352Du;Hash^=Hash>>15;
|
||||
Hash*=0x846CA68Bu;Hash^=Hash>>16;
|
||||
return Hash;
|
||||
}
|
||||
|
||||
static VoxelForgeCore::FarFieldPrimitiveGpu ToFarFieldGpuPrimitive(const VF_FieldPrimitive& S)
|
||||
{
|
||||
VoxelForgeCore::FarFieldPrimitiveGpu G{};
|
||||
G.flags=S.flags;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
G.minVoxel[A]=S.minVoxel[A];G.maxVoxel[A]=S.maxVoxel[A];
|
||||
G.center[A]=static_cast<float>(S.center[A]);
|
||||
G.capsuleStart[A]=static_cast<float>(S.capsuleStart[A]);
|
||||
G.capsuleDelta[A]=static_cast<float>(S.capsuleDelta[A]);
|
||||
G.roundedHalfExtent[A]=static_cast<float>(S.roundedHalfExtent[A]);
|
||||
}
|
||||
for(int32 I=0;I<9;++I)G.q[I]=static_cast<float>(S.q[I]);
|
||||
G.minRadius=static_cast<float>(S.minRadius);
|
||||
G.capsuleAxisLengthSquared=static_cast<float>(S.capsuleAxisLengthSquared);
|
||||
G.capsuleAxisLength=static_cast<float>(S.capsuleAxisLength);
|
||||
G.capsuleStartRadius=static_cast<float>(S.capsuleStartRadius);
|
||||
G.capsuleEndRadius=static_cast<float>(S.capsuleEndRadius);
|
||||
G.profileStartT=static_cast<float>(S.profileStartT);G.profileEndT=static_cast<float>(S.profileEndT);
|
||||
G.profileStartRadius=static_cast<float>(S.profileStartRadius);G.profileEndRadius=static_cast<float>(S.profileEndRadius);
|
||||
G.radiusSlopeStart=static_cast<float>(S.radiusSlopeStart);G.radiusSlopeEnd=static_cast<float>(S.radiusSlopeEnd);
|
||||
G.rounding=static_cast<float>(S.rounding);
|
||||
G.blendRadius=static_cast<float>(S.blendRadius);G.floorBlendRadius=static_cast<float>(S.floorBlendRadius);
|
||||
G.floorCenterZ=static_cast<float>(S.floorCenterZ);G.floorSlopeX=static_cast<float>(S.floorSlopeX);
|
||||
G.floorSlopeY=static_cast<float>(S.floorSlopeY);
|
||||
G.floorCurveOriginX=static_cast<float>(S.floorCurveOriginX);G.floorCurveOriginY=static_cast<float>(S.floorCurveOriginY);
|
||||
G.floorCurveDeltaX=static_cast<float>(S.floorCurveDeltaX);G.floorCurveDeltaY=static_cast<float>(S.floorCurveDeltaY);
|
||||
G.floorCurveLength=static_cast<float>(S.floorCurveLength);
|
||||
G.floorCurveStartZ=static_cast<float>(S.floorCurveStartZ);G.floorCurveEndZ=static_cast<float>(S.floorCurveEndZ);
|
||||
G.floorCurveTangentStart=static_cast<float>(S.floorCurveTangentStart);
|
||||
G.floorCurveTangentEnd=static_cast<float>(S.floorCurveTangentEnd);
|
||||
G.floorCurveStartBlend=static_cast<float>(S.floorCurveStartBlend);
|
||||
G.floorCurveEndBlend=static_cast<float>(S.floorCurveEndBlend);
|
||||
return G;
|
||||
}
|
||||
|
||||
std::shared_ptr<const VoxelForgeCore::FarFieldWorldInput> GetFarFieldWorldInput(
|
||||
std::string& Error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(FarFieldGpuInputMutex);
|
||||
if(CachedFarFieldGpuInput)return CachedFarFieldGpuInput;
|
||||
if(!FarBuilt||SpatialBins.empty()||SharedFieldPrimitives.empty())
|
||||
{Error="far world has no shared primitives or spatial bins";return {};}
|
||||
if(SpatialBins.size()>std::numeric_limits<uint32>::max()/2u)
|
||||
{Error="far world spatial-bin table exceeds the GPU 32-bit slot limit";return {};}
|
||||
const size_t TableCapacity=std::bit_ceil(std::max<size_t>(2,SpatialBins.size()*2));
|
||||
if(TableCapacity>std::numeric_limits<uint32>::max())
|
||||
{Error="far world spatial-bin table exceeds the D3D11 element limit";return {};}
|
||||
auto Data=std::make_shared<VoxelForgeCore::FarFieldWorldInput>();
|
||||
Data->spatialBinSize=static_cast<uint32>(ActiveSpatialBinSize);
|
||||
Data->spatialBinMask=static_cast<uint32>(TableCapacity-1);
|
||||
Data->shapes.reserve(SharedFieldPrimitives.size());
|
||||
for(const VF_FieldPrimitive& Shape:SharedFieldPrimitives)
|
||||
Data->shapes.push_back(ToFarFieldGpuPrimitive(Shape));
|
||||
Data->spatialBins.resize(TableCapacity);
|
||||
size_t ReferenceCount=0;
|
||||
for(const auto& Bin:SpatialBins)ReferenceCount+=Bin.second.size();
|
||||
Data->spatialBinShapeIds.reserve(ReferenceCount);
|
||||
for(const auto& [Key,Items]:SpatialBins)
|
||||
{
|
||||
if(Items.size()>std::numeric_limits<uint32>::max()
|
||||
||Data->spatialBinShapeIds.size()+Items.size()>std::numeric_limits<uint32>::max())
|
||||
{Error="far world spatial-bin shape IDs exceed the GPU 32-bit offset limit";return {};}
|
||||
uint32 Slot=FarFieldBinHash(Key.X,Key.Y,Key.Z)&Data->spatialBinMask;
|
||||
while(Data->spatialBins[Slot].count!=0)Slot=(Slot+1)&Data->spatialBinMask;
|
||||
VoxelForgeCore::FarFieldSpatialBin& GpuBin=Data->spatialBins[Slot];
|
||||
GpuBin.x=Key.X;GpuBin.y=Key.Y;GpuBin.z=Key.Z;
|
||||
GpuBin.offset=static_cast<uint32>(Data->spatialBinShapeIds.size());
|
||||
GpuBin.count=static_cast<uint32>(Items.size());
|
||||
for(const uint32 Id:Items)Data->spatialBinShapeIds.push_back(Id);
|
||||
}
|
||||
CachedFarFieldGpuInput=std::move(Data);
|
||||
const auto& G=*CachedFarFieldGpuInput;
|
||||
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",
|
||||
G.shapes.size(),SpatialBins.size(),G.spatialBins.size(),G.spatialBinShapeIds.size(),
|
||||
ActiveSpatialBinSize,UploadMiB);
|
||||
return CachedFarFieldGpuInput;
|
||||
}
|
||||
|
||||
bool ExportFarFieldTileInput(const FIntVector& Origin,int32 Cells,int32 Step,
|
||||
VoxelForgeCore::FarFieldTileInput& Out,std::string& Error) const
|
||||
{
|
||||
@@ -471,8 +564,9 @@ public:
|
||||
{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;
|
||||
std::vector<uint8> Used(Primitives.size(),0);
|
||||
Out={};Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim;
|
||||
Out={};Error.clear();Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim;
|
||||
Out.worldInput=GetFarFieldWorldInput(Error);
|
||||
if(!Out.worldInput)return false;
|
||||
if(LayerRanges.empty())
|
||||
{Out.strataLo=-100000.0f;Out.strataHi=100000.0f;}
|
||||
else
|
||||
@@ -499,11 +593,72 @@ public:
|
||||
Out.stratumBoundaries.end(),[](const auto& A,const auto& B)
|
||||
{return std::abs(A.z-B.z)<0.01f;}),Out.stratumBoundaries.end());
|
||||
}
|
||||
Out.sampleShapeRanges.resize(SampleCount);
|
||||
Out.protectedAir.assign((SampleCount+31u)/32u,0u);
|
||||
Out.protectedSolid.assign((SampleCount+31u)/32u,0u);
|
||||
Out.protectedThin.assign((SampleCount+31u)/32u,0u);
|
||||
|
||||
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,
|
||||
Origin.Z+(Cells+1)*Step};
|
||||
std::vector<uint32> CriticalCandidates,OverlayCandidates;
|
||||
for(uint32 Id=0;Id<Primitives.size();++Id)
|
||||
{
|
||||
const FPrimitive& Shape=Primitives[Id];
|
||||
bool CriticalOverlap=Shape.bCarve&&Shape.bCritical;
|
||||
bool OverlayOverlap=!Shape.bCarve;
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
CriticalOverlap&=Shape.Max[Axis]>=LatticeMin[Axis]-4
|
||||
&&Shape.Min[Axis]<=LatticeMax[Axis]+4;
|
||||
OverlayOverlap&=Shape.Max[Axis]>=LatticeMin[Axis]
|
||||
&&Shape.Min[Axis]<=LatticeMax[Axis];
|
||||
}
|
||||
if(CriticalOverlap)CriticalCandidates.push_back(Id);
|
||||
if(OverlayOverlap)OverlayCandidates.push_back(Id);
|
||||
}
|
||||
const auto InsideCritical=[&](int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
for(const uint32 Id:CriticalCandidates)
|
||||
{
|
||||
const FPrimitive& Shape=Primitives[Id];
|
||||
if(X<Shape.Min[0]||X>Shape.Max[0]||Y<Shape.Min[1]||Y>Shape.Max[1]
|
||||
||Z<Shape.Min[2]||Z>Shape.Max[2])continue;
|
||||
if(ShapeContains(Shape,X,Y,Z))return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto NearCriticalBoundary=[&](int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
constexpr double Margin=4.0;
|
||||
for(const uint32 Id:CriticalCandidates)
|
||||
{
|
||||
const FPrimitive& Shape=Primitives[Id];
|
||||
if(X<Shape.Min[0]-Margin||X>Shape.Max[0]+Margin
|
||||
||Y<Shape.Min[1]-Margin||Y>Shape.Max[1]+Margin
|
||||
||Z<Shape.Min[2]-Margin||Z>Shape.Max[2]+Margin)continue;
|
||||
float Distance=ShapeDistance(Shape,X,Y,Z);
|
||||
if(Shape.bHasFloorClip)
|
||||
{
|
||||
const float FloorDistance=static_cast<float>(FloorAt(Shape,X,Y)-Z);
|
||||
Distance=SmoothMaxDistance(Distance,FloorDistance,
|
||||
static_cast<float>(Shape.FloorBlendRadius));
|
||||
}
|
||||
if(std::abs(Distance)<=Margin)return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto InsideSolidOverlay=[&](int32 X,int32 Y,int32 Z)
|
||||
{
|
||||
for(const uint32 Id:OverlayCandidates)
|
||||
{
|
||||
const FPrimitive& Shape=Primitives[Id];
|
||||
if(Shape.bCarve||X<Shape.Min[0]||X>Shape.Max[0]
|
||||
||Y<Shape.Min[1]||Y>Shape.Max[1]||Z<Shape.Min[2]||Z>Shape.Max[2])continue;
|
||||
if(ShapeDistance(Shape,X,Y,Z)<=0.0f)return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto& Connectors=GetFarConnectorSegments();
|
||||
// CPU repair panels are only used by the survival path at LOD6 and above.
|
||||
// Keep this sparse repair out of LOD2..5, where the base GPU surface has
|
||||
@@ -539,18 +694,6 @@ public:
|
||||
}
|
||||
const double Length=std::sqrt(Delta[0]*Delta[0]+Delta[1]*Delta[1]+Delta[2]*Delta[2]);
|
||||
if(!Intersects||Length<=1.0e-6||TMax-TMin<=1.0e-7)continue;
|
||||
const double ShapeMargin=static_cast<double>(Connector.Radius)+64.0;
|
||||
for(uint32 ShapeId=0;ShapeId<Primitives.size();++ShapeId)
|
||||
{
|
||||
const FPrimitive& Shape=Primitives[ShapeId];bool Overlaps=true;
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
const double Lo=std::min(Start[Axis],Start[Axis]+Delta[Axis])-ShapeMargin;
|
||||
const double Hi=std::max(Start[Axis],Start[Axis]+Delta[Axis])+ShapeMargin;
|
||||
if(Shape.Max[Axis]<Lo||Shape.Min[Axis]>Hi){Overlaps=false;break;}
|
||||
}
|
||||
if(Overlaps)Used[ShapeId]=1;
|
||||
}
|
||||
const uint32 AxialSampleCount=std::max<uint32>(1,static_cast<uint32>(std::ceil(Length/128.0)));
|
||||
const double FirstValue=std::ceil(TMin*AxialSampleCount-0.5-1.0e-8);
|
||||
const double LastValue=std::floor(TMax*AxialSampleCount-0.5+1.0e-8);
|
||||
@@ -582,12 +725,12 @@ 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;}
|
||||
if(IsInsideCriticalAir(WX,WY,WZ))
|
||||
if(InsideCritical(WX,WY,WZ))
|
||||
{
|
||||
Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u);
|
||||
++Out.protectedSampleCount;
|
||||
}
|
||||
else if(IsNearCriticalAirBoundary(WX,WY,WZ,4.0))
|
||||
else if(NearCriticalBoundary(WX,WY,WZ))
|
||||
{
|
||||
// Preserve exact signs within roughly one far-field lattice step of
|
||||
// route air. This catches thin walls/floors at the route boundary
|
||||
@@ -622,73 +765,13 @@ public:
|
||||
bSealSample=true;
|
||||
break;
|
||||
}
|
||||
if(!bSealSample&&IsInsideSolidOverlay(WX,WY,WZ))
|
||||
if(!bSealSample&&InsideSolidOverlay(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())
|
||||
{
|
||||
Out.sampleShapeRanges[SampleIndex]={static_cast<uint32>(Out.sampleShapeIds.size()),0};
|
||||
continue;
|
||||
}
|
||||
const auto& Ids=Found->second;
|
||||
if(Out.sampleShapeIds.size()+Ids.size()>std::numeric_limits<uint32>::max())
|
||||
{Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;}
|
||||
Out.sampleShapeRanges[SampleIndex]={static_cast<uint32>(Out.sampleShapeIds.size()),
|
||||
static_cast<uint32>(Ids.size())};
|
||||
Out.sampleShapeIds.insert(Out.sampleShapeIds.end(),Ids.begin(),Ids.end());
|
||||
for(const uint32 Id:Ids)if(Id<Used.size())Used[Id]=1;
|
||||
}
|
||||
|
||||
std::vector<uint32> Remap(Primitives.size(),std::numeric_limits<uint32>::max());
|
||||
Out.shapes.reserve(std::count(Used.begin(),Used.end(),uint8{1}));
|
||||
for(uint32 Id=0;Id<Used.size();++Id)
|
||||
{
|
||||
if(!Used[Id])continue;
|
||||
Remap[Id]=static_cast<uint32>(Out.shapes.size());
|
||||
const VF_FieldPrimitive& S=SharedFieldPrimitives[Id];
|
||||
VoxelForgeCore::FarFieldPrimitiveGpu G{};
|
||||
G.flags=S.flags;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
G.minVoxel[A]=S.minVoxel[A];G.maxVoxel[A]=S.maxVoxel[A];
|
||||
G.center[A]=static_cast<float>(S.center[A]);
|
||||
G.capsuleStart[A]=static_cast<float>(S.capsuleStart[A]);
|
||||
G.capsuleDelta[A]=static_cast<float>(S.capsuleDelta[A]);
|
||||
G.roundedHalfExtent[A]=static_cast<float>(S.roundedHalfExtent[A]);
|
||||
}
|
||||
for(int32 I=0;I<9;++I)G.q[I]=static_cast<float>(S.q[I]);
|
||||
G.minRadius=static_cast<float>(S.minRadius);
|
||||
G.capsuleAxisLengthSquared=static_cast<float>(S.capsuleAxisLengthSquared);
|
||||
G.capsuleAxisLength=static_cast<float>(S.capsuleAxisLength);
|
||||
G.capsuleStartRadius=static_cast<float>(S.capsuleStartRadius);
|
||||
G.capsuleEndRadius=static_cast<float>(S.capsuleEndRadius);
|
||||
G.profileStartT=static_cast<float>(S.profileStartT);G.profileEndT=static_cast<float>(S.profileEndT);
|
||||
G.profileStartRadius=static_cast<float>(S.profileStartRadius);G.profileEndRadius=static_cast<float>(S.profileEndRadius);
|
||||
G.radiusSlopeStart=static_cast<float>(S.radiusSlopeStart);G.radiusSlopeEnd=static_cast<float>(S.radiusSlopeEnd);
|
||||
G.rounding=static_cast<float>(S.rounding);
|
||||
G.blendRadius=static_cast<float>(S.blendRadius);G.floorBlendRadius=static_cast<float>(S.floorBlendRadius);
|
||||
G.floorCenterZ=static_cast<float>(S.floorCenterZ);G.floorSlopeX=static_cast<float>(S.floorSlopeX);
|
||||
G.floorSlopeY=static_cast<float>(S.floorSlopeY);
|
||||
G.floorCurveOriginX=static_cast<float>(S.floorCurveOriginX);G.floorCurveOriginY=static_cast<float>(S.floorCurveOriginY);
|
||||
G.floorCurveDeltaX=static_cast<float>(S.floorCurveDeltaX);G.floorCurveDeltaY=static_cast<float>(S.floorCurveDeltaY);
|
||||
G.floorCurveLength=static_cast<float>(S.floorCurveLength);
|
||||
G.floorCurveStartZ=static_cast<float>(S.floorCurveStartZ);G.floorCurveEndZ=static_cast<float>(S.floorCurveEndZ);
|
||||
G.floorCurveTangentStart=static_cast<float>(S.floorCurveTangentStart);
|
||||
G.floorCurveTangentEnd=static_cast<float>(S.floorCurveTangentEnd);
|
||||
G.floorCurveStartBlend=static_cast<float>(S.floorCurveStartBlend);
|
||||
G.floorCurveEndBlend=static_cast<float>(S.floorCurveEndBlend);
|
||||
Out.shapes.push_back(G);
|
||||
}
|
||||
for(uint32& Id:Out.sampleShapeIds)
|
||||
{
|
||||
if(Id>=Remap.size()||Remap[Id]==std::numeric_limits<uint32>::max())
|
||||
{Error="far tile shape remap is inconsistent";return false;}
|
||||
Id=Remap[Id];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1706,6 +1789,7 @@ private:
|
||||
|
||||
void RebuildSharedFieldPrimitives()
|
||||
{
|
||||
InvalidateFarFieldGpuInput();
|
||||
SharedFieldPrimitives.clear();SharedFieldPrimitives.reserve(Primitives.size());
|
||||
for(const FPrimitive& P:Primitives)
|
||||
SharedFieldPrimitives.push_back(MakeSharedFieldPrimitive(P));
|
||||
@@ -2572,6 +2656,7 @@ private:
|
||||
|
||||
void BuildSpatialBins(int32 BinResolution,bool bBuildAxisBins=true)
|
||||
{
|
||||
InvalidateFarFieldGpuInput();
|
||||
SpatialBins.clear();
|
||||
CriticalAirBins.clear();
|
||||
ActiveSpatialBinSize=BinResolution;
|
||||
@@ -4402,6 +4487,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void InvalidateFarFieldGpuInput()
|
||||
{
|
||||
std::lock_guard<std::mutex> Lock(FarFieldGpuInputMutex);
|
||||
CachedFarFieldGpuInput.reset();
|
||||
}
|
||||
|
||||
FStandaloneOwnerWorld* Owner=nullptr;
|
||||
int32 WorkerCount=6;
|
||||
const VoxelForgeCore::JobScheduler* Scheduler=nullptr;
|
||||
@@ -4427,6 +4518,8 @@ private:
|
||||
std::array<FAxisBins,3> AxisBins;
|
||||
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
|
||||
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;
|
||||
mutable std::mutex FarFieldGpuInputMutex;
|
||||
mutable std::shared_ptr<const VoxelForgeCore::FarFieldWorldInput> CachedFarFieldGpuInput;
|
||||
std::vector<uint64> RockWords;
|
||||
std::unique_ptr<int16[]> SurfaceDistanceQ;
|
||||
std::array<std::vector<uint64>,3> CandidateAxisWords;
|
||||
|
||||
Reference in new issue
Block a user