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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-06 13:50:07 +02:00
1 parent 247682a8e0
commit 0e730cdbba
9 files changed
+697 -260

No files matched your search

@@ -138,7 +138,13 @@ RWStructuredBuffer<uint> CellCases : register(u0);
RWStructuredBuffer<uint> CellLocalOffsets : register(u1);
RWStructuredBuffer<uint> GroupVertexCounts : register(u2);
struct FarFieldSampleRange { uint offset; uint count; };
struct FarFieldSpatialBin
{
int3 coord;
uint offset;
uint count;
uint padding;
};
struct FarFieldConnectorPanel
{
float3 start; float radius;
@@ -159,8 +165,8 @@ struct FarConnectorRing
float3 padding;
};
StructuredBuffer<VF_FieldPrimitive> FarShapes : register(t10);
StructuredBuffer<FarFieldSampleRange> FarSampleShapeRanges : register(t11);
StructuredBuffer<uint> FarSampleShapeIds : register(t12);
StructuredBuffer<FarFieldSpatialBin> FarSpatialBins : register(t11);
StructuredBuffer<uint> FarSpatialBinShapeIds : register(t12);
StructuredBuffer<uint> FarProtectedAir : register(t13);
StructuredBuffer<uint> FarProtectedSolid : register(t14);
RWStructuredBuffer<float> FarFieldDensity : register(u7);
@@ -178,8 +184,45 @@ cbuffer FarFieldConstants : register(b1)
float FarStrataHi;
float FarStrataBlend;
uint FarSampleCount;
uint FarEvalCounterEnabled;
uint FarSpatialBinMask;
uint FarSpatialBinSize;
uint FarSpatialBinCapacity;
};
RWStructuredBuffer<uint> FarFieldEvalCounters : register(u6);
uint FarFieldBinHash(int3 coord)
{
uint hash = 2166136261u;
hash = (hash ^ asuint(coord.x)) * 16777619u;
hash = (hash ^ asuint(coord.y)) * 16777619u;
hash = (hash ^ asuint(coord.z)) * 16777619u;
hash ^= hash >> 16; hash *= 0x7feb352du; hash ^= hash >> 15;
hash *= 0x846ca68bu; hash ^= hash >> 16;
return hash;
}
FarFieldSpatialBin FarFieldFindBin(float3 position)
{
FarFieldSpatialBin emptyBin;
emptyBin.coord = int3(0,0,0);
emptyBin.offset = 0;
emptyBin.count = 0;
emptyBin.padding = 0;
const int3 coord = int3(floor(position / max(1.0f, (float)FarSpatialBinSize)));
uint slot = FarFieldBinHash(coord) & FarSpatialBinMask;
[loop]
for (uint probe = 0; probe < FarSpatialBinCapacity; ++probe)
{
const FarFieldSpatialBin bin = FarSpatialBins[slot];
if (bin.count == 0) return emptyBin;
if (all(bin.coord == coord)) return bin;
slot = (slot + 1) & FarSpatialBinMask;
}
return emptyBin;
}
[numthreads(64,1,1)]
void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
{
@@ -190,13 +233,14 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
const uint z = index / (SampleDim * SampleDim);
const float3 position = float3(OriginX, OriginY, OriginZ)
+ float3(int(x) - 1, int(y) - 1, int(z) - 1) * StepVoxels;
const FarFieldSampleRange range = FarSampleShapeRanges[index];
const FarFieldSpatialBin range = FarFieldFindBin(position);
float cave = 100000.0f;
float overlay = -100000.0f;
uint distanceEvaluations = 0;
[loop]
for (uint item = 0; item < range.count; ++item)
{
const VF_FieldPrimitive shape = FarShapes[FarSampleShapeIds[range.offset + item]];
const VF_FieldPrimitive shape = FarShapes[FarSpatialBinShapeIds[range.offset + item]];
if ((shape.flags & VF_FIELD_PRIMITIVE_CARVE) != 0)
{
if (position.x < shape.minVoxel[0] - 6 || position.x > shape.maxVoxel[0] + 6
@@ -205,6 +249,7 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
continue;
float distance = VF_FieldPrimitiveDistance(shape,
position.x, position.y, position.z);
++distanceEvaluations;
if ((shape.flags & VF_FIELD_PRIMITIVE_HAS_FLOOR) != 0)
{
const float floorZ = VF_FieldPrimitiveFloorAt(shape, position.x, position.y);
@@ -219,6 +264,7 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
{
overlay = max(overlay, -VF_FieldPrimitiveDistance(shape,
position.x, position.y, position.z));
++distanceEvaluations;
}
}
const float base = min(position.z - FarStrataLo, FarStrataHi - position.z);
@@ -231,16 +277,21 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
if (protectedAir != 0) density = magnitude;
else if (protectedSolid != 0) density = -magnitude;
FarFieldDensity[index] = density;
if (FarEvalCounterEnabled != 0) FarFieldEvalCounters[index] = distanceEvaluations;
}
float FarRockDistanceAt(float3 position)
float FarRockDistanceAt(float3 position,int3 cachedBinCoord,FarFieldSpatialBin cachedRange)
{
float cave = 100000.0f;
float overlay = -100000.0f;
const int3 positionBinCoord=int3(floor(position/max(1.0f,(float)FarSpatialBinSize)));
FarFieldSpatialBin range;
if(all(positionBinCoord==cachedBinCoord))range=cachedRange;
else range=FarFieldFindBin(position);
[loop]
for (uint shapeId = 0; shapeId < FarShapeCount; ++shapeId)
for (uint item = 0; item < range.count; ++item)
{
const VF_FieldPrimitive shape = FarShapes[shapeId];
const VF_FieldPrimitive shape = FarShapes[FarSpatialBinShapeIds[range.offset + item]];
if ((shape.flags & VF_FIELD_PRIMITIVE_CARVE) != 0)
{
if (position.x < shape.minVoxel[0] - 6 || position.x > shape.maxVoxel[0] + 6
@@ -269,29 +320,43 @@ float FarRockDistanceAt(float3 position)
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.
// Keep the bounded signed-distance refinement, but stop once the residual
// reaches the same acceptance bound used by the original fallback. A
// spatial-bin cache avoids re-hashing nearby probes.
const float3 nominalPosition=center+direction*nominalRadius;
const int3 cachedBinCoord=int3(floor(nominalPosition/max(1.0f,(float)FarSpatialBinSize)));
const FarFieldSpatialBin cachedRange=FarFieldFindBin(nominalPosition);
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)
float residual=FarRockDistanceAt(center+direction*radius,cachedBinCoord,cachedRange);
bool converged=abs(residual)<=1.0f;
if(!converged)
{
const float distance=FarRockDistanceAt(center+direction*radius);
const float nextDistance=FarRockDistanceAt(center+direction*(radius+4.0f));
// Most connector fields are close to a unit-gradient SDF along the
// radial ray. Try that correction first and verify it before accepting.
radius=clamp(radius-residual,minRadius,maxRadius);
residual=FarRockDistanceAt(center+direction*radius,cachedBinCoord,cachedRange);
converged=abs(residual)<=1.0f;
}
[unroll]
for(uint refine=0;refine<2&&!converged;++refine)
{
const float distance=residual;
const float nextDistance=FarRockDistanceAt(center+direction*(radius+4.0f),cachedBinCoord,cachedRange);
const float slope=(nextDistance-distance)*0.25f;
const float correction=abs(slope)>0.125f?distance/slope:distance;
radius=clamp(radius-correction,minRadius,maxRadius);
residual=FarRockDistanceAt(center+direction*radius,cachedBinCoord,cachedRange);
converged=abs(residual)<=1.0f;
}
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 previousDistance=FarRockDistanceAt(center+direction*previous,cachedBinCoord,cachedRange);
float bestRadius=radius;
float bestDifference=100000.0f;
[loop]
@@ -299,7 +364,7 @@ float FarConnectorRadius(float3 center, float3 direction, float nominalRadius)
{
const float distance=min(searchMin+sample*8.0f,searchMax);
if(distance<=previous+1.0e-5f)break;
const float currentDistance=FarRockDistanceAt(center+direction*distance);
const float currentDistance=FarRockDistanceAt(center+direction*distance,cachedBinCoord,cachedRange);
if((previousDistance>=0.0f)!=(currentDistance>=0.0f))
{
float lo=previous,hi=distance;
@@ -308,7 +373,7 @@ float FarConnectorRadius(float3 center, float3 direction, float nominalRadius)
for(uint refine=0;refine<8;++refine)
{
const float mid=(lo+hi)*0.5f;
if((FarRockDistanceAt(center+direction*mid)>=0.0f)==previousSolid)lo=mid;
if((FarRockDistanceAt(center+direction*mid,cachedBinCoord,cachedRange)>=0.0f)==previousSolid)lo=mid;
else hi=mid;
}
const float crossing=(lo+hi)*0.5f;
@@ -324,65 +389,136 @@ float FarConnectorRadius(float3 center, float3 direction, float nominalRadius)
return radius;
}
[numthreads(64,1,1)]
void BuildFarConnectorRingsCS(uint3 dispatchId : SV_DispatchThreadID)
uint FarConnectorPanelForRing(uint ringId)
{
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];
uint low = 0;
uint high = FarConnectorCount;
[loop]
for (uint i = 0; i < FarConnectorCount; ++i)
while (low < high)
{
const FarFieldConnectorPanel candidate = FarConnectorPanels[i];
if (ringId >= candidate.ringOffset && ringId < candidate.ringOffset + candidate.ringCount)
const uint middle = low + ((high - low) >> 1);
const FarFieldConnectorPanel candidate = FarConnectorPanels[middle];
if (ringId < candidate.ringOffset) high = middle;
else if (ringId >= candidate.ringOffset + candidate.ringCount) low = middle + 1;
else return middle;
}
return 0;
}
uint FarConnectorPanelForVertex(uint vertexId)
{
uint low = 0;
uint high = FarConnectorCount;
[loop]
while (low < high)
{
const uint middle = low + ((high - low) >> 1);
const FarFieldConnectorPanel candidate = FarConnectorPanels[middle];
const uint candidateVertexCount = (candidate.ringCount - 1) * 24;
if (vertexId < candidate.vertexOffset) high = middle;
else if (vertexId >= candidate.vertexOffset + candidateVertexCount) low = middle + 1;
else return middle;
}
return 0;
}
groupshared float FarConnectorGroupRadii[64];
groupshared float4 FarConnectorGroupCenterT[16];
groupshared float4 FarConnectorGroupURadius[16];
groupshared float4 FarConnectorGroupV[16];
[numthreads(64,1,1)]
void BuildFarConnectorRingsCS(uint3 groupId : SV_GroupID,
uint3 groupThreadId : SV_GroupThreadID)
{
// Four lanes cooperate on one ring. Lane zero loads the panel and builds
// its shared frame once; each lane then refines one radial direction.
// One 64-thread group is one AMD wave, keeping radial work local.
const uint ringId = groupId.x * 16 + groupThreadId.x / 4;
const uint radialId = groupThreadId.x & 3;
const uint ringSlot = groupThreadId.x / 4;
if (radialId == 0)
{
const bool validRing = ringId < FarConnectorRingCount;
if (validRing)
{
connector = candidate;
break;
const FarFieldConnectorPanel connector = FarConnectorPanels[FarConnectorPanelForRing(ringId)];
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 = 0;
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;
FarConnectorGroupCenterT[ringSlot] = float4(center,t);
FarConnectorGroupURadius[ringSlot] = float4(u,connector.radius);
FarConnectorGroupV[ringSlot] = float4(v,0);
}
else
{
FarConnectorGroupCenterT[ringSlot] = 0;
FarConnectorGroupURadius[ringSlot] = 0;
FarConnectorGroupV[ringSlot] = 0;
}
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);
}
}
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;
GroupMemoryBarrierWithGroupSync();
const bool validRing = ringId < FarConnectorRingCount;
const float4 centerT = FarConnectorGroupCenterT[ringSlot];
const float4 uRadius = FarConnectorGroupURadius[ringSlot];
const float3 center = centerT.xyz;
const float3 u = uRadius.xyz;
const float3 v = FarConnectorGroupV[ringSlot].xyz;
const float3 direction = radialId == 0 ? u : radialId == 1 ? -u : radialId == 2 ? v : -v;
const float radius = validRing ? FarConnectorRadius(center,direction,uRadius.w) : 0;
FarConnectorGroupRadii[groupThreadId.x] = radius;
GroupMemoryBarrierWithGroupSync();
if (!validRing) return;
if (radialId == 0) FarConnectorRingOutput[ringId].t = centerT.w;
const uint ringBase = groupThreadId.x - radialId;
const float ru = FarConnectorGroupRadii[ringBase + 0];
const float rmu = FarConnectorGroupRadii[ringBase + 1];
const float rv = FarConnectorGroupRadii[ringBase + 2];
const float rmv = FarConnectorGroupRadii[ringBase + 3];
if (radialId == 0)
{
FarConnectorRingOutput[ringId].corners[0] = float4(center + u*ru + v*rv, 0);
FarConnectorRingOutput[ringId].normals[0] = float4(-normalize(u+v),0);
}
else if (radialId == 1)
{
FarConnectorRingOutput[ringId].corners[1] = float4(center - u*rmu + v*rv, 0);
FarConnectorRingOutput[ringId].normals[1] = float4(-normalize(-u+v),0);
}
else if (radialId == 2)
{
FarConnectorRingOutput[ringId].corners[2] = float4(center - u*rmu - v*rmv, 0);
FarConnectorRingOutput[ringId].normals[2] = float4(-normalize(-u-v),0);
}
else
{
FarConnectorRingOutput[ringId].corners[3] = float4(center + u*ru - v*rmv, 0);
FarConnectorRingOutput[ringId].normals[3] = float4(-normalize(u-v),0);
}
}
[numthreads(64,1,1)]
@@ -390,18 +526,7 @@ 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 FarFieldConnectorPanel connector = FarConnectorPanels[FarConnectorPanelForVertex(vertexId)];
const uint localVertex = vertexId - connector.vertexOffset;
const uint interval = localVertex / 24;
const uint cornerInQuad = localVertex % 6;