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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-06 11:01:03 +02:00
1 parent 782f195bdb
commit 247682a8e0
18 files changed
+1519 -191

No files matched your search

@@ -25,7 +25,19 @@ cbuffer TileConstants : register(b0)
int OriginZ;
int StepVoxels;
float VoxelSizeCm;
float3 ConstantPad;
uint OutputBaseVertex;
uint OutputArgsByteOffset;
uint ConstantPad;
};
cbuffer FarConnectorConstants : register(b2)
{
uint FarConnectorCount;
uint FarConnectorRingCount;
uint FarConnectorVertexCount;
uint FarConnectorPanelVertexCapacity;
uint FarShapeCount;
uint3 FarConnectorPadding;
};
static const int3 CornerOffset[8] = {
@@ -127,6 +139,25 @@ RWStructuredBuffer<uint> CellLocalOffsets : register(u1);
RWStructuredBuffer<uint> GroupVertexCounts : register(u2);
struct FarFieldSampleRange { uint offset; uint count; };
struct FarFieldConnectorPanel
{
float3 start; float radius;
float3 end; float tMin;
float tMax;
uint ringCount;
uint ringOffset;
uint vertexOffset;
uint axialSampleStart;
uint axialSampleCount;
uint2 padding;
};
struct FarConnectorRing
{
float4 corners[4];
float4 normals[4];
float t;
float3 padding;
};
StructuredBuffer<VF_FieldPrimitive> FarShapes : register(t10);
StructuredBuffer<FarFieldSampleRange> FarSampleShapeRanges : register(t11);
StructuredBuffer<uint> FarSampleShapeIds : register(t12);
@@ -134,6 +165,12 @@ StructuredBuffer<uint> FarProtectedAir : register(t13);
StructuredBuffer<uint> FarProtectedSolid : register(t14);
RWStructuredBuffer<float> FarFieldDensity : register(u7);
StructuredBuffer<float> GpuSignDensity : register(t15);
StructuredBuffer<FarFieldConnectorPanel> FarConnectorPanels : register(t8);
StructuredBuffer<FarConnectorRing> FarConnectorRingInput : register(t9);
RWStructuredBuffer<FarConnectorRing> FarConnectorRingOutput : register(u7);
RWByteAddressBuffer FarConnectorDrawArgs : register(u4);
RWByteAddressBuffer FarConnectorVertexBytes : register(u5);
RWByteAddressBuffer FarConnectorIndexBytes : register(u6);
cbuffer FarFieldConstants : register(b1)
{
@@ -196,6 +233,205 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
FarFieldDensity[index] = density;
}
float FarRockDistanceAt(float3 position)
{
float cave = 100000.0f;
float overlay = -100000.0f;
[loop]
for (uint shapeId = 0; shapeId < FarShapeCount; ++shapeId)
{
const VF_FieldPrimitive shape = FarShapes[shapeId];
if ((shape.flags & VF_FIELD_PRIMITIVE_CARVE) != 0)
{
if (position.x < shape.minVoxel[0] - 6 || position.x > shape.maxVoxel[0] + 6
|| position.y < shape.minVoxel[1] - 6 || position.y > shape.maxVoxel[1] + 6
|| position.z < shape.minVoxel[2] - 6 || position.z > shape.maxVoxel[2] + 6)
continue;
float distance = VF_FieldPrimitiveDistance(shape, position.x, position.y, position.z);
if ((shape.flags & VF_FIELD_PRIMITIVE_HAS_FLOOR) != 0)
{
const float floorZ = VF_FieldPrimitiveFloorAt(shape, position.x, position.y);
distance = VF_FieldSmoothMax(distance, floorZ - position.z, shape.floorBlendRadius);
}
cave = VF_FieldSmoothMin(cave, distance, shape.blendRadius);
}
else if (position.x >= shape.minVoxel[0] && position.x <= shape.maxVoxel[0]
&& position.y >= shape.minVoxel[1] && position.y <= shape.maxVoxel[1]
&& position.z >= shape.minVoxel[2] && position.z <= shape.maxVoxel[2])
{
overlay = max(overlay, -VF_FieldPrimitiveDistance(shape,
position.x, position.y, position.z));
}
}
const float base = min(position.z - FarStrataLo, FarStrataHi - position.z);
return VF_FieldSmoothMin(base, max(cave, overlay), FarStrataBlend);
}
float FarConnectorRadius(float3 center, float3 direction, float nominalRadius)
{
// The coarse far field is a signed-distance composition. Two bounded
// distance steps follow its connector wall closely without a CPU readback
// or a long radial scan for each sparse panel ring.
const float maxOffset=max(16.0f,nominalRadius*0.75f);
const float minRadius=max(0.0f,nominalRadius-maxOffset);
const float maxRadius=nominalRadius+maxOffset;
float radius=nominalRadius;
[unroll]
for(uint refine=0;refine<3;++refine)
{
const float distance=FarRockDistanceAt(center+direction*radius);
const float nextDistance=FarRockDistanceAt(center+direction*(radius+4.0f));
const float slope=(nextDistance-distance)*0.25f;
const float correction=abs(slope)>0.125f?distance/slope:distance;
radius=clamp(radius-correction,minRadius,maxRadius);
}
const float residual=FarRockDistanceAt(center+direction*radius);
if(abs(residual)>1.0f)
{
const float searchMin=max(0.0f,nominalRadius-maxOffset-8.0f);
const float searchMax=nominalRadius+maxOffset;
float previous=searchMin;
float previousDistance=FarRockDistanceAt(center+direction*previous);
float bestRadius=radius;
float bestDifference=100000.0f;
[loop]
for(uint sample=1;sample<=32;++sample)
{
const float distance=min(searchMin+sample*8.0f,searchMax);
if(distance<=previous+1.0e-5f)break;
const float currentDistance=FarRockDistanceAt(center+direction*distance);
if((previousDistance>=0.0f)!=(currentDistance>=0.0f))
{
float lo=previous,hi=distance;
const bool previousSolid=previousDistance>=0.0f;
[unroll]
for(uint refine=0;refine<8;++refine)
{
const float mid=(lo+hi)*0.5f;
if((FarRockDistanceAt(center+direction*mid)>=0.0f)==previousSolid)lo=mid;
else hi=mid;
}
const float crossing=(lo+hi)*0.5f;
const float difference=abs(crossing-nominalRadius);
if(difference<=maxOffset&&difference<bestDifference)
{bestRadius=crossing;bestDifference=difference;}
}
previous=distance;previousDistance=currentDistance;
if(distance>=searchMax-1.0e-5f)break;
}
radius=bestRadius;
}
return radius;
}
[numthreads(64,1,1)]
void BuildFarConnectorRingsCS(uint3 dispatchId : SV_DispatchThreadID)
{
const uint ringId = dispatchId.x;
if (ringId == 0)
{
const uint maxVerticesPerTile = CellCount * 15;
const uint slot = maxVerticesPerTile > 0 ? OutputBaseVertex / maxVerticesPerTile : 0;
const uint panelBaseVertex = slot * FarConnectorPanelVertexCapacity;
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 20, FarConnectorVertexCount);
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 24, FarConnectorVertexCount > 0 ? 1 : 0);
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 28, panelBaseVertex);
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 32, 0);
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 36, 0);
}
if (ringId >= FarConnectorRingCount) return;
FarFieldConnectorPanel connector = FarConnectorPanels[0];
[loop]
for (uint i = 0; i < FarConnectorCount; ++i)
{
const FarFieldConnectorPanel candidate = FarConnectorPanels[i];
if (ringId >= candidate.ringOffset && ringId < candidate.ringOffset + candidate.ringCount)
{
connector = candidate;
break;
}
}
const float3 start = connector.start;
const float3 delta = connector.end - start;
const float connectorLength = length(delta);
const float3 axis = delta / max(connectorLength, 1.0e-8f);
float3 u = cross(axis, float3(0,0,1));
if (dot(u,u) < 0.01f) u = cross(axis, float3(0,1,0));
u = normalize(u);
const float3 v = normalize(cross(axis,u));
const uint localRing = ringId - connector.ringOffset;
float t;
if (localRing == 0) t = connector.tMin;
else if (localRing + 1 == connector.ringCount) t = connector.tMax;
else t = ((float)(connector.axialSampleStart + localRing - 1) + 0.5f)
/ max(1.0f, (float)connector.axialSampleCount);
const float3 center = start + delta * t;
const float ru = FarConnectorRadius(center,u,connector.radius);
const float rmu = FarConnectorRadius(center,-u,connector.radius);
const float rv = FarConnectorRadius(center,v,connector.radius);
const float rmv = FarConnectorRadius(center,-v,connector.radius);
FarConnectorRing ring;
ring.corners[0] = float4(center + u*ru + v*rv, 0);
ring.corners[1] = float4(center - u*rmu + v*rv, 0);
ring.corners[2] = float4(center - u*rmu - v*rmv, 0);
ring.corners[3] = float4(center + u*ru - v*rmv, 0);
ring.normals[0] = float4(-normalize(u+v),0);
ring.normals[1] = float4(-normalize(-u+v),0);
ring.normals[2] = float4(-normalize(-u-v),0);
ring.normals[3] = float4(-normalize(u-v),0);
ring.t = t;
ring.padding = 0;
FarConnectorRingOutput[ringId] = ring;
}
[numthreads(64,1,1)]
void EmitFarConnectorPanelsCS(uint3 dispatchId : SV_DispatchThreadID)
{
const uint vertexId = dispatchId.x;
if (vertexId >= FarConnectorVertexCount) return;
FarFieldConnectorPanel connector = FarConnectorPanels[0];
[loop]
for (uint i = 0; i < FarConnectorCount; ++i)
{
const FarFieldConnectorPanel candidate = FarConnectorPanels[i];
const uint candidateCount = (candidate.ringCount - 1) * 24;
if (vertexId >= candidate.vertexOffset && vertexId < candidate.vertexOffset + candidateCount)
{
connector = candidate;
break;
}
}
const uint localVertex = vertexId - connector.vertexOffset;
const uint interval = localVertex / 24;
const uint cornerInQuad = localVertex % 6;
const uint side = (localVertex / 6) % 4;
const uint nextSide = (side + 1) & 3;
const uint ringA = connector.ringOffset + interval;
const uint ringB = ringA + 1;
uint ringIndex, cornerIndex;
if (cornerInQuad == 0) { ringIndex=ringA; cornerIndex=side; }
else if (cornerInQuad == 1) { ringIndex=ringB; cornerIndex=side; }
else if (cornerInQuad == 2) { ringIndex=ringA; cornerIndex=nextSide; }
else if (cornerInQuad == 3) { ringIndex=ringA; cornerIndex=nextSide; }
else if (cornerInQuad == 4) { ringIndex=ringB; cornerIndex=side; }
else { ringIndex=ringB; cornerIndex=nextSide; }
const FarConnectorRing ring = FarConnectorRingInput[ringIndex];
GpuVertex vertex;
vertex.position = ring.corners[cornerIndex].xyz * VoxelSizeCm;
vertex.normal = ring.normals[cornerIndex].xyz;
const float connectorLength = length(connector.end - connector.start);
vertex.uv = float2(ring.t * connectorLength / 256.0f, (float)side / 4.0f);
const uint maxVerticesPerTile = CellCount * 15;
const uint slot = maxVerticesPerTile > 0 ? OutputBaseVertex / maxVerticesPerTile : 0;
const uint base = slot * FarConnectorPanelVertexCapacity;
const uint byteAddress = (base + vertexId) * 32;
FarConnectorVertexBytes.Store3(byteAddress, asuint(vertex.position));
FarConnectorVertexBytes.Store3(byteAddress + 12, asuint(vertex.normal));
FarConnectorVertexBytes.Store2(byteAddress + 24, asuint(vertex.uv));
FarConnectorIndexBytes.Store((base + vertexId) * 4, base + vertexId);
}
groupshared uint ScanValues[256];
void ClassifyAndScan(uint index, uint3 groupId, uint3 groupThreadId, uint caseIndex)
@@ -283,11 +519,16 @@ void ScanGroupsCS(uint3 dispatchId : SV_DispatchThreadID)
GroupOffsets[i] = offset;
offset += InputGroupVertexCounts[i];
}
DrawArgs.Store(0, offset);
DrawArgs.Store(4, 1);
DrawArgs.Store(8, 0);
DrawArgs.Store(12, 0);
DrawArgs.Store(16, 0);
DrawArgs.Store(OutputArgsByteOffset + 0, offset);
DrawArgs.Store(OutputArgsByteOffset + 4, 1);
DrawArgs.Store(OutputArgsByteOffset + 8, OutputBaseVertex);
DrawArgs.Store(OutputArgsByteOffset + 12, 0);
DrawArgs.Store(OutputArgsByteOffset + 16, 0);
DrawArgs.Store(OutputArgsByteOffset + 20, 0);
DrawArgs.Store(OutputArgsByteOffset + 24, 0);
DrawArgs.Store(OutputArgsByteOffset + 28, 0);
DrawArgs.Store(OutputArgsByteOffset + 32, 0);
DrawArgs.Store(OutputArgsByteOffset + 36, 0);
}
StructuredBuffer<float> Density : register(t2);
@@ -306,7 +547,7 @@ void EmitCS(uint3 dispatchId : SV_DispatchThreadID)
const uint index = dispatchId.x;
if (index >= CellCount) return;
const uint caseIndex = Cases[index];
const uint base = LocalOffsets[index] + PrefixGroups[index / 256];
const uint base = OutputBaseVertex + LocalOffsets[index] + PrefixGroups[index / 256];
const uint3 cell = uint3(index % CellsPerAxis,
(index / CellsPerAxis) % CellsPerAxis,
index / (CellsPerAxis * CellsPerAxis));