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
@@ -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));
|
||||
|
||||
Reference in new issue
Block a user