wip(standalone): GPU step 9 -- remote band closed by CPU fallback; CPU still wins whole view

Re-verified f2de8fe (hashes, walk 5,642/5,642, far topology 0 diffs vs CPU and
WARP, GPU survival). The remote LOD3/4 band was a direct-GPU coverage loss
(overlapping pixels identical, surface pieces missing, e.g. (6,2,0,lod3));
LOD3/4 now mesh on CPU, root cause in the GPU route still unidentified.
A minimal first-submit repro (pools, first-touch dispatch, device flags) did
not reproduce the 2.4 s stall. Whole view: CPU 1.438 s vs GPU 4.704 s median.
Crossing probe overflowed GPU output slots: no valid crossing latency.

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 04:36:34 +02:00
1 parent f2de8fe9c6
commit b0da995978
21 files changed
+698 -4

No files matched your search

@@ -0,0 +1,44 @@
[CmdletBinding()]
param([string]$OutputName='VoxelForgeGpuFirstSubmitRepro.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($OutputName)-ne$OutputName-or[IO.Path]::GetExtension($OutputName)-ne'.exe')
{throw 'OutputName must be an executable filename.'}
$BuildDir=Join-Path $StandaloneRoot 'Build\GpuFirstSubmitRepro'
$BinDir=Join-Path $StandaloneRoot 'bin'
$Source=Join-Path $PSScriptRoot 'GpuFirstSubmitRepro.cpp'
$Output=Join-Path $BinDir $OutputName
$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($BinDir)|Out-Null
$Rsp=Join-Path $BuildDir 'compile.rsp'
$Lines=@('/nologo','/std:c++20','/O2','/EHsc','/MP8','/D_CRT_SECURE_NO_WARNINGS',
('/Fe"{0}"' -f $Output),('"{0}"' -f $Source),'/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib')
[IO.File]::WriteAllLines($Rsp,$Lines,[Text.UTF8Encoding]::new($false))
Push-Location $BuildDir
try
{
$Command='call "{0}" >nul && cl.exe @"{1}"' -f $VcVars,$Rsp
$Attempt=0
do
{
& $env:ComSpec /d /s /c $Command
$Code=$LASTEXITCODE
if($Code-eq-1073741502-and$Attempt-eq0)
{
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 one retry; stopping.'}
if($Code-ne0){throw "GPU first-submit repro build failed with exit code $Code"}
break
}while($true)
}
finally{Pop-Location}
if(-not(Test-Path -LiteralPath $Output -PathType Leaf)){throw "Repro executable missing: $Output"}
Write-Host "Built $Output"
@@ -0,0 +1,89 @@
from pathlib import Path
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent
PAIRS = (
("chamber", "step9final_cpu_chamber.png", "step9final_gpu_chamber.png"),
("tunnel", "step9final_cpu_tunnel.png", "step9final_gpu_tunnel.png"),
("passage", "step9final_cpu_passage.png", "step9final_gpu_passage.png"),
("far", "step9final_cpu_far.png", "step9final_gpu_far.png"),
(
"passage_floor_closeup",
"step9final_cpu_passage_floor_closeup.png",
"step9final_gpu_passage_floor_closeup.png",
),
("tunnel_bend", "step9final_cpu_tunnel_bend.png", "step9final_gpu_tunnel_bend.png"),
("remote_yaw0", "step9final_cpu_remote_yaw0.png", "step9final_gpu_remote_yaw0.png"),
("remote_yaw90", "step9final_cpu_remote_yaw90.png", "step9final_gpu_remote_yaw90.png"),
("remote_yaw180", "step9final_cpu_remote_yaw180.png", "step9final_gpu_remote_yaw180.png"),
("remote_yaw270", "step9final_cpu_remote_yaw270.png", "step9final_gpu_remote_yaw270.png"),
("control_tile_4_3_-1_lod3", "step9fresh_tile43m1_cpu.png", "step9fresh_tile43m1_gpu.png"),
(
"direct_gpu_candidate_6_2_0_lod3",
"step9_tileprobe_6_2_0_lod3_cpu.png",
"step9_tileprobe_6_2_0_lod3_gpu.png",
),
(
"direct_gpu_vs_forced_cpu_candidate_6_2_0_lod3",
"step9_route_6_2_0_gpu.png",
"step9_route_6_2_0_forced_cpu_gpu.png",
),
(
"cpu_reference_vs_forced_cpu_candidate_6_2_0_lod3",
"step9_tileprobe_6_2_0_lod3_cpu.png",
"step9_route_6_2_0_forced_cpu_gpu.png",
),
(
"final_candidate_tile_6_2_0_lod3",
"step9final_tile_6_2_0_cpu.png",
"step9final_tile_6_2_0_gpu.png",
),
("final_remote_lod3", "step9final_lod3_remote_cpu.png", "step9final_lod3_remote_gpu.png"),
("final_remote_lod4", "step9final_lod4_remote_cpu.png", "step9final_lod4_remote_gpu.png"),
)
rows = []
for name, cpu_name, gpu_name in PAIRS:
cpu_path = ROOT / cpu_name
gpu_path = ROOT / gpu_name
cpu = np.asarray(Image.open(cpu_path).convert("RGB"), dtype=np.int16)
gpu = np.asarray(Image.open(gpu_path).convert("RGB"), dtype=np.int16)
if cpu.shape != gpu.shape:
raise RuntimeError(f"dimension mismatch for {name}: {cpu.shape} vs {gpu.shape}")
diff = np.abs(cpu - gpu)
pixel_max = diff.max(axis=2)
rows.append(
{
"name": name,
"size": f"{cpu.shape[1]}x{cpu.shape[0]}",
"mae": float(diff.mean()),
"rmse": float(np.sqrt(np.square(diff, dtype=np.float64).mean())),
"changed": float(np.count_nonzero(pixel_max) * 100.0 / pixel_max.size),
"over32": float(np.count_nonzero(pixel_max > 32) * 100.0 / pixel_max.size),
"max": int(pixel_max.max()),
}
)
lines = [
"# Step 9 CPU/GPU capture comparisons",
"",
"CPU and GPU captures are compared numerically as RGB arrays. The view matrix was taken only after each run logged `[InitialClipmapReady]`; the render-only tile probes retained the full desired target set.",
"",
"`MAE` and `RMSE` use the 0-255 RGB channel scale. `Changed` and `>32` are the percent of pixels whose maximum channel difference is nonzero or above 32.",
"",
"| Pair | Size | MAE RGB | RMSE RGB | Changed | >32 | Max channel |",
"|---|---:|---:|---:|---:|---:|---:|",
]
for row in rows:
lines.append(
f"| {row['name']} | {row['size']} | {row['mae']:.4f} | {row['rmse']:.4f} | "
f"{row['changed']:.2f}% | {row['over32']:.2f}% | {row['max']} |"
)
report_path = ROOT / "step9final_capture_comparison.md"
report_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print("\n".join(lines))
print(f"\nWrote {report_path}")
@@ -0,0 +1,339 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#include <dxgi1_4.h>
#include <wrl/client.h>
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <stdexcept>
#include <string_view>
#include <thread>
#include <vector>
using Microsoft::WRL::ComPtr;
using Clock = std::chrono::steady_clock;
namespace
{
constexpr UINT PoolSlots = 1438;
constexpr UINT SlotsPerChunk = 128;
constexpr UINT MaxVerticesPerTile = 19456;
constexpr UINT PanelVerticesPerTile = 32768;
constexpr UINT VertexStride = 32;
void Check(HRESULT Result, const char* Operation)
{
if (FAILED(Result))
{
char Message[256]{};
std::snprintf(Message, sizeof(Message), "%s failed: hr=0x%08lx", Operation,
static_cast<unsigned long>(Result));
throw std::runtime_error(Message);
}
}
double Milliseconds(Clock::time_point Start)
{
return std::chrono::duration<double, std::milli>(Clock::now() - Start).count();
}
struct FPoolChunk
{
ComPtr<ID3D11Buffer> Vertices, Indices, Args, PanelVertices, PanelIndices;
ComPtr<ID3D11UnorderedAccessView> VertexUav, IndexUav, ArgsUav, PanelVertexUav, PanelIndexUav;
ComPtr<ID3D11ShaderResourceView> VertexSrv;
};
LRESULT CALLBACK ProbeWindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam)
{
return DefWindowProcW(Window, Message, WParam, LParam);
}
HWND CreateProbeWindow(HINSTANCE Instance)
{
WNDCLASSEXW Class{};
Class.cbSize = sizeof(Class);
Class.lpfnWndProc = ProbeWindowProc;
Class.hInstance = Instance;
Class.lpszClassName = L"VoxelForgeGpuFirstSubmitRepro";
if (!RegisterClassExW(&Class) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
throw std::runtime_error("RegisterClassExW failed");
HWND Window = CreateWindowExW(0, Class.lpszClassName, Class.lpszClassName,
WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, nullptr, nullptr, Instance, nullptr);
if (!Window) throw std::runtime_error("CreateWindowExW failed");
return Window;
}
ComPtr<ID3D11Buffer> CreateRawBuffer(ID3D11Device* Device, UINT ByteWidth,
UINT BindFlags, UINT ExtraMiscFlags = 0)
{
D3D11_BUFFER_DESC Desc{};
Desc.ByteWidth = ByteWidth;
Desc.Usage = D3D11_USAGE_DEFAULT;
Desc.BindFlags = BindFlags;
Desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS | ExtraMiscFlags;
ComPtr<ID3D11Buffer> Buffer;
Check(Device->CreateBuffer(&Desc, nullptr, &Buffer), "CreateBuffer(pool)");
return Buffer;
}
ComPtr<ID3D11UnorderedAccessView> CreateRawUav(ID3D11Device* Device, ID3D11Buffer* Buffer,
UINT DwordCount)
{
D3D11_UNORDERED_ACCESS_VIEW_DESC Desc{};
Desc.Format = DXGI_FORMAT_R32_TYPELESS;
Desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
Desc.Buffer.NumElements = DwordCount;
Desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
ComPtr<ID3D11UnorderedAccessView> View;
Check(Device->CreateUnorderedAccessView(Buffer, &Desc, &View), "CreateUnorderedAccessView(pool)");
return View;
}
ComPtr<ID3D11ShaderResourceView> CreateRawSrv(ID3D11Device* Device, ID3D11Buffer* Buffer,
UINT DwordCount)
{
D3D11_SHADER_RESOURCE_VIEW_DESC Desc{};
Desc.Format = DXGI_FORMAT_R32_TYPELESS;
Desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
Desc.BufferEx.NumElements = DwordCount;
Desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
ComPtr<ID3D11ShaderResourceView> View;
Check(Device->CreateShaderResourceView(Buffer, &Desc, &View), "CreateShaderResourceView(pool)");
return View;
}
std::vector<FPoolChunk> ReserveViewerPools(ID3D11Device* Device)
{
std::vector<FPoolChunk> Chunks;
const UINT ChunkCount = (PoolSlots + SlotsPerChunk - 1) / SlotsPerChunk;
Chunks.reserve(ChunkCount);
for (UINT ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex)
{
const UINT Slots = std::min(SlotsPerChunk, PoolSlots - ChunkIndex * SlotsPerChunk);
const UINT VertexBytes = Slots * MaxVerticesPerTile * VertexStride;
const UINT IndexBytes = Slots * MaxVerticesPerTile * sizeof(UINT);
const UINT ArgsBytes = Slots * 11u * sizeof(UINT);
const UINT PanelVertexBytes = Slots * PanelVerticesPerTile * VertexStride;
const UINT PanelIndexBytes = Slots * PanelVerticesPerTile * sizeof(UINT);
FPoolChunk Chunk;
Chunk.Vertices = CreateRawBuffer(Device, VertexBytes,
D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_SHADER_RESOURCE);
Chunk.Indices = CreateRawBuffer(Device, IndexBytes,
D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_INDEX_BUFFER);
Chunk.Args = CreateRawBuffer(Device, ArgsBytes, D3D11_BIND_UNORDERED_ACCESS,
D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS);
Chunk.PanelVertices = CreateRawBuffer(Device, PanelVertexBytes,
D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER);
Chunk.PanelIndices = CreateRawBuffer(Device, PanelIndexBytes,
D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_INDEX_BUFFER);
Chunk.VertexUav = CreateRawUav(Device, Chunk.Vertices.Get(), VertexBytes / 4);
Chunk.VertexSrv = CreateRawSrv(Device, Chunk.Vertices.Get(), VertexBytes / 4);
Chunk.IndexUav = CreateRawUav(Device, Chunk.Indices.Get(), IndexBytes / 4);
Chunk.ArgsUav = CreateRawUav(Device, Chunk.Args.Get(), ArgsBytes / 4);
Chunk.PanelVertexUav = CreateRawUav(Device, Chunk.PanelVertices.Get(), PanelVertexBytes / 4);
Chunk.PanelIndexUav = CreateRawUav(Device, Chunk.PanelIndices.Get(), PanelIndexBytes / 4);
Chunks.push_back(std::move(Chunk));
}
return Chunks;
}
void TouchPoolOnce(ID3D11Device* Device, ID3D11DeviceContext* Context,
const std::vector<FPoolChunk>& Chunks)
{
static constexpr char ShaderText[] =
"RWByteAddressBuffer A:register(u0); RWByteAddressBuffer B:register(u1);"
"RWByteAddressBuffer C:register(u2); RWByteAddressBuffer D:register(u3);"
"RWByteAddressBuffer E:register(u4);"
"[numthreads(1,1,1)] void CS(uint3 id:SV_DispatchThreadID){"
"A.Store(0,1);B.Store(0,2);C.Store(0,3);D.Store(0,4);E.Store(0,5);}";
ComPtr<ID3DBlob> Bytecode, Errors;
Check(D3DCompile(ShaderText, sizeof(ShaderText) - 1, nullptr, nullptr, nullptr,
"CS", "cs_5_0", 0, 0, &Bytecode, &Errors), "D3DCompile(trivial shader)");
ComPtr<ID3D11ComputeShader> Shader;
Check(Device->CreateComputeShader(Bytecode->GetBufferPointer(), Bytecode->GetBufferSize(),
nullptr, &Shader), "CreateComputeShader(trivial shader)");
Context->CSSetShader(Shader.Get(), nullptr, 0);
for (const FPoolChunk& Chunk : Chunks)
{
ID3D11UnorderedAccessView* Views[] = {
Chunk.VertexUav.Get(), Chunk.IndexUav.Get(), Chunk.ArgsUav.Get(),
Chunk.PanelVertexUav.Get(), Chunk.PanelIndexUav.Get() };
Context->CSSetUnorderedAccessViews(0, 5, Views, nullptr);
Context->Dispatch(1, 1, 1);
}
ID3D11UnorderedAccessView* NullViews[5]{};
Context->CSSetUnorderedAccessViews(0, 5, NullViews, nullptr);
Context->CSSetShader(nullptr, nullptr, 0);
}
void TouchSmallBuffer(ID3D11Device* Device, ID3D11DeviceContext* Context,
ID3D11UnorderedAccessView* Uav)
{
static constexpr char ShaderText[] =
"RWByteAddressBuffer A:register(u0);"
"[numthreads(1,1,1)] void CS(uint3 id:SV_DispatchThreadID){A.Store(0,1);}";
ComPtr<ID3DBlob> Bytecode, Errors;
Check(D3DCompile(ShaderText, sizeof(ShaderText) - 1, nullptr, nullptr, nullptr,
"CS", "cs_5_0", 0, 0, &Bytecode, &Errors), "D3DCompile(small control shader)");
ComPtr<ID3D11ComputeShader> Shader;
Check(Device->CreateComputeShader(Bytecode->GetBufferPointer(), Bytecode->GetBufferSize(),
nullptr, &Shader), "CreateComputeShader(small control shader)");
Context->CSSetShader(Shader.Get(), nullptr, 0);
Context->CSSetUnorderedAccessViews(0, 1, &Uav, nullptr);
Context->Dispatch(1, 1, 1);
ID3D11UnorderedAccessView* NullUav = nullptr;
Context->CSSetUnorderedAccessViews(0, 1, &NullUav, nullptr);
Context->CSSetShader(nullptr, nullptr, 0);
}
double WaitForGpu(ID3D11Device* Device, ID3D11DeviceContext* Context)
{
D3D11_QUERY_DESC Desc{D3D11_QUERY_EVENT, 0};
ComPtr<ID3D11Query> Query;
Check(Device->CreateQuery(&Desc, &Query), "CreateQuery(event)");
Context->End(Query.Get());
Context->Flush();
const auto Start = Clock::now();
for (;;)
{
const HRESULT Result = Context->GetData(Query.Get(), nullptr, 0,
D3D11_ASYNC_GETDATA_DONOTFLUSH);
if (Result == S_OK) break;
if (FAILED(Result)) Check(Result, "GetData(event)");
if (Milliseconds(Start) > 30000.0) throw std::runtime_error("GPU event query timed out");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return Milliseconds(Start);
}
bool QueryLocalMemory(ID3D11Device* Device, UINT64& UsageBytes, UINT64& BudgetBytes)
{
ComPtr<IDXGIDevice> DxgiDevice;
if (FAILED(Device->QueryInterface(IID_PPV_ARGS(&DxgiDevice)))) return false;
ComPtr<IDXGIAdapter> Adapter;
if (FAILED(DxgiDevice->GetAdapter(&Adapter))) return false;
ComPtr<IDXGIAdapter3> Adapter3;
if (FAILED(Adapter.As(&Adapter3))) return false;
DXGI_QUERY_VIDEO_MEMORY_INFO Info{};
if (FAILED(Adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &Info))) return false;
UsageBytes = Info.CurrentUsage;
BudgetBytes = Info.Budget;
return true;
}
void Run(std::wstring_view Mode, bool SingleThreadedDevice)
{
HWND Window = CreateProbeWindow(GetModuleHandleW(nullptr));
DXGI_SWAP_CHAIN_DESC SwapDesc{};
SwapDesc.BufferCount = 1;
SwapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
SwapDesc.OutputWindow = Window;
SwapDesc.SampleDesc.Count = 1;
SwapDesc.Windowed = TRUE;
SwapDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
const UINT DeviceFlags = SingleThreadedDevice ? D3D11_CREATE_DEVICE_SINGLETHREADED : 0;
ComPtr<IDXGISwapChain> SwapChain;
ComPtr<ID3D11Device> Device;
ComPtr<ID3D11DeviceContext> Context;
D3D_FEATURE_LEVEL Feature{};
const auto DeviceStart = Clock::now();
Check(D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
DeviceFlags, nullptr, 0, D3D11_SDK_VERSION, &SwapDesc, &SwapChain, &Device,
&Feature, &Context), "D3D11CreateDeviceAndSwapChain");
const double DeviceMs = Milliseconds(DeviceStart);
ComPtr<ID3D11Texture2D> BackBuffer;
Check(SwapChain->GetBuffer(0, IID_PPV_ARGS(&BackBuffer)), "GetBuffer(back buffer)");
ComPtr<ID3D11RenderTargetView> RenderTarget;
Check(Device->CreateRenderTargetView(BackBuffer.Get(), nullptr, &RenderTarget),
"CreateRenderTargetView");
UINT64 UsageBefore = 0, Budget = 0;
const bool HasMemoryInfo = QueryLocalMemory(Device.Get(), UsageBefore, Budget);
std::vector<FPoolChunk> Pool;
double PoolAllocMs = 0.0;
if (Mode == L"alloc" || Mode == L"dispatch")
{
const auto PoolStart = Clock::now();
Pool = ReserveViewerPools(Device.Get());
PoolAllocMs = Milliseconds(PoolStart);
}
double DispatchCpuMs = 0.0, QueryWaitMs = 0.0;
if (Mode == L"dispatch")
{
const auto DispatchStart = Clock::now();
TouchPoolOnce(Device.Get(), Context.Get(), Pool);
DispatchCpuMs = Milliseconds(DispatchStart);
QueryWaitMs = WaitForGpu(Device.Get(), Context.Get());
}
else if (Mode == L"small-dispatch")
{
D3D11_BUFFER_DESC Desc{};
Desc.ByteWidth = 4096;
Desc.Usage = D3D11_USAGE_DEFAULT;
Desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
Desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;
ComPtr<ID3D11Buffer> Buffer;
Check(Device->CreateBuffer(&Desc, nullptr, &Buffer), "CreateBuffer(small control)");
auto Uav = CreateRawUav(Device.Get(), Buffer.Get(), Desc.ByteWidth / 4);
const auto DispatchStart = Clock::now();
TouchSmallBuffer(Device.Get(), Context.Get(), Uav.Get());
DispatchCpuMs = Milliseconds(DispatchStart);
QueryWaitMs = WaitForGpu(Device.Get(), Context.Get());
}
const float Clear[4] = {0.05f, 0.07f, 0.10f, 1.0f};
Context->OMSetRenderTargets(1, RenderTarget.GetAddressOf(), nullptr);
Context->ClearRenderTargetView(RenderTarget.Get(), Clear);
const UINT64 UsageAfter = [&]()
{
UINT64 Usage = 0, LocalBudget = 0;
QueryLocalMemory(Device.Get(), Usage, LocalBudget);
return Usage;
}();
const auto PresentStart = Clock::now();
const HRESULT PresentResult = SwapChain->Present(0, DXGI_PRESENT_DO_NOT_WAIT);
const double PresentMs = Milliseconds(PresentStart);
std::printf("[FirstSubmitRepro] mode=%.*ls device_flags=%s feature_level=0x%04x device_ms=%.3f pool_slots=%u pool_chunks=%zu pool_alloc_ms=%.3f dispatch_cpu_ms=%.3f query_wait_ms=%.3f present_ms=%.3f present_hr=0x%08lx local_mb_before=%.1f local_mb_after=%.1f local_budget_mb=%.1f\n",
static_cast<int>(Mode.size()), Mode.data(), SingleThreadedDevice ? "singlethreaded" : "none",
static_cast<unsigned>(Feature), DeviceMs, Pool.empty() ? 0u : PoolSlots, Pool.size(), PoolAllocMs,
DispatchCpuMs, QueryWaitMs, PresentMs, static_cast<unsigned long>(PresentResult),
HasMemoryInfo ? UsageBefore / 1048576.0 : -1.0,
HasMemoryInfo ? UsageAfter / 1048576.0 : -1.0,
HasMemoryInfo ? Budget / 1048576.0 : -1.0);
std::fflush(stdout);
DestroyWindow(Window);
if (FAILED(PresentResult) && PresentResult != DXGI_ERROR_WAS_STILL_DRAWING)
Check(PresentResult, "Present");
}
}
int wmain(int Argc, wchar_t** Argv)
{
try
{
std::wstring_view Mode = L"alloc";
bool SingleThreadedDevice = false;
for (int Index = 1; Index < Argc; ++Index)
{
const std::wstring_view Arg = Argv[Index];
if (Arg.starts_with(L"--mode=")) Mode = Arg.substr(7);
else if (Arg == L"--singlethreaded") SingleThreadedDevice = true;
}
if (Mode != L"none" && Mode != L"alloc" && Mode != L"dispatch" && Mode != L"small-dispatch")
throw std::runtime_error("mode must be none, alloc, dispatch, or small-dispatch");
Run(Mode, SingleThreadedDevice);
return 0;
}
catch (const std::exception& Error)
{
std::fprintf(stderr, "[FirstSubmitReproError] %s\n", Error.what());
return 1;
}
}
@@ -0,0 +1,60 @@
[CmdletBinding()]
param([string]$ExecutableName='VoxelForgeGpuFieldStep9Measured.exe')
$ErrorActionPreference='Stop'
$HostProcess=Get-Process -Id $PID
$HostProcess.PriorityClass='BelowNormal'
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
{throw 'ExecutableName must be an executable filename.'}
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "Viewer not found: $ExecutablePath"}
$Sequence=@(
@{Mode='cpu';Name='step9visible_cpu_A_fresh'},
@{Mode='gpu';Name='step9visible_gpu_A_fresh'},
@{Mode='gpu';Name='step9visible_gpu_B_warmcache'},
@{Mode='cpu';Name='step9visible_cpu_B_warmcache'},
@{Mode='cpu';Name='step9visible_cpu_C_warmcache'},
@{Mode='gpu';Name='step9visible_gpu_C_warmcache'}
)
foreach($Run in $Sequence)
{
$Arguments=@('--clip-radius=3','--workers=8','--quit-after-clipmap',"--$($Run.Mode)-far")
$StdoutPath=Join-Path $PSScriptRoot ($Run.Name+'.stdout.log')
$StderrPath=Join-Path $PSScriptRoot ($Run.Name+'.stderr.log')
Write-Output "NORMAL_WINDOW_RUN mode=$($Run.Mode) command=`"$ExecutablePath`" $($Arguments -join ' ')"
for($Attempt=0;$Attempt-lt2;$Attempt++)
{
if($Attempt-gt0)
{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 $Arguments -WorkingDirectory $StandaloneRoot `
-WindowStyle Normal -PassThru -RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakWorkingSet=0L;$PeakPrivateMemory=0L;$OverMemoryLimit=$false
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)
if($Child.PrivateMemorySize64-ge8GB)
{$OverMemoryLimit=$true;$Child.Kill();$Child.WaitForExit();break}
Start-Sleep -Milliseconds 50
}
$Child.WaitForExit();$Child.Refresh()
if($Child.ExitCode-eq-1073741502-and$Attempt-eq0){continue}
if($Child.ExitCode-eq-1073741502){throw '0xC0000142 repeated after its single retry; stopping.'}
if($OverMemoryLimit){throw "Viewer stopped at the private-memory limit: $($Run.Name)"}
$LogText=Get-Content -LiteralPath $StdoutPath -Raw
if(-not$LogText.Contains('[InitialClipmapReady]'))
{throw "Full clipmap was not ready in $($Run.Name); exit=$($Child.ExitCode)"}
$Present=[regex]::Match($LogText,'(?m)^\[WholeViewPresented\] at_ms=([0-9.]+) present_ms=([0-9.]+) sync=\d+ hr=0x([0-9A-Fa-f]{8})')
if(-not$Present.Success){throw "Visible present marker missing in $($Run.Name)"}
$HResult=$Present.Groups[3].Value.ToUpperInvariant()
Write-Output "RUN_EXIT mode=$($Run.Mode) log=$($Run.Name) pid=$($Child.Id) priority=BelowNormal affinity=0xFF00 peak_private_gb=$([math]::Round($PeakPrivateMemory/1GB,2)) exit=$($Child.ExitCode) at_ms=$($Present.Groups[1].Value) present_ms=$($Present.Groups[2].Value) present_hr=0x$HResult"
if($Child.ExitCode-ne0){throw "Viewer returned $($Child.ExitCode): $($Run.Name)"}
break
}
}
@@ -0,0 +1,64 @@
# GPU field port: step 9
Checkpoint: `f2de8fe` on `standalone/world`. The measured viewer and the far agreement runner built successfully with `/MP8`. No commit, push, or stash was made. The CPU field, layout, and mesh reference code are unchanged.
## Revalidation
Fresh `--stream-benchmark --shape-world --force-generate --stream-hash --clip-radius=3` runs at one and eight workers both generated all 2,226 tiles and passed walkability at **5,642/5,642**. Their full clipmap hashes are identical:
| Workers | Headless CPU clipmap generation |
|---:|---:|
| 1 | 4.258 s |
| 8 | 0.793 s |
Density `0xdb2991c73215af4c`, mesh `0x8236c5edd7fc8e4f`, payload `0x4c48fa75a543d58e`, topology `0xbea22e76b34a4dfc`. These also match the saved step 8 reference.
The rebuilt far audit covered **1,150 eligible tiles** at the remote camera and skipped 116 near-transition tiles. CPU and WARP each had **zero sign/topology differences** from the RX 7900 path. It audited 145,494 protected exceptions and 88 thin-feature cells with zero differences; repeated GPU outputs were bit-identical. The viewer's GPU-mesh survival audit passed: 7,200/7,200 boundary surfaces, 10,976 connectors, 178,351 connector samples, and 710,047 rays, with no missing samples, holes, or unsampled connectors.
Logs: `step9measured_headless_cpu_w1.stdout.log`, `step9measured_headless_cpu_w8.stdout.log`, `step9measured_whole_far_remote.stdout.log`, and `step9measured_gpu_far_survival.stdout.log`.
## Remote LOD3/4 band
The one-tile render probe uses the full **2,226-tile target set** and filters only drawing. The `(4,3,-1,lod3)` control differs by just 0.35% of pixels, with a maximum channel difference of 14, so that tile did not explain the band.
The controlled `(6,2,0,lod3)` direct-GPU render did: against the CPU reference it differs on 1.46% of pixels (RGB MAE 0.9228, max channel 82), with the missing foreground on the CPU side and no GPU-only foreground. Forcing that same tile through the CPU mesh path makes it pixel-identical to the CPU reference. The inspected draw arguments were valid: face mask `0x24`, output slot 518, 2,040 surface indices, 192 skirt-panel indices, expected start index 196,608, and no overflow. This proves a **coverage loss in the direct GPU LOD3 route**; it does not identify the internal shader/emitter cause.
The viewer now routes all remote LOD3 and LOD4 tiles through CPU meshing: 208 LOD3 and 144 LOD4 tiles in the default clipmap. With that route, the isolated remote LOD3 and LOD4 captures and the `(6,2,0,lod3)` tile control are pixel-identical to CPU. The other LODs remain eligible for GPU work.
All ten final CPU/GPU view pairs were recaptured after `[InitialClipmapReady]`. The six fixed views remain close to CPU (RGB MAE 0.6076–1.6092). The four full remote yaw views have MAE 0.1994, 0.4083, 0.0484, and 0.2355 for yaw 0, 90, 180, and 270 respectively. Small image differences remain outside the isolated LOD3/4 checks. Detailed numerical comparisons, including the tile controls, are in [step9final_capture_comparison.md](step9final_capture_comparison.md); the measurements are reproducible with [CompareStep9Captures.py](CompareStep9Captures.py).
## First GPU submission
The earlier no-hold viewer controls in [step8_report.md](step8_report.md) measured a 2.36–2.59 s first GPU-backed `Present`; those calls returned `DXGI_STATUS_OCCLUDED`. The new standalone control in `GpuFirstSubmitRepro.cpp` allocates the raw-buffer/UAV pools, optionally touches their resources with a trivial dispatch, and varies `D3D11_CREATE_DEVICE_SINGLETHREADED`.
The repro did **not** reproduce a multi-second delay. Pool allocation took about 4.0 ms and `Present` took 0.87–0.96 ms. Touching each of the five pool resources per chunk moved sampled local usage to 2,590 MB; its event wait was 83 ms (73 ms with the single-threaded flag), and `Present` was still about 0.9–1.1 ms. This rules out pool allocation, this limited first-touch dispatch, and the tested device flag as a standalone explanation. It does not isolate what the full field/mesh workload or viewer queue pump is doing.
In the measured viewer runs, warm GPU queue envelopes were about 3.59 s, while timestamped GPU work was 0.66–0.71 s. The viewer's pump CPU maximum was about 2.56–2.58 s, and estimated idle/poll time was 2.89–2.94 s. That locates a large part of the delay in the viewer's GPU pumping/submission path, but does not identify an internal driver operation. The front-buffer hold hides the early wait from the first CPU-ready frame; it does not shorten time to the completed GPU view.
## Whole-view race
Headless generation has a CPU measurement, not an equivalent end-to-end GPU mode. For the audited far subset, CPU `generateTile` summed to 2.090 s, while RX 7900 field plus meshing timestamps summed to 0.125 s (field dispatch alone: 0.014 s). That is a real far-tile compute advantage, but it excludes the rest of the clipmap, CPU setup, submission, and presentation.
The interleaved process-start race used new processes for every run. The first sequence was CPU A, GPU A, GPU B, CPU B, CPU C, GPU C:
| Run | Process start to full clipmap readiness and first following `Present` return |
|---|---:|
| CPU A, first launch | 1.516 s |
| GPU A, first launch | 5.146 s |
| GPU B, later launch | 4.704 s |
| CPU B, later launch | 1.438 s |
| CPU C, later launch | 1.437 s |
| GPU C, later launch | 4.702 s |
The CPU median is **1.438 s**; the GPU median is **4.704 s**, about **3.27x slower**. A second normal-window launch sequence also stayed slower: CPU was 1.409–1.416 s and GPU was 4.022–4.262 s. Those six calls also returned `DXGI_STATUS_OCCLUDED`. Therefore these automated runs measure full clipmap readiness plus the subsequent `Present` return, but they do not prove a physical on-screen scanout. Their CPU results are consistent with the previously observed ~1.49 s viewer result; the GPU path is still several seconds behind on the readiness clock. The GPU accumulated 0.64–0.79 s of timestamped dispatch work per viewer run; CPU setup, input preparation, uploads, and rendering also remain on the path, with some stages overlapping.
There is no current successful movement-crossing latency sample. The final-build crossing probe completed the initial clipmap, then logged `[GpuFarPoolError] no free far-output slot for queued tile` after the target set moved. It disabled the GPU path and never emitted `[FlightBenchmarkComplete]`; the run was stopped. The earlier 22.931 ms nearest-LOD0 crossing measurement in step 6 predates this build and is not a current result. The dynamic far-output pool needs a reuse/overlap fix before claiming a GPU movement-readiness benefit.
## Limits and artifacts
- Direct GPU rendering still has an unresolved LOD3/4 coverage bug; the CPU fallback costs GPU coverage at those levels.
- The first-submit repro narrows the stall but does not identify the driver's internal work.
- Automated `Present` calls returned `DXGI_STATUS_OCCLUDED`, including the normal-window attempt, so the current process race is not a verified on-screen timing.
- Capture runs peaked at about 3.53 GB private memory. In the interleaved timing runs, private memory peaked around 3.34 GB and sampled DXGI local usage at 2.404 GB, a conservative sum of about 5.75 GB. No run exceeded the 8 GB limit or hit `0xC0000142`; started viewers were closed.
Primary artifacts: `step9measured_*` logs, `step9firstsubmit_*` logs, `step9final_*` capture pairs, [CompareStep9Captures.py](CompareStep9Captures.py), and [RunStep9NormalWindowInterleaved.ps1](RunStep9NormalWindowInterleaved.ps1). No capture was opened in an image viewer; image comparisons were numerical.
@@ -0,0 +1,25 @@
# Step 9 CPU/GPU capture comparisons
CPU and GPU captures are compared numerically as RGB arrays. The view matrix was taken only after each run logged `[InitialClipmapReady]`; the render-only tile probes retained the full desired target set.
`MAE` and `RMSE` use the 0-255 RGB channel scale. `Changed` and `>32` are the percent of pixels whose maximum channel difference is nonzero or above 32.
| Pair | Size | MAE RGB | RMSE RGB | Changed | >32 | Max channel |
|---|---:|---:|---:|---:|---:|---:|
| chamber | 1280x800 | 0.6076 | 1.8655 | 42.64% | 0.02% | 124 |
| tunnel | 1280x800 | 0.6383 | 3.3799 | 31.73% | 0.28% | 130 |
| passage | 1280x800 | 1.6092 | 3.6878 | 50.50% | 0.17% | 52 |
| far | 1280x800 | 0.7909 | 2.1558 | 44.83% | 0.02% | 128 |
| passage_floor_closeup | 1280x800 | 0.9314 | 2.4800 | 43.02% | 0.05% | 100 |
| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.96% | 0.07% | 55 |
| remote_yaw0 | 1280x800 | 0.1994 | 2.6632 | 4.25% | 0.24% | 125 |
| remote_yaw90 | 1280x800 | 0.4083 | 4.3552 | 2.24% | 0.69% | 94 |
| remote_yaw180 | 1280x800 | 0.0484 | 1.3206 | 2.98% | 0.03% | 102 |
| remote_yaw270 | 1280x800 | 0.2355 | 3.1104 | 5.56% | 0.41% | 107 |
| control_tile_4_3_-1_lod3 | 1280x800 | 0.0311 | 0.6024 | 0.35% | 0.00% | 14 |
| direct_gpu_candidate_6_2_0_lod3 | 1280x800 | 0.9228 | 7.9137 | 1.46% | 1.46% | 82 |
| direct_gpu_vs_forced_cpu_candidate_6_2_0_lod3 | 1280x800 | 0.9228 | 7.9137 | 1.46% | 1.46% | 82 |
| cpu_reference_vs_forced_cpu_candidate_6_2_0_lod3 | 1280x800 | 0.0000 | 0.0000 | 0.00% | 0.00% | 0 |
| final_candidate_tile_6_2_0_lod3 | 1280x800 | 0.0000 | 0.0000 | 0.00% | 0.00% | 0 |
| final_remote_lod3 | 1280x800 | 0.0000 | 0.0000 | 0.00% | 0.00% | 0 |
| final_remote_lod4 | 1280x800 | 0.0000 | 0.0000 | 0.00% | 0.00% | 0 |
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

