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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-07 07:41:31 +02:00
1 parent 05b1523c54
commit a96fd0b95b
18 files changed
+776 -75

No files matched your search

@@ -5,6 +5,7 @@
// containers, engine types, and vector intrinsics so it remains usable by both compilers.
#if defined(VF_FIELD_HLSL)
#define VF_FIELD_INLINE
#define VF_FIELD_LOOP [loop]
typedef int VF_FieldI32;
typedef uint VF_FieldU32;
typedef float VF_FieldReal;
@@ -23,6 +24,7 @@
#include <cmath>
#include <cstdint>
#define VF_FIELD_INLINE inline
#define VF_FIELD_LOOP
typedef std::int32_t VF_FieldI32;
typedef std::uint32_t VF_FieldU32;
typedef double VF_FieldReal;
@@ -333,6 +335,7 @@ VF_FIELD_INLINE float VF_FieldOwnerFractal(float x,float y,float z,VF_FieldI32 o
VF_FieldI32 mode)
{
float total=0.0f,maxValue=0.0f,frequency=1.0f,amplitude=1.0f,weight=1.0f;
VF_FIELD_LOOP
for(VF_FieldI32 i=0;i<octaves;++i)
{
const float noise=VF_FieldPerlin3D(x*frequency,y*frequency,z*frequency);
@@ -362,6 +365,7 @@ VF_FIELD_INLINE float VF_FieldOwnerCellular(float x,float y,float z)
const float fracX=x-VF_FIELD_CAST_F32(cellX),fracY=y-VF_FIELD_CAST_F32(cellY),
fracZ=z-VF_FIELD_CAST_F32(cellZ);
float first=3.402823466e+38f,second=3.402823466e+38f;
VF_FIELD_LOOP
for(VF_FieldI32 dz=-1;dz<=1;++dz)
for(VF_FieldI32 dy=-1;dy<=1;++dy)
for(VF_FieldI32 dx=-1;dx<=1;++dx)
@@ -120,7 +120,7 @@ if ($Target -eq 'All') {
Sort-Object -Property FullName -Descending | Select-Object -First 1
if ($FxC) {
[IO.Directory]::CreateDirectory($PrecompiledShaderDir) | Out-Null
foreach ($Entry in @('EvaluateFarFieldCS','EvaluateNearFieldCS','ClassifyCS','ClassifyFarCS','ScanGroupsCS','EmitCS','EmitNearCS','NoiseProbeCS','BuildFarConnectorRingsCS','EmitFarConnectorPanelsCS','ClassifyFarSkirtsCS','ScanFarSkirtsCS','EmitFarSkirtsCS')) {
foreach ($Entry in @('EvaluateFarFieldCS','EvaluateNearFieldCS','ApplyNearFieldDetailCS','ClassifyCS','ClassifyFarCS','ScanGroupsCS','EmitCS','EmitNearCS','NoiseProbeCS','BuildFarConnectorRingsCS','EmitFarConnectorPanelsCS','ClassifyFarSkirtsCS','ScanFarSkirtsCS','EmitFarSkirtsCS')) {
$ShaderBinary = Join-Path $PrecompiledShaderDir ($Entry+'.cso')
$CommandLine = '"{0}" /nologo /T cs_5_0 /E {1} /O3 /Gis /Ges /Fo "{2}" "{3}"' -f $FxC.FullName,$Entry,$ShaderBinary,$ShaderSource
Invoke-FieldViewerBuildCommand $CommandLine "precompile HLSL entry $Entry"
@@ -0,0 +1,59 @@
from pathlib import Path
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent
VIEWS = (
"chamber",
"tunnel",
"passage",
"far",
"passage_floor_closeup",
"tunnel_bend",
"remote_yaw0",
"remote_yaw90",
"remote_yaw180",
"remote_yaw270",
)
rows = []
for view in VIEWS:
cpu_path = ROOT / f"step13_cpu_{view}.png"
gpu_path = ROOT / f"step13_gpu_{view}.png"
cpu = np.asarray(Image.open(cpu_path).convert("RGB"), dtype=np.int16)
gpu = np.asarray(Image.open(gpu_path).convert("RGB"), dtype=np.int16)
if cpu.shape != gpu.shape:
raise RuntimeError(f"dimension mismatch for {view}: {cpu.shape} vs {gpu.shape}")
diff = np.abs(cpu - gpu)
pixel_max = diff.max(axis=2)
rows.append(
(
view,
f"{cpu.shape[1]}x{cpu.shape[0]}",
float(diff.mean()),
float(np.sqrt(np.square(diff, dtype=np.float64).mean())),
float(np.count_nonzero(pixel_max) * 100.0 / pixel_max.size),
float(np.count_nonzero(pixel_max > 32) * 100.0 / pixel_max.size),
int(pixel_max.max()),
)
)
lines = [
"# Step 13 full-residency capture comparison",
"",
"All ten CPU/GPU pairs were captured after `[InitialClipmapReady]`. The files were compared numerically as RGB arrays and were not opened in an image viewer.",
"",
"| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel |",
"|---|---:|---:|---:|---:|---:|---:|",
]
for row in rows:
view, size, mae, rmse, changed, over32, maximum = row
lines.append(
f"| {view} | {size} | {mae:.4f} | {rmse:.4f} | {changed:.4f}% | {over32:.4f}% | {maximum} |"
)
report = ROOT / "step13_capture_comparison.md"
report.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(report.read_text(encoding="utf-8"), end="")
@@ -148,6 +148,19 @@ namespace
std::int32_t detailMinVoxel[4], detailMaxVoxel[4];
};
static_assert(sizeof(FarFieldConstants) == 80);
struct NearFieldDetailBaseGpu
{
float geometry[4];
float influences[4];
};
static_assert(sizeof(NearFieldDetailBaseGpu) == 32);
struct FarSkirtCachedSamplesGpu
{
float endpointDensities[2];
float centerDensities[4];
std::uint32_t metadata[4];
};
static_assert(sizeof(FarSkirtCachedSamplesGpu) == 40);
static_assert(sizeof(FarFieldPrimitiveGpu) == 224);
static_assert(sizeof(FarFieldSpatialBin) == 24);
static_assert(sizeof(FarFieldStratumBoundary) == 8);
@@ -527,17 +540,20 @@ namespace
struct FarScratch
{
std::uint32_t sampleCount=0,cellCount=0,groupCount=0;
ComPtr<ID3D11Buffer> signs,tri,density,nearNormalDensity,midpointIds,midpointRecords;
ComPtr<ID3D11Buffer> signs,tri,density,nearNormalDensity,nearDetailBase,midpointIds,midpointRecords;
ComPtr<ID3D11Buffer> cases,localOffsets,groupCounts,groupOffsets;
ComPtr<ID3D11Buffer> tileConstants,farConstants;
ComPtr<ID3D11Buffer> connectorConstants,connectorRings;
ComPtr<ID3D11ShaderResourceView> signSrv,triSrv,densitySrv,nearNormalDensitySrv,midpointIdSrv,midpointRecordSrv;
ComPtr<ID3D11ShaderResourceView> signSrv,triSrv,densitySrv,nearNormalDensitySrv,nearDetailBaseSrv,midpointIdSrv,midpointRecordSrv;
ComPtr<ID3D11ShaderResourceView> caseSrv,localOffsetSrv,groupCountSrv,groupOffsetSrv;
ComPtr<ID3D11ShaderResourceView> connectorRingSrv;
ComPtr<ID3D11UnorderedAccessView> densityUav,nearNormalDensityUav,caseUav,localOffsetUav,groupCountUav,groupOffsetUav;
ComPtr<ID3D11UnorderedAccessView> densityUav,nearNormalDensityUav,nearDetailBaseUav,caseUav,localOffsetUav,groupCountUav,groupOffsetUav;
ComPtr<ID3D11UnorderedAccessView> connectorRingUav;
ComPtr<ID3D11Buffer> skirtScratch;
ComPtr<ID3D11UnorderedAccessView> skirtScratchUav;
ComPtr<ID3D11Buffer> skirtProbeData;
ComPtr<ID3D11ShaderResourceView> skirtProbeSrv;
ComPtr<ID3D11UnorderedAccessView> skirtProbeUav;
std::uint32_t skirtScratchCapacity=0;
DynamicInput shapes,ranges,ids,protectedAir,protectedSolid,nearPackedProxy;
DynamicInput connectors;
@@ -551,7 +567,7 @@ namespace
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
ComPtr<ID3D11DeviceContext> immediateContext,deferredContext;
ComPtr<ID3D11ComputeShader> fieldEval, fieldEvalNear, classify, fieldClassify, scan, emit, emitNear, noiseProbe;
ComPtr<ID3D11ComputeShader> fieldEval, fieldEvalNear, fieldEvalNearDetail, classify, fieldClassify, scan, emit, emitNear, noiseProbe;
ComPtr<ID3D11ComputeShader> connectorRings,connectorPanels;
ComPtr<ID3D11ComputeShader> classifyFarSkirts,scanFarSkirts,emitFarSkirts;
ComPtr<IDXGIAdapter3> memoryAdapter;
@@ -596,6 +612,7 @@ namespace
};
Create("EvaluateFarFieldCS",fieldEval);
Create("EvaluateNearFieldCS",fieldEvalNear);
Create("ApplyNearFieldDetailCS",fieldEvalNearDetail);
Create("ClassifyCS",classify);
Create("ClassifyFarCS",fieldClassify);
Create("ScanGroupsCS",scan);
@@ -614,8 +631,9 @@ namespace
void QueueTinyShaderWarmup(bool dispatchShaders,const std::string& entryFilter)
{
const auto WarmupStart=Clock::now();
static constexpr std::array<std::string_view,13> entries={
static constexpr std::array<std::string_view,14> entries={
"EvaluateFarFieldCS","EvaluateNearFieldCS","ClassifyCS","ClassifyFarCS","ScanGroupsCS","EmitCS",
"ApplyNearFieldDetailCS",
"EmitNearCS","BuildFarConnectorRingsCS","EmitFarConnectorPanelsCS",
"ClassifyFarSkirtsCS","ScanFarSkirtsCS","EmitFarSkirtsCS","NoiseProbeCS"};
if(!entryFilter.empty()&&std::find(entries.begin(),entries.end(),entryFilter)==entries.end())
@@ -625,7 +643,7 @@ namespace
tile.cells=1;tile.sampleDim=3;tile.cellCount=cellCount;tile.groupCount=groupCount;
tile.stepVoxels=entryFilter=="EmitNearCS"?1:2;
tile.nearFieldMode=(entryFilter.empty()||entryFilter=="EvaluateNearFieldCS"
||entryFilter=="EmitNearCS")?1u:0u;
||entryFilter=="ApplyNearFieldDetailCS"||entryFilter=="EmitNearCS")?1u:0u;
tile.voxelSizeCm=25.0f;tile.outputVertexCapacity=3;
FarFieldConstants field{};
field.strataLo=-100.0f;field.strataHi=100.0f;field.strataBlend=12.0f;
@@ -659,6 +677,9 @@ namespace
std::array<float,sampleCount> zeroDensity{};
auto density=Structured(zeroDensity.data(),sampleCount,D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
auto nearNormal=Structured(zeroDensity.data(),sampleCount,D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
std::array<NearFieldDetailBaseGpu,sampleCount> zeroNearDetailBase{};
auto nearDetailBase=Structured(zeroNearDetailBase.data(),sampleCount,
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
auto evalCounters=Structured<std::uint32_t>(nullptr,sampleCount,D3D11_BIND_UNORDERED_ACCESS);
auto signs=Structured(&zeroWord,1,D3D11_BIND_SHADER_RESOURCE);
std::array<std::int32_t,256*16> triTable{};triTable.fill(-1);
@@ -677,9 +698,12 @@ namespace
FarConnectorRingRecord emptyRing{};
auto connectorRingBuffer=Structured(&emptyRing,1,D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
auto skirtScratch=Structured<std::uint32_t>(nullptr,3,D3D11_BIND_UNORDERED_ACCESS);
auto skirtProbeData=Structured<FarSkirtCachedSamplesGpu>(nullptr,3,
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
auto densitySrv=Srv<float>(density.Get(),sampleCount);
auto nearNormalSrv=Srv<float>(nearNormal.Get(),sampleCount);
auto nearDetailBaseSrv=Srv<NearFieldDetailBaseGpu>(nearDetailBase.Get(),sampleCount);
auto shapeSrv=Srv<FarFieldPrimitiveGpu>(shapes.Get(),1);
auto binSrv=Srv<FarFieldSpatialBin>(bins.Get(),1);
auto shapeIdSrv=Srv<std::uint32_t>(shapeIds.Get(),1);
@@ -697,8 +721,10 @@ namespace
auto midpointRecordSrv=Srv<MidpointRecord>(midpointRecords.Get(),1);
auto connectorPanelSrv=Srv<FarFieldConnectorPanel>(panelBuffer.Get(),1);
auto connectorRingSrv=Srv<FarConnectorRingRecord>(connectorRingBuffer.Get(),1);
auto skirtProbeSrv=Srv<FarSkirtCachedSamplesGpu>(skirtProbeData.Get(),3);
auto densityUav=Uav(density.Get(),sampleCount);
auto nearNormalUav=Uav(nearNormal.Get(),sampleCount);
auto nearDetailBaseUav=Uav(nearDetailBase.Get(),sampleCount);
auto evalCounterUav=Uav(evalCounters.Get(),sampleCount);
auto caseUav=Uav(cases.Get(),cellCount);
auto localOffsetUav=Uav(localOffsets.Get(),cellCount);
@@ -706,6 +732,7 @@ namespace
auto groupOffsetUav=Uav(groupOffsets.Get(),groupCount);
auto connectorRingUav=Uav(connectorRingBuffer.Get(),1);
auto skirtScratchUav=Uav(skirtScratch.Get(),3);
auto skirtProbeUav=Uav(skirtProbeData.Get(),3);
auto args=RawMeshBuffer(11u*sizeof(std::uint32_t),D3D11_BIND_UNORDERED_ACCESS,
D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS|D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS);
@@ -746,9 +773,9 @@ namespace
ID3D11Buffer* constants[3]={tileCb.Get(),fieldCb.Get(),connectorCb.Get()};
context->CSSetConstantBuffers(0,3,constants);
std::uint32_t dispatchCount=0;
auto ClearBindings=[&](UINT srvCount=20)
auto ClearBindings=[&](UINT srvCount=22)
{
ID3D11ShaderResourceView* nullSrvs[20]{};
ID3D11ShaderResourceView* nullSrvs[22]{};
ID3D11UnorderedAccessView* nullUavs[8]{};
context->CSSetShaderResources(0,srvCount,nullSrvs);
context->CSSetUnorderedAccessViews(0,8,nullUavs,nullptr);
@@ -765,17 +792,28 @@ namespace
std::chrono::duration<double,std::milli>(Clock::now()-start).count());
};
ID3D11ShaderResourceView* evalSrvs[20]{};
ID3D11ShaderResourceView* evalSrvs[21]{};
evalSrvs[10]=shapeSrv.Get();evalSrvs[11]=binSrv.Get();evalSrvs[12]=shapeIdSrv.Get();
evalSrvs[13]=protectedAirSrv.Get();evalSrvs[14]=protectedSolidSrv.Get();
evalSrvs[16]=detailSrv.Get();evalSrvs[17]=proxySrv.Get();
context->CSSetShaderResources(0,20,evalSrvs);
ID3D11UnorderedAccessView* evalUavs[8]{};
evalUavs[1]=nearDetailBaseUav.Get();
evalUavs[5]=nearNormalUav.Get();evalUavs[6]=evalCounterUav.Get();evalUavs[7]=densityUav.Get();
context->CSSetUnorderedAccessViews(0,8,evalUavs,nullptr);
Dispatch(fieldEval.Get(),"EvaluateFarFieldCS");ClearBindings();
Dispatch(fieldEvalNear.Get(),"EvaluateNearFieldCS");ClearBindings();
ID3D11ShaderResourceView* nearDetailSrvs[21]{};
nearDetailSrvs[13]=protectedAirSrv.Get();nearDetailSrvs[14]=protectedSolidSrv.Get();
nearDetailSrvs[16]=detailSrv.Get();nearDetailSrvs[17]=proxySrv.Get();
nearDetailSrvs[20]=nearDetailBaseSrv.Get();
context->CSSetShaderResources(0,21,nearDetailSrvs);
ID3D11UnorderedAccessView* nearDetailUavs[8]{};
nearDetailUavs[5]=nearNormalUav.Get();nearDetailUavs[7]=densityUav.Get();
context->CSSetUnorderedAccessViews(0,8,nearDetailUavs,nullptr);
Dispatch(fieldEvalNearDetail.Get(),"ApplyNearFieldDetailCS");ClearBindings();
ID3D11ShaderResourceView* classifySrvs[16]{};
classifySrvs[0]=signSrv.Get();classifySrvs[1]=triangleSrv.Get();
context->CSSetShaderResources(0,16,classifySrvs);
@@ -818,12 +856,14 @@ namespace
context->CSSetUnorderedAccessViews(0,8,panelUavs,nullptr);
Dispatch(connectorPanels.Get(),"EmitFarConnectorPanelsCS");ClearBindings();
ID3D11ShaderResourceView* skirtSrvs[20]{};
ID3D11ShaderResourceView* skirtSrvs[22]{};
skirtSrvs[10]=shapeSrv.Get();skirtSrvs[11]=binSrv.Get();skirtSrvs[12]=shapeIdSrv.Get();
skirtSrvs[13]=protectedAirSrv.Get();skirtSrvs[14]=protectedSolidSrv.Get();skirtSrvs[19]=vertexSrv.Get();
context->CSSetShaderResources(0,20,skirtSrvs);
skirtSrvs[21]=skirtProbeSrv.Get();
context->CSSetShaderResources(0,22,skirtSrvs);
ID3D11UnorderedAccessView* classifySkirtUavs[8]{};
classifySkirtUavs[0]=skirtScratchUav.Get();classifySkirtUavs[4]=argsUav.Get();
classifySkirtUavs[0]=skirtScratchUav.Get();classifySkirtUavs[2]=skirtProbeUav.Get();
classifySkirtUavs[4]=argsUav.Get();
context->CSSetUnorderedAccessViews(0,8,classifySkirtUavs,nullptr);
Dispatch(classifyFarSkirts.Get(),"ClassifyFarSkirtsCS");ClearBindings();
@@ -832,7 +872,7 @@ namespace
context->CSSetUnorderedAccessViews(0,8,scanSkirtUavs,nullptr);
Dispatch(scanFarSkirts.Get(),"ScanFarSkirtsCS");ClearBindings();
context->CSSetShaderResources(0,20,skirtSrvs);
context->CSSetShaderResources(0,22,skirtSrvs);
ID3D11UnorderedAccessView* emitSkirtUavs[8]{};
emitSkirtUavs[0]=skirtScratchUav.Get();emitSkirtUavs[4]=argsUav.Get();
emitSkirtUavs[5]=panelVertexUav.Get();emitSkirtUavs[6]=panelIndexUav.Get();
@@ -1165,6 +1205,8 @@ namespace
scratch.density=Structured<float>(nullptr,samples,D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
scratch.nearNormalDensity=Structured<float>(nullptr,samples,
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
scratch.nearDetailBase=Structured<NearFieldDetailBaseGpu>(nullptr,samples,
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
std::vector<std::uint32_t> invalidIds(cells,InvalidIndex);
scratch.midpointIds=Structured(invalidIds.data(),invalidIds.size(),D3D11_BIND_SHADER_RESOURCE);
const MidpointRecord zeroRecord{};
@@ -1176,10 +1218,13 @@ namespace
scratch.skirtScratchCapacity=std::max<std::uint32_t>(1,farMaxVerticesPerTile);
scratch.skirtScratch=Structured<std::uint32_t>(nullptr,scratch.skirtScratchCapacity,
D3D11_BIND_UNORDERED_ACCESS);
scratch.skirtProbeData=Structured<FarSkirtCachedSamplesGpu>(nullptr,
scratch.skirtScratchCapacity,D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
scratch.signSrv=Srv<std::uint32_t>(scratch.signs.Get(),1);
scratch.triSrv=Srv<std::int32_t>(scratch.tri.Get(),256u*16u);
scratch.densitySrv=Srv<float>(scratch.density.Get(),samples);
scratch.nearNormalDensitySrv=Srv<float>(scratch.nearNormalDensity.Get(),samples);
scratch.nearDetailBaseSrv=Srv<NearFieldDetailBaseGpu>(scratch.nearDetailBase.Get(),samples);
scratch.midpointIdSrv=Srv<std::uint32_t>(scratch.midpointIds.Get(),cells);
scratch.midpointRecordSrv=Srv<MidpointRecord>(scratch.midpointRecords.Get(),1);
scratch.caseSrv=Srv<std::uint32_t>(scratch.cases.Get(),cells);
@@ -1188,11 +1233,15 @@ namespace
scratch.groupOffsetSrv=Srv<std::uint32_t>(scratch.groupOffsets.Get(),groups);
scratch.densityUav=Uav(scratch.density.Get(),samples);
scratch.nearNormalDensityUav=Uav(scratch.nearNormalDensity.Get(),samples);
scratch.nearDetailBaseUav=Uav(scratch.nearDetailBase.Get(),samples);
scratch.caseUav=Uav(scratch.cases.Get(),cells);
scratch.localOffsetUav=Uav(scratch.localOffsets.Get(),cells);
scratch.groupCountUav=Uav(scratch.groupCounts.Get(),groups);
scratch.groupOffsetUav=Uav(scratch.groupOffsets.Get(),groups);
scratch.skirtScratchUav=Uav(scratch.skirtScratch.Get(),scratch.skirtScratchCapacity);
scratch.skirtProbeSrv=Srv<FarSkirtCachedSamplesGpu>(scratch.skirtProbeData.Get(),
scratch.skirtScratchCapacity);
scratch.skirtProbeUav=Uav(scratch.skirtProbeData.Get(),scratch.skirtScratchCapacity);
const auto DynamicConstant=[&](UINT bytes,const char* label)
{
D3D11_BUFFER_DESC desc{};desc.ByteWidth=bytes;desc.Usage=D3D11_USAGE_DYNAMIC;
@@ -1324,23 +1373,24 @@ namespace
if (farField)
result.d3dBufferBytes += farField->protectedAir.size() * sizeof(std::uint32_t)
+ farField->protectedSolid.size() * sizeof(std::uint32_t)
+ farField->nearPackedProxy.size() * sizeof(std::uint32_t)
+ farField->connectorPanels.size() * sizeof(FarFieldConnectorPanel)
+ tile.density.size() * sizeof(float)
+ sizeof(FarFieldConstants);
+ farField->nearPackedProxy.size() * sizeof(std::uint32_t)
+ farField->connectorPanels.size() * sizeof(FarFieldConnectorPanel)
+ tile.density.size() * sizeof(float)
+ tile.density.size() * sizeof(NearFieldDetailBaseGpu)
+ sizeof(FarFieldConstants);
if (readback)
result.d3dBufferBytes += cellCount * sizeof(std::uint32_t)
+ std::max<std::uint32_t>(1, tile.expectedVertexCount) * sizeof(GpuVertex)
+ std::max<std::uint32_t>(1, tile.expectedVertexCount) * sizeof(std::uint32_t)
+ 5 * sizeof(std::uint32_t);
ComPtr<ID3D11Buffer> signs,triBuffer,density,nearNormalDensity,midpointIds,midpointRecords;
ComPtr<ID3D11Buffer> signs,triBuffer,density,nearNormalDensity,nearDetailBase,midpointIds,midpointRecords;
ComPtr<ID3D11Buffer> cases,localOffsets,groupCounts,groupOffsets;
ComPtr<ID3D11ShaderResourceView> signSrv,triSrv,densitySrv,nearNormalDensitySrv,farShapeSrv,farRangeSrv,farIdSrv;
ComPtr<ID3D11ShaderResourceView> signSrv,triSrv,densitySrv,nearNormalDensitySrv,nearDetailBaseSrv,farShapeSrv,farRangeSrv,farIdSrv;
ComPtr<ID3D11ShaderResourceView> farProtectedSrv,farProtectedSolidSrv,farNearProxySrv;
ComPtr<ID3D11ShaderResourceView> farConnectorSrv,farConnectorRingSrv;
ComPtr<ID3D11ShaderResourceView> midpointIdSrv,midpointRecordSrv,caseSrv,localOffsetSrv;
ComPtr<ID3D11ShaderResourceView> groupCountSrv,groupOffsetSrv;
ComPtr<ID3D11UnorderedAccessView> caseUav,localOffsetUav,groupCountUav,groupOffsetUav,densityUav,nearNormalDensityUav;
ComPtr<ID3D11UnorderedAccessView> caseUav,localOffsetUav,groupCountUav,groupOffsetUav,densityUav,nearNormalDensityUav,nearDetailBaseUav;
ComPtr<ID3D11UnorderedAccessView> farConnectorRingUav;
ComPtr<ID3D11Buffer> farConnectorConstantsBuffer;
ComPtr<ID3D11Buffer> farConnectorRingBuffer;
@@ -1359,15 +1409,18 @@ namespace
FarScratch& scratch=farScratch;
signs=scratch.signs;triBuffer=scratch.tri;density=scratch.density;
nearNormalDensity=scratch.nearNormalDensity;
nearDetailBase=scratch.nearDetailBase;
midpointIds=scratch.midpointIds;midpointRecords=scratch.midpointRecords;
cases=scratch.cases;localOffsets=scratch.localOffsets;
groupCounts=scratch.groupCounts;groupOffsets=scratch.groupOffsets;
signSrv=scratch.signSrv;triSrv=scratch.triSrv;densitySrv=scratch.densitySrv;
nearNormalDensitySrv=scratch.nearNormalDensitySrv;
nearDetailBaseSrv=scratch.nearDetailBaseSrv;
midpointIdSrv=scratch.midpointIdSrv;midpointRecordSrv=scratch.midpointRecordSrv;
caseSrv=scratch.caseSrv;localOffsetSrv=scratch.localOffsetSrv;
groupCountSrv=scratch.groupCountSrv;groupOffsetSrv=scratch.groupOffsetSrv;
densityUav=scratch.densityUav;nearNormalDensityUav=scratch.nearNormalDensityUav;
nearDetailBaseUav=scratch.nearDetailBaseUav;
caseUav=scratch.caseUav;
localOffsetUav=scratch.localOffsetUav;groupCountUav=scratch.groupCountUav;
groupOffsetUav=scratch.groupOffsetUav;
@@ -1398,8 +1451,12 @@ namespace
density=Structured(farField?static_cast<const float*>(nullptr):tile.density.data(),
tile.density.size(),D3D11_BIND_SHADER_RESOURCE|(farField?D3D11_BIND_UNORDERED_ACCESS:0));
if(farField)
{
nearNormalDensity=Structured<float>(nullptr,tile.density.size(),
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
nearDetailBase=Structured<NearFieldDetailBaseGpu>(nullptr,tile.density.size(),
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
}
midpointIds=Structured(tile.midpointIds.data(),tile.midpointIds.size(),D3D11_BIND_SHADER_RESOURCE);
const MidpointRecord zeroRecord{};
midpointRecords=Structured(tile.midpointRecords.empty()?&zeroRecord:tile.midpointRecords.data(),
@@ -1412,7 +1469,10 @@ namespace
triSrv=Srv<std::int32_t>(triBuffer.Get(),triCount);
densitySrv=Srv<float>(density.Get(),tile.density.size());
if(farField)
{
nearNormalDensitySrv=Srv<float>(nearNormalDensity.Get(),tile.density.size());
nearDetailBaseSrv=Srv<NearFieldDetailBaseGpu>(nearDetailBase.Get(),tile.density.size());
}
midpointIdSrv=Srv<std::uint32_t>(midpointIds.Get(),tile.midpointIds.size());
midpointRecordSrv=Srv<MidpointRecord>(midpointRecords.Get(),std::max<std::size_t>(1,tile.midpointRecords.size()));
caseSrv=Srv<std::uint32_t>(cases.Get(),cellCount);
@@ -1424,6 +1484,8 @@ namespace
densityUav=farField?Uav(density.Get(),tile.density.size()):ComPtr<ID3D11UnorderedAccessView>{};
nearNormalDensityUav=farField?Uav(nearNormalDensity.Get(),tile.density.size()):
ComPtr<ID3D11UnorderedAccessView>{};
nearDetailBaseUav=farField?Uav(nearDetailBase.Get(),tile.density.size()):
ComPtr<ID3D11UnorderedAccessView>{};
if(farField)
{
const std::uint32_t zeroWord=0;
@@ -1463,6 +1525,9 @@ namespace
ComPtr<ID3D11UnorderedAccessView> connectorVertexUav,connectorIndexUav;
ComPtr<ID3D11Buffer> skirtScratchBuffer;
ComPtr<ID3D11UnorderedAccessView> skirtScratchUav;
ComPtr<ID3D11Buffer> skirtProbeBuffer;
ComPtr<ID3D11ShaderResourceView> skirtProbeSrv;
ComPtr<ID3D11UnorderedAccessView> skirtProbeUav;
const std::uint32_t connectorPanelCapacity=pooledChunk?FarPanelVertexCapacity:
std::max<std::uint32_t>(1,farField&&(farField->visibleSkirtFaceMask!=0)
?FarPanelVertexCapacity:(farField?farField->connectorVertexCount:0));
@@ -1476,6 +1541,8 @@ namespace
connectorVertices=pooledChunk->connectorVertices;connectorIndices=pooledChunk->connectorIndices;
connectorVertexUav=pooledChunk->connectorVertexUav;connectorIndexUav=pooledChunk->connectorIndexUav;
skirtScratchBuffer=farScratch.skirtScratch;skirtScratchUav=farScratch.skirtScratchUav;
skirtProbeBuffer=farScratch.skirtProbeData;skirtProbeSrv=farScratch.skirtProbeSrv;
skirtProbeUav=farScratch.skirtProbeUav;
}
else
{
@@ -1500,6 +1567,12 @@ namespace
skirtScratchBuffer=Structured<std::uint32_t>(nullptr,
static_cast<std::uint32_t>(maxVertices),D3D11_BIND_UNORDERED_ACCESS);
skirtScratchUav=Uav(skirtScratchBuffer.Get(),static_cast<UINT>(maxVertices));
skirtProbeBuffer=Structured<FarSkirtCachedSamplesGpu>(nullptr,
static_cast<std::uint32_t>(maxVertices),
D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS);
skirtProbeSrv=Srv<FarSkirtCachedSamplesGpu>(skirtProbeBuffer.Get(),
static_cast<UINT>(maxVertices));
skirtProbeUav=Uav(skirtProbeBuffer.Get(),static_cast<UINT>(maxVertices));
}
}
D3D11_BUFFER_DESC cbDesc{};
@@ -1615,7 +1688,8 @@ namespace
beginKernel(0);
context->CSSetConstantBuffers(1,1,farCb.GetAddressOf());
context->CSSetConstantBuffers(2,1,farConnectorConstantsBuffer.GetAddressOf());
context->CSSetShader(constants.nearFieldMode!=0?fieldEvalNear.Get():fieldEval.Get(),nullptr,0);
const bool evaluateNear=constants.nearFieldMode!=0;
context->CSSetShader(evaluateNear?fieldEvalNear.Get():fieldEval.Get(),nullptr,0);
ID3D11ShaderResourceView* fieldSrvs[18]{};
fieldSrvs[10]=farShapeSrv.Get();fieldSrvs[11]=farRangeSrv.Get();
fieldSrvs[12]=farIdSrv.Get();fieldSrvs[13]=farProtectedSrv.Get();
@@ -1623,16 +1697,32 @@ namespace
fieldSrvs[16]=farWorldResources.detailLayers.srv.Get();
fieldSrvs[17]=farNearProxySrv.Get();
context->CSSetShaderResources(0,18,fieldSrvs);
ID3D11UnorderedAccessView* fieldUavs[8]{};fieldUavs[5]=nearNormalDensityUav.Get();
ID3D11UnorderedAccessView* fieldUavs[8]{};fieldUavs[1]=nearDetailBaseUav.Get();
fieldUavs[5]=nearNormalDensityUav.Get();
fieldUavs[6]=farEvalCounterUav.Get();
fieldUavs[7]=densityUav.Get();
context->CSSetUnorderedAccessViews(0,8,fieldUavs,nullptr);
context->Dispatch(static_cast<UINT>((tile.density.size()+63)/64),1,1);
endKernel(0);
ID3D11ShaderResourceView* clearSrvs[19]{};
ID3D11ShaderResourceView* clearSrvs[21]{};
ID3D11UnorderedAccessView* clearUavs[8]{};
context->CSSetShaderResources(0,19,clearSrvs);
context->CSSetShaderResources(0,21,clearSrvs);
context->CSSetUnorderedAccessViews(0,8,clearUavs,nullptr);
if(evaluateNear)
{
context->CSSetShader(fieldEvalNearDetail.Get(),nullptr,0);
ID3D11ShaderResourceView* nearDetailSrvs[21]{};
nearDetailSrvs[13]=farProtectedSrv.Get();nearDetailSrvs[14]=farProtectedSolidSrv.Get();
nearDetailSrvs[16]=farWorldResources.detailLayers.srv.Get();
nearDetailSrvs[17]=farNearProxySrv.Get();nearDetailSrvs[20]=nearDetailBaseSrv.Get();
context->CSSetShaderResources(0,21,nearDetailSrvs);
ID3D11UnorderedAccessView* nearDetailUavs[8]{};
nearDetailUavs[5]=nearNormalDensityUav.Get();nearDetailUavs[7]=densityUav.Get();
context->CSSetUnorderedAccessViews(0,8,nearDetailUavs,nullptr);
context->Dispatch(static_cast<UINT>((tile.density.size()+63)/64),1,1);
context->CSSetShaderResources(0,21,clearSrvs);
context->CSSetUnorderedAccessViews(0,8,clearUavs,nullptr);
}
endKernel(0);
if(captureGpuTiming&&!batchTimingMode)context->End(fieldEndQuery.Get());
}
beginKernel(1);
@@ -1712,22 +1802,24 @@ namespace
{
ID3D11Buffer* skirtCbs[3]={cb.Get(),farCb.Get(),farConnectorConstantsBuffer.Get()};
context->CSSetConstantBuffers(0,3,skirtCbs);
ID3D11ShaderResourceView* skirtSrvs[20]{};
ID3D11ShaderResourceView* skirtSrvs[22]{};
skirtSrvs[10]=farShapeSrv.Get();skirtSrvs[11]=farRangeSrv.Get();
skirtSrvs[12]=farIdSrv.Get();skirtSrvs[13]=farProtectedSrv.Get();
skirtSrvs[14]=farProtectedSolidSrv.Get();skirtSrvs[19]=surfaceVertexSrv.Get();
context->CSSetShaderResources(0,20,skirtSrvs);
skirtSrvs[21]=skirtProbeSrv.Get();
context->CSSetShaderResources(0,22,skirtSrvs);
ID3D11UnorderedAccessView* classifySkirtUavs[8]{};
classifySkirtUavs[0]=skirtScratchUav.Get();classifySkirtUavs[4]=argsUav.Get();
classifySkirtUavs[0]=skirtScratchUav.Get();classifySkirtUavs[2]=skirtProbeUav.Get();
classifySkirtUavs[4]=argsUav.Get();
const std::uint32_t skirtDispatchVertices=constants.outputVertexCapacity;
beginKernel(6);
context->CSSetShader(classifyFarSkirts.Get(),nullptr,0);
context->CSSetUnorderedAccessViews(0,8,classifySkirtUavs,nullptr);
context->Dispatch((skirtDispatchVertices+63u)/64u,1,1);
endKernel(6);
ID3D11ShaderResourceView* nullSkirtSrvs[20]{};
ID3D11ShaderResourceView* nullSkirtSrvs[22]{};
ID3D11UnorderedAccessView* nullSkirtUavs[8]{};
context->CSSetShaderResources(0,20,nullSkirtSrvs);
context->CSSetShaderResources(0,22,nullSkirtSrvs);
context->CSSetUnorderedAccessViews(0,8,nullSkirtUavs,nullptr);
beginKernel(7);
@@ -1739,7 +1831,7 @@ namespace
endKernel(7);
context->CSSetUnorderedAccessViews(0,8,nullSkirtUavs,nullptr);
context->CSSetShaderResources(0,20,skirtSrvs);
context->CSSetShaderResources(0,22,skirtSrvs);
beginKernel(8);
context->CSSetShader(emitFarSkirts.Get(),nullptr,0);
ID3D11UnorderedAccessView* emitSkirtUavs[8]{};
@@ -1748,7 +1840,7 @@ namespace
context->CSSetUnorderedAccessViews(0,8,emitSkirtUavs,nullptr);
context->Dispatch((skirtDispatchVertices+63u)/64u,1,1);
endKernel(8);
context->CSSetShaderResources(0,20,nullSkirtSrvs);
context->CSSetShaderResources(0,22,nullSkirtSrvs);
context->CSSetUnorderedAccessViews(0,8,nullSkirtUavs,nullptr);
}
if(captureGpuTiming)
@@ -0,0 +1,80 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep13Warmup.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Views=@(
@{Name='chamber';Camera='-11.75,-123.25,-19.85,0,0'},
@{Name='tunnel';Camera='-11.75,-123.25,-19.85,90,0'},
@{Name='passage';Camera='-39.19,6.27,-28.51,63,0'},
@{Name='far';Camera='-11.61244,-118.2212,-19.8452,0,0'},
@{Name='passage_floor_closeup';Camera='-39.19,6.27,-25.51,63,-25'},
@{Name='tunnel_bend';Camera='-198.97,-99.57,-45.58,33.59,23.17'},
@{Name='remote_yaw0';Camera='500,0,0,0,0'},
@{Name='remote_yaw90';Camera='500,0,0,90,0'},
@{Name='remote_yaw180';Camera='500,0,0,180,0'},
@{Name='remote_yaw270';Camera='500,0,0,270,0'}
)
$EightGB=8L*1024L*1024L*1024L
foreach($View in $Views)
{
foreach($Mode in @('cpu','gpu'))
{
$Stem="step13_${Mode}_$($View.Name)"
$CaptureName="$Stem.png"
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--capture-after-clipmap',
"--camera=$($View.Camera)","--$Mode-far",
"--capture=Research/OwnerShapeWorld/gpu_field_port/$CaptureName")
$StdoutPath=Join-Path $PSScriptRoot ($Stem+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Stem+'.stderr.log')
$CapturePath=Join-Path $PSScriptRoot $CaptureName
Write-Output "STEP13_CAPTURE mode=$Mode view=$($View.Name) affinity=0xFF00 command=`"$ExecutablePath`" $($Arguments -join ' ')"
for($Attempt=0;$Attempt-lt2;$Attempt++)
{
if($Attempt-gt0)
{Write-Output '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.';Start-Sleep -Seconds 60}
Remove-Item -LiteralPath $StdoutPath,$StderrPath -Force -ErrorAction SilentlyContinue
$Child=Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments -WorkingDirectory $StandaloneRoot `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakPrivate=0L;$OverMemoryLimit=$false
try
{
while(-not$Child.HasExited)
{
$Child.Refresh();if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal';$Child.ProcessorAffinity=[IntPtr]0xFF00
$PeakPrivate=[Math]::Max($PeakPrivate,$Child.PrivateMemorySize64)
if($PeakPrivate-ge$EightGB)
{$OverMemoryLimit=$true;$Child.Kill();$Child.WaitForExit();break}
Start-Sleep -Milliseconds 100
}
}
finally
{
if(-not$Child.HasExited){$Child.Kill();$Child.WaitForExit()}
}
$Child.WaitForExit();$Child.Refresh();$ChildExitCode=[int]$Child.ExitCode
if($ChildExitCode-eq-1073741502-and$Attempt-eq0){continue}
if($ChildExitCode-eq-1073741502){throw '0xC0000142 repeated after its single permitted retry.'}
if($OverMemoryLimit){throw "Capture viewer stopped at the 8 GiB private-memory ceiling: $Stem"}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
if(-not$LogText.Contains('[InitialClipmapReady]')-or-not(Test-Path -LiteralPath $CapturePath -PathType Leaf))
{throw "Full-residency capture missing: $Stem; exit=$ChildExitCode"}
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
$CombinedGB=$PeakPrivate/1GB
if($Memory.Success){$CombinedGB=($PeakPrivate+[double]$Memory.Groups[1].Value*1MB)/1GB}
if($CombinedGB-ge8.0){throw "Capture exceeded conservative private + DXGI-local 8 GiB ceiling: $Stem ($CombinedGB GiB)"}
$Ready=[regex]::Match($LogText,'(?m)^\[InitialClipmapReady\] ([0-9.]+) s')
Write-Output "STEP13_CAPTURE_DONE mode=$Mode view=$($View.Name) ready_s=$($Ready.Groups[1].Value) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) path=$CapturePath exit=$ChildExitCode"
if($ChildExitCode-ne0){throw "Capture viewer returned $ChildExitCode`: $Stem"}
break
}
}
}
@@ -0,0 +1,63 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep13Warmup.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Reference=$null
foreach($Workers in @(1,8))
{
$Name="step13_cpu_reference_w$Workers"
$StdoutPath=Join-Path $PSScriptRoot ($Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Name+'.stderr.log')
$Arguments=@('--stream-benchmark','--shape-world','--force-generate','--stream-hash',
'--clip-radius=3',"--workers=$Workers")
Write-Output "STEP13_CPU_REFERENCE workers=$Workers affinity=0xFF00 priority=BelowNormal command=`"$ExecutablePath`" $($Arguments -join ' ')"
for($Attempt=0;$Attempt-lt2;$Attempt++)
{
if($Attempt-gt0)
{Write-Output '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.';Start-Sleep -Seconds 60}
Remove-Item -LiteralPath $StdoutPath,$StderrPath -Force -ErrorAction SilentlyContinue
$Child=Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments -WorkingDirectory $StandaloneRoot `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakPrivate=0L;$OverMemoryLimit=$false
try
{
while(-not$Child.HasExited)
{
$Child.Refresh();if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal';$Child.ProcessorAffinity=[IntPtr]0xFF00
$PeakPrivate=[Math]::Max($PeakPrivate,$Child.PrivateMemorySize64)
if($PeakPrivate-ge8GB)
{$OverMemoryLimit=$true;$Child.Kill();$Child.WaitForExit();break}
Start-Sleep -Milliseconds 100
}
}
finally
{
if(-not$Child.HasExited){$Child.Kill();$Child.WaitForExit()}
}
$Child.WaitForExit();$Child.Refresh();$ChildExitCode=[int]$Child.ExitCode
if($ChildExitCode-eq-1073741502-and$Attempt-eq0){continue}
if($ChildExitCode-eq-1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
if($OverMemoryLimit){throw "CPU reference stopped at the 8 GiB private-memory ceiling: W$Workers"}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
$Stream=[regex]::Match($LogText,'(?m)^\[StreamFullClipmap\].*generated=2226/2226.*density_hash=(0x[0-9a-fA-F]+) mesh_hash=(0x[0-9a-fA-F]+) payload_hash=(0x[0-9a-fA-F]+) topology_hash=(0x[0-9a-fA-F]+)')
$Walkability=[regex]::Match($LogText,'(?m)^\[Walkability\] PASS 5642/5642')
if(-not$Stream.Success-or-not$Walkability.Success)
{throw "CPU reference failed full-generation/hash/walkability checks for W$Workers; exit=$ChildExitCode"}
$Hashes=@($Stream.Groups[1].Value.ToLowerInvariant(),$Stream.Groups[2].Value.ToLowerInvariant(),
$Stream.Groups[3].Value.ToLowerInvariant(),$Stream.Groups[4].Value.ToLowerInvariant())
if($null-eq$Reference){$Reference=$Hashes}
elseif(($Reference-join'|')-ne($Hashes-join'|')){throw 'CPU hashes differ between W1 and W8.'}
Write-Output "STEP13_CPU_REFERENCE_RESULT workers=$Workers hashes=$($Hashes -join ',') walkability=5642/5642 peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) exit=$ChildExitCode"
if($ChildExitCode-ne0){throw "CPU reference returned $ChildExitCode`: W$Workers"}
break
}
}
@@ -0,0 +1,77 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep13Warmup.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Runs=@(
@{Mode='cpu';Name='step13_race_cpu_A'},
@{Mode='gpu';Name='step13_race_gpu_A'},
@{Mode='gpu';Name='step13_race_gpu_B'},
@{Mode='cpu';Name='step13_race_cpu_B'}
)
$EightGB=8L*1024L*1024L*1024L
foreach($Run in $Runs)
{
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap',"--$($Run.Mode)-far")
if($Run.Mode-eq'gpu'){$Arguments+='--gpu-far-no-present-hold'}
$StdoutPath=Join-Path $PSScriptRoot ($Run.Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Run.Name+'.stderr.log')
Write-Output "STEP13_RACE_RUN mode=$($Run.Mode) name=$($Run.Name) priority=BelowNormal affinity=0xFF00 workers=8 command=`"$ExecutablePath`" $($Arguments -join ' ')"
for($Attempt=0;$Attempt-lt2;$Attempt++)
{
if($Attempt-gt0)
{Write-Output '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.';Start-Sleep -Seconds 60}
Remove-Item -LiteralPath $StdoutPath,$StderrPath -Force -ErrorAction SilentlyContinue
$HostRunClock=[Diagnostics.Stopwatch]::StartNew()
$Child=Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments -WorkingDirectory $StandaloneRoot `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakPrivate=0L;$OverMemoryLimit=$false
try
{
while(-not$Child.HasExited)
{
$Child.Refresh();if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal';$Child.ProcessorAffinity=[IntPtr]0xFF00
$PeakPrivate=[Math]::Max($PeakPrivate,$Child.PrivateMemorySize64)
if($Child.PrivateMemorySize64-ge$EightGB)
{$OverMemoryLimit=$true;$Child.Kill();$Child.WaitForExit();break}
Start-Sleep -Milliseconds 50
}
}
finally
{
if(-not$Child.HasExited){$Child.Kill();$Child.WaitForExit()}
}
$Child.WaitForExit();$Child.Refresh();$HostRunClock.Stop();$HostRunMs=$HostRunClock.Elapsed.TotalMilliseconds
$ViewerExitCode=[int]$Child.ExitCode
if($ViewerExitCode-eq-1073741502-and$Attempt-eq0){continue}
if($ViewerExitCode-eq-1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
if($OverMemoryLimit){throw "Viewer stopped at the 8 GiB private-memory ceiling: $($Run.Name)"}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
if(-not$LogText.Contains('[InitialClipmapReady]'))
{throw "Full clipmap was not ready in $($Run.Name); exit=$ViewerExitCode"}
$Ready=[regex]::Match($LogText,'(?m)^\[InitialClipmapReady\] ([0-9.]+) s')
$Presented=[regex]::Match($LogText,'(?m)^\[WholeViewPresented\] at_ms=([0-9.]+) present_ms=([0-9.]+).*hr=0x([0-9A-Fa-f]{8})')
if(-not$Presented.Success){throw "Whole-view present marker missing in $($Run.Name)"}
$Warmup=[regex]::Match($LogText,'(?m)^\[GpuShaderWarmup\] dispatched=[0-9]+ .*queue_ms=([0-9.]+)')
$Wait=[regex]::Match($LogText,'(?m)^\[GpuShaderWarmup\] completion_wait_ms=([0-9.]+)')
$WarmupMs=if($Warmup.Success){[double]$Warmup.Groups[1].Value+$(if($Wait.Success){[double]$Wait.Groups[1].Value}else{0.0})}else{0.0}
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
$CombinedGB=$PeakPrivate/1GB
if($Memory.Success){$CombinedGB=($PeakPrivate+[double]$Memory.Groups[1].Value*1MB)/1GB}
if($CombinedGB-ge8.0){throw "Viewer exceeded conservative private + DXGI-local 8 GiB ceiling: $($Run.Name) ($CombinedGB GiB)"}
Write-Output "STEP13_RACE_RESULT mode=$($Run.Mode) name=$($Run.Name) warmup_ms=$([math]::Round($WarmupMs,3)) ready_ms=$([math]::Round([double]$Ready.Groups[1].Value*1000,3)) viewer_whole_view_present_return_ms=$($Presented.Groups[1].Value) present_ms=$($Presented.Groups[2].Value) present_hr=0x$($Presented.Groups[3].Value.ToUpperInvariant()) host_process_exit_ms=$([math]::Round($HostRunMs,3)) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) exit=$ViewerExitCode"
$Controls=[regex]::Match($LogText,'(?m)^\[ProcessControls\].*')
if($Controls.Success){Write-Output $Controls.Value}
if($ViewerExitCode-ne0){throw "Viewer returned $ViewerExitCode`: $($Run.Name)"}
break
}
}
@@ -0,0 +1,59 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep13Warmup.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--gpu-far-audit','--gpu-far-prewarm')
$Name='step13_gpu_survival'
$StdoutPath=Join-Path $PSScriptRoot ($Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Name+'.stderr.log')
$EightGB=8L*1024L*1024L*1024L
Write-Output "STEP13_SURVIVAL_RUN command=`"$ExecutablePath`" $($Arguments -join ' ')"
for($Attempt=0;$Attempt-lt2;$Attempt++)
{
if($Attempt-gt0)
{Write-Output '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.';Start-Sleep -Seconds 60}
Remove-Item -LiteralPath $StdoutPath,$StderrPath -Force -ErrorAction SilentlyContinue
$Child=Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments -WorkingDirectory $StandaloneRoot `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakPrivate=0L;$OverMemoryLimit=$false
try
{
while(-not$Child.HasExited)
{
$Child.Refresh();if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal';$Child.ProcessorAffinity=[IntPtr]0xFF00
$PeakPrivate=[Math]::Max($PeakPrivate,$Child.PrivateMemorySize64)
if($Child.PrivateMemorySize64-ge$EightGB)
{$OverMemoryLimit=$true;$Child.Kill();$Child.WaitForExit();break}
Start-Sleep -Milliseconds 100
}
}
finally
{
if(-not$Child.HasExited){$Child.Kill();$Child.WaitForExit()}
}
$Child.WaitForExit();$Child.Refresh();$ChildExitCode=[int]$Child.ExitCode
if($ChildExitCode-eq-1073741502-and$Attempt-eq0){continue}
if($ChildExitCode-eq-1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
if($OverMemoryLimit){throw 'GPU survival audit stopped at the private-memory ceiling.'}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
if(-not$LogText.Contains('[InitialClipmapReady]')){throw "GPU survival audit did not reach full residency; exit=$ChildExitCode"}
$Audit=[regex]::Match($LogText,'(?m)^\[GpuFarSurvivalAudit\].*status=(PASS|FAIL)')
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
if(-not$Audit.Success){throw 'GPU survival audit result marker missing.'}
$DxgiMB=if($Memory.Success){[double]$Memory.Groups[1].Value}else{0.0}
$CombinedGB=($PeakPrivate+$DxgiMB*1MB)/1GB
if($CombinedGB-ge8.0){throw "GPU survival exceeded conservative private + DXGI-local 8 GiB ceiling: $CombinedGB GiB"}
Write-Output "STEP13_SURVIVAL_RESULT audit=$($Audit.Groups[1].Value) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) dxgi_local_peak_mb=$DxgiMB conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) exit=$ChildExitCode stdout=$StdoutPath stderr=$StderrPath"
if($Audit.Groups[1].Value-ne'PASS'){throw 'GPU survival audit failed.'}
if($ChildExitCode-ne0){throw "Viewer returned $ChildExitCode"}
break
}
@@ -0,0 +1,75 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep13Warmup.exe',[string]$OnlyEntry='')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Entries=@('EvaluateFarFieldCS','EvaluateNearFieldCS','ApplyNearFieldDetailCS','ClassifyCS','ClassifyFarCS','ScanGroupsCS','EmitCS','EmitNearCS',
'BuildFarConnectorRingsCS','EmitFarConnectorPanelsCS','ClassifyFarSkirtsCS',
'ScanFarSkirtsCS','EmitFarSkirtsCS','NoiseProbeCS')
if($OnlyEntry)
{
if($OnlyEntry-notin$Entries){throw "Unknown shader entry: $OnlyEntry"}
$Entries=@($OnlyEntry)
}
$EightGB=8L*1024L*1024L*1024L
foreach($Entry in $Entries)
{
$Name='step13_shader_'+$Entry
$StdoutPath=Join-Path $PSScriptRoot ($Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Name+'.stderr.log')
$Arguments=@('--workers=8','--gpu-far-warmup-only',"--gpu-far-warmup-entry=$Entry")
Write-Output "STEP13_SHADER_RUN entry=$Entry freshness=fresh_process priority=BelowNormal affinity=0xFF00 command=`"$ExecutablePath`" $($Arguments -join ' ')"
for($Attempt=0;$Attempt-lt2;$Attempt++)
{
if($Attempt-gt0)
{Write-Output '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.';Start-Sleep -Seconds 60}
Remove-Item -LiteralPath $StdoutPath,$StderrPath -Force -ErrorAction SilentlyContinue
$HostRunClock=[Diagnostics.Stopwatch]::StartNew()
$Child=Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments -WorkingDirectory $StandaloneRoot `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakPrivate=0L;$OverMemoryLimit=$false
try
{
while(-not$Child.HasExited)
{
$Child.Refresh();if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal';$Child.ProcessorAffinity=[IntPtr]0xFF00
$PeakPrivate=[Math]::Max($PeakPrivate,$Child.PrivateMemorySize64)
if($Child.PrivateMemorySize64-ge$EightGB)
{$OverMemoryLimit=$true;$Child.Kill();$Child.WaitForExit();break}
Start-Sleep -Milliseconds 50
}
}
finally
{
if(-not$Child.HasExited){$Child.Kill();$Child.WaitForExit()}
}
$Child.WaitForExit();$Child.Refresh();$HostRunClock.Stop();$HostRunMs=$HostRunClock.Elapsed.TotalMilliseconds
$ViewerExitCode=[int]$Child.ExitCode
if($ViewerExitCode-eq-1073741502-and$Attempt-eq0){continue}
if($ViewerExitCode-eq-1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
if($OverMemoryLimit){throw "Warmup process stopped at the 8 GiB private-memory ceiling: $Entry"}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
$Selected=[regex]::Match($LogText,'(?m)^\[GpuShaderWarmup\] dispatched=1 selected='+[regex]::Escape($Entry)+'.*queue_ms=([0-9.]+)')
$Dispatch=[regex]::Match($LogText,'(?m)^\[GpuShaderWarmup\] entry='+[regex]::Escape($Entry)+' dispatch_call_ms=([0-9.]+)')
$Wait=[regex]::Match($LogText,'(?m)^\[GpuShaderWarmup\] completion_wait_ms=([0-9.]+)')
$Create=[regex]::Match($LogText,'(?m)^\[GpuPipelineProfile\] stage=shader_object_create entry='+[regex]::Escape($Entry)+' duration_ms=([0-9.]+)')
if(-not$Selected.Success-or-not$Dispatch.Success-or-not$Wait.Success-or-not$Create.Success)
{throw "Isolated warmup markers missing for $Entry; exit=$ViewerExitCode"}
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
$CombinedGB=$PeakPrivate/1GB
if($Memory.Success){$CombinedGB=($PeakPrivate+[double]$Memory.Groups[1].Value*1MB)/1GB}
if($CombinedGB-ge8.0){throw "Warmup exceeded conservative private + DXGI-local 8 GiB ceiling: $Entry ($CombinedGB GiB)"}
Write-Output "STEP13_SHADER_RESULT entry=$Entry object_create_ms=$($Create.Groups[1].Value) dispatch_call_ms=$($Dispatch.Groups[1].Value) isolated_queue_ms=$($Selected.Groups[1].Value) completion_wait_ms=$($Wait.Groups[1].Value) host_process_exit_ms=$([math]::Round($HostRunMs,3)) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) exit=$ViewerExitCode"
if($ViewerExitCode-ne0){throw "Warmup process returned $ViewerExitCode for $Entry"}
break
}
}
@@ -0,0 +1,78 @@
# GPU port, step 13: near detail, connector rings, and skirts
## Result
The near evaluator is now split into a compact base pass and a detail pass. This moved the base shader's cold first use to 4.2 ms, but the detail pass still takes 1.596 s. The connector-ring cold first use fell from 630 ms to 409 ms. Reusing skirt probe values between classification and emission reduced those two shader first uses from about 1.409 s combined to 366 ms combined.
The production hybrid remains slower than CPU: in the fresh interleaved race its median full-view readiness was 3.044 s versus 1.356 s CPU. Shader warm-up measured about 2.04 s, and near detail remains the largest individual cold cost.
## Changes
- Added an intermediate near-field base buffer. `EvaluateNearFieldCS` writes geometry and influence values; `ApplyNearFieldDetailCS` evaluates owner/detail noise and applies the existing protected overrides. The detail noise uses one looped generic routine for the macro, mid, and fine bands. The shared octave and cellular loops use an HLSL `[loop]` annotation; the macro expands to nothing in C++.
- Reworked `FarConnectorRadius` so nominal evaluation, correction, and refinement share a loop body while retaining the same query order and convergence checks. These connector probes are off the sample lattice, so the connector still evaluates its own field probes.
- Classification now caches each skirt edge's endpoint and center probe densities in a 40-byte record. Emission consumes those values instead of evaluating the same probes again. The clipping, inside tests, and emitted geometry rules remain the same.
- Added Step 13 build, cold-first-use, CPU reference, GPU survival, capture, numeric comparison, and race scripts. No shader cache or driver settings were changed.
## Cold first-use timings
Fresh-process per-shader dispatch timings, under `BelowNormal` priority and affinity `0xFF00`:
| Shader | Step 13 cold first use |
|---|---:|
| `EvaluateFarFieldCS` | 3.008 ms |
| `EvaluateNearFieldCS` (base) | 4.233 ms |
| `ApplyNearFieldDetailCS` | 1,595.548 ms |
| `ClassifyCS` | 3.828 ms |
| `ClassifyFarCS` | 2.666 ms |
| `ScanGroupsCS` | 2.158 ms |
| `EmitCS` | 2.756 ms |
| `EmitNearCS` | 2,235.559 ms |
| `BuildFarConnectorRingsCS` | 408.708 ms |
| `EmitFarConnectorPanelsCS` | 4.822 ms |
| `ClassifyFarSkirtsCS` | 258.476 ms |
| `ScanFarSkirtsCS` | 3.844 ms |
| `EmitFarSkirtsCS` | 107.877 ms |
| `NoiseProbeCS` | 2.922 ms |
Cold-table checkpoints during the work:
| Checkpoint | Near shader timing | Connector rings | Skirt classify | Skirt emit |
|---|---:|---:|---:|---:|
| Shared `[loop]` annotations only | 1,795 ms | 531 ms | 466 ms | 966 ms |
| Near base/detail split, before connector/skirt restructuring | 3.2 ms base + 1,642 ms detail | 584 ms | 469 ms | 983 ms |
| Final Step 13 | 4.233 ms base + 1,595.548 ms detail | 408.708 ms | 258.476 ms | 107.877 ms |
The loop annotations alone did not improve near-detail first use. The pass split isolated the cost in `ApplyNearFieldDetailCS`; the later connector and skirt changes produced the reductions shown in the final row.
`EmitNearCS` remains expensive when measured alone, but it is a diagnostic near-meshing entry and is not on the production far-startup path. The production viewer's two race runs measured shader warm-up at 2,025.079 and 2,058.319 ms. The near detail pass is still the main isolated first-use floor; connector and skirt classification add several hundred milliseconds each.
| Work | Step 12 baseline | Step 13 | Change |
|---|---:|---:|---:|
| Near evaluation | 1,782.9 ms | 1,599.8 ms base + detail | 10.3% lower |
| Connector rings | 630.3 ms | 408.7 ms | 35.2% lower |
| Skirt classification + emission | about 1,409 ms | 366.4 ms | about 74.0% lower |
## Correctness and survival
- Whole near-detail agreement covered 960 tiles. Hardware and WARP both had zero topology differences; all 229,376 protected exception cells matched, with zero protected differences. Repeated GPU output was bit-identical.
- Whole far agreement covered 1,114 eligible tiles and skipped 152 near-transition tiles. Hardware and WARP both had zero topology differences; all 127,401 protected exception cells and 353 thin-feature cells matched. Repeated GPU output was bit-identical.
- CPU reference at one and eight workers produced the same hashes: density `0xdb2991c73215af4c`, mesh `0x8236c5edd7fc8e4f`, payload `0x4c48fa75a543d58e`, topology `0xbea22e76b34a4dfc`. Walkability passed at 5,642/5,642. These match Step 12.
- GPU survival audit passed after full clipmap residency. Peak private memory was 3.12 GiB and sampled DXGI local memory was 2,043.3 MiB, or 5.11 GiB combined by the conservative accounting used by the runner.
- All ten CPU/GPU capture pairs reached full residency at 1280×800. GPU viewer readiness ranged from 3.215 to 3.484 s; the highest reported combined private plus DXGI local usage was 5.44 GiB. Captures were compared numerically as RGB arrays and were not opened in an image viewer. Pairwise RGB MAE ranged from 0.0484 to 1.6092; per-view values are in [step13_capture_comparison.md](step13_capture_comparison.md).
## Interleaved fresh-launch race
Order was CPU A, GPU A, GPU B, CPU B. The viewers ran hidden, under `BelowNormal`, affinity `0xFF00`, and eight workers. The present call returned `DXGI_STATUS_OCCLUDED`, so the clocks below measure clipmap readiness and API return, not physical scanout.
| Run | Viewer readiness / present return | Shader warm-up | Host process exit |
|---|---:|---:|---:|
| CPU A | 1,359 ms | — | 1,512.945 ms |
| GPU A | 3,003 ms | 2,025.079 ms | 3,153.714 ms |
| GPU B | 3,084 ms | 2,058.319 ms | 3,275.952 ms |
| CPU B | 1,353 ms | — | 1,484.837 ms |
Median viewer readiness was **1.356 s CPU vs 3.044 s hybrid**, a **1.688 s / 2.25× CPU lead**. Median host-clock process exit was **1.499 s CPU vs 3.215 s hybrid**, a **1.716 s / 2.15× CPU lead**. The hybrid does not beat CPU yet.
## Remaining work
The main remaining first-use cost is near detail at 1.596 s. The capture and survival audits pass, but the direct-GPU LOD3/4 coverage case `(6,2,0,lod3)` was not addressed in this step.
@@ -0,0 +1,16 @@
# Step 13 full-residency capture comparison
All ten CPU/GPU pairs were captured after `[InitialClipmapReady]`. The files were compared numerically as RGB arrays and were not opened in an image viewer.
| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel |
|---|---:|---:|---:|---:|---:|---:|
| chamber | 1280x800 | 0.6076 | 1.8655 | 42.6368% | 0.0183% | 124 |
| tunnel | 1280x800 | 0.6383 | 3.3799 | 31.7335% | 0.2772% | 130 |
| passage | 1280x800 | 1.6092 | 3.6878 | 50.4982% | 0.1733% | 52 |
| far | 1280x800 | 0.7909 | 2.1558 | 44.8325% | 0.0198% | 128 |
| passage_floor_closeup | 1280x800 | 0.9314 | 2.4800 | 43.0219% | 0.0500% | 100 |
| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.9629% | 0.0708% | 55 |
| remote_yaw0 | 1280x800 | 0.1989 | 2.6627 | 4.2433% | 0.2415% | 125 |
| remote_yaw90 | 1280x800 | 0.4083 | 4.3552 | 2.2432% | 0.6948% | 94 |
| remote_yaw180 | 1280x800 | 0.0484 | 1.3206 | 2.9787% | 0.0341% | 102 |
| remote_yaw270 | 1280x800 | 0.2355 | 3.1104 | 5.5592% | 0.4098% | 107 |
@@ -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