wip(standalone): GPU step 4 -- batched far generation, connector panels on GPU; far survival passes
Up to 32 tiles per deferred command list, one polled event per batch, no per-tile waits; CPU upload/enqueue 723 -> 46 ms. GPU far meshes pass far survival (7,200 strata boundaries, 10,976 connectors, 0 missing/holes). But hybrid cold 4.36 s vs CPU 2.29 s: whole-tile GPU timestamps sum to 646 ms (0.58 ms/tile; step 3's 0.03 ms covered part of the work only), divergent per-thread shape loops; CPU far-input export 1.6 worker-s. CPU 44/44 hashes unchanged. 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
782f195bdb
commit
247682a8e0
18 files changed
+1519
-191
No files matched your search
@@ -205,6 +205,20 @@ namespace VoxelForgeCore
|
||||
float z = 0.0f, halfWidth = 8.0f;
|
||||
};
|
||||
|
||||
// Sparse connector repair panels for the GPU far renderer. Each record describes
|
||||
// the tile-clipped axial interval and output ranges; signs and source radii are
|
||||
// evaluated by the GPU from the far shape field.
|
||||
struct FarFieldConnectorPanel
|
||||
{
|
||||
float start[3]{}, radius = 0.0f;
|
||||
float end[3]{}, tMin = 0.0f;
|
||||
float tMax = 1.0f;
|
||||
std::uint32_t ringCount = 0, ringOffset = 0, vertexOffset = 0;
|
||||
std::uint32_t axialSampleStart = 0, axialSampleCount = 0;
|
||||
std::uint32_t padding[2]{};
|
||||
};
|
||||
static_assert(sizeof(FarFieldConnectorPanel) == 64);
|
||||
|
||||
struct FarFieldTileInput
|
||||
{
|
||||
Vec3i origin{};
|
||||
@@ -222,6 +236,8 @@ namespace VoxelForgeCore
|
||||
std::vector<std::uint32_t> protectedSolid;
|
||||
std::vector<std::uint32_t> protectedThin;
|
||||
std::vector<FarFieldStratumBoundary> stratumBoundaries;
|
||||
std::vector<FarFieldConnectorPanel> connectorPanels;
|
||||
std::uint32_t connectorRingCount = 0, connectorVertexCount = 0;
|
||||
std::uint64_t protectedSampleCount = 0;
|
||||
std::uint64_t protectedSolidSampleCount = 0;
|
||||
std::uint64_t protectedSealSampleCount = 0;
|
||||
|
||||
@@ -471,7 +471,6 @@ 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<std::vector<uint32>> PerSample(SampleCount);
|
||||
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;
|
||||
if(LayerRanges.empty())
|
||||
@@ -505,6 +504,76 @@ public:
|
||||
Out.protectedSolid.assign((SampleCount+31u)/32u,0u);
|
||||
Out.protectedThin.assign((SampleCount+31u)/32u,0u);
|
||||
|
||||
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
|
||||
// sufficient resolution and the CPU path emits no panel geometry.
|
||||
if(Cells*Step>=(CHUNK_SIZE<<6))
|
||||
{
|
||||
std::vector<uint32> ConnectorCandidates;
|
||||
FindFarConnectorCandidates(
|
||||
FIntVector(Origin.X-64,Origin.Y-64,Origin.Z-64),
|
||||
FIntVector(Origin.X+Cells*Step+64,Origin.Y+Cells*Step+64,
|
||||
Origin.Z+Cells*Step+64),ConnectorCandidates);
|
||||
const double TileMin[3]={static_cast<double>(Origin.X),static_cast<double>(Origin.Y),
|
||||
static_cast<double>(Origin.Z)};
|
||||
const double TileMax[3]={TileMin[0]+Cells*Step,TileMin[1]+Cells*Step,
|
||||
TileMin[2]+Cells*Step};
|
||||
for(const uint32 ConnectorIndex:ConnectorCandidates)
|
||||
{
|
||||
if(ConnectorIndex>=Connectors.size())continue;
|
||||
const FFarConnectorSegment& Connector=Connectors[ConnectorIndex];
|
||||
const double Start[3]={Connector.Start.X,Connector.Start.Y,Connector.Start.Z};
|
||||
const double Delta[3]={Connector.End.X-Connector.Start.X,
|
||||
Connector.End.Y-Connector.Start.Y,Connector.End.Z-Connector.Start.Z};
|
||||
double TMin=0.0,TMax=1.0;bool Intersects=true;
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
if(std::abs(Delta[Axis])<1.0e-9)
|
||||
{if(Start[Axis]<TileMin[Axis]||Start[Axis]>TileMax[Axis])Intersects=false;continue;}
|
||||
double A=(TileMin[Axis]-Start[Axis])/Delta[Axis];
|
||||
double B=(TileMax[Axis]-Start[Axis])/Delta[Axis];
|
||||
if(A>B)std::swap(A,B);
|
||||
TMin=std::max(TMin,A);TMax=std::min(TMax,B);
|
||||
if(TMin>TMax){Intersects=false;break;}
|
||||
}
|
||||
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);
|
||||
const uint32 FirstSample=static_cast<uint32>(std::clamp(FirstValue,0.0,
|
||||
static_cast<double>(AxialSampleCount)));
|
||||
const uint32 SampleCount=LastValue>=FirstValue?static_cast<uint32>(std::max(0.0,
|
||||
std::min(LastValue,static_cast<double>(AxialSampleCount-1))-FirstValue+1.0)):0;
|
||||
const uint32 RingCount=SampleCount+2;
|
||||
const uint64 VertexCount=static_cast<uint64>(RingCount-1)*24u;
|
||||
if(Out.connectorVertexCount+VertexCount>std::numeric_limits<uint32>::max())
|
||||
{Error="far connector panel output exceeds the 32-bit tile limit";return false;}
|
||||
VoxelForgeCore::FarFieldConnectorPanel Panel{};
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{Panel.start[Axis]=static_cast<float>(Start[Axis]);Panel.end[Axis]=static_cast<float>(Start[Axis]+Delta[Axis]);}
|
||||
Panel.radius=Connector.Radius;Panel.tMin=static_cast<float>(TMin);Panel.tMax=static_cast<float>(TMax);
|
||||
Panel.ringCount=RingCount;Panel.ringOffset=Out.connectorRingCount;
|
||||
Panel.vertexOffset=Out.connectorVertexCount;
|
||||
Panel.axialSampleStart=FirstSample;Panel.axialSampleCount=AxialSampleCount;
|
||||
Out.connectorRingCount+=Panel.ringCount;Out.connectorVertexCount+=static_cast<uint32>(VertexCount);
|
||||
Out.connectorPanels.push_back(Panel);
|
||||
}
|
||||
}
|
||||
|
||||
size_t SampleIndex=0;
|
||||
for(int32 Z=-1;Z<=Cells+1;++Z)
|
||||
for(int32 Y=-1;Y<=Cells+1;++Y)
|
||||
@@ -561,9 +630,17 @@ public:
|
||||
}
|
||||
}
|
||||
const auto Found=SpatialBins.find(PointBin(WX,WY,WZ));
|
||||
if(Found==SpatialBins.end())continue;
|
||||
auto& Ids=PerSample[SampleIndex];
|
||||
Ids.assign(Found->second.begin(),Found->second.end());
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -607,22 +684,11 @@ public:
|
||||
G.floorCurveEndBlend=static_cast<float>(S.floorCurveEndBlend);
|
||||
Out.shapes.push_back(G);
|
||||
}
|
||||
uint64 CandidateCount=0;
|
||||
for(const auto& Ids:PerSample)CandidateCount+=Ids.size();
|
||||
if(CandidateCount>std::numeric_limits<uint32>::max())
|
||||
{Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;}
|
||||
Out.sampleShapeIds.reserve(static_cast<size_t>(CandidateCount));
|
||||
for(size_t I=0;I<PerSample.size();++I)
|
||||
for(uint32& Id:Out.sampleShapeIds)
|
||||
{
|
||||
const auto& Ids=PerSample[I];
|
||||
Out.sampleShapeRanges[I]={static_cast<uint32>(Out.sampleShapeIds.size()),
|
||||
static_cast<uint32>(Ids.size())};
|
||||
for(const uint32 Id:Ids)
|
||||
{
|
||||
if(Id>=Remap.size()||Remap[Id]==std::numeric_limits<uint32>::max())
|
||||
{Error="far tile shape remap is inconsistent";return false;}
|
||||
Out.sampleShapeIds.push_back(Remap[Id]);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user