+77 -4
View File
@@ -1323,6 +1323,7 @@ namespace
ID3D11Buffer* VertexBuffer=nullptr;
ID3D11Buffer* IndexBuffer=nullptr;
UINT IndexCount=0;
UINT DrawOnlyIndexStart=0,DrawOnlyIndexCount=0;
uint64 VertexCount=0;
};
struct FCrossingWork
@@ -1534,6 +1535,7 @@ namespace
bool bAutoCaptureWritten=false;
bool bFullDetailReported=false;
bool bInitialClipmapReported=false;
bool bWholeViewPresented=false;
bool bProfileAttribution=false;
bool bProfileDetailBand=false;
bool bCompareEvaluators=false;
@@ -1567,6 +1569,8 @@ namespace
int32 CaptureOnlyLod=-1;
bool bCaptureOnlyTile=false;
int32 CaptureOnlyTileX=0,CaptureOnlyTileY=0,CaptureOnlyTileZ=0,CaptureOnlyTileLod=0;
bool bDrawOnlyTile=false;
int32 DrawOnlyTileX=0,DrawOnlyTileY=0,DrawOnlyTileZ=0,DrawOnlyTileLod=0;
int32 CaptureOutputSlot=-1;
int32 Width=1280,Height=800;
float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f;
@@ -1683,6 +1687,12 @@ namespace
V3 Forward() const
{return Normalize({std::sin(Yaw)*std::cos(Pitch),std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)});}
bool IsDrawOnlyTile(const FVoxelTileKey& Key) const
{
return !bDrawOnlyTile||(Key.Level==DrawOnlyTileLod&&Key.Coord.X==DrawOnlyTileX
&&Key.Coord.Y==DrawOnlyTileY&&Key.Coord.Z==DrawOnlyTileZ);
}
FIntVector CameraChunk() const
{return StandaloneWorldMesher::CameraChunk(FVector(Position.X*4.0,Position.Y*4.0,Position.Z*4.0));}
@@ -2104,7 +2114,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-ColdProcessStart).count());
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels);
bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false;
bFullDetailReported=false;bInitialClipmapReported=false;bWholeViewPresented=false;bAutoCaptureWritten=false;
if(bProfileAttribution)
{
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Attribution,128);
@@ -2174,6 +2184,20 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
}
DesiredTileSet.clear();DesiredTileSet.reserve(DesiredTiles.size()*2+1);
for(const FVoxelTileKey& Key:DesiredTiles)DesiredTileSet.insert(Key);
if(bDrawOnlyTile)
{
const FVoxelTileKey DrawKey(FIntVector(DrawOnlyTileX,DrawOnlyTileY,DrawOnlyTileZ),
DrawOnlyTileLod);
if(!DesiredTileSet.contains(DrawKey))
{
Error="the requested draw-only tile is not in the full desired clipmap";
std::fprintf(stderr,"World load failed: %s\n",Error.c_str());
PostMessageA(Window,WM_CLOSE,0,0);
return;
}
std::printf("[DrawOnlyTile] key=(%d,%d,%d,lod%d) desired_targets=%zu filter=draw_only\n",
DrawOnlyTileX,DrawOnlyTileY,DrawOnlyTileZ,DrawOnlyTileLod,DesiredTiles.size());
}
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
const int32 StreamWorkers=bShapePipelineActive?(WorkerCount>=8?2:1):WorkerCount;
@@ -2434,10 +2458,21 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::vector<FVoxelTileKey> CpuTiles;CpuTiles.reserve(Keys.size());
std::vector<FVoxelTileKey> Eligible;Eligible.reserve(Keys.size());
uint64 SkirtTransitionFallbackTiles=0;
uint64 RemoteLod3CpuFallbackTiles=0,RemoteLod4CpuFallbackTiles=0;
for(const FVoxelTileKey& Key:Keys)
{
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
const bool NearGpuEligible=Key.Level<World->FullResClipLevels;
if(Key.Level==3||Key.Level==4)
{
CpuTiles.push_back(Key);
if(Key.Level==3)++RemoteLod3CpuFallbackTiles;
else ++RemoteLod4CpuFallbackTiles;
if(bDrawOnlyTile&&IsDrawOnlyTile(Key))
std::printf("[DrawOnlyTileRoute] key=(%d,%d,%d,lod%d) route=cpu_remote_band_fallback\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level);
continue;
}
if(NearGpuEligible||ShapeWorld->isFarFieldTileEligible(CoreKey))
{
const uint8 VisibleSkirtFaces=World->bGenerateSkirts
@@ -2464,7 +2499,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
else Eligible.push_back(Key);
}
else CpuTiles.push_back(Key);
if(bDrawOnlyTile&&IsDrawOnlyTile(Key))
std::printf("[DrawOnlyTileRoute] key=(%d,%d,%d,lod%d) route=%s\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
Eligible.empty()||Eligible.back()!=Key?"cpu_fallback":"gpu_eligible");
}
if(RemoteLod3CpuFallbackTiles+RemoteLod4CpuFallbackTiles>0)
std::printf("[GpuRemoteBandFallback] lod3_cpu_tiles=%llu lod4_cpu_tiles=%llu reason=remote_capture_pixel_coverage\n",
static_cast<unsigned long long>(RemoteLod3CpuFallbackTiles),
static_cast<unsigned long long>(RemoteLod4CpuFallbackTiles));
if(Eligible.size()>GpuFarOutputCapacitySlots)
{
for(auto& Pair:GpuFarTiles)ReleaseGpuFarTile(Pair.second);
@@ -3027,9 +3070,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{std::fprintf(stderr,"LOD %d batch exceeded D3D11 limits.\n",Key.Level);return false;}
std::vector<FGpuVertex> Vertices;Vertices.reserve(VertexCount);
std::vector<uint32> Indices;Indices.reserve(IndexCount);
UINT DrawOnlyIndexStart=0,DrawOnlyIndexCount=0;
for(const FVoxelTileKey& Tile:Members)
{
const auto It=ResidentTiles.find(Tile);if(It==ResidentTiles.end())continue;
const size_t TileIndexStart=Indices.size();
const uint32 Base=static_cast<uint32>(Vertices.size());
const FResidentTileMesh& Resident=It->second;
for(const FStandaloneCpuVertex& V:Resident.Vertices)
@@ -3047,6 +3092,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
Indices.push_back(Base+Resident.Indices[Start+1]);
Indices.push_back(Base+Resident.Indices[Start+2]);
}
if(bDrawOnlyTile&&IsDrawOnlyTile(Tile))
{
DrawOnlyIndexStart=static_cast<UINT>(TileIndexStart);
DrawOnlyIndexCount=static_cast<UINT>(Indices.size()-TileIndexStart);
}
}
FStreamingGpuBatch NewBatch;
const bool bVertex=MakeBuffer(&NewBatch.VertexBuffer,Vertices.data(),Vertices.size()*sizeof(FGpuVertex),D3D11_BIND_VERTEX_BUFFER);
@@ -3058,6 +3108,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
return false;
}
NewBatch.IndexCount=static_cast<UINT>(Indices.size());NewBatch.VertexCount=Vertices.size();
NewBatch.DrawOnlyIndexStart=DrawOnlyIndexStart;
NewBatch.DrawOnlyIndexCount=DrawOnlyIndexCount;
if(Existing!=StreamingBatches.end())
{
TotalVertices-=Existing->second.VertexCount;TotalIndices-=Existing->second.IndexCount;
@@ -3459,9 +3511,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
const FStreamingGpuBatch& L=Pair.second;
if(!bShowLevel[Pair.first.Level]
||(CaptureOnlyLod>=0&&Pair.first.Level!=CaptureOnlyLod)
||!L.VertexBuffer||!L.IndexBuffer||!L.IndexCount)continue;
||!L.VertexBuffer||!L.IndexBuffer||!L.IndexCount
||(bDrawOnlyTile&&L.DrawOnlyIndexCount==0))continue;
const UINT Stride=sizeof(FGpuVertex),Offset=0;Context->IASetVertexBuffers(0,1,&L.VertexBuffer,&Stride,&Offset);
Context->IASetIndexBuffer(L.IndexBuffer,DXGI_FORMAT_R32_UINT,0);Context->DrawIndexed(L.IndexCount,0,0);
Context->IASetIndexBuffer(L.IndexBuffer,DXGI_FORMAT_R32_UINT,0);
if(bDrawOnlyTile)
Context->DrawIndexed(L.DrawOnlyIndexCount,L.DrawOnlyIndexStart,0);
else Context->DrawIndexed(L.IndexCount,0,0);
}
// Avoid issuing hundreds of tiny indirect draw calls while the
// initial far batches are still being built. Once the clipmap is
@@ -3474,7 +3530,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
for(auto& Pair:GpuFarTiles)
{
FGpuFarTile& Tile=Pair.second;
if(!bShowLevel[Pair.first.Level]
if(!IsDrawOnlyTile(Pair.first)||!bShowLevel[Pair.first.Level]
||(CaptureOnlyLod>=0&&Pair.first.Level!=CaptureOnlyLod)
||!Tile.VertexBuffer||!Tile.IndexBuffer||!Tile.IndirectArgs)continue;
if(!bInitialClipmapReported&&Pair.first.Level>=World->FullResClipLevels)continue;
@@ -3580,6 +3636,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
const auto PresentCallStart=std::chrono::steady_clock::now();
const HRESULT PresentResult=SwapChain->Present(PresentSyncInterval,PresentFlags);
const auto PresentCallEnd=std::chrono::steady_clock::now();
if(bInitialClipmapReported&&!bWholeViewPresented)
{
bWholeViewPresented=true;
std::printf("[WholeViewPresented] at_ms=%.3f present_ms=%.3f sync=%u hr=0x%08lx\n",
std::chrono::duration<double,std::milli>(PresentCallEnd-ColdProcessStart).count(),
std::chrono::duration<double,std::milli>(PresentCallEnd-PresentCallStart).count(),
PresentSyncInterval,static_cast<unsigned long>(PresentResult));
std::fflush(stdout);
}
if(bGpuWarmupHoldReported&&!bGpuWarmupReleaseReported)
{
bGpuWarmupReleaseReported=true;
@@ -5533,6 +5598,14 @@ int main(int Argc,char** Argv)
App.bCaptureOnlyTile=true;
else {std::fprintf(stderr,"Invalid --capture-only-tile value: %s\n",Values.c_str());return 2;}
}
else if(Arg.starts_with("--draw-only-tile="))
{
const std::string Values(Arg.substr(std::string_view("--draw-only-tile=").size()));
if(std::sscanf(Values.c_str(),"%d,%d,%d,%d",&App.DrawOnlyTileX,
&App.DrawOnlyTileY,&App.DrawOnlyTileZ,&App.DrawOnlyTileLod)==4)
App.bDrawOnlyTile=true;
else {std::fprintf(stderr,"Invalid --draw-only-tile value: %s\n",Values.c_str());return 2;}
}
else if(Arg.starts_with("--capture-output-slot="))
App.CaptureOutputSlot=std::max(-1,std::atoi(
std::string(Arg.substr(std::string_view("--capture-output-slot=").size())).c_str()));