wip(standalone): GPU field step 2 -- far-field shapes/strata/relief in shared C++/HLSL, GPU field+mesh

Seven LOD2-8 tiles: RX 7900 XTX field+meshing 0.335 ms vs CPU generateTile 15.81 ms; repeat runs
bit-identical; CPU W1/W8 44/44 hashes unchanged. Not yet a real gain: rock/air signs still come from
CPU masks for every sample. Viewer does not draw GPU tiles yet; no whole-view timing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-06 08:02:02 +02:00
1 parent d4b4377d30
commit b61ccff549
11 files changed
+2361 -9

No files matched your search

@@ -0,0 +1,105 @@
[CmdletBinding()]
param([switch]$SkipCore)
$ErrorActionPreference = 'Stop'
$HostProcess = Get-Process -Id $PID
$HostProcess.PriorityClass = 'BelowNormal'
$HostProcess.ProcessorAffinity = [IntPtr]0xFF00
$Standalone = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$BuildDir = Join-Path $Standalone 'Build\VoxelForgeGpuFieldPort'
$WrapperDir = Join-Path $BuildDir 'wrappers'
$BinDir = Join-Path $Standalone 'bin'
$Output = Join-Path $BinDir 'VoxelForgeGpuFieldPort.exe'
$CoreLib = Join-Path $BuildDir 'VoxelForgeGpuFieldPortCore.lib'
$VcVars = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat'
if (-not (Test-Path -LiteralPath $VcVars -PathType Leaf)) { throw "MSVC setup script missing: $VcVars" }
[IO.Directory]::CreateDirectory($BuildDir) | Out-Null
[IO.Directory]::CreateDirectory($WrapperDir) | Out-Null
[IO.Directory]::CreateDirectory($BinDir) | Out-Null
$Manifest = Join-Path $Standalone 'Core\core_sources.txt'
$Sources = @(Get-Content -LiteralPath $Manifest | ForEach-Object { $_.Trim() } |
Where-Object { $_ -and -not $_.StartsWith('#') })
$CompileSources = [Collections.Generic.List[string]]::new()
foreach ($SourceName in $Sources) {
$Source = [IO.Path]::GetFullPath((Join-Path $Standalone $SourceName))
if (-not $Source.StartsWith($Standalone, [StringComparison]::OrdinalIgnoreCase)) {
throw "Core source escapes Standalone: $SourceName"
}
if ($SourceName.Replace('\','/') -eq 'Core/src/VoxelForgeCore.cpp') {
$CompileSources.Add($Source)
continue
}
$WrapperName = 'gpu_vfcore_' + ($SourceName.Replace('\','/') -replace '[^A-Za-z0-9]+','_')
$WrapperPath = Join-Path $WrapperDir ($WrapperName + '.cpp')
$SourceInclude = $Source.Replace('\','/')
$WrapperText = @"
#include "CoreNamespacePreamble.h"
namespace vfcore::detail
{
#include "$SourceInclude"
}
"@
[IO.File]::WriteAllText($WrapperPath, $WrapperText, [Text.UTF8Encoding]::new($false))
$CompileSources.Add($WrapperPath)
}
$IncludeArgs = @(
('/I"{0}"' -f (Join-Path $Standalone 'Core\include')),
('/I"{0}"' -f (Join-Path $Standalone 'Core\src')),
('/I"{0}"' -f (Join-Path $Standalone 'Compat')),
('/I"{0}"' -f (Join-Path $Standalone 'Source')),
('/I"{0}"' -f (Join-Path $Standalone 'Source\VoxelForge\Public')),
('/I"{0}"' -f (Join-Path $Standalone 'Source\VoxelForge\Private'))
)
$Common = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/MP8','/D_CRT_SECURE_NO_WARNINGS') + $IncludeArgs
$CoreRsp = Join-Path $BuildDir 'field_core_compile.rsp'
[IO.File]::WriteAllLines($CoreRsp, $Common + @('/c') + @($CompileSources | ForEach-Object { '"{0}"' -f $_ }), [Text.UTF8Encoding]::new($false))
$ObjectNames = @($CompileSources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
$LibRsp = Join-Path $BuildDir 'field_core_library.rsp'
[IO.File]::WriteAllLines($LibRsp, @('/nologo',('/OUT:"{0}"' -f $CoreLib)) + $ObjectNames, [Text.UTF8Encoding]::new($false))
$RunnerRsp = Join-Path $BuildDir 'field_runner_link.rsp'
$Runner = Join-Path $PSScriptRoot 'GpuFieldPort.cpp'
$RunnerRspLines = $Common + @(
('/Fe"{0}"' -f $Output),
('"{0}"' -f $Runner),
('"{0}"' -f $CoreLib),
'/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gdi32.lib','psapi.lib'
)
[IO.File]::WriteAllLines($RunnerRsp, $RunnerRspLines, [Text.UTF8Encoding]::new($false))
Push-Location $BuildDir
try {
$BuildCommands = [Collections.Generic.List[string]]::new()
if (-not $SkipCore) {
$BuildCommands.Add(('call "{0}" >nul && cl.exe @"{1}"' -f $VcVars, $CoreRsp))
$BuildCommands.Add(('call "{0}" >nul && lib.exe @"{1}"' -f $VcVars, $LibRsp))
}
$BuildCommands.Add(('call "{0}" >nul && cl.exe @"{1}"' -f $VcVars, $RunnerRsp))
foreach ($BuildCommand in $BuildCommands) {
$attempt = 0
do {
& $env:ComSpec /d /s /c $BuildCommand
$code = $LASTEXITCODE
if ($code -eq -1073741502 -and $attempt -eq 0) {
Write-Host '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.'
Start-Sleep -Seconds 60
$attempt++
continue
}
if ($code -eq -1073741502) { throw '0xC0000142 repeated after the single retry; stopping.' }
if ($code -ne 0) { throw "GPU port build command failed with exit code $code" }
break
} while ($true)
}
if (-not $SkipCore) {
& (Join-Path $Standalone 'Core\CheckCoreSymbols.ps1') -LibraryPath $CoreLib
if ($LASTEXITCODE -ne 0) { throw "GPU port Core symbol check failed with exit code $LASTEXITCODE" }
}
if (-not (Test-Path -LiteralPath $Output -PathType Leaf)) { throw "GPU runner missing: $Output" }
} finally {
Pop-Location
}
Write-Host "Built $CoreLib"
Write-Host "Built $Output"
@@ -0,0 +1,142 @@
[CmdletBinding()]
param(
[string]$OutputName = 'VoxelForgeGpuFieldViewer.exe',
[ValidateSet('All','Core')][string]$Target = 'All'
)
$ErrorActionPreference = 'Stop'
$BuildHostProcess = Get-Process -Id $PID
$BuildHostProcess.PriorityClass = 'BelowNormal'
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00
$Root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$BuildDir = Join-Path $Root 'Build\VoxelForgeGpuFieldViewer'
$BinDir = Join-Path $Root 'bin'
[IO.Directory]::CreateDirectory($BuildDir) | Out-Null
[IO.Directory]::CreateDirectory($BinDir) | Out-Null
if ($Target -eq 'All' -and ([IO.Path]::GetFileName($OutputName) -ne $OutputName -or [IO.Path]::GetExtension($OutputName) -ne '.exe')) {
throw "OutputName must be an executable filename, not a path: $OutputName"
}
$OutputPath = Join-Path $BinDir $OutputName
$CoreLibrary = Join-Path $BuildDir 'VoxelForgeGpuFieldViewerCore.lib'
$VcVars = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat'
if (-not (Test-Path -LiteralPath $VcVars -PathType Leaf)) { throw "MSVC environment script not found: $VcVars" }
function Invoke-FieldViewerBuildCommand([string]$CommandLine,[string]$Label) {
$BuildRetry = 0
do {
& $env:ComSpec /d /s /c $CommandLine
$BuildExit = $LASTEXITCODE
if ($BuildExit -eq -1073741502 -and $BuildRetry -eq 0) {
Write-Host '0xC0000142 encountered; waiting 60 seconds before the single permitted retry.'
Start-Sleep -Seconds 60
$BuildRetry++
continue
}
if ($BuildExit -eq -1073741502) { throw "0xC0000142 repeated after one retry during $Label; stopping." }
if ($BuildExit -ne 0) { throw "$Label failed with exit code $BuildExit" }
break
} while ($true)
}
$SourceManifest = Join-Path $Root 'Core\core_sources.txt'
if (-not (Test-Path -LiteralPath $SourceManifest -PathType Leaf)) { throw "Core source manifest not found: $SourceManifest" }
$CoreSourceNames = @(Get-Content -LiteralPath $SourceManifest | ForEach-Object { $_.Trim() } |
Where-Object { $_ -and -not $_.StartsWith('#') })
if ($CoreSourceNames.Count -eq 0) { throw "Core source manifest is empty: $SourceManifest" }
$CoreSources = @($CoreSourceNames | ForEach-Object {
$SourcePath = [IO.Path]::GetFullPath((Join-Path $Root $_))
if (-not $SourcePath.StartsWith($Root,[StringComparison]::OrdinalIgnoreCase)) {
throw "Core source path escapes Standalone: $_"
}
$SourcePath
})
foreach ($Source in $CoreSources) {
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw "Required core source is missing: $Source" }
}
# Keep the canonical source manifest shared with UBT, but compile every copied implementation
# source through a generated one-source shim that places its Unreal-shaped internals in a private
# namespace. The public API implementation owns its boundary and is compiled directly.
$WrapperDir = Join-Path $BuildDir 'wrappers'
[IO.Directory]::CreateDirectory($WrapperDir) | Out-Null
$CompileSources = [Collections.Generic.List[string]]::new()
foreach ($Index in 0..($CoreSources.Count-1)) {
$ManifestName = $CoreSourceNames[$Index].Replace('\','/')
if ($ManifestName -eq 'Core/src/VoxelForgeCore.cpp') {
$CompileSources.Add($CoreSources[$Index])
continue
}
$WrapperName = 'vfcore_' + ($ManifestName -replace '[^A-Za-z0-9]+','_')
$WrapperPath = Join-Path $WrapperDir ($WrapperName + '.cpp')
$SourceInclude = $CoreSources[$Index].Replace('\','/')
$WrapperText = @"
#include "CoreNamespacePreamble.h"
namespace vfcore::detail
{
#include "$SourceInclude"
}
"@
[IO.File]::WriteAllText($WrapperPath,$WrapperText,[Text.UTF8Encoding]::new($false))
$CompileSources.Add($WrapperPath)
}
$IncludeArgs = @(
('/I"{0}"' -f (Join-Path $Root 'Core\include')),
('/I"{0}"' -f (Join-Path $Root 'Core\src')),
('/I"{0}"' -f (Join-Path $Root 'Compat')),
('/I"{0}"' -f (Join-Path $Root 'Source')),
('/I"{0}"' -f (Join-Path $Root 'Source\VoxelForge\Public')),
('/I"{0}"' -f (Join-Path $Root 'Source\VoxelForge\Private'))
)
$CommonFlags = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/MP8','/D_CRT_SECURE_NO_WARNINGS') + $IncludeArgs
$CoreRsp = Join-Path $BuildDir 'core-compile.rsp'
$CoreRspLines = $CommonFlags + @('/c') + @($CompileSources | ForEach-Object { '"{0}"' -f $_ })
[IO.File]::WriteAllLines($CoreRsp,$CoreRspLines,[Text.UTF8Encoding]::new($false))
$Objects = @($CompileSources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
$LibRsp = Join-Path $BuildDir 'core-library.rsp'
[IO.File]::WriteAllLines($LibRsp,@('/nologo',('/OUT:"{0}"' -f $CoreLibrary)) + $Objects,[Text.UTF8Encoding]::new($false))
$LegacySources = @()
$LegacyObjects = @()
$LegacyRsp = Join-Path $BuildDir 'viewer-reference-compile.rsp'
if ($Target -eq 'All') {
for ($Index=0; $Index -lt $CoreSources.Count; ++$Index) {
if ($CoreSourceNames[$Index].Replace('\','/') -eq 'Core/src/VoxelForgeCore.cpp') { continue }
$LegacySources += $CoreSources[$Index]
}
$LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
$LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ })
[IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false))
}
$ViewerRsp = Join-Path $BuildDir 'viewer-link.rsp'
$ViewerRspLines = $CommonFlags + @('/Fe"{0}"' -f $OutputPath) + $LegacyObjects + @(
('"{0}"' -f (Join-Path $Root 'Source\StandaloneViewer.cpp')),
('"{0}"' -f $CoreLibrary),
'/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gdi32.lib'
)
[IO.File]::WriteAllLines($ViewerRsp,$ViewerRspLines,[Text.UTF8Encoding]::new($false))
Push-Location $BuildDir
try {
# One cl process with /MP8 bounds compiler work to eight processes; MSVC, lib.exe, and the
# viewer link inherit BelowNormal priority and the 0xFF00 process affinity from this host.
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && lib.exe @"{2}"' -f $VcVars,$CoreRsp,$LibRsp
Invoke-FieldViewerBuildCommand $CommandLine 'VoxelForgeCore compile/library step'
if (-not (Test-Path -LiteralPath $CoreLibrary -PathType Leaf)) { throw "Core library was not produced: $CoreLibrary" }
$SymbolCheck = Join-Path $Root 'Core\CheckCoreSymbols.ps1'
& $SymbolCheck -LibraryPath $CoreLibrary
if ($Target -eq 'All') {
# The standalone viewer retains a global compatibility build only for legacy comparison
# and audits. Those objects are linked into the viewer executable, never into Core.lib.
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && cl.exe @"{2}"' -f $VcVars,$LegacyRsp,$ViewerRsp
Invoke-FieldViewerBuildCommand $CommandLine 'standalone viewer compile/link step'
}
if ($Target -eq 'All' -and -not (Test-Path -LiteralPath $OutputPath -PathType Leaf)) { throw "Viewer was not produced: $OutputPath" }
} finally {
Pop-Location
}
Write-Host "Built $CoreLibrary"
if ($Target -eq 'All') { Write-Host "Built $OutputPath" }
@@ -0,0 +1,50 @@
[CmdletBinding()]
param(
[string]$ReferenceLog = (Join-Path $PSScriptRoot '..\speed_round\gpu_field_head_source_w8.stdout.log'),
[string]$CandidateLog = (Join-Path $PSScriptRoot '..\speed_round\gpu_field_shared_far_w8.stdout.log')
)
$ErrorActionPreference = 'Stop'
$HashPattern = '\[(StreamFullDetail|StreamFullClipmap|StreamLevel)\].*?(?:lod=(\d+).*?)?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]+)'
function Read-Hashes([string]$Path) {
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Hash log not found: $Path" }
$Hashes = @{}
foreach ($Line in Get-Content -LiteralPath $Path) {
if ($Line -match $HashPattern) {
$Kind = $Matches[1]
$Key = if ($Kind -eq 'StreamLevel') { "lod$($Matches[2])" } else { $Kind }
$Hashes[$Key] = @($Matches[3], $Matches[4], $Matches[5], $Matches[6])
}
}
return $Hashes
}
$Reference = Read-Hashes $ReferenceLog
$Candidate = Read-Hashes $CandidateLog
$Differences = 0
$Fields = 0
foreach ($Key in $Reference.Keys) {
if (-not $Candidate.ContainsKey($Key)) {
Write-Output "MISSING candidate=$Key"
$Differences += 4
continue
}
for ($Index = 0; $Index -lt 4; ++$Index) {
++$Fields
if ($Reference[$Key][$Index] -ne $Candidate[$Key][$Index]) {
++$Differences
Write-Output "DIFF key=$Key field=$Index reference=$($Reference[$Key][$Index]) candidate=$($Candidate[$Key][$Index])"
}
}
}
foreach ($Key in $Candidate.Keys) {
if (-not $Reference.ContainsKey($Key)) {
Write-Output "EXTRA candidate=$Key"
$Differences += 4
}
}
$Status = if ($Differences -eq 0 -and $Fields -eq 44 -and $Reference.Count -eq 11 -and $Candidate.Count -eq 11) { 'PASS' } else { 'FAIL' }
Write-Output "[FarCpuHashCompare] status=$Status hash_fields=$Fields differences=$Differences reference_keys=$($Reference.Count) candidate_keys=$($Candidate.Count)"
if ($Status -ne 'PASS') { exit 1 }
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,69 @@
# GPU field port, step 2
## Result
This is a **far-field tile prototype**, not the requested viewer port. It now evaluates coarse shape and strata distances in an HLSL compute shader, then runs the existing GPU marching-cubes path. The CPU still compiles the world and exports selected tile records, candidate shape references, and exact rock/air and protected-air sign words. The CPU sign words drive the cell cases; the GPU does not choose topology.
The field math shared so far is in [VoxelForgeFieldShared.h](../../../Core/src/VoxelForgeFieldShared.h). Its C++/HLSL subset contains analytic ellipsoid, capsule, tapered/smooth capsule, and rounded-box distances; flat and profiled floors; smooth min/max; integer lattice hashing; value noise and the surface-relief sum; and edge-root refinement. The CPU far `CoarseRockDistance` path now calls the shared primitive-distance, floor, and blend functions. The GPU `EvaluateFarFieldCS` uses the same primitive-distance, floor, and blend source for its coarse shapes and strata. The fine LOD0/1 field, partition-of-unity detail relief, and its evaluator remain CPU-only. Although the shared relief helper exists, this far shader does not apply relief: the current canonical coarse field has no fine relief term. Strata shell repair geometry and connector shell patches are not generated by this port.
The current upload record also contains the exact per-sample candidate ranges/IDs and CPU sign words needed to preserve decisions. `FarFieldTileInput` exports strata-boundary records, but the current shader only consumes the coarse lower/upper stratum heights and blend width; it does not yet use the boundary list to emit shells.
## CPU reference hashes
The current shared-field build produced the same 44 density, mesh, payload, and topology hash fields as the fresh `/MP8` build from the clean `566d1f3` CPU source (`d4b4377` parent). [CompareFarCpuHashes.ps1](CompareFarCpuHashes.ps1) reports **44/44 fields equal** for the retained W8 logs. This comparison uses a fresh build from the current CPU source as the reference. The older retained `VoxelForgeRound11Final.exe` hash log is stale and does not match a fresh build of that source.
The input and comparison logs are [baseline](../speed_round/gpu_field_head_source_w8.stdout.log) and [shared field](../speed_round/gpu_field_shared_far_w8.stdout.log). No canonical LOD0/1 evaluator was redirected through float GPU math.
## Tile measurements
The run selected one surface-intersecting 16³-cell tile from each LOD2 through LOD8, 6,859 lattice samples per tile. The GPU was an AMD Radeon RX 7900 XTX. WARP was Microsoft’s D3D11 software device at feature level 11_0. These are seven representative tiles, not all far tiles in the view.
| Measurement across the 7 selected tiles | RX 7900 XTX | WARP | CPU reference |
| --- | ---: | ---: | ---: |
| Field evaluation timestamps | 0.1062 ms | 6.9466 ms | included in `generateTile` below |
| Field + GPU meshing timestamps | 0.3349 ms | 75.2699 ms | — |
| Full D3D wall time including upload, dispatch, and readback | 9.5323 ms summed | 136.3023 ms summed | — |
| `World::generateTile` wall time, sequential sum | — | — | 15.8055 ms |
| CPU shape/sign export and upload preparation, sequential sum | — | — | 5.8304 ms |
The measured 0.3349 ms is compute-only field plus meshing across these seven probes. The seven-tile D3D wall total includes per-tile setup and readback and is not a viewer frame cost. The CPU API time is for the same seven keys, generated sequentially by the probe, not the full-view eight-worker total. On a 90 Hz display the full frame budget is 11.11 ms; these seven kernel dispatches account for 0.335 ms (3.0% of that budget), or fit within a 0.5 ms compute slice by themselves. This says nothing about scheduling all 1,266 LOD2+ tiles, viewer draw cost, or a steady-state frame because neither is integrated.
The current CPU-only W8 clipmap benchmark took 0.691 s cold for 2,226 tiles in the baseline-source build. There is **no cold whole-view measurement with GPU far plus CPU near** yet, and no GPU chunk-crossing measurement in this step.
## Agreement and repeatability
Across all 7 tiles (48,013 lattice samples), GPU-vs-CPU and WARP-vs-CPU had zero sign differences. The largest absolute field-value difference was `8.3923e-5` voxels on both adapters; per-tile mean absolute error was `5.98e-8` to `1.384e-6` voxels. Each GPU and WARP cell case was derived from the CPU’s exact sign mask, so there were zero cell case differences by construction. RX repeat runs had zero differing vertex-component bits, cases, or indirect-draw arguments on every tested tile.
The vertex metric is the symmetric mean/max of the two directed nearest-vertex distances (CPU-to-GPU and GPU-to-CPU); it is not a continuous-surface Hausdorff distance. Per tile:
| LOD | RX field max error / WARP max error (vox) | RX/CPU vertex mean / max (cm) | WARP/CPU vertex mean / max (cm) | RX/WARP vertex mean / max (cm) | Case diffs RX / WARP |
| ---: | ---: | ---: | ---: | ---: | ---: |
| 2 | 8.3923e-5 / 8.3923e-5 | 7.8943e-5 / 0.0029297 | 7.7662e-5 / 0.0029297 | 1.5237e-5 / 0.00097656 | 0 / 0 |
| 3 | 9.5367e-6 / 9.5367e-6 | 7.0617e-5 / 0.00097656 | 7.5598e-5 / 0.00097656 | 6.8048e-6 / 0.00024414 | 0 / 0 |
| 4 | 1.1444e-5 / 9.5367e-6 | 1.3605e-4 / 0.0097656 | 1.3279e-4 / 0.0097656 | 1.8673e-5 / 0.0019531 | 0 / 0 |
| 5 | 1.5736e-5 / 1.5259e-5 | 1.8690e-4 / 0.0078125 | 2.4954e-4 / 0.0078125 | 1.4741e-4 / 0.0078125 | 0 / 0 |
| 6 | 3.0518e-5 / 3.0518e-5 | 8.7221e-5 / 0.0078125 | 5.6992e-5 / 0.0078125 | 5.0486e-5 / 0.0039063 | 0 / 0 |
| 7 | 3.0518e-5 / 3.0518e-5 | 1.0709e-4 / 0.015625 | 7.8328e-5 / 0.015625 | 7.7681e-5 / 0.015625 | 0 / 0 |
| 8 | 2.2888e-5 / 2.2888e-5 | 1.5299e-4 / 0.03125 | 1.3583e-4 / 0.03125 | 5.6220e-5 / 0.015625 | 0 / 0 |
Across tiles, the mean of the per-tile means was `0.0001171 cm` (about `1.17 µm`) for RX-vs-CPU and `0.0001152 cm` for WARP-vs-CPU. The largest per-tile maximum was `0.03125 cm` (0.3125 mm) for either CPU comparison. RX-vs-WARP mean-of-tile-means was `0.0000532 cm`, with a maximum of `0.015625 cm`. Their float outputs are not bit-identical: the field math differs slightly on the two implementations, while their sign masks and classified topology agree.
No selected tile contained a protected-air sample or critical cell. Therefore these probes do not establish route-critical or thin-wall behavior; the route-critical sample count was zero, thin features were not audited, and the required “zero differences on protected/thin features” result remains unverified. The GPU far-survival audit against shell-repaired meshes was not run.
The separate 4,096-point shared relief-helper probe was bit-identical between C++ and RX/WARP in this run. That verifies the helper on those points; it is not an end-to-end relief-field comparison because the far shader does not apply relief.
## GTX 1060-class estimate
Using the earlier first-port report’s nominal 61.4 TFLOPS RX 7900 XTX figure and the owner-provided 4.4 TFLOPS for the GTX 1060 class gives a peak-FP32 ratio of about 14:1. A direct shader-only scaling of the current seven-tile dispatch sum gives roughly **4.7 ms for field plus meshing across these seven probes** (about 0.67 ms per probe), of which the field-stage share is about 1.5 ms total. This is a rough extrapolation, not a measurement or a whole-view estimate. Pascal’s integer throughput, the field’s divergent candidate loops, clocks, and driver overhead make simple FP32 scaling especially uncertain.
## Missing deliverables
- The viewer does not yet draw the new tile buffers directly. The harness reads back GPU data for comparison.
- There is no whole-view GPU-far/CPU-near cold timing or scheduled per-frame whole-far slice measurement.
- Six fixed-view and four remote-view CPU-vs-GPU captures are not available.
- The route-critical/thin-feature GPU test and far-survival audit against GPU meshes are not available.
- GPU far shell repairs/connectors, the strata boundary list, and the relief term are not in the shader path; LOD0/1 detail relief has not been ported.
## Reproduction
The isolated build scripts are [BuildGpuFieldPort.ps1](BuildGpuFieldPort.ps1) and [BuildGpuFieldViewer.ps1](BuildGpuFieldViewer.ps1); the probe runner and final log are [RunGpuFieldPort.ps1](RunGpuFieldPort.ps1) and [far_field_lod2_8_fixed.stdout.log](far_field_lod2_8_fixed.stdout.log). Builds use `/MP8` and the host scripts and runner set BelowNormal priority with affinity `0xFF00`. The run reported peak process working set of 440.49 MiB and private memory of 611.45 MiB. The largest per-tile D3D buffer allocation was 3.135 MiB. No `0xC0000142` occurred. All build and result artifacts are under `Standalone\` with new output names; nothing was committed, pushed, or stashed.
@@ -0,0 +1,41 @@
[CmdletBinding()]
param([string]$LogStem='far_field_lod2_8')
$ErrorActionPreference='Stop'
if([IO.Path]::GetFileName($LogStem)-ne$LogStem){throw 'LogStem must be a filename stem.'}
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot 'bin\VoxelForgeGpuFieldPort.exe'
$StdoutPath=Join-Path $PSScriptRoot ($LogStem+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($LogStem+'.stderr.log')
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU field runner not found: $ExecutablePath"}
for($Attempt=0;$Attempt -lt 2;$Attempt++)
{
if($Attempt -gt 0)
{
Write-Host '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 @() -WorkingDirectory $StandaloneRoot `
-WindowStyle Hidden -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakWorkingSet=0L;$PeakPrivateMemory=0L
while(-not$Child.HasExited)
{
$Child.Refresh()
if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal'
$Child.ProcessorAffinity=[IntPtr]0xFF00
$PeakWorkingSet=[Math]::Max($PeakWorkingSet,$Child.WorkingSet64)
$PeakPrivateMemory=[Math]::Max($PeakPrivateMemory,$Child.PrivateMemorySize64)
Start-Sleep -Milliseconds 50
}
$Child.WaitForExit();$Child.Refresh()
if($Child.ExitCode -eq -1073741502 -and $Attempt -eq 0){continue}
if($Child.ExitCode -eq -1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
Write-Output "RUN_EXIT pid=$($Child.Id) priority=BelowNormal affinity=0xFF00 peak_working_set_gb=$([math]::Round($PeakWorkingSet/1GB,2)) peak_private_gb=$([math]::Round($PeakPrivateMemory/1GB,2)) exit_code=$($Child.ExitCode) stdout=$StdoutPath stderr=$StderrPath"
exit $Child.ExitCode
}
@@ -126,6 +126,73 @@ RWStructuredBuffer<uint> CellCases : register(u0);
RWStructuredBuffer<uint> CellLocalOffsets : register(u1);
RWStructuredBuffer<uint> GroupVertexCounts : register(u2);
struct FarFieldSampleRange { uint offset; uint count; };
StructuredBuffer<VF_FieldPrimitive> FarShapes : register(t10);
StructuredBuffer<FarFieldSampleRange> FarSampleShapeRanges : register(t11);
StructuredBuffer<uint> FarSampleShapeIds : register(t12);
StructuredBuffer<uint> FarRockAirSigns : register(t13);
StructuredBuffer<uint> FarProtectedAir : register(t14);
RWStructuredBuffer<float> FarFieldDensity : register(u7);
cbuffer FarFieldConstants : register(b1)
{
float FarStrataLo;
float FarStrataHi;
float FarStrataBlend;
uint FarSampleCount;
};
[numthreads(64,1,1)]
void EvaluateFarFieldCS(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 FarFieldSampleRange range = FarSampleShapeRanges[index];
float cave = 100000.0f;
float overlay = -100000.0f;
[loop]
for (uint item = 0; item < range.count; ++item)
{
const VF_FieldPrimitive shape = FarShapes[FarSampleShapeIds[range.offset + item]];
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);
float rockDistance = VF_FieldSmoothMin(base, max(cave, overlay), FarStrataBlend);
const uint protectedAir = (FarProtectedAir[index >> 5] >> (index & 31)) & 1;
if (protectedAir != 0) rockDistance = min(rockDistance, -0.0625f);
const float rawDensity = -rockDistance;
const uint air = (FarRockAirSigns[index >> 5] >> (index & 31)) & 1;
const float magnitude = max(abs(rawDensity), 0.05f);
FarFieldDensity[index] = air != 0 ? magnitude : -magnitude;
}
groupshared uint ScanValues[256];
[numthreads(256,1,1)]