wip(standalone): GPU step 17 -- far skirt-depth fix (LOD3/4 back on GPU); adaptive start trades cached speed for first-launch

Coverage: the broad missing patch on far tiles was GPU skirt depth using the LOD key
step instead of the exported coarse mesher step (800 vs 1,600 cm at (6,2,0,lod3)).
Fixed; tile pixel-identical to CPU, 680/680 + 64/64 triangles, 1,434 GPU-eligible
tiles (was 1,082). Audits: hashes w1/w8, walk 5,642/5,642, thin/protected 0 diffs
vs CPU and WARP, survival 6.15 GB.

Adaptive start: CPU owns the first view while the shader queue is pending.
My runs outside the sandbox (Step17.exe, interleaved):
  CPU 1.375 / 1.363 / 1.354 s
  GPU 2.040 (first launch of build; 0 GPU tiles, CPU worker sum 6.98 vs 5.05 s)
      / 1.279 / 1.337 / 1.328 s
Cached warm-up queue now 329-632 ms (step 16: ~3 ms), so GPU took 258 tiles
once and 0 twice: cached GPU ~= CPU, a regression from step 16's 1.26 s.
First launch improved 3.09 -> 2.04 s but still loses to CPU. Also adds
STEP16_REPORT.md, missed in 666dfb8.

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 20:36:40 +02:00
1 parent 666dfb811c
commit 6f2d33e0a9
23 files changed
+1682 -85

No files matched your search

