wip(assembler): round S5 -- streaming gates + trace; coarse-first order kept opt-in (no win)
Walkable/enclosed/refined gates defined; JSONL trace joins request, worker start, build/apply and discards. Coarse-first + dynamic near reserve behind voxel.StreamingCoarseFirst / voxel.StreamingDynamicNearReserve (default 0 = S4 order): matched runs showed no walkability or full-view win and slower LOD0/1 refinement. Instruments settled: 3.1 s standalone reproduced (16.7 s was another generation route); black early captures were clear-only frames. Hashes equal S4; Core-off, M2, SurfaceWorld/FloatingIslands BuildHost pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
9d3c50ca95
commit
c0596c5f24
32 files changed
+1049
-65
No files matched your search
@@ -0,0 +1,175 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Label,
|
||||
[Parameter(Mandatory = $true)][string[]]$ViewerArguments,
|
||||
[ValidateSet('Spawn', 'Flight')][string]$CaptureMode = 'Spawn',
|
||||
[switch]$WaitForFlightDrain
|
||||
)
|
||||
|
||||
# Capture through the viewer's D3D back buffer; no desktop or window capture is used.
|
||||
# Capturer le tampon D3D du viewer; aucune capture du bureau ou de la fenetre n'est utilisee.
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../../../..'))
|
||||
$ViewerExe = Join-Path $PluginRoot 'Standalone/bin/VoxelForgeCoreStandalone.exe'
|
||||
$CaptureRoot = Join-Path $PSScriptRoot 'captures'
|
||||
$LogRoot = Join-Path $PSScriptRoot 'viewer_logs'
|
||||
[IO.Directory]::CreateDirectory($CaptureRoot) | Out-Null
|
||||
[IO.Directory]::CreateDirectory($LogRoot) | Out-Null
|
||||
|
||||
if (-not ('VoxelForgeR9MemoryProbe' -as [type])) {
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public static class VoxelForgeR9MemoryProbe {
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
private struct MEMORYSTATUSEX {
|
||||
public uint dwLength, dwMemoryLoad;
|
||||
public ulong ullTotalPhys, ullAvailPhys, ullTotalPageFile, ullAvailPageFile;
|
||||
public ulong ullTotalVirtual, ullAvailVirtual, ullAvailExtendedVirtual;
|
||||
}
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX status);
|
||||
public static ulong AvailablePhysicalBytes() {
|
||||
var status = new MEMORYSTATUSEX();
|
||||
status.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
|
||||
if (!GlobalMemoryStatusEx(ref status)) throw new InvalidOperationException("GlobalMemoryStatusEx failed.");
|
||||
return status.ullAvailPhys;
|
||||
}
|
||||
}
|
||||
'@
|
||||
}
|
||||
|
||||
if ([VoxelForgeR9MemoryProbe]::AvailablePhysicalBytes() -lt 4GB) {
|
||||
throw 'Viewer launch skipped: less than 4 GiB of free physical memory.'
|
||||
}
|
||||
if (Get-Process UnrealEditor* -ErrorAction SilentlyContinue) {
|
||||
throw 'Viewer launch skipped: an UnrealEditor process is still running.'
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $ViewerExe -PathType Leaf)) {
|
||||
throw "Standalone viewer is missing: $ViewerExe"
|
||||
}
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
$SafeLabel = $Label -replace '[^A-Za-z0-9_-]', '_'
|
||||
$SequenceSuffix = if ($CaptureMode -eq 'Spawn') { 'spawn' } else { 'mid_sprint' }
|
||||
$CapturePrefix = Join-Path $CaptureRoot "${SafeLabel}_${SequenceSuffix}"
|
||||
$RunArguments = [Collections.Generic.List[string]]::new()
|
||||
foreach ($Argument in $ViewerArguments) { $RunArguments.Add($Argument) }
|
||||
if ($CaptureMode -eq 'Spawn') {
|
||||
$RunArguments.Add("--capture-spawn-sequence-prefix=$CapturePrefix")
|
||||
$RunArguments.Add('--quit-after-spawn-captures')
|
||||
$ExpectedIndices = @(0, 1, 2)
|
||||
$TimeLimitSeconds = 60
|
||||
} else {
|
||||
$RunArguments.Add("--capture-flight-sequence-prefix=$CapturePrefix")
|
||||
if (-not $WaitForFlightDrain) { $RunArguments.Add('--flight-capture-close-index=1') }
|
||||
$ExpectedIndices = @(1)
|
||||
$TimeLimitSeconds = if ($WaitForFlightDrain) { 900 } else { 45 }
|
||||
}
|
||||
$StdoutPath = Join-Path $LogRoot "$SafeLabel`_$SequenceSuffix.stdout.log"
|
||||
$StderrPath = Join-Path $LogRoot "$SafeLabel`_$SequenceSuffix.stderr.log"
|
||||
$QuotedArguments = @($RunArguments | ForEach-Object { '"' + $_.Replace('"', '\"') + '"' })
|
||||
$ArgumentLine = $QuotedArguments -join ' '
|
||||
$Process = Start-Process -FilePath $ViewerExe -ArgumentList $ArgumentLine `
|
||||
-WorkingDirectory (Split-Path -Parent $ViewerExe) -PassThru -WindowStyle Normal `
|
||||
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
|
||||
$Started = [Diagnostics.Stopwatch]::StartNew()
|
||||
try {
|
||||
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
|
||||
$Process.ProcessorAffinity = [IntPtr]0xFFF
|
||||
while ($true) {
|
||||
$Process.Refresh()
|
||||
if ($Process.HasExited) { break }
|
||||
if ($Process.PrivateMemorySize64 -gt 8GB) {
|
||||
throw "Owned viewer PID $($Process.Id) exceeded the 8 GiB private-memory limit."
|
||||
}
|
||||
if ($Started.Elapsed.TotalSeconds -gt $TimeLimitSeconds) {
|
||||
throw "Owned viewer PID $($Process.Id) exceeded the $TimeLimitSeconds-second capture limit."
|
||||
}
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
$Process.WaitForExit()
|
||||
$Process.Refresh()
|
||||
if ($Process.ExitCode -ne 0) {
|
||||
throw "Owned viewer PID $($Process.Id) exited with code $($Process.ExitCode)."
|
||||
}
|
||||
} catch {
|
||||
$Process.Refresh()
|
||||
if (-not $Process.HasExited) {
|
||||
[void]$Process.CloseMainWindow()
|
||||
if (-not $Process.WaitForExit(3000)) {
|
||||
Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
|
||||
$Process.WaitForExit()
|
||||
}
|
||||
}
|
||||
throw
|
||||
}
|
||||
|
||||
function Save-DownscaledJpeg([string]$Source, [string]$Destination, [long]$Quality) {
|
||||
$Original = [Drawing.Image]::FromFile($Source)
|
||||
try {
|
||||
$Width = [Math]::Min(960, $Original.Width)
|
||||
$Height = [int][Math]::Round($Original.Height * $Width / $Original.Width)
|
||||
$Small = New-Object Drawing.Bitmap($Width, $Height)
|
||||
$Graphics = [Drawing.Graphics]::FromImage($Small)
|
||||
try {
|
||||
$Graphics.InterpolationMode = [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
||||
$Graphics.PixelOffsetMode = [Drawing.Drawing2D.PixelOffsetMode]::HighQuality
|
||||
$Graphics.DrawImage($Original, 0, 0, $Width, $Height)
|
||||
$Codec = [Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
|
||||
Where-Object { $_.MimeType -eq 'image/jpeg' } | Select-Object -First 1
|
||||
$Parameters = New-Object Drawing.Imaging.EncoderParameters(1)
|
||||
try {
|
||||
$Parameters.Param[0] = New-Object Drawing.Imaging.EncoderParameter(
|
||||
[Drawing.Imaging.Encoder]::Quality, $Quality)
|
||||
$Small.Save($Destination, $Codec, $Parameters)
|
||||
} finally {
|
||||
$Parameters.Dispose()
|
||||
}
|
||||
} finally {
|
||||
$Graphics.Dispose()
|
||||
$Small.Dispose()
|
||||
}
|
||||
} finally {
|
||||
$Original.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$ViewerLog = Get-Content -LiteralPath $StdoutPath -Raw
|
||||
foreach ($Index in $ExpectedIndices) {
|
||||
$PngPath = '{0}_{1:D2}.png' -f $CapturePrefix, $Index
|
||||
if (-not (Test-Path -LiteralPath $PngPath -PathType Leaf)) {
|
||||
throw "Viewer exited normally but did not write required back-buffer capture index $Index."
|
||||
}
|
||||
if ($CaptureMode -eq 'Spawn') {
|
||||
$CaptureTime = @('0.5s', '1.0s', '2.0s')[$Index]
|
||||
$JpegPath = Join-Path $CaptureRoot "${SafeLabel}_spawn_$CaptureTime.jpg"
|
||||
} else {
|
||||
$JpegPath = Join-Path $CaptureRoot "${SafeLabel}_mid_sprint.jpg"
|
||||
}
|
||||
Save-DownscaledJpeg $PngPath $JpegPath 72
|
||||
if ((Get-Item -LiteralPath $JpegPath).Length -ge 300KB) {
|
||||
Save-DownscaledJpeg $PngPath $JpegPath 48
|
||||
}
|
||||
$Jpeg = Get-Item -LiteralPath $JpegPath
|
||||
if ($Jpeg.Length -ge 300KB) { throw "Downscaled capture exceeds 300 KB: $($Jpeg.Name)" }
|
||||
Remove-Item -LiteralPath $PngPath -Force
|
||||
$Timing = [regex]::Match($ViewerLog, "\[(Spawn|Flight)SequenceCapture\] index=$Index elapsed_s=([0-9.]+)")
|
||||
$Elapsed = if ($Timing.Success) { $Timing.Groups[2].Value } else { 'missing' }
|
||||
Write-Host ('capture={0} elapsed_s={1} size={2} bytes' -f $Jpeg.Name, $Elapsed, $Jpeg.Length)
|
||||
}
|
||||
if ($CaptureMode -eq 'Flight') {
|
||||
# Keep only the reviewed mid-flight JPG; remove this run's unused raw D3D frames.
|
||||
# Garder uniquement le JPG de vol examine ; supprimer les tampons D3D bruts inutilises de ce run.
|
||||
foreach ($Index in 0..4) {
|
||||
$PngPath = '{0}_{1:D2}.png' -f $CapturePrefix, $Index
|
||||
if (Test-Path -LiteralPath $PngPath -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $PngPath -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host ('viewer_closed=1 pid={0} exit_code={1} elapsed_s={2:0.000} priority=Normal affinity=0xFFF' -f
|
||||
$Process.Id, $Process.ExitCode, $Started.Elapsed.TotalSeconds)
|
||||
Reference in new issue
Block a user