wip(standalone): GPU step 13 -- near base/skirts/connectors cheaper to compile; warm-up still ~2.0 s
Cold first use: near base 4.2 ms, near detail 1,595 ms (the floor), connector rings 630 -> 409, skirt classify+emit ~1,409 -> 366. Production warm-up is still ~2.03-2.06 s; whole view median CPU 1.356 s vs hybrid 3.044 s. Near 960 + far 1,114 tiles: 0 topology/protected/thin diffs vs CPU and WARP; hashes, walk 5,642/5,642, survival pass. 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
05b1523c54
commit
a96fd0b95b
18 files changed
+776
-75
No files matched your search
@@ -334,7 +334,33 @@ float NearOwnerRoughness(float3 position,float surfaceSdf,float caveInfluence,
|
||||
return (anchoredWeight*anchored+directWeight*direct)*normalization;
|
||||
}
|
||||
|
||||
float NearFieldDensityAt(float3 position,uint clampNearZero,out uint distanceEvaluations)
|
||||
void NearDetailValueNoise(float3 position,out float macro,out float mid,out float fine)
|
||||
{
|
||||
macro=0.0f;mid=0.0f;fine=0.0f;
|
||||
[loop]
|
||||
for(uint octave=0;octave<3;++octave)
|
||||
{
|
||||
const int wavelength=octave==0?32:(octave==1?16:4);
|
||||
const uint seedOffset=octave==0?0xB5297A4Du:
|
||||
(octave==1?0x63D83595u:0xA511E9B3u);
|
||||
const float value=VF_FieldValueNoise(position.x,position.y,position.z,
|
||||
wavelength,FarFieldSeed^seedOffset);
|
||||
if(octave==0)macro=value;
|
||||
else if(octave==1)mid=value;
|
||||
else fine=value;
|
||||
}
|
||||
}
|
||||
|
||||
struct NearFieldBaseData
|
||||
{
|
||||
float4 geometry;
|
||||
float4 influences;
|
||||
};
|
||||
|
||||
StructuredBuffer<NearFieldBaseData> FarNearDetailBaseInput : register(t20);
|
||||
RWStructuredBuffer<NearFieldBaseData> FarNearDetailBaseOutput : register(u1);
|
||||
|
||||
NearFieldBaseData NearFieldBaseAt(float3 position,out uint distanceEvaluations)
|
||||
{
|
||||
const FarFieldSpatialBin range=FarFieldFindBin(position);
|
||||
float cave=100000.0f,overlay=-100000.0f;
|
||||
@@ -383,18 +409,27 @@ float NearFieldDensityAt(float3 position,uint clampNearZero,out uint distanceEva
|
||||
const float floorInfluence=caveInfluence*caveFloorInfluence;
|
||||
const float passageInfluence=caveInfluence*cavePassageInfluence;
|
||||
const float surfaceSdf=base<=carveOrOverlay?base:(cave>=overlay?cave:overlay);
|
||||
float rockDistance=VF_FieldSmoothMin(base,carveOrOverlay,FarStrataBlend);
|
||||
const float rockDistance=VF_FieldSmoothMin(base,carveOrOverlay,FarStrataBlend);
|
||||
NearFieldBaseData result;
|
||||
result.geometry=float4(rockDistance,surfaceSdf,caveInfluence,floorInfluence);
|
||||
result.influences=float4(passageInfluence,0,0,0);
|
||||
return result;
|
||||
}
|
||||
|
||||
float NearFieldDetailRockDistance(float3 position,NearFieldBaseData baseData)
|
||||
{
|
||||
float rockDistance=baseData.geometry.x;
|
||||
const float surfaceSdf=baseData.geometry.y;
|
||||
const float caveInfluence=baseData.geometry.z;
|
||||
const float floorInfluence=baseData.geometry.w;
|
||||
const float passageInfluence=baseData.influences.x;
|
||||
if(NearFieldMode!=0
|
||||
&&all(position>=float3(FarDetailMinVoxel.xyz))
|
||||
&&all(position<=float3(FarDetailMaxVoxel.xyz))
|
||||
&&abs(rockDistance)<=4.125f)
|
||||
{
|
||||
const float macro=VF_FieldValueNoise(position.x,position.y,position.z,32,
|
||||
FarFieldSeed^0xB5297A4Du);
|
||||
const float mid=VF_FieldValueNoise(position.x,position.y,position.z,16,
|
||||
FarFieldSeed^0x63D83595u);
|
||||
const float fine=VF_FieldValueNoise(position.x,position.y,position.z,4,
|
||||
FarFieldSeed^0xA511E9B3u);
|
||||
float macro,mid,fine;
|
||||
NearDetailValueNoise(position,macro,mid,fine);
|
||||
const float relief=1.20f*macro+(0.80f+0.25f*macro)*(0.40f*mid+0.60f*fine);
|
||||
const float passageBandRelief=1.20f*macro+(0.80f+0.25f*macro)*(0.40f*mid);
|
||||
float ownerRelief=0.0f;
|
||||
@@ -446,6 +481,13 @@ float NearFieldDensityAt(float3 position,uint clampNearZero,out uint distanceEva
|
||||
}
|
||||
rockDistance+=clamp(detail,-3.9f,3.9f)*fade;
|
||||
}
|
||||
return rockDistance;
|
||||
}
|
||||
|
||||
float NearFieldDensityAt(float3 position,uint clampNearZero,out uint distanceEvaluations)
|
||||
{
|
||||
const NearFieldBaseData baseData=NearFieldBaseAt(position,distanceEvaluations);
|
||||
const float rockDistance=NearFieldDetailRockDistance(position,baseData);
|
||||
float density=-rockDistance;
|
||||
if(clampNearZero!=0&&abs(density)<0.05f)
|
||||
density=rockDistance>=0.0f?-0.05f:0.05f;
|
||||
@@ -517,7 +559,21 @@ void EvaluateNearFieldCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||
const float3 position=float3(OriginX,OriginY,OriginZ)
|
||||
+float3(int(x)-1,int(y)-1,int(z)-1)*StepVoxels;
|
||||
uint distanceEvaluations=0;
|
||||
const float nearNormalDensity=NearFieldDensityAt(position,0,distanceEvaluations);
|
||||
FarNearDetailBaseOutput[index]=NearFieldBaseAt(position,distanceEvaluations);
|
||||
if(FarEvalCounterEnabled!=0)FarFieldEvalCounters[index]=distanceEvaluations;
|
||||
}
|
||||
|
||||
[numthreads(64,1,1)]
|
||||
void ApplyNearFieldDetailCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||
{
|
||||
const uint index=dispatchId.x;
|
||||
if(index>=FarSampleCount)return;
|
||||
const uint x=index%SampleDim;
|
||||
const uint y=(index/SampleDim)%SampleDim;
|
||||
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 float nearNormalDensity=-NearFieldDetailRockDistance(position,FarNearDetailBaseInput[index]);
|
||||
float density=nearNormalDensity;
|
||||
if(abs(density)<0.05f)
|
||||
density=density<0.0f?-0.05f:(density>0.0f?0.05f:-0.05f);
|
||||
@@ -540,7 +596,6 @@ void EvaluateNearFieldCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||
else if(protectedSolid!=0)density=-max(abs(density),1.0f/256.0f);
|
||||
FarFieldDensity[index]=density;
|
||||
FarNearNormalDensityOutput[index]=nearNormalDensity;
|
||||
if(FarEvalCounterEnabled!=0)FarFieldEvalCounters[index]=distanceEvaluations;
|
||||
}
|
||||
|
||||
float FarRockDistanceAt(float3 position,int3 cachedBinCoord,FarFieldSpatialBin cachedRange)
|
||||
@@ -610,6 +665,16 @@ struct FarSkirtVertex
|
||||
float density;
|
||||
};
|
||||
|
||||
struct FarSkirtCachedSamples
|
||||
{
|
||||
float2 endpointDensities;
|
||||
float4 centerDensities;
|
||||
uint4 metadata;
|
||||
};
|
||||
|
||||
StructuredBuffer<FarSkirtCachedSamples> FarSkirtCachedProbeInput : register(t21);
|
||||
RWStructuredBuffer<FarSkirtCachedSamples> FarSkirtCachedProbeOutput : register(u2);
|
||||
|
||||
float FarSkirtDensityAtVoxel(float3 position)
|
||||
{
|
||||
const int3 binCoord=int3(floor(position/max(1.0f,(float)FarSpatialBinSize)));
|
||||
@@ -635,7 +700,9 @@ FarSkirtVertex FarSkirtInterpolate(FarSkirtVertex a,FarSkirtVertex b,float t)
|
||||
FarSkirtVertex result;
|
||||
result.position=lerp(a.position,b.position,t);
|
||||
result.normal=normalize(lerp(a.normal,b.normal,t));
|
||||
result.density=FarSkirtDensityAtCm(result.position);
|
||||
// Clipping only uses the interpolated position and normal after insertion.
|
||||
// Its density is never fed back into an edge decision or emitted vertex.
|
||||
result.density=lerp(a.density,b.density,t);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -662,7 +729,8 @@ void FarSkirtClipTriangle(FarSkirtVertex a,FarSkirtVertex b,FarSkirtVertex c,
|
||||
}
|
||||
}
|
||||
|
||||
uint FarSkirtSourceTriangleOutputCount(FarSkirtVertex a,FarSkirtVertex b,FarSkirtVertex c)
|
||||
uint FarSkirtSourceTriangleOutputCount(FarSkirtVertex a,FarSkirtVertex b,FarSkirtVertex c,
|
||||
inout FarSkirtCachedSamples cached)
|
||||
{
|
||||
FarSkirtVertex polygon[5];uint polygonCount=0;
|
||||
FarSkirtClipTriangle(a,b,c,polygon,polygonCount);
|
||||
@@ -673,7 +741,13 @@ uint FarSkirtSourceTriangleOutputCount(FarSkirtVertex a,FarSkirtVertex b,FarSkir
|
||||
{
|
||||
const float3 centerVoxel=(polygon[0].position+polygon[fan].position
|
||||
+polygon[fan+1].position)/(3.0f*VoxelSizeCm);
|
||||
if(FarSkirtDensityAtVoxel(centerVoxel)<=0.0f)vertexCount+=6;
|
||||
const float centerDensity=FarSkirtDensityAtVoxel(centerVoxel);
|
||||
const uint centerIndex=cached.metadata.x++;
|
||||
if(centerIndex==0)cached.centerDensities.x=centerDensity;
|
||||
else if(centerIndex==1)cached.centerDensities.y=centerDensity;
|
||||
else if(centerIndex==2)cached.centerDensities.z=centerDensity;
|
||||
else cached.centerDensities.w=centerDensity;
|
||||
if(centerDensity<=0.0f)vertexCount+=6;
|
||||
}
|
||||
return vertexCount;
|
||||
}
|
||||
@@ -711,8 +785,19 @@ FarSkirtVertex FarSkirtExtrudedVertex(FarSkirtVertex source)
|
||||
return vertex;
|
||||
}
|
||||
|
||||
uint FarSkirtEdgeOutputCount(uint edgeVertexId)
|
||||
FarSkirtVertex FarSkirtExtrudedVertexCached(FarSkirtVertex source,float density)
|
||||
{
|
||||
FarSkirtVertex vertex=source;
|
||||
vertex.position-=source.normal*(max(1.0f,FarSkirtDepthVoxels)*VoxelSizeCm);
|
||||
vertex.density=density;
|
||||
return vertex;
|
||||
}
|
||||
|
||||
uint FarSkirtEdgeOutputCount(uint edgeVertexId,out FarSkirtCachedSamples cached)
|
||||
{
|
||||
cached.endpointDensities=0.0f;
|
||||
cached.centerDensities=0.0f;
|
||||
cached.metadata=0;
|
||||
const uint surfaceVertexCount=min(FarConnectorDrawArgs.Load(OutputArgsByteOffset),OutputVertexCapacity);
|
||||
if(edgeVertexId>=surfaceVertexCount)return 0;
|
||||
const uint triangleBase=edgeVertexId-(edgeVertexId%3);
|
||||
@@ -722,8 +807,9 @@ uint FarSkirtEdgeOutputCount(uint edgeVertexId)
|
||||
if((FarSkirtBoundaryFaceMask(a.position,b.position)&FarSkirtFaceMask)==0)return 0;
|
||||
const FarSkirtVertex a2=FarSkirtExtrudedVertex(a);
|
||||
const FarSkirtVertex b2=FarSkirtExtrudedVertex(b);
|
||||
return FarSkirtSourceTriangleOutputCount(a,b,b2)
|
||||
+FarSkirtSourceTriangleOutputCount(a,b2,a2);
|
||||
cached.endpointDensities=float2(a2.density,b2.density);
|
||||
return FarSkirtSourceTriangleOutputCount(a,b,b2,cached)
|
||||
+FarSkirtSourceTriangleOutputCount(a,b2,a2,cached);
|
||||
}
|
||||
|
||||
void FarSkirtStoreVertex(uint outputId,FarSkirtVertex vertex,uint panelBaseVertex)
|
||||
@@ -752,6 +838,7 @@ void FarSkirtEmitTriangle(FarSkirtVertex a,FarSkirtVertex b,FarSkirtVertex c,
|
||||
}
|
||||
|
||||
void FarSkirtEmitSourceTriangle(FarSkirtVertex a,FarSkirtVertex b,FarSkirtVertex c,
|
||||
float4 centerDensities,inout uint centerIndex,
|
||||
uint panelBaseVertex,uint edgeBase,inout uint edgeCursor)
|
||||
{
|
||||
FarSkirtVertex polygon[5];uint polygonCount=0;
|
||||
@@ -760,9 +847,10 @@ void FarSkirtEmitSourceTriangle(FarSkirtVertex a,FarSkirtVertex b,FarSkirtVertex
|
||||
[loop]
|
||||
for(uint fan=1;fan+1<polygonCount;++fan)
|
||||
{
|
||||
const float3 centerVoxel=(polygon[0].position+polygon[fan].position
|
||||
+polygon[fan+1].position)/(3.0f*VoxelSizeCm);
|
||||
if(FarSkirtDensityAtVoxel(centerVoxel)<=0.0f)
|
||||
const uint index=centerIndex++;
|
||||
const float centerDensity=index==0?centerDensities.x:index==1?centerDensities.y:
|
||||
index==2?centerDensities.z:centerDensities.w;
|
||||
if(centerDensity<=0.0f)
|
||||
FarSkirtEmitTriangle(polygon[0],polygon[fan],polygon[fan+1],
|
||||
panelBaseVertex,edgeBase,edgeCursor);
|
||||
}
|
||||
@@ -780,24 +868,30 @@ float FarConnectorRadius(float3 center, float3 direction, float nominalRadius)
|
||||
const float minRadius=max(0.0f,nominalRadius-maxOffset);
|
||||
const float maxRadius=nominalRadius+maxOffset;
|
||||
float radius=nominalRadius;
|
||||
float residual=FarRockDistanceAt(center+direction*radius,cachedBinCoord,cachedRange);
|
||||
bool converged=abs(residual)<=1.0f;
|
||||
if(!converged)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
float residual=0.0f;
|
||||
bool converged=false;
|
||||
// Keep nominal probing, the first correction, and the two refinement
|
||||
// iterations in one loop body. This gives the compiler one shared field
|
||||
// probe site for the repeated residual evaluation without changing order.
|
||||
[loop]
|
||||
for(uint refine=0;refine<2&&!converged;++refine)
|
||||
for(uint phase=0;phase<4&&!converged;++phase)
|
||||
{
|
||||
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);
|
||||
if(phase==0)
|
||||
radius=nominalRadius;
|
||||
else if(phase==1)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -1193,7 +1287,9 @@ void ClassifyFarSkirtsCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||
{
|
||||
const uint edgeVertexId=dispatchId.x;
|
||||
if(edgeVertexId>=OutputVertexCapacity)return;
|
||||
FarSkirtScratch[edgeVertexId]=FarSkirtEdgeOutputCount(edgeVertexId);
|
||||
FarSkirtCachedSamples cached;
|
||||
FarSkirtScratch[edgeVertexId]=FarSkirtEdgeOutputCount(edgeVertexId,cached);
|
||||
FarSkirtCachedProbeOutput[edgeVertexId]=cached;
|
||||
}
|
||||
|
||||
[numthreads(1,1,1)]
|
||||
@@ -1230,19 +1326,21 @@ void EmitFarSkirtsCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||
const uint surfaceVertexCount=min(FarConnectorDrawArgs.Load(OutputArgsByteOffset),
|
||||
OutputVertexCapacity);
|
||||
if(edgeVertexId>=surfaceVertexCount)return;
|
||||
if(FarSkirtEdgeOutputCount(edgeVertexId)==0)return;
|
||||
const uint triangleBase=edgeVertexId-(edgeVertexId%3);
|
||||
const uint edge=edgeVertexId%3;
|
||||
const FarSkirtVertex a=FarSkirtSurfaceVertex(triangleBase+edge);
|
||||
const FarSkirtVertex b=FarSkirtSurfaceVertex(triangleBase+(edge+1)%3);
|
||||
if((FarSkirtBoundaryFaceMask(a.position,b.position)&FarSkirtFaceMask)==0)return;
|
||||
const FarSkirtVertex a2=FarSkirtExtrudedVertex(a);
|
||||
const FarSkirtVertex b2=FarSkirtExtrudedVertex(b);
|
||||
const FarSkirtCachedSamples cached=FarSkirtCachedProbeInput[edgeVertexId];
|
||||
const FarSkirtVertex a2=FarSkirtExtrudedVertexCached(a,cached.endpointDensities.x);
|
||||
const FarSkirtVertex b2=FarSkirtExtrudedVertexCached(b,cached.endpointDensities.y);
|
||||
const uint panelBaseVertex=FarConnectorDrawArgs.Load(OutputArgsByteOffset+28);
|
||||
const uint edgeBase=FarConnectorVertexCount+FarSkirtScratch[edgeVertexId];
|
||||
uint edgeCursor=0;
|
||||
FarSkirtEmitSourceTriangle(a,b,b2,panelBaseVertex,edgeBase,edgeCursor);
|
||||
FarSkirtEmitSourceTriangle(a,b2,a2,panelBaseVertex,edgeBase,edgeCursor);
|
||||
uint edgeCursor=0,centerIndex=0;
|
||||
FarSkirtEmitSourceTriangle(a,b,b2,cached.centerDensities,centerIndex,
|
||||
panelBaseVertex,edgeBase,edgeCursor);
|
||||
FarSkirtEmitSourceTriangle(a,b2,a2,cached.centerDensities,centerIndex,
|
||||
panelBaseVertex,edgeBase,edgeCursor);
|
||||
}
|
||||
|
||||
// Smoke probe for the dual-language noise path. This is intentionally separate from the mesh
|
||||
|
||||
Reference in new issue
Block a user