@@ -21,6 +21,15 @@ VIEWS = (
"remote_yaw180",
"remote_yaw270",
)
ROCK = np.array((96, 86, 68))
BACKGROUND = np.array((14, 19, 27))
def mask_bounds(mask):
ys, xs = np.nonzero(mask)
if not len(xs):
return "none"
return f"({xs.min()},{ys.min()})-({xs.max()},{ys.max()})"
rows = []
for view in VIEWS:
@@ -32,6 +41,10 @@ for view in VIEWS:
raise RuntimeError(f"dimension mismatch for {view}: {cpu.shape} vs {gpu.shape}")
diff = np.abs(cpu - gpu)
pixel_max = diff.max(axis=2)
cpu_rock = np.all(cpu == ROCK, axis=2)
gpu_rock = np.all(gpu == ROCK, axis=2)
cpu_to_gpu_background = cpu_rock & np.all(gpu == BACKGROUND, axis=2)
gpu_to_cpu_background = gpu_rock & np.all(cpu == BACKGROUND, axis=2)
rows.append(
(
view,
@@ -41,6 +54,9 @@ for view in VIEWS:
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()),
int(np.count_nonzero(cpu_to_gpu_background)),
int(np.count_nonzero(gpu_to_cpu_background)),
mask_bounds(cpu_to_gpu_background),
)
)
@@ -49,13 +65,15 @@ lines = [
"",
"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 |",
"|---|---:|---:|---:|---:|---:|---:|",
"`CPU rock to GPU background` isolates the prior missing-coverage signature. Captures were loaded as RGB arrays only; no full-size image was opened in a viewer.",
"",
"| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel | CPU rock to GPU background | GPU rock to CPU background | Missing-pixel bounds |",
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---|",
]
for row in rows:
view, size, mae, rmse, changed, over32, maximum = row
view, size, mae, rmse, changed, over32, maximum, missing, extra, bounds = row
lines.append(
f"| {view} | {size} | {mae:.4f} | {rmse:.4f} | {changed:.4f}% | {over32:.4f}% | {maximum} |"
f"| {view} | {size} | {mae:.4f} | {rmse:.4f} | {changed:.4f}% | {over32:.4f}% | {maximum} | {missing} | {extra} | {bounds} |"
)
report = ROOT / f"{PREFIX}_capture_comparison.md"
@@ -0,0 +1,85 @@
import argparse
from pathlib import Path
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent
DEFAULT_CPU_PATH = "step17_tilecontrol_cpu.png"
DEFAULT_CONTROLS = (
("GPU indirect draw", "step17_tilecontrol_gpu.png"),
("GPU direct draw", "step17_directdraw_gpu_direct_draw.png"),
("GPU readback copy (cm)", "step17_readbackcopy_gpu_readback_copy.png"),
("GPU readback copy (m)", "step17_metercopy_gpu_meter_copy.png"),
("CPU mesh in GPU draw loop", "step17_cpumeshcopy_gpu_cpu_mesh_copy.png"),
("GPU depth test disabled", "step17_tilecontrol_gpu_depth_disabled.png"),
("GPU skirts disabled", "step17_tilecontrol_gpu_skirts_off.png"),
("GPU connectors disabled", "step17_tilecontrol_gpu_connectors_off.png"),
("GPU skirt-depth fix", "step17_r19_fixed_gpu.png"),
(
"GPU skirts and connectors disabled",
"step17_tilecontrol_gpu_skirts_connectors_off.png",
),
)
parser = argparse.ArgumentParser()
parser.add_argument("--cpu", default=DEFAULT_CPU_PATH)
parser.add_argument("--control", action="append", default=[], metavar="LABEL=PNG")
parser.add_argument("--report", default="step17_tilecontrol_comparison.md")
parser.add_argument("--include-existing-controls", action="store_true")
args = parser.parse_args()
if Path(args.cpu).name != args.cpu or Path(args.report).name != args.report:
raise RuntimeError("CPU image and report must be filenames within this directory")
controls = list(DEFAULT_CONTROLS) if args.include_existing_controls else []
for item in args.control:
label, separator, filename = item.partition("=")
if not separator or not label or Path(filename).name != filename:
raise RuntimeError(f"invalid --control value: {item!r}")
controls.append((label, filename))
CPU_PATH = ROOT / args.cpu
cpu = np.asarray(Image.open(CPU_PATH).convert("RGB"), dtype=np.int16)
cpu_rock = np.all(cpu == (96, 86, 68), axis=2)
background = np.array((14, 19, 27))
rows = []
for label, filename in controls:
path = ROOT / filename
gpu = np.asarray(Image.open(path).convert("RGB"), dtype=np.int16)
if cpu.shape != gpu.shape:
raise RuntimeError(f"dimension mismatch for {label}: {cpu.shape} vs {gpu.shape}")
diff = np.abs(cpu - gpu)
pixel_max = diff.max(axis=2)
missing_cpu_rock = cpu_rock & np.all(gpu == background, axis=2)
rows.append(
(
label,
f"{gpu.shape[1]}x{gpu.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),
int(np.count_nonzero(missing_cpu_rock)),
int(pixel_max.max()),
)
)
lines = [
"# Step 17 LOD3 tile control comparison",
"",
"The `(6,2,0,lod3)` target was the only drawn tile, with the full desired clipmap retained. Captures were loaded as RGB arrays and compared numerically; full-size images were not opened in a viewer.",
"",
"`CPU rock to GPU background` counts pixels where the CPU target is rock `(96,86,68)` and the control is background `(14,19,27)`, the prior missing-coverage signature.",
"",
"| Control | Size | RGB MAE | RGB RMSE | Changed pixels | CPU rock to GPU background | Max channel |",
"|---|---:|---:|---:|---:|---:|---:|",
]
for label, size, mae, rmse, changed, missing, maximum in rows:
lines.append(
f"| {label} | {size} | {mae:.4f} | {rmse:.4f} | {changed:.4f}% | {missing} | {maximum} |"
)
report = ROOT / args.report
report.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(report.read_text(encoding="utf-8"), end="")
@@ -0,0 +1,29 @@
[CmdletBinding()]
param([string]$Entry='ApplyNearFieldDetailCS')
$ErrorActionPreference='Stop'
$BuildHostProcess=Get-Process -Id $PID
$BuildHostProcess.PriorityClass='BelowNormal'
$BuildHostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ShaderSource=Join-Path $StandaloneRoot 'Research\OwnerShapeWorld\gpu_first_port\VoxelForgeGpuMesher.hlsl'
$OutputPath=Join-Path $PSScriptRoot ('precompiled\'+$Entry+'.cso')
if(-not(Test-Path -LiteralPath $ShaderSource -PathType Leaf)){throw "Shader source missing: $ShaderSource"}
if($Entry-notin@('ApplyNearFieldDetailCS')){throw "Unsupported Step 14 shader entry: $Entry"}
$FxCRoot='C:\Program Files (x86)\Windows Kits\10\bin'
$FxC=Get-ChildItem -LiteralPath $FxCRoot -Filter fxc.exe -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match '\\x64\\fxc\.exe$' } |
Sort-Object -Property FullName -Descending | Select-Object -First 1
if(-not$FxC){throw 'fxc.exe was not found.'}
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}
& $FxC.FullName /nologo /T cs_5_0 /E $Entry /O3 /Gis /Ges /Fo $OutputPath $ShaderSource
$BuildExit=$LASTEXITCODE
if($BuildExit-eq-1073741502-and$Attempt-eq0){continue}
if($BuildExit-eq-1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
if($BuildExit-ne0){throw "fxc failed for $Entry with exit code $BuildExit"}
break
}
Write-Output "STEP14_SHADER_BUILD entry=$Entry output=$OutputPath exit=0"
@@ -15,7 +15,16 @@ namespace VoxelForgeGpuFarViewer
{
inline constexpr std::uint32_t PooledOutputVertexCapacityPerTile=19456;
struct CpuVertex { float position[3]{}, normal[3]{}; };
struct CpuMesh { std::vector<CpuVertex> vertices; std::vector<std::uint32_t> indices; };
struct CpuMesh
{
std::vector<CpuVertex> vertices;
std::vector<std::uint32_t> indices;
std::uint32_t surfaceVertexCount=0;
std::uint32_t connectorPanelVertexCount=0;
std::uint32_t skirtVertexCount=0;
bool sequentialIndices=false;
bool indirectArgsValid=false;
};
struct GpuTimingQueries
{
ID3D11Query* start=nullptr;
@@ -2352,7 +2352,8 @@ namespace
}
std::vector<FarSelection> SelectFarTiles(World& world,const WorldConfiguration& config,
Vec3d camera,bool wholeFar,bool wholeNear)
Vec3d camera,bool wholeFar,bool wholeNear,
bool findThinFeature)
{
std::vector<FarSelection> result;
const auto desired=GetFullDesired(world,config,camera);
@@ -2386,6 +2387,43 @@ namespace
static_cast<unsigned long long>(perLod[1]));
return result;
}
if(findThinFeature)
{
std::uint64_t examined=0,eligible=0;
for(const TileKey& key:desired)
{
if(key.lod<std::max(config.fullResolutionLodCount,2)||key.lod>lastLod)continue;
++examined;
const auto origin=TileOrigin(key);
const std::int32_t cells=std::max(1,config.coarseTileCells);
const std::int32_t step=std::max(1,(32<<key.lod)/cells);
if(world.classifyBox({origin[0],origin[1],origin[2]},step,cells)!=TileClass::mixed)
continue;
++eligible;
std::string error;
const auto exportStart=Clock::now();
auto input=world.exportFarFieldTile(key,error);
const double exportPreparationMs=std::chrono::duration<double,std::milli>(
Clock::now()-exportStart).count();
if(!input)
{
if(error.find("near analytic/detail region")!=std::string::npos)continue;
throw std::runtime_error("thin-feature search far field export failed: "+error);
}
if(input->protectedThinSampleCount==0)continue;
std::printf("[ThinFeatureTileSelected] key=(%d,%d,%d) lod=%d protected_thin_samples=%llu examined=%llu eligible=%llu\n",
key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod,
static_cast<unsigned long long>(input->protectedThinSampleCount),
static_cast<unsigned long long>(examined),
static_cast<unsigned long long>(eligible));
result.push_back({key,std::move(*input),exportPreparationMs});
return result;
}
std::printf("[ThinFeatureSearch] examined=%llu eligible=%llu result=no_protected_thin_samples_found\n",
static_cast<unsigned long long>(examined),
static_cast<unsigned long long>(eligible));
return result;
}
for(int32_t lod=std::max(config.fullResolutionLodCount,2);lod<=lastLod;++lod)
{
std::vector<TileKey> candidates;
@@ -2978,6 +3016,7 @@ namespace
const std::filesystem::path shaderPath = root / "Research" / "OwnerShapeWorld" / "gpu_first_port" / "VoxelForgeGpuMesher.hlsl";
bool wholeFar=false;
bool wholeNear=false;
bool findThinFeature=false;
bool profileGpuKernels=false;
bool skirtAuditOnly=false;
int clipRadiusOverride=-1;
@@ -2990,6 +3029,7 @@ namespace
if(std::string_view(argv[i])=="--whole-near")wholeNear=true;
if(std::string_view(argv[i])=="--profile-kernels")profileGpuKernels=true;
if(std::string_view(argv[i])=="--skirt-audit")skirtAuditOnly=true;
if(std::string_view(argv[i])=="--find-thin-feature")findThinFeature=true;
const std::string_view arg=argv[i];
if(arg.starts_with("--tile-limit="))
tileLimit=std::max(0,std::atoi(std::string(arg.substr(13)).c_str()));
@@ -3068,7 +3108,8 @@ namespace
noiseWarp.maximum, static_cast<unsigned long long>(noiseWarp.differingBits));
std::vector<FarSelection> selected = skirtAuditOnly
?std::vector<FarSelection>{}:SelectFarTiles(world, config, camera,wholeFar,wholeNear);
?std::vector<FarSelection>{}:SelectFarTiles(world, config, camera,wholeFar,wholeNear,
findThinFeature);
if(skirtAuditOnly)
{
if(!config.generateSkirts)
@@ -3411,6 +3452,12 @@ namespace VoxelForgeGpuFarViewer
auditReadback!=nullptr,auditReadback!=nullptr,outputSlot,true,captureGpuTiming,batchTimingMode);
if(auditReadback)
{
auditReadback->surfaceVertexCount=static_cast<std::uint32_t>(result.drawArgs[0]);
auditReadback->connectorPanelVertexCount=static_cast<std::uint32_t>(
result.vertices.size()-std::min<std::size_t>(result.vertices.size(),result.drawArgs[0]));
auditReadback->skirtVertexCount=static_cast<std::uint32_t>(result.skirtVertices.size());
auditReadback->sequentialIndices=result.sequentialIndices;
auditReadback->indirectArgsValid=result.indexedIndirectArgsValid;
const std::size_t completeGpuVertexCount=result.vertices.size()+result.skirtVertices.size();
auditReadback->vertices.resize(completeGpuVertexCount);
for(std::size_t i=0;i<result.vertices.size();++i)
@@ -4,7 +4,8 @@ param(
[string]$LogStem='gpu_far_viewer',
[int]$ClipRadius=3,
[string]$CaptureName='',
[string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe'
[string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe',
[switch]$WarmupBeforeWorkers
)
$ErrorActionPreference='Stop'
@@ -23,6 +24,7 @@ $Arguments=@("--clip-radius=$ClipRadius",'--workers=8','--quit-after-clipmap')
if($Mode-eq'gpu'){$Arguments+='--gpu-far'}
elseif($Mode-eq'cpu'){$Arguments+='--cpu-far'}
else{$Arguments+='--gpu-far-audit'}
if($Mode-eq'gpu'-and$WarmupBeforeWorkers){$Arguments+='--gpu-far-warmup-before-workers'}
if($CaptureName)
{
if([IO.Path]::GetFileName($CaptureName)-ne$CaptureName-or[IO.Path]::GetExtension($CaptureName)-ne'.png')
@@ -1,5 +1,9 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep15Timeline.exe',[string]$Prefix='step15')
param(
[string]$ExecutableName='VoxelForgeGpuFieldStep15Timeline.exe',
[string]$Prefix='step15',
[switch]$GpuWarmupBeforeWorkers
)
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
@@ -32,6 +36,7 @@ foreach($View in $Views)
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--capture-after-clipmap',
"--camera=$($View.Camera)","--$Mode-far",
"--capture=Research/OwnerShapeWorld/gpu_field_port/$CaptureName")
if($Mode-eq'gpu'-and$GpuWarmupBeforeWorkers){$Arguments+='--gpu-far-warmup-before-workers'}
$StdoutPath=Join-Path $PSScriptRoot ($Stem+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Stem+'.stderr.log')
$CapturePath=Join-Path $PSScriptRoot $CaptureName
@@ -0,0 +1,68 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep16FairRace.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Runs=@(
@{Drain=$true;Name='step16_cpu_drain_A'},
@{Drain=$false;Name='step16_cpu_paced_A'},
@{Drain=$false;Name='step16_cpu_paced_B'},
@{Drain=$true;Name='step16_cpu_drain_B'}
)
$EightGB=8L*1024L*1024L*1024L
foreach($Run in $Runs)
{
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--gpu-far-timeline','--cpu-far')
if(-not$Run.Drain){$Arguments+='--gpu-far-no-cold-drain'}
$StdoutPath=Join-Path $PSScriptRoot ($Run.Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Run.Name+'.stderr.log')
Write-Output "STEP16_CPU_CONTROL_RUN name=$($Run.Name) drain=$(if($Run.Drain){'unlimited_before_ready'}else{'paced_4_batches_or_2ms'}) 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()
$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
$Ready=[regex]::Match($LogText,'(?m)^\[InitialClipmapReady\] ([0-9.]+) s')
$Presented=[regex]::Match($LogText,'(?m)^\[WholeViewPresented\] at_ms=([0-9.]+) present_ms=([0-9.]+) sync=(\d+) hr=0x([0-9A-Fa-f]{8})')
$Frames=[regex]::Matches($LogText,'(?m)^\[ColdFrame\] stage=begin frame=(\d+)')
if(-not$Ready.Success-or-not$Presented.Success){throw "Readiness/present marker missing in $($Run.Name); exit=$ViewerExitCode"}
$FrameCount=if($Frames.Count){[int]$Frames[$Frames.Count-1].Groups[1].Value}else{0}
Write-Output "STEP16_CPU_CONTROL_RESULT name=$($Run.Name) drain=$(if($Run.Drain){'unlimited_before_ready'}else{'paced_4_batches_or_2ms'}) ready_internal_ms=$([math]::Round([double]$Ready.Groups[1].Value*1000,3)) whole_view_present_internal_ms=$($Presented.Groups[1].Value) present_ms=$($Presented.Groups[2].Value) present_hr=0x$($Presented.Groups[4].Value.ToUpperInvariant()) cold_frames=$FrameCount host_process_ms=$([math]::Round($HostRunClock.Elapsed.TotalMilliseconds,3)) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) exit=$ViewerExitCode stdout=$StdoutPath stderr=$StderrPath"
if($ViewerExitCode-ne0){throw "Viewer returned $ViewerExitCode`: $($Run.Name)"}
break
}
}
@@ -0,0 +1,93 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep16FairRace.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
if(-not('Step16WindowControl' -as [type]))
{
Add-Type @'
using System;
using System.Runtime.InteropServices;
public static class Step16WindowControl
{
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
}
'@
}
$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"}
# Same paced CPU flags and workload; only viewer startup visibility changes.
$Runs=@(
@{WindowStyle='Hidden';Name='step16_cpu_paced_hidden_A'},
@{WindowStyle='Normal';Name='step16_cpu_paced_visible_A'},
@{WindowStyle='Normal';Name='step16_cpu_paced_visible_B'},
@{WindowStyle='Hidden';Name='step16_cpu_paced_hidden_B'}
)
$EightGB=8L*1024L*1024L*1024L
foreach($Run in $Runs)
{
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--gpu-far-timeline','--cpu-far','--gpu-far-no-cold-drain')
$StdoutPath=Join-Path $PSScriptRoot ($Run.Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Run.Name+'.stderr.log')
Write-Output "STEP16_PRESENT_CONTROL_RUN name=$($Run.Name) startup_window=$($Run.WindowStyle) 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 $Run.WindowStyle -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakPrivate=0L;$OverMemoryLimit=$false;$ForegroundRaised=$false
try
{
while(-not$Child.HasExited)
{
$Child.Refresh();if($Child.HasExited){break}
$Child.PriorityClass='BelowNormal';$Child.ProcessorAffinity=[IntPtr]0xFF00
if($Run.WindowStyle-eq'Normal'-and-not$ForegroundRaised)
{
$WindowHandle=$Child.MainWindowHandle
if($WindowHandle-ne[IntPtr]::Zero)
{
[void][Step16WindowControl]::ShowWindow($WindowHandle,5)
$ForegroundRaised=[Step16WindowControl]::SetForegroundWindow($WindowHandle)
}
}
$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()
$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
$Ready=[regex]::Match($LogText,'(?m)^\[InitialClipmapReady\] ([0-9.]+) s')
$Presented=[regex]::Match($LogText,'(?m)^\[WholeViewPresented\] at_ms=([0-9.]+) present_ms=([0-9.]+) sync=(\d+) hr=0x([0-9A-Fa-f]{8})')
$Shape=[regex]::Match($LogText,'(?m)^\[ColdTimeline\] stage=shape_compile end_ms=[0-9.]+ duration_ms=([0-9.]+)')
$Frames=[regex]::Matches($LogText,'(?m)^\[ColdFrame\] stage=begin frame=(\d+)')
if(-not$Ready.Success-or-not$Presented.Success-or-not$Shape.Success)
{throw "Required timing/present marker missing in $($Run.Name); exit=$ViewerExitCode"}
$PresentHR=$Presented.Groups[4].Value.ToUpperInvariant()
$PresentState=if($PresentHR-eq'087A0001'){'occluded'}elseif($PresentHR-eq'00000000'){'visible_success'}else{'other'}
$FrameCount=if($Frames.Count){[int]$Frames[$Frames.Count-1].Groups[1].Value}else{0}
Write-Output "STEP16_PRESENT_CONTROL_RESULT name=$($Run.Name) startup_window=$($Run.WindowStyle) foreground_raised=$ForegroundRaised ready_internal_ms=$([math]::Round([double]$Ready.Groups[1].Value*1000,3)) whole_view_present_internal_ms=$($Presented.Groups[1].Value) present_ms=$($Presented.Groups[2].Value) present_sync=$($Presented.Groups[3].Value) present_hr=0x$PresentHR present_state=$PresentState cold_frames=$FrameCount shape_compile_ms=$($Shape.Groups[1].Value) host_process_ms=$([math]::Round($HostRunClock.Elapsed.TotalMilliseconds,3)) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) exit=$ViewerExitCode stdout=$StdoutPath stderr=$StderrPath"
if($ViewerExitCode-ne0){throw "Viewer returned $ViewerExitCode`: $($Run.Name)"}
break
}
}
@@ -0,0 +1,84 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep16FairRace.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
# Counterbalance which mode goes first in each pair while keeping launches interleaved.
$Runs=@(
@{Mode='gpu';Name='step16_race_gpu_A'},
@{Mode='cpu';Name='step16_race_cpu_A'},
@{Mode='cpu';Name='step16_race_cpu_B'},
@{Mode='gpu';Name='step16_race_gpu_B'},
@{Mode='gpu';Name='step16_race_gpu_C'},
@{Mode='cpu';Name='step16_race_cpu_C'},
@{Mode='cpu';Name='step16_race_cpu_D'},
@{Mode='gpu';Name='step16_race_gpu_D'}
)
$EightGB=8L*1024L*1024L*1024L
$ModeCounts=@{cpu=0;gpu=0}
foreach($Run in $Runs)
{
$ModeCounts[$Run.Mode]++
$LaunchClass=if($ModeCounts[$Run.Mode]-eq1){'first_mode_launch'}else{'subsequent_mode_launch'}
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--gpu-far-timeline',"--$($Run.Mode)-far")
$StdoutPath=Join-Path $PSScriptRoot ($Run.Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Run.Name+'.stderr.log')
Write-Output "STEP16_RUN mode=$($Run.Mode) name=$($Run.Name) launch_class=$LaunchClass 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()
$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
$Ready=[regex]::Match($LogText,'(?m)^\[InitialClipmapReady\] ([0-9.]+) s')
$Presented=[regex]::Match($LogText,'(?m)^\[WholeViewPresented\] at_ms=([0-9.]+) present_ms=([0-9.]+) sync=(\d+) hr=0x([0-9A-Fa-f]{8})')
$Shape=[regex]::Match($LogText,'(?m)^\[ColdTimeline\] stage=shape_compile end_ms=[0-9.]+ duration_ms=([0-9.]+)')
$Frames=[regex]::Matches($LogText,'(?m)^\[ColdFrame\] stage=begin frame=(\d+)')
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
if(-not$Ready.Success-or-not$Presented.Success-or-not$Shape.Success)
{throw "Required timing/present marker missing in $($Run.Name); exit=$ViewerExitCode"}
$PresentHR=$Presented.Groups[4].Value.ToUpperInvariant()
$PresentState=if($PresentHR-eq'087A0001'){'occluded'}elseif($PresentHR-eq'00000000'){'visible_success'}else{'other'}
$DxgiMB=if($Memory.Success){[double]$Memory.Groups[1].Value}else{$null}
$CombinedGB=$PeakPrivate/1GB
if($null-ne$DxgiMB){$CombinedGB=($PeakPrivate+$DxgiMB*1MB)/1GB}
if($CombinedGB-ge8.0){throw "Viewer exceeded conservative private + DXGI-local 8 GiB ceiling: $($Run.Name) ($CombinedGB GiB)"}
$FrameCount=if($Frames.Count){[int]$Frames[$Frames.Count-1].Groups[1].Value}else{0}
Write-Output "STEP16_RESULT mode=$($Run.Mode) name=$($Run.Name) launch_class=$LaunchClass ready_internal_ms=$([math]::Round([double]$Ready.Groups[1].Value*1000,3)) whole_view_present_internal_ms=$($Presented.Groups[1].Value) present_ms=$($Presented.Groups[2].Value) present_sync=$($Presented.Groups[3].Value) present_hr=0x$PresentHR present_state=$PresentState cold_frames=$FrameCount shape_compile_ms=$($Shape.Groups[1].Value) host_process_ms=$([math]::Round($HostRunClock.Elapsed.TotalMilliseconds,3)) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) dxgi_local_peak_mb=$(if($null-ne$DxgiMB){$DxgiMB}else{'not_reported'}) conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) exit=$ViewerExitCode stdout=$StdoutPath stderr=$StderrPath"
if($ViewerExitCode-ne0){throw "Viewer returned $ViewerExitCode`: $($Run.Name)"}
break
}
}
@@ -0,0 +1,75 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep16FairRace.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
# Counterbalance warm-up and no-warm-up launches; all other arguments stay fixed.
$Runs=@(
@{Prewarm=$true;Name='step16_gpu_warmup_A'},
@{Prewarm=$false;Name='step16_gpu_no_prewarm_A'},
@{Prewarm=$false;Name='step16_gpu_no_prewarm_B'},
@{Prewarm=$true;Name='step16_gpu_warmup_B'}
)
$EightGB=8L*1024L*1024L*1024L
foreach($Run in $Runs)
{
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--gpu-far-timeline','--gpu-far')
if(-not$Run.Prewarm){$Arguments+='--gpu-far-no-prewarm'}
$StdoutPath=Join-Path $PSScriptRoot ($Run.Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Run.Name+'.stderr.log')
Write-Output "STEP16_GPU_WARMUP_CONTROL_RUN name=$($Run.Name) prewarm=$($Run.Prewarm) 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()
$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
$Ready=[regex]::Match($LogText,'(?m)^\[InitialClipmapReady\] ([0-9.]+) s')
$Presented=[regex]::Match($LogText,'(?m)^\[WholeViewPresented\] at_ms=([0-9.]+) present_ms=([0-9.]+) sync=(\d+) hr=0x([0-9A-Fa-f]{8})')
$Shape=[regex]::Match($LogText,'(?m)^\[ColdTimeline\] stage=shape_compile end_ms=[0-9.]+ duration_ms=([0-9.]+)')
$WarmupQueue=[regex]::Match($LogText,'(?m)^\[GpuShaderWarmup\] dispatched=.* queue_ms=([0-9.]+)')
$FirstPresent=[regex]::Match($LogText,'(?m)^\[PresentProfile\] ordinal=1 .* call_ms=([0-9.]+)')
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
if(-not$Ready.Success-or-not$Presented.Success-or-not$Shape.Success)
{throw "Required timing/present marker missing in $($Run.Name); exit=$ViewerExitCode"}
$DxgiMB=if($Memory.Success){[double]$Memory.Groups[1].Value}else{0.0}
$CombinedGB=($PeakPrivate+$DxgiMB*1MB)/1GB
if($CombinedGB-ge8.0){throw "Viewer exceeded conservative private + DXGI-local 8 GiB ceiling: $($Run.Name) ($CombinedGB GiB)"}
Write-Output "STEP16_GPU_WARMUP_CONTROL_RESULT name=$($Run.Name) prewarm=$($Run.Prewarm) ready_internal_ms=$([math]::Round([double]$Ready.Groups[1].Value*1000,3)) whole_view_present_internal_ms=$($Presented.Groups[1].Value) present_ms=$($Presented.Groups[2].Value) present_hr=0x$($Presented.Groups[4].Value.ToUpperInvariant()) shape_compile_ms=$($Shape.Groups[1].Value) warmup_queue_ms=$(if($WarmupQueue.Success){$WarmupQueue.Groups[1].Value}else{'not_reported'}) first_present_call_ms=$(if($FirstPresent.Success){$FirstPresent.Groups[1].Value}else{'not_reported'}) host_process_ms=$([math]::Round($HostRunClock.Elapsed.TotalMilliseconds,3)) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) dxgi_local_peak_mb=$DxgiMB conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) exit=$ViewerExitCode stdout=$StdoutPath stderr=$StderrPath"
if($ViewerExitCode-ne0){throw "Viewer returned $ViewerExitCode`: $($Run.Name)"}
break
}
}
@@ -0,0 +1,117 @@
[CmdletBinding()]
param(
[string]$ExecutableName='VoxelForgeGpuFieldStep17.exe',
[string]$Prefix='step17_tilecontrol',
[ValidateSet('all','cpu','gpu','gpu_direct_draw','gpu_readback_copy','gpu_meter_copy','gpu_cpu_mesh_copy','gpu_depth_disabled','gpu_skirts_off','gpu_connectors_off','gpu_skirts_connectors_off')]
[string]$OnlyControl='all'
)
$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"}
$Controls=@(
@{Name='cpu';Flags=@('--cpu-far')},
@{Name='gpu';Flags=@('--gpu-far-warmup-before-workers')},
@{Name='gpu_direct_draw';Flags=@('--gpu-far-warmup-before-workers','--gpu-far-direct-draw-audit')},
@{Name='gpu_readback_copy';Flags=@('--gpu-far-warmup-before-workers','--gpu-far-readback-copy-draw-audit')},
@{Name='gpu_meter_copy';Flags=@('--gpu-far-warmup-before-workers','--gpu-far-meter-copy-draw-audit')},
@{Name='gpu_cpu_mesh_copy';Flags=@('--gpu-far-warmup-before-workers','--gpu-far-cpu-mesh-draw-audit')},
@{Name='gpu_depth_disabled';Flags=@('--gpu-far-warmup-before-workers','--gpu-far-depth-disabled-audit')},
@{Name='gpu_skirts_off';Flags=@('--gpu-far-warmup-before-workers','--gpu-skirts-disabled')},
@{Name='gpu_connectors_off';Flags=@('--gpu-far-warmup-before-workers','--gpu-connectors-disabled')},
@{Name='gpu_skirts_connectors_off';Flags=@('--gpu-far-warmup-before-workers','--gpu-skirts-disabled','--gpu-connectors-disabled')}
)
if($OnlyControl-ne'all'){$Controls=@($Controls|Where-Object{$_.Name-eq$OnlyControl})}
$EightGB=8L*1024L*1024L*1024L
foreach($Control in $Controls)
{
$Stem="${Prefix}_$($Control.Name)"
$CaptureName="$Stem.png"
$CapturePath=Join-Path $PSScriptRoot $CaptureName
$StdoutPath=Join-Path $PSScriptRoot ($Stem+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Stem+'.stderr.log')
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap','--capture-after-clipmap',
'--camera=500,0,0,0,0',
'--draw-only-tile=6,2,0,3','--gpu-skirt-draw-audit',"--capture=Research/OwnerShapeWorld/gpu_field_port/$CaptureName")
$Arguments+=$Control.Flags
Write-Output "STEP17_TILE_CONTROL name=$($Control.Name) 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
$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 "Tile control stopped at the 8 GiB private-memory ceiling: $($Control.Name)"}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
if(-not$LogText.Contains('[InitialClipmapReady]')-or-not(Test-Path -LiteralPath $CapturePath -PathType Leaf))
{throw "Full-residency tile control capture missing: $($Control.Name); exit=$ChildExitCode"}
if($Control.Name-ne'cpu')
{
if(-not$LogText.Contains('key=(6,2,0,lod3) route=gpu_eligible'))
{throw "Target tile was not directly GPU eligible: $($Control.Name)"}
if(-not$LogText.Contains('[GpuSkirtDrawArgs] key=(6,2,0,lod3)'))
{throw "Target draw arguments were not audited: $($Control.Name)"}
$PooledOutputAudit=[regex]::Match($LogText,'(?m)^\[GpuPooledOutputReadback\] key=\(6,2,0,lod3\).*sequential_indices=(\d+) indirect_args_valid=(\d+).*status=(PASS|FAIL)')
if(-not$PooledOutputAudit.Success-or$PooledOutputAudit.Groups[1].Value-ne'1'-or$PooledOutputAudit.Groups[2].Value-ne'1'-or$PooledOutputAudit.Groups[3].Value-ne'PASS')
{throw "Target pooled GPU indices or indirect arguments failed readback audit: $($Control.Name)"}
}
$Memory=[regex]::Match($LogText,'(?m)^\[GpuFarMemory\] dxgi_local_sampled_peak_mb=([0-9.]+)')
$DxgiMB=if($Memory.Success){[double]$Memory.Groups[1].Value}else{0.0}
$CombinedGB=($PeakPrivate+$DxgiMB*1MB)/1GB
if($CombinedGB-ge8.0){throw "Tile control exceeded private + DXGI-local 8 GiB: $($Control.Name) ($CombinedGB GiB)"}
$ArgsAudit=[regex]::Match($LogText,'(?m)^\[GpuSkirtDrawArgs\] key=\(6,2,0,lod3\).*surface_indices=(\d+).*surface_start_index=(\d+).*expected_surface_start_index=(\d+).*skirt_panel_indices=(\d+).*connector_start_index=(\d+).*expected_connector_start_index=(\d+)')
if($Control.Name-ne'cpu')
{
$ArgsSummary=[regex]::Match($LogText,'(?m)^\[GpuSkirtDrawArgsSummary\].*status=(PASS|FAIL)')
if(-not$ArgsAudit.Success-or-not$ArgsSummary.Success-or$ArgsSummary.Groups[1].Value-ne'PASS')
{throw "Target indirect arguments failed audit: $($Control.Name)"}
if($ArgsAudit.Groups[2].Value-ne$ArgsAudit.Groups[3].Value-or$ArgsAudit.Groups[5].Value-ne$ArgsAudit.Groups[6].Value)
{throw "Target pooled draw starts mismatch: $($Control.Name)"}
$TriangleAudit=[regex]::Match($LogText,'(?m)^\[GpuCpuTriangleAudit\] key=\(6,2,0,lod3\).*cpu_surface_triangles=(\d+) gpu_surface_triangles=(\d+).*unmatched_cpu=(\d+) unmatched_gpu=(\d+)')
if(-not$TriangleAudit.Success-or$TriangleAudit.Groups[1].Value-ne$TriangleAudit.Groups[2].Value-or$TriangleAudit.Groups[3].Value-ne'0'-or$TriangleAudit.Groups[4].Value-ne'0')
{throw "Target CPU/GPU surface triangle audit failed: $($Control.Name)"}
if($Control.Name-eq'gpu_direct_draw'-and-not$LogText.Contains('[GpuDirectDrawAudit] key=(6,2,0,lod3)'))
{throw 'Direct GPU draw control did not use the audited direct path.'}
if(($Control.Name -eq 'gpu_readback_copy' -or $Control.Name -eq 'gpu_meter_copy') -and
-not $LogText.Contains('[GpuReadbackCopyDrawAudit] key=(6,2,0,lod3)'))
{throw 'Readback-copy GPU draw control did not create an unpooled draw buffer.'}
if($Control.Name -eq 'gpu_cpu_mesh_copy' -and
-not $LogText.Contains('[GpuCpuMeshDrawAudit] key=(6,2,0,lod3)'))
{throw 'CPU-mesh GPU draw control did not create its independent draw buffer.'}
if($Control.Name -eq 'gpu_depth_disabled' -and
-not $LogText.Contains('[GpuFarDepthDisabledAudit] key=(6,2,0,lod3) depth_test=disabled'))
{throw 'Depth-disabled GPU draw control did not disable depth testing on the target tile.'}
}
Write-Output "STEP17_TILE_CONTROL_DONE name=$($Control.Name) peak_private_gb=$([math]::Round($PeakPrivate/1GB,2)) dxgi_local_peak_mb=$DxgiMB conservative_private_plus_dxgi_gb=$([math]::Round($CombinedGB,2)) target_draw_args=$(if($ArgsAudit.Success){$ArgsAudit.Value}else{'not_applicable'}) capture=$CapturePath exit=$ChildExitCode stdout=$StdoutPath stderr=$StderrPath"
if($ChildExitCode-ne0){throw "Viewer returned $ChildExitCode`: $($Control.Name)"}
break
}
}
@@ -0,0 +1,70 @@
# GPU field port — Step 16 report
## Result
The CPU viewer now drains all dirty CPU upload batches before the first fully resident view. Once `[InitialClipmapReady]` is reported, the existing four-batch / 2 ms per-frame upload budget applies again. In the interleaved Step 16 race, the hybrid still loses: median internal readiness was **3.152 s GPU vs 1.354 s CPU**, a **1.799 s CPU lead**. Median host process time was **3.341 s GPU vs 1.484 s CPU**.
The upload cap is no longer the CPU cold-start gate. In this machine's occluded-window runs, worker completion was the final gate, so unlimited draining did not materially improve the total CPU time over the paced control.
## CPU cold drain
`UploadDirtyBatches` now drains without the per-frame caps until the initial clipmap is ready, in both CPU and hybrid modes. `--gpu-far-no-cold-drain` remains the paced control. The normal four-batch / 2 ms limit resumes after readiness for VR frame pacing. See [StandaloneViewer.cpp](../../../Source/StandaloneViewer.cpp) and [CPU drain control runner](RunStep16CpuDrainControl.ps1).
The control logs show the cap being bypassed and where the remaining time goes:
| CPU run | Upload mode | Max uploaded batches in a frame | Last CPU job finish | Last upload | Ready |
|---|---|---:|---:|---:|---:|
| Drain A | Unlimited | 45 | 1,428.551 ms, frame 503 | 1,433.374 ms, frame 504 | 1,433 ms |
| Drain B | Unlimited | 63 | 1,364.572 ms, frame 315 | 1,368.638 ms, frame 316 | 1,369 ms |
| Paced A | 4 batches / 2 ms | 4 | 1,381.804 ms, frame 154 | 1,388.175 ms, frame 156 | 1,388 ms |
| Paced B | 4 batches / 2 ms | 4 | 1,381.767 ms, frame 151 | 1,389.015 ms, frame 154 | 1,389 ms |
The cold-drain path uploads all currently dirty batches, with up to 45–63 batch rebuilds in one frame. The final worker result then reaches the renderer in the next draw pass, about 4–9 ms later. The paced control happened to finish producing dirty work gradually on this machine, so its total readiness was similar; it never had more than four uploaded batches in a frame. Cold readiness still waits for CPU generation and the next renderer pass, but it no longer waits for a multi-frame upload-budget tail.
## Fair race
The race used one binary, `VoxelForgeGpuFieldStep16FairRace.exe`, and identical shared flags for all eight runs: `--clip-radius=3 --workers=8 --quit-after-clipmap --gpu-far-timeline`. Only `--cpu-far` versus `--gpu-far` differed. Each process ran at BelowNormal priority with affinity `0xFF00`; launches were interleaved and counterbalanced. Each run reports the viewer's internal ready time and the host stopwatch time through process exit. “First process” means the first viewer launch for that mode in this race; no OS or driver cache was cleared.
| Run | Launch class | Ready (internal) | Host exit | Cold frames | Shape compile | Whole-view present |
|---|---|---:|---:|---:|---:|---|
| GPU A | First GPU process | 3,176 ms | 3,359 ms | 3 | 849 ms | Occluded, 0.372 ms |
| CPU A | First CPU process | 1,408 ms | 1,558 ms | 205 | 627 ms | Occluded, 0.095 ms |
| CPU B | Subsequent | 1,357 ms | 1,470 ms | 26 | 597 ms | Occluded, 0.071 ms |
| GPU B | Subsequent | 3,098 ms | 3,253 ms | 3 | 864 ms | Occluded, 0.377 ms |
| GPU C | Subsequent | 3,255 ms | 3,439 ms | 3 | 846 ms | Occluded, 0.424 ms |
| CPU C | Subsequent | 1,349 ms | 1,464 ms | 111 | 584 ms | Occluded, 0.082 ms |
| CPU D | Subsequent | 1,350 ms | 1,497 ms | 52 | 591 ms | Occluded, 0.101 ms |
| GPU D | Subsequent | 3,128 ms | 3,322 ms | 4 | 851 ms | Occluded, 0.267 ms |
First process launch for each mode: GPU **3.176 s** vs CPU **1.408 s** internal readiness. Subsequent-launch medians: GPU **3.128 s** vs CPU **1.350 s**. Across all four launches per mode, the medians are **3.152 s GPU vs 1.354 s CPU** internal and **3.341 s GPU vs 1.484 s CPU** host exit. The hybrid does **not** beat CPU in this race.
All eight whole-view presents returned `DXGI_STATUS_OCCLUDED` (`0x087A0001`). The CPU cold-frame ordinal ranged from 26 to 205 while readiness stayed within 59 ms; GPU ordinals were 3–4. The frame counts are in the timeline logs, but are not a direct proxy for elapsed time: CPU frame work varied with arriving worker results, while the GPU path spent most of its interval inside one long pump. The older Step 15 race and `claude_s15` runs did not enable frame telemetry, so their exact frame counts cannot be recovered.
GPU local memory in the race peaked at 1,764.2 MB; combined with peak private memory, the largest sampled total was **4.96 GB**. CPU private memory peaked at **0.88 GB**. CPU mode does not emit the GPU-local sample; its final uploaded geometry payload is about 55 MiB (1,471,806 24-byte vertices plus 5,584,440 32-bit indices).
## Why timings vary
The old CPU shape-compile measurements are close across the two sets of Step 15 logs: **585–586 ms** in the Step 15 race and **589–599 ms** in the `claude_s15` runs. That does not explain the readiness gap between the user's **2.06–2.25 s** CPU runs and the Step 15 race's **1.36–1.42 s** runs.
The present logs show a real difference between those sets. The `claude_s15` CPU and GPU runs returned `S_OK`; CPU startup present calls ranged from 14 ms to 194 ms. The Step 15 race and all Step 16 race runs returned `DXGI_STATUS_OCCLUDED`; whole-view present calls were below 0.4 ms. This presentation difference is consistent with frame-paced work progressing differently, but it was not isolated as the cause: a Step 16 hidden-versus-normal launch control still returned occluded in both cases, so it did not change the renderer's actual presentation state.
For the user's Step 15 GPU outlier, `claude_s15_gpu_a` reported **2.902 s** readiness, **895 ms** shape compilation, **1,226 ms** shader-warmup queue time, and a **322 ms** first present call. The two later GPU runs reported **1.252 / 1.209 s**, **594 / 583 ms** shape compilation, **3.2 / 3.4 ms** warmup queue time, and **1.8 ms** first present calls. Those markers move together, but that run set does not isolate one as the cause.
In the final workspace state, Step 16 shape compilation was **845–864 ms GPU** (median 850 ms) and **584–627 ms CPU** (median 594 ms). I ran a controlled warmup toggle with two launches per setting. Async warmup produced **876–912 ms** shape-compile times; `--gpu-far-no-prewarm` produced **839–865 ms**. Disabling warmup reduced the median compile time by about **42 ms**, but total readiness stayed around **3.1–3.3 s**: warmup median **3.205 s**, no-prewarm median **3.221 s**. The no-prewarm first-present calls were 22 ms, compared with 222–343 ms with warmup, but whole-view readiness did not improve; these runs do not show where the saved present-call time is paid.
The final race's GPU pump wall time was **1.71–1.90 s**; first submission came at **2.82–2.94 s** and completion at **3.09–3.24 s**. The GPU dispatch timestamp ranged from **199 to 400 ms**, with one run's slowest tile at 201 ms. The GPU first-present call ranged from **123 to 300 ms**, although those calls returned the occluded status. These measured stages account for most of the GPU time and vary substantially; the logs do not isolate why the pump or GPU dispatch slowed on some launches.
CPU last-job completion varied from **1,343 to 1,402 ms**, followed by final upload within **6–7 ms**. The shape-compile gap is real, and the no-prewarm control indicates async warmup accounts for a modest part of it. Warmup alone does not explain the roughly 1.8 s race gap: the remaining cost is inside the GPU pump, submission, and dispatch path. The GPU slowdown seen here was present on both the first and subsequent process launches. The Step 15 `2.9 s` first-GPU outlier is therefore not a one-time outlier in this final-state run set; its earlier shorter repeats were not reproduced.
## Correctness and resource checks
- CPU reference W1 and W8 hashes match: density `0xdb2991c73215af4c`, mesh `0x8236c5edd7fc8e4f`, payload `0x4c48fa75a543d58e`, topology `0xbea22e76b34a4dfc`. Walkability passed **5,642/5,642**. Logs: [W1](step16_cpu_reference_w1.stdout.log), [W8](step16_cpu_reference_w8.stdout.log).
- Whole-near agreement passed on **960 tiles**: zero GPU/WARP topology differences; **229,376** protected-exception cells with zero GPU/WARP sign or topology differences. Whole-far passed on **1,114 tiles** (152 transition tiles skipped): zero GPU/WARP sign differences across tested far-field samples; **127,401** protected-exception cells and **353** thin-feature cells with zero GPU/WARP sign or topology differences. Logs: [whole-near](step16_whole_near_full_agreement.stdout.log), [whole-far](step16_whole_far_agreement.stdout.log).
- The GPU survival audit passed at **3.11 GB private + 2,043.8 MB DXGI local = 5.10 GB**. Log: [survival audit](step16_gpu_survival.stdout.log).
- All ten full-residency CPU/GPU capture pairs completed. The 20 PNGs were only read as numeric RGB arrays; no full-size capture was opened in an image viewer. Results are in [capture comparison](step16_capture_comparison.md); the largest sampled GPU capture total was 5.22 GB.
- Build used `/MP8`; runner processes used BelowNormal and `0xFF00`. Runners allowed one retry on `0xC0000142` and closed every viewer they launched. No commit, push, or stash was made.
- The build helper regenerated tracked `precompiled/EmitNearCS.cso`; I restored it from `HEAD` before the final race and GPU checks, and it now matches `HEAD`. No system or driver cache was cleared or modified.
## Remaining coverage limitation
Direct-GPU LOD3/4 coverage remains unchanged. The hybrid currently routes all LOD3 and LOD4 tiles to CPU for `remote_capture_pixel_coverage`; this run had 208 LOD3 and 144 LOD4 remote-band fallback tiles and 1,144 CPU fallback tiles total. The `(6,2,0,lod3)` direct-GPU coverage issue remains open; no direct-GPU coverage change was attempted in this step.
@@ -0,0 +1,42 @@
# GPU port, step 17
## Status
Goal 2 is fixed and validated on the final r29 build. Goal 3 is measured below. Goal 1 remains open: cold adaptive startup still loses to CPU-only startup in this sandbox, and the cached race was not measured here.
## Goal 1: adaptive startup
Final r29 cold run, `--gpu-far` adaptive mode:
- CPU reference: 1.352 s to the initial clipmap; CPU mesh worker sum 5.046 s.
- Adaptive GPU mode: 1.867 s to the initial clipmap; CPU mesh worker sum 6.690 s; GPU busy timestamp sum 0 ms.
- The cold shader queue took 1,087 ms. All 1,434 GPU-eligible tiles were already started on CPU at handoff; the handoff audit passed, but no tile was available for GPU takeover.
Two cache-probe experiments were rejected and removed from the viewer source. The single-shader probe took 404 ms on r27 and 912 ms on r28, slowing startup further. Their logs remain as evidence. No driver or shader cache was read or changed. The owner should run the cached race outside the sandbox.
## Goal 2: LOD3/4 coverage
The broad missing patch came from GPU skirt depth using `Key.StepVoxels()` instead of the exported coarse mesher step. At `(6,2,0,lod3)`, that made the GPU skirt 800 cm deep while the CPU skirt was 1,600 cm deep. The viewer now uses `FarFieldTileInput.step` for skirt depth in [StandaloneViewer.cpp](../../../Source/StandaloneViewer.cpp).
On r29, the isolated `(6,2,0,lod3)` tile passed the GPU readback, indirect-argument, and one-to-one triangle audits: 680 CPU/GPU surface triangles and 64 visible skirt triangles matched; maximum surface vertex error was 0.001892 cm. The CPU and GPU captures were pixel-identical at 1280×800, with zero missing pixels. The numerical report is [step17_final_r29_tilecontrol_comparison.md](step17_final_r29_tilecontrol_comparison.md).
All ten r29 CPU/GPU view pairs completed after full clipmap residency. Eight views had zero CPU-rock-to-GPU-background pixels. `remote_yaw180` and `remote_yaw270` each had two such pixels; neither showed the broad patch. Peak private plus DXGI-local memory across the matrix was 6.17 GB. See [step17_final_r29_capture_comparison.md](step17_final_r29_capture_comparison.md).
The final r29 GPU survival audit passed (`mesh_source=GPU`, required field readback); peak private plus DXGI-local memory was 6.15 GB. The thin-feature selector in [GpuFieldPort.cpp](GpuFieldPort.cpp) found tile `(-1,0,-1,lod3)`: 18 thin-feature cells and 35 protected-exception cells were audited, with zero topology differences against CPU on both hardware GPU and WARP. See `step17_thin_feature_audit.stdout.log`.
CPU reference hashes matched at 1 and 8 workers, with walkability 5,642/5,642:
`0xdb2991c73215af4c, 0x8236c5edd7fc8e4f, 0x4c48fa75a543d58e, 0xbea22e76b34a4dfc`
## Goal 3: timing
| Mode | CPU mesh worker sum | CPU input/export worker sum | GPU busy timestamp sum | Initial clipmap wall |
|---|---:|---:|---:|---:|
| CPU-only, r29 baseline | 5.046 s | — | 0 ms | 1.352 s |
| Adaptive GPU, cold r29 | 6.690 s | 0 s | 0 ms | 1.867 s |
| GPU hybrid, ten cold full-residency views | 0.830–1.408 s | 0.569–0.971 s | 219.181–226.235 ms | 1.813–2.207 s |
| GPU survival audit, r29 | 1.166 s | 1.207 s | 793.829 ms | 44.802 s |
The capture-matrix hybrid runs used `--gpu-far-warmup-before-workers` so the captures exercise the GPU path even with a cold sandbox driver. Their GPU timing is a sum of per-dispatch GPU timestamps, not wall time. The survival run includes additional audit/readback work.
All work and artifacts remain under `Standalone/`. No images were opened in a viewer; capture comparisons were numerical. Nothing was committed, pushed, or stashed.
@@ -0,0 +1,16 @@
# Step16 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.2002 | 2.6637 | 4.2874% | 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.2360 | 3.1110 | 5.5744% | 0.4098% | 107 |
@@ -0,0 +1,18 @@
# Step17_Final 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.
`CPU rock to GPU background` isolates the prior missing-coverage signature. Captures were loaded as RGB arrays only; no full-size image was opened in a viewer.
| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel | CPU rock to GPU background | GPU rock to CPU background | Missing-pixel bounds |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---|
| chamber | 1280x800 | 0.6075 | 1.8638 | 42.6368% | 0.0183% | 123 | 0 | 0 | none |
| tunnel | 1280x800 | 0.6383 | 3.3799 | 31.7335% | 0.2772% | 130 | 0 | 0 | none |
| passage | 1280x800 | 1.6092 | 3.6878 | 50.4982% | 0.1733% | 52 | 0 | 0 | none |
| far | 1280x800 | 0.7910 | 2.1578 | 44.8325% | 0.0198% | 128 | 0 | 0 | none |
| passage_floor_closeup | 1280x800 | 0.9314 | 2.4800 | 43.0219% | 0.0500% | 100 | 0 | 0 | none |
| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.9629% | 0.0708% | 55 | 0 | 0 | none |
| remote_yaw0 | 1280x800 | 0.0416 | 1.0679 | 2.7991% | 0.0305% | 90 | 0 | 0 | none |
| remote_yaw90 | 1280x800 | 0.0100 | 0.4323 | 0.9746% | 0.0035% | 94 | 0 | 0 | none |
| remote_yaw180 | 1280x800 | 0.0470 | 1.3091 | 2.9463% | 0.0311% | 102 | 2 | 0 | (415,172)-(834,173) |
| remote_yaw270 | 1280x800 | 0.0826 | 1.6874 | 5.1076% | 0.0580% | 107 | 2 | 0 | (835,204)-(886,205) |
@@ -0,0 +1,18 @@
# Step17_Final_R29 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.
`CPU rock to GPU background` isolates the prior missing-coverage signature. Captures were loaded as RGB arrays only; no full-size image was opened in a viewer.
| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel | CPU rock to GPU background | GPU rock to CPU background | Missing-pixel bounds |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---|
| chamber | 1280x800 | 0.6075 | 1.8638 | 42.6368% | 0.0183% | 123 | 0 | 0 | none |
| tunnel | 1280x800 | 0.6383 | 3.3799 | 31.7335% | 0.2772% | 130 | 0 | 0 | none |
| passage | 1280x800 | 1.6092 | 3.6878 | 50.4982% | 0.1733% | 52 | 0 | 0 | none |
| far | 1280x800 | 0.7910 | 2.1578 | 44.8325% | 0.0198% | 128 | 0 | 0 | none |
| passage_floor_closeup | 1280x800 | 0.9314 | 2.4800 | 43.0219% | 0.0500% | 100 | 0 | 0 | none |
| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.9629% | 0.0708% | 55 | 0 | 0 | none |
| remote_yaw0 | 1280x800 | 0.0416 | 1.0679 | 2.7991% | 0.0305% | 90 | 0 | 0 | none |
| remote_yaw90 | 1280x800 | 0.0100 | 0.4323 | 0.9746% | 0.0035% | 94 | 0 | 0 | none |
| remote_yaw180 | 1280x800 | 0.0470 | 1.3091 | 2.9463% | 0.0311% | 102 | 2 | 0 | (415,172)-(834,173) |
| remote_yaw270 | 1280x800 | 0.0831 | 1.6885 | 5.1229% | 0.0580% | 107 | 2 | 0 | (835,204)-(886,205) |
@@ -0,0 +1,9 @@
# Step 17 LOD3 tile control comparison
The `(6,2,0,lod3)` target was the only drawn tile, with the full desired clipmap retained. Captures were loaded as RGB arrays and compared numerically; full-size images were not opened in a viewer.
`CPU rock to GPU background` counts pixels where the CPU target is rock `(96,86,68)` and the control is background `(14,19,27)`, the prior missing-coverage signature.
| Control | Size | RGB MAE | RGB RMSE | Changed pixels | CPU rock to GPU background | Max channel |
|---|---:|---:|---:|---:|---:|---:|
| GPU final build r29 | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 |
@@ -0,0 +1,9 @@
# Step 17 LOD3 tile control comparison
The `(6,2,0,lod3)` target was the only drawn tile, with the full desired clipmap retained. Captures were loaded as RGB arrays and compared numerically; full-size images were not opened in a viewer.
`CPU rock to GPU background` counts pixels where the CPU target is rock `(96,86,68)` and the control is background `(14,19,27)`, the prior missing-coverage signature.
| Control | Size | RGB MAE | RGB RMSE | Changed pixels | CPU rock to GPU background | Max channel |
|---|---:|---:|---:|---:|---:|---:|
| GPU final build r26 | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 |
@@ -0,0 +1,18 @@
# Step 17 LOD3 tile control comparison
The `(6,2,0,lod3)` target was the only drawn tile, with the full desired clipmap retained. Captures were loaded as RGB arrays and compared numerically; full-size images were not opened in a viewer.
`CPU rock to GPU background` counts pixels where the CPU target is rock `(96,86,68)` and the control is background `(14,19,27)`, the prior missing-coverage signature.
| Control | Size | RGB MAE | RGB RMSE | Changed pixels | CPU rock to GPU background | Max channel |
|---|---:|---:|---:|---:|---:|---:|
| GPU indirect draw | 1280x800 | 0.9228 | 7.9137 | 1.4571% | 14921 | 82 |
| GPU direct draw | 1280x800 | 0.9228 | 7.9137 | 1.4571% | 14921 | 82 |
| GPU readback copy (cm) | 1280x800 | 0.9228 | 7.9137 | 1.4571% | 14921 | 82 |
| GPU readback copy (m) | 1280x800 | 0.9228 | 7.9137 | 1.4571% | 14921 | 82 |
| CPU mesh in GPU draw loop | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 |
| GPU depth test disabled | 1280x800 | 0.9325 | 7.9277 | 1.5908% | 14921 | 82 |
| GPU skirts disabled | 1280x800 | 1.6339 | 10.5157 | 2.5859% | 26055 | 82 |
| GPU connectors disabled | 1280x800 | 0.9228 | 7.9137 | 1.4571% | 14921 | 82 |
| GPU skirt-depth fix | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 |
| GPU skirts and connectors disabled | 1280x800 | 1.6339 | 10.5157 | 2.5859% | 26055 | 82 |
File diff suppressed because it is too large. Load diff
+73 -1
View File
@@ -478,7 +478,7 @@ public:
for(std::thread& Worker:Workers)if(Worker.joinable())Worker.join();
Workers.clear();
std::lock_guard<std::mutex> Lock(Mutex);
Ready.clear();Outstanding.clear();Desired.clear();
Ready.clear();Outstanding.clear();Desired.clear();Running.clear();
}
FStandaloneStreamingUpdate UpdateDesired(
@@ -512,6 +512,75 @@ public:
return Out;
}
FStandaloneStreamingUpdate RestrictDesiredTileFilterKeepingStarted(
const std::vector<FVoxelTileKey>& BaseFilter,
const std::vector<FVoxelTileKey>& CandidateKeys,
const std::vector<FVoxelTileKey>& ResidentKeys,
std::vector<FVoxelTileKey>& RetainedCandidateKeys)
{
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Candidates;
Candidates.reserve(CandidateKeys.size()*2+1);
for(const FVoxelTileKey& Key:CandidateKeys)Candidates.insert(Key);
FStandaloneStreamingUpdate Stats;
std::lock_guard<std::mutex> Lock(Mutex);
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Retained;
Retained.reserve(CandidateKeys.size()*2+1);
for(const FVoxelTileKey& Key:Running)
if(Candidates.contains(Key))Retained.insert(Key);
for(const FStandaloneCpuTile& Tile:Ready)
if(Candidates.contains(Tile.Key))Retained.insert(Tile.Key);
for(const FVoxelTileKey& Key:ResidentKeys)
if(Candidates.contains(Key))Retained.insert(Key);
TileFilter.clear();
TileFilter.reserve((BaseFilter.size()+Retained.size())*2+1);
for(const FVoxelTileKey& Key:BaseFilter)TileFilter.insert(Key);
for(const FVoxelTileKey& Key:Retained)TileFilter.insert(Key);
bUseTileFilter=true;
FStandaloneDesiredTileSet NewDesired=CurrentDesired;
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
[&](const FVoxelTileKey& Key){return !TileFilter.contains(Key);}),NewDesired.Tiles.end());
Stats.CenterChunk=NewDesired.CenterChunk;
Stats.DesiredTiles=static_cast<int32>(NewDesired.Tiles.size());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> ResidentSet;
ResidentSet.reserve(ResidentKeys.size()*2+1);
for(const FVoxelTileKey& Key:ResidentKeys)ResidentSet.insert(Key);
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
if(ResidentSet.contains(Key))++Stats.RetainedResidentTiles;
}
Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key);
FullResTileCount=Stats.FullResTiles;
std::unordered_map<uint64,uint64> CancelledByCrossing;
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
if(bColdTimelineTrace)TraceTileEvent("cancelled",Job.Key,FClock::now(),Job.QueuedAt);
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;
++CancelledByCrossing[Job.RequestedCrossing];return true;
});
Queue.erase(QueueEnd,Queue.end());
std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Stats.CancelledByCrossing.reserve(CancelledByCrossing.size());
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
CurrentDesired=std::move(NewDesired);
RetainedCandidateKeys.assign(Retained.begin(),Retained.end());
std::sort(RetainedCandidateKeys.begin(),RetainedCandidateKeys.end(),
[](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
if(A.Level!=B.Level)return A.Level<B.Level;
if(A.Coord.Z!=B.Coord.Z)return A.Coord.Z<B.Coord.Z;
if(A.Coord.Y!=B.Coord.Y)return A.Coord.Y<B.Coord.Y;
return A.Coord.X<B.Coord.X;
});
return Stats;
}
bool IsDesired(const FVoxelTileKey& Key)const
{std::lock_guard<std::mutex> Lock(Mutex);return Desired.contains(Key);}
int32 DesiredCount()const
@@ -739,6 +808,7 @@ private:
if(bStop)return;
std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{});
Job=Queue.back();Queue.pop_back();Job.StartedAt=FClock::now();
Running.insert(Job.Key);
}
if(bColdTimelineTrace)TraceTileEvent("started",Job.Key,Job.StartedAt,Job.QueuedAt,Job.StartedAt);
FStandaloneCpuTile Tile=Generate(Job);
@@ -747,6 +817,7 @@ private:
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return;
Running.erase(Tile.Key);
Ready.push_back(std::move(Tile));
}
}
@@ -788,6 +859,7 @@ private:
std::vector<std::thread> Workers;
std::vector<FStandaloneTileJob> Queue;
std::vector<FStandaloneCpuTile> Ready;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Running;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> TileFilter;