From bb082f3ae963154e51239d5b3fb00106866dc0a9 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Thu, 8 Oct 2026 00:32:13 +0200 Subject: [PATCH] wip(standalone): GPU step 23 -- skip empty connector-ring dispatch; cached 1.07-1.17 s Connector-ring kernel skipped when a tile has no rings, no connector vertices and no visible skirts (skirted tiles keep it: an earlier variant lost remote coverage, caught by the captures). Outlier controls: the step 22 67 ms tile runs in ~23 us isolated, also after a pool touch; no-present control did not remove outliers. Cause unknown. My runs outside the sandbox (Step23.exe, marker removed first, interleaved, -Timeline): CPU 1.360 / 1.294 / 1.287 s GPU cached 1.168 / 1.072 / 1.151 s (GPU timestamps 295 / 245 / 264 ms, max dispatch 52.7 / 9.2 / 35.0 ms) GPU marker miss 1.449 s. Measured: on the miss route, device-init end to generation-setup begin takes ~132-155 ms (s22 a/e, s23 a) vs ~3.5 ms on the match route; s22 f (miss) was 3.5 ms. That gap explains the slow first launches. Audits: hashes w1/w8, walk 5,642/5,642, near 960 / far 1,114 0 diffs vs CPU and WARP, thin 353, survival ~6.14 GB, ten captures as step 22. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ --- .../gpu_field_port/GpuFarViewer.h | 1 + .../gpu_field_port/GpuFieldPort.cpp | 171 +++++++++++++++--- .../OwnerShapeWorld/gpu_field_port/RUNNERS.md | 22 +++ .../gpu_field_port/RunGpuFarAgreement.ps1 | 40 +++- .../gpu_field_port/RunGpuFarViewer.ps1 | 4 +- .../gpu_field_port/STEP23_REPORT.md | 41 +++++ .../step23_final_capture_comparison.md | 18 ++ .../step23_ring_skip_capture_comparison.md | 18 ++ ...tep23_step22_control_capture_comparison.md | 18 ++ Standalone/Source/StandaloneViewer.cpp | 35 +++- 10 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP23_REPORT.md create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_final_capture_comparison.md create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_ring_skip_capture_comparison.md create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_step22_control_capture_comparison.md diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h index f6ed10f..c69370e 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h @@ -14,6 +14,7 @@ struct ID3D11Query; namespace VoxelForgeGpuFarViewer { inline constexpr std::uint32_t PooledOutputVertexCapacityPerTile=19456; + inline constexpr std::uint32_t PooledOutputSlotsPerChunk=128; struct CpuVertex { float position[3]{}, normal[3]{}; }; struct CpuMesh { diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp index 42ff09c..083fe23 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp @@ -575,7 +575,8 @@ namespace UINT64 timestampFrequency = 0; std::vector farOutputChunks; std::uint32_t farOutputCapacitySlots=0; - std::uint32_t farOutputSlotsPerChunk=128,farMaxVerticesPerTile=0; + std::uint32_t farOutputSlotsPerChunk=VoxelForgeGpuFarViewer::PooledOutputSlotsPerChunk, + farMaxVerticesPerTile=0; ComPtr shaderWarmupCompletion; static constexpr std::uint32_t FarOutputVertexCapacityPerTile= VoxelForgeGpuFarViewer::PooledOutputVertexCapacityPerTile; @@ -1713,6 +1714,7 @@ namespace ComPtr startQuery, endQuery, disjointQuery; ComPtr fieldStartQuery,fieldEndQuery; std::array,9> kernelStartQueries,kernelEndQueries; + std::array kernelStageUsed{}; if(captureGpuTiming) { Check(device->CreateQuery(×tampDesc, &startQuery), "CreateQuery(start)"); @@ -1735,7 +1737,7 @@ namespace } const auto beginKernel=[&](std::size_t stage) - {if(profileGpuKernels)context->End(kernelStartQueries[stage].Get());}; + {if(profileGpuKernels){kernelStageUsed[stage]=true;context->End(kernelStartQueries[stage].Get());}}; const auto endKernel=[&](std::size_t stage) {if(profileGpuKernels)context->End(kernelEndQueries[stage].Get());}; @@ -1833,20 +1835,27 @@ namespace if(farField) { - ID3D11Buffer* connectorCbs[3]={cb.Get(),farCb.Get(),farConnectorConstantsBuffer.Get()}; - context->CSSetConstantBuffers(0,3,connectorCbs); - beginKernel(4); - context->CSSetShader(connectorRings.Get(),nullptr,0); - ID3D11ShaderResourceView* connectorRingSrvs[5]={farConnectorSrv.Get(),nullptr, - farShapeSrv.Get(),farRangeSrv.Get(),farIdSrv.Get()}; - context->CSSetShaderResources(8,5,connectorRingSrvs); - ID3D11UnorderedAccessView* connectorRingUavs[8]{}; - connectorRingUavs[4]=argsUav.Get();connectorRingUavs[7]=farConnectorRingUav.Get(); - context->CSSetUnorderedAccessViews(0,8,connectorRingUavs,nullptr); - context->Dispatch(std::max(1,(farField->connectorRingCount+15u)/16u),1,1); - endKernel(4); - context->CSSetShaderResources(8,5,nullSrvs); - context->CSSetUnorderedAccessViews(0,8,nullUavs,nullptr); + // Skirts share the connector output buffer; the ring kernel sets its + // per-slot base vertex even when no connector rings are present. + // Skip only when neither connectors nor skirts use that output slot. + if(farField->connectorRingCount||farField->connectorVertexCount + ||farField->visibleSkirtFaceMask) + { + ID3D11Buffer* connectorCbs[3]={cb.Get(),farCb.Get(),farConnectorConstantsBuffer.Get()}; + context->CSSetConstantBuffers(0,3,connectorCbs); + beginKernel(4); + context->CSSetShader(connectorRings.Get(),nullptr,0); + ID3D11ShaderResourceView* connectorRingSrvs[5]={farConnectorSrv.Get(),nullptr, + farShapeSrv.Get(),farRangeSrv.Get(),farIdSrv.Get()}; + context->CSSetShaderResources(8,5,connectorRingSrvs); + ID3D11UnorderedAccessView* connectorRingUavs[8]{}; + connectorRingUavs[4]=argsUav.Get();connectorRingUavs[7]=farConnectorRingUav.Get(); + context->CSSetUnorderedAccessViews(0,8,connectorRingUavs,nullptr); + context->Dispatch(std::max(1,(farField->connectorRingCount+15u)/16u),1,1); + endKernel(4); + context->CSSetShaderResources(8,5,nullSrvs); + context->CSSetUnorderedAccessViews(0,8,nullUavs,nullptr); + } if(farField->connectorVertexCount) { beginKernel(5); @@ -2011,7 +2020,8 @@ namespace if(profileGpuKernels&&!disjoint.Disjoint&&disjoint.Frequency) for(std::size_t stage=0;stageconnectorVertexCount==0) + if(!kernelStageUsed[stage] + ||(stage==5&&farField->connectorVertexCount==0) ||(stage>=6&&farField->visibleSkirtFaceMask==0))continue; const UINT64 stageStart=ReadQuery(kernelStartQueries[stage].Get(), "GetData(kernel stage start)"); @@ -3019,8 +3029,15 @@ namespace bool findThinFeature=false; bool profileGpuKernels=false; bool skirtAuditOnly=false; + bool hasTileKeyOverride=false; + bool pooledOutput=false; + bool warmOutputPool=false; + TileKey tileKeyOverride{}; + std::uint32_t outputPoolSlots=0; + std::uint32_t outputSlot=0; int clipRadiusOverride=-1; int tileLimit=0; + int gpuRepeatCount=2; bool hasCameraOverride=false; Vec3d cameraOverrideVoxels{}; for(int i=1;i=outputPoolSlots) + throw std::runtime_error("--output-slot must be smaller than --pool-slots"); + } bool allPass = noiseCpuGpu.differingBits == 0 && noiseRepeat.differingBits == 0 && noiseWarp.differingBits == 0; std::uint64_t testedTiles=0;double cpuTileWallSum=0.0,cpuFieldPrepSum=0.0,cpuExportPrepSum=0.0; double gpuFieldSum=0.0,gpuTotalSum=0.0,warpFieldSum=0.0; - std::array gpuKernelStageSum{}; + std::array gpuKernelStageSum{},gpuKernelRepeatStageSum{}; std::uint64_t gpuFieldDistanceEvaluations=0,gpuFieldSamples=0; std::uint32_t gpuFieldMaxDistanceEvaluationsPerSample=0; std::uint64_t nearTransitionSkipped=0,topologyDiffGpuTotal=0,topologyDiffWarpTotal=0; @@ -3195,9 +3247,59 @@ namespace const auto inputStart = Clock::now(); TileData tile = MakeTileData(world, key, config, *cpuMesh, true,farInput); const double inputWallMs = std::chrono::duration(Clock::now() - inputStart).count(); - const GpuResult gpu1 = hardware.Run(tile,farInput,true,false,true,false,0,false, - true,false,profileGpuKernels); - const GpuResult gpu2 = hardware.Run(tile,farInput); + if(profileGpuKernels&&pooledOutput&&testedTiles==0) + { + hardware.EnsureFarOutputPool(outputPoolSlots,tile.cells); + const GpuResult shaderWarmup=hardware.Run(tile,farInput,false,false,true,false, + 0,false,true,false,false); + std::printf("[GpuShaderOnlyWarmup] key=(%d,%d,%d,lod%d) pooled_output_touched=0 dispatch_ms=%.6f wall_ms=%.6f\n", + key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod, + shaderWarmup.dispatchMs,shaderWarmup.wallMs); + if(warmOutputPool) + { + const GpuResult poolTouch=hardware.Run(tile,farInput,false,false,true,false, + outputSlot,true,true,false,false); + std::printf("[GpuOutputPoolTouch] key=(%d,%d,%d,lod%d) output_slot=%u pool_slots=%u dispatch_ms=%.6f wall_ms=%.6f\n", + key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod, + outputSlot,outputPoolSlots,poolTouch.dispatchMs,poolTouch.wallMs); + } + } + const GpuResult gpu1 = hardware.Run(tile,farInput,true,false,true,false, + outputSlot,pooledOutput,true,false,profileGpuKernels); + const GpuResult gpu2 = hardware.Run(tile,farInput,true,false,true,false, + outputSlot,pooledOutput,true,false,profileGpuKernels); + bool additionalRepeatsMatch=true; + for(int repeatIndex=2;repeatIndexnearDetailEnabled?1:0, + farInput->connectorRingCount,farInput->connectorVertexCount, + static_cast(farInput->visibleSkirtFaceMask), + static_cast(farInput->protectedSampleCount), + static_cast(farInput->protectedThinSampleCount), + gpu1.dispatchMs,gpu2.dispatchMs, + gpu1.kernelMs[0],gpu2.kernelMs[0],gpu1.kernelMs[1],gpu2.kernelMs[1], + gpu1.kernelMs[2],gpu2.kernelMs[2],gpu1.kernelMs[3],gpu2.kernelMs[3], + gpu1.kernelMs[4],gpu2.kernelMs[4],gpu1.kernelMs[5],gpu2.kernelMs[5], + gpu1.kernelMs[6],gpu2.kernelMs[6],gpu1.kernelMs[7],gpu2.kernelMs[7], + gpu1.kernelMs[8],gpu2.kernelMs[8]); std::printf("[GpuFieldPipelineWall] key=(%d,%d,%d) lod=%d cpu_export_shape_sign_upload_prep_ms=%.4f cpu_generateTile_ms=%.4f cpu_reference_lattice_topology_check_ms=%.4f rx7900_field_gpu_ms=%.4f rx7900_mesh_plus_field_gpu_ms=%.4f rx7900_wall_upload_dispatch_readback_ms=%.4f warp_field_gpu_ms=%.4f warp_mesh_plus_field_wall_ms=%.4f\n", key.coordinate.x, key.coordinate.y, key.coordinate.z, key.lod, exportMs,cpuWallMs,inputWallMs,gpu1.fieldMs,gpu1.dispatchMs,gpu1.wallMs, @@ -3238,14 +3355,17 @@ namespace && warpResult.cases == tile.expectedCases && gpu1.drawArgs == gpu2.drawArgs &&gpu1.connectorDrawArgs==gpu2.connectorDrawArgs && agreementPass; - allPass &= tilePass; + allPass &= tilePass&&additionalRepeatsMatch; ++testedTiles;cpuTileWallSum+=cpuWallMs;cpuFieldPrepSum+=inputWallMs; cpuExportPrepSum+=exportMs; gpuFieldSum+=gpu1.fieldMs;gpuTotalSum+=gpu1.dispatchMs;warpFieldSum+=warpResult.fieldMs; if(profileGpuKernels) { for(std::size_t stage=0;stage(protectedSolidOverlaySamples)); if(profileGpuKernels) { - std::printf("[GpuKernelProfile] tiles=%llu field_ms=%.6f classify_ms=%.6f scan_ms=%.6f emit_ms=%.6f connector_rings_ms=%.6f connector_panels_ms=%.6f skirt_classify_ms=%.6f skirt_scan_ms=%.6f skirt_emit_ms=%.6f field_shape_distance_evaluations=%llu field_lattice_samples=%llu average_shape_distance_evaluations_per_lattice_sample=%.4f max_shape_distance_evaluations_per_lattice_sample=%u\n", - static_cast(testedTiles),gpuKernelStageSum[0],gpuKernelStageSum[1], + std::printf("[GpuKernelProfile] tile_pairs=%llu passes_per_tile=%d first_field_ms=%.6f first_classify_ms=%.6f first_scan_ms=%.6f first_emit_ms=%.6f first_connector_rings_ms=%.6f first_connector_panels_ms=%.6f first_skirt_classify_ms=%.6f first_skirt_scan_ms=%.6f first_skirt_emit_ms=%.6f repeat_field_ms=%.6f repeat_classify_ms=%.6f repeat_scan_ms=%.6f repeat_emit_ms=%.6f repeat_connector_rings_ms=%.6f repeat_connector_panels_ms=%.6f repeat_skirt_classify_ms=%.6f repeat_skirt_scan_ms=%.6f repeat_skirt_emit_ms=%.6f field_shape_distance_evaluations=%llu field_lattice_samples=%llu average_shape_distance_evaluations_per_lattice_sample=%.4f max_shape_distance_evaluations_per_lattice_sample=%u\n", + static_cast(testedTiles),gpuRepeatCount,gpuKernelStageSum[0],gpuKernelStageSum[1], gpuKernelStageSum[2],gpuKernelStageSum[3],gpuKernelStageSum[4],gpuKernelStageSum[5], gpuKernelStageSum[6],gpuKernelStageSum[7],gpuKernelStageSum[8], + gpuKernelRepeatStageSum[0],gpuKernelRepeatStageSum[1],gpuKernelRepeatStageSum[2], + gpuKernelRepeatStageSum[3],gpuKernelRepeatStageSum[4],gpuKernelRepeatStageSum[5], + gpuKernelRepeatStageSum[6],gpuKernelRepeatStageSum[7],gpuKernelRepeatStageSum[8], static_cast(gpuFieldDistanceEvaluations), static_cast(gpuFieldSamples), gpuFieldSamples?static_cast(gpuFieldDistanceEvaluations)/gpuFieldSamples:0.0, diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md index 8819abb..8e24b86 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md @@ -56,3 +56,25 @@ Step 21 passed the CPU W1/W8 reference checks, whole-near and whole-far CPU/GPU/ - `Research\OwnerShapeWorld\gpu_field_port\RunStep15CaptureMatrix.ps1 -ExecutableName VoxelForgeGpuFarStep22.exe -Prefix -GpuWarmupBeforeWorkers` then `py -3 Research\OwnerShapeWorld\gpu_field_port\CompareStep15Captures.py --prefix ` — captures ten full-residency CPU/GPU pairs and compares numeric RGB arrays. Do not open the full-size captures in an image viewer. Step 22's numeric result is [step22_final_capture_comparison.md](step22_final_capture_comparison.md). Step 22's batch trace showed that event-query completion was followed by resident consumption within 0.012–0.120 ms, with no output copy/upload. In the final diagnostic trace, batches 0–1 were GPU-contiguous, followed by one 38.742 ms GPU gap; batch 2 became CPU-ready at 1662.063 ms and submitted at 1680.422 ms, then batches 2–7 ran contiguously. The batch-level rows and remaining wait are documented in [STEP22_REPORT.md](STEP22_REPORT.md). The output pool now reserves all candidate slots before the warm-up-pending branch defers GPU ownership, removing the variable warm-up-handoff delay that previously moved pool start by about 190 ms. + +## Step 23 + +- `Research\OwnerShapeWorld\gpu_field_port\BuildGpuFieldPort.ps1 -OutputName VoxelForgeGpuFarAgreementStep23.exe` and `BuildGpuFieldViewer.ps1 -OutputName VoxelForgeGpuFarStep23.exe` — build the agreement runner and viewer with `/MP8`; runners set BelowNormal and affinity `0xFF00` and retry once after `0xC0000142`. +- `RunGpuFarAgreement.ps1 -ExecutableName VoxelForgeGpuFarAgreementStep23.exe -TileKey 0,-1,1,2 -ProfileKernels -PooledOutput -PoolSlots 256 -OutputSlot 128 -RepeatCount 16 -LogStem step23_repeat_cold_pool` — profiles the former `(0,-1,1,lod2)` viewer outlier on one pooled output slot. Repeat with `-WarmOutputPool` and another log stem to touch that slot before measurement. The shader-only warmup intentionally does not write the pool. Confirm `[GpuAgreement] repeated_gpu_outputs_identical=1` and compare first/repeat stage times in `[GpuKernelRepeatProfile]`. +- `RunGpuFarAgreement.ps1 -ExecutableName VoxelForgeGpuFarAgreementStep23.exe -WholeFar -TileLimit 32 -ProfileKernels -PooledOutput -WarmOutputPool -PoolSlots 256 -OutputSlot 128 -LogStem step23_profile_far32` — profile the first 32 selected far tiles. Repeat with `-WholeNear` to compare the near-detail stages. Pool touching requires profiling and pooled output. +- `RunGpuFarViewer.ps1 -Mode gpu -WarmupBeforeWorkers -NoColdDrain -Timeline -ExecutableName VoxelForgeGpuFarStep23.exe -LogStem step23_present_trace` — viewer throughput trace. Add `-NoPresent` for the control that preserves the CPU-ready handoff presentation and suppresses later presents; it reports `[GpuPresentationControl]`. `[GpuFarSlowDispatchTile]` now includes output slot and pool chunk; `[GpuFarMaxDispatchTile]` records the max dispatch key. +- `RunGpuFarViewer.ps1 -Mode gpu -WarmupPolicy auto -NoColdDrain -Timeline -ExecutableName VoxelForgeGpuFarStep23.exe -LogStem step23_auto_owner` — cached-path measurement only when the marker matches and GPU submissions are nonzero. A marker match with zero submissions is CPU fallback and cannot validate the cached-GPU target. +- `RunGpuFarAgreement.ps1 -ExecutableName VoxelForgeGpuFarAgreementStep23.exe -WholeNear -LogStem step23_final_whole_near` (repeat with `-WholeFar`) — final-build CPU/hardware/WARP agreement. Require `far_field_topology_protected_status=PASS`, zero topology differences, zero protected exception differences, and zero thin-feature differences. +- `RunStep15CpuReference.ps1 -ExecutableName VoxelForgeGpuFarStep23.exe -LogPrefix step23_cpu_reference` — unchanged CPU W1/W8 hashes and 5,642/5,642 walkability. +- `RunStep15GpuSurvival.ps1 -ExecutableName VoxelForgeGpuFarStep23.exe -LogName step23_final_gpu_survival` — full-residency GPU survival with required field readback. Require both survival audits to pass, `[InitialClipmapReady]`, exit 0, and private plus sampled DXGI-local memory below 8 GB. +- `RunStep15CaptureMatrix.ps1 -ExecutableName VoxelForgeGpuFarStep23.exe -Prefix step23_final -GpuWarmupBeforeWorkers` then `py -3 Research\OwnerShapeWorld\gpu_field_port\CompareStep15Captures.py --prefix step23_final` — ten full-residency CPU/GPU pairs and numeric RGB comparison. Do not open full-size captures in an image viewer; see [step23_final_capture_comparison.md](step23_final_capture_comparison.md). + +Step 23 profiles show that the connector-ring kernel was being dispatched for ordinary tiles with no connectors. That kernel also initializes the per-slot output base used by skirts, so it is skipped only when there are no connector rings, no connector vertices, and no visible skirt faces. On 32 far tiles it previously cost 1.703 ms total (53.2 μs/tile); on 32 near tiles it cost 1.489 ms (46.5 μs/tile). After the guarded skip, the field/classify/scan/emit stages took about 1.39 ms for those far tiles and 2.72 ms for the near tiles. The final capture matrix caught an intermediate version that skipped the kernel on skirted tiles; restoring it when skirt faces are visible returned the missing remote coverage to the Step 22 baseline. The final build passes the full survival audit and numeric capture comparison. + +The isolated repeat control did not reproduce the viewer's long dispatch: the former 67 ms tile measured 22.44–23.92 μs across 16 repeated pooled dispatches. Touching the same output slot first made no material difference, and repeats were bit-identical. Viewer traces still had variable 24–31 ms maximum dispatches. Suppressing presentations after the CPU handoff did not remove them: two final traces totaled 282.455 and 279.019 ms of GPU timestamps, with different maximum tile keys. This argues against deterministic tile work and this tested slot's first-touch as causes, and shows no clear present-related reduction. It does not distinguish queue contention, residency elsewhere, or timestamp/scheduling artifacts; the viewer outlier cause remains unproven. + +Near-first dispatch ordering already exists in `GpuFarKeyLess`: queued tiles sort by LOD, then by camera-center distance. No new scheduling-order change was made in Step 23. + +Final-build whole-near tested 960 tiles and matched all 229,376 protected exception cells on hardware and WARP with zero topology differences. Whole-far tested 1,114 eligible tiles (152 near-transition tiles skipped), matching 127,401 protected cells and 353 thin-feature cells with zero topology differences on hardware and WARP. The auxiliary vertex-distance tolerance diagnostic still flags known high-LOD outliers documented in earlier step reports; this does not change the zero sign/topology/protected/thin difference results. CPU W1/W8 hashes remain density `0xdb2991c73215af4c`, mesh `0x8236c5edd7fc8e4f`, payload `0x4c48fa75a543d58e`, topology `0xbea22e76b34a4dfc`; walkability is 5,642/5,642. The final GPU survival audit passed, with 7,200/7,200 shell boundary surfaces and 10,976/10,976 connectors; peak private memory was 3.53 GB and sampled DXGI-local memory 2,670.8 MB (conservative sum about 6.14 GB). All ten final captures match the Step 22 missing-coverage signature: zero CPU-rock-to-GPU-background pixels in eight views and two in each of `remote_yaw180` and `remote_yaw270`. + +The cached target remains unverified in this environment. The CPU viewer completed in 1.331 s. Two marker-matched `WarmupPolicy auto` runs submitted zero GPU tiles and fell back to CPU, completing in 1.909 and 1.966 s; do not treat those as cached-GPU timings. The GPU throughput traces (1.959 and 2.113 s) include startup and are not cached-launch timings. No cache was manually cleared or modified and no driver setting was changed; the application naturally recorded `Standalone\bin\VoxelForgeGpuWarmup.state` during its warm-up route. No commit, push, or stash was made. See [STEP23_REPORT.md](STEP23_REPORT.md) for the measurements and limits. diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 index 14fcf3a..1d2b582 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 @@ -1,5 +1,20 @@ [CmdletBinding()] -param([string]$ExecutableName='VoxelForgeGpuFarAgreement.exe',[string]$LogStem='whole_far_rx7900_warp',[switch]$WholeFar,[switch]$WholeNear,[switch]$FindThinFeature,[int]$ClipRadius=3) +param( + [string]$ExecutableName='VoxelForgeGpuFarAgreement.exe', + [string]$LogStem='whole_far_rx7900_warp', + [switch]$WholeFar, + [switch]$WholeNear, + [switch]$FindThinFeature, + [switch]$ProfileKernels, + [switch]$PooledOutput, + [switch]$WarmOutputPool, + [int]$TileLimit=0, + [string]$TileKey='', + [int]$PoolSlots=0, + [int]$OutputSlot=0, + [int]$RepeatCount=2, + [int]$ClipRadius=3 +) $ErrorActionPreference='Stop' if([IO.Path]::GetFileName($LogStem)-ne$LogStem){throw 'LogStem must be a filename stem.'} @@ -18,6 +33,29 @@ if($WholeNear){$Arguments+='--whole-near'} if($WholeFar-and$WholeNear){throw 'Choose either WholeFar or WholeNear, not both.'} if($FindThinFeature-and($WholeFar-or$WholeNear)){throw 'FindThinFeature selects one targeted tile; do not combine it with WholeFar or WholeNear.'} if($FindThinFeature){$Arguments+='--find-thin-feature'} +if($TileLimit-lt 0){throw 'TileLimit must be zero or positive.'} +if($TileLimit-gt 0){$Arguments+=("--tile-limit=$TileLimit")} +if($RepeatCount-lt 2-or$RepeatCount-gt 64){throw 'RepeatCount must be from 2 through 64.'} +if($RepeatCount-ne 2){$Arguments+=("--repeat-count=$RepeatCount")} +if($TileKey) +{ + if($TileKey-notmatch'^-?\d+,-?\d+,-?\d+,\d+$'){throw 'TileKey must be x,y,z,lod.'} + $Arguments+=("--tile-key=$TileKey") +} +if($ProfileKernels){$Arguments+='--profile-kernels'} +if($PooledOutput) +{ + if($PoolSlots-le 0){$PoolSlots=[Math]::Max(1,$OutputSlot+1)} + if($OutputSlot-lt 0-or$OutputSlot-ge$PoolSlots){throw 'OutputSlot must be in the requested pool.'} + $Arguments+='--pooled-output' + $Arguments+=("--pool-slots=$PoolSlots") + $Arguments+=("--output-slot=$OutputSlot") +} +if($WarmOutputPool) +{ + if(-not$PooledOutput-or-not$ProfileKernels){throw 'WarmOutputPool requires PooledOutput and ProfileKernels.'} + $Arguments+='--warm-output-pool' +} for($Attempt=0;$Attempt -lt 2;$Attempt++) { diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 index 4f9bf23..dadd117 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 @@ -7,6 +7,7 @@ param( [string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe', [switch]$WarmupBeforeWorkers, [switch]$Timeline, + [switch]$NoPresent, [switch]$NoColdDrain, [switch]$NoPrewarm, [ValidateSet('auto','cold','cached')][string]$WarmupPolicy='auto' @@ -32,6 +33,7 @@ else{$Arguments+='--gpu-far-audit'} if($Mode-eq'gpu' -and $WarmupPolicy-eq'cold'){$Arguments+='--gpu-far-force-cold-start'} elseif($Mode-eq'gpu' -and $WarmupPolicy-eq'cached'){$Arguments+='--gpu-far-force-cached-start'} if($Mode-eq'gpu'-and$WarmupBeforeWorkers){$Arguments+='--gpu-far-warmup-before-workers'} +if($Mode-eq'gpu'-and$NoPresent){$Arguments+='--gpu-far-no-present'} if($Mode-eq'gpu'-and$NoPrewarm){$Arguments+='--gpu-far-no-prewarm'} if($Timeline){$Arguments+='--gpu-far-timeline'} if($Mode-eq'gpu'-and$NoColdDrain){$Arguments+='--gpu-far-no-cold-drain'} @@ -43,7 +45,7 @@ if($CaptureName) $Arguments+=("--capture=Research/OwnerShapeWorld/gpu_field_port/$CaptureName") } $Command="`"$ExecutablePath`" $($Arguments -join ' ')" -Write-Output "VIEWER_COMMAND mode=$Mode timeline=$([bool]$Timeline) no_prewarm=$([bool]$NoPrewarm) $Command" +Write-Output "VIEWER_COMMAND mode=$Mode timeline=$([bool]$Timeline) no_prewarm=$([bool]$NoPrewarm) no_present=$([bool]$NoPresent) $Command" if($Mode-eq'gpu'){Write-Output "WARMUP_POLICY requested=$WarmupPolicy state_file=bin\VoxelForgeGpuWarmup.state"} for($Attempt=0;$Attempt -lt 2;$Attempt++) diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP23_REPORT.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP23_REPORT.md new file mode 100644 index 0000000..48fa340 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP23_REPORT.md @@ -0,0 +1,41 @@ +# GPU field port — Step 23 report + +## Result + +Step 23 found avoidable work in ordinary-tile meshing and removed it without changing the CPU reference or final GPU output. Viewer dispatch outliers remain unexplained: isolated tile repeats, an output-pool touch control, and a no-present viewer control did not reproduce or remove them. The ≤1.0 s cached-launch target was not reached or validated because this environment's marker-matched auto route submitted no GPU tiles. + +## Changes + +- Added a shared pooled-output chunk size constant and used it when reporting output chunk indices. +- Added agreement-runner controls for exact tile selection, repeated dispatches on one pooled slot, shader warming without touching the output pool, optional pool touching, and per-kernel GPU timestamp profiling. +- Added output slot and chunk fields to `[GpuFarSlowDispatchTile]` and a `-NoPresent` viewer control. The control preserves the CPU-ready handoff presentation, then suppresses later presents while still submitting render commands. +- Skipped the connector-ring dispatch when a tile has no connector rings, no connector vertices, and no visible skirt faces. The kernel initializes a per-slot base used by skirt geometry even when its connector-ring count is zero, so skirted tiles retain the dispatch. +- Confirmed the existing GPU queue already orders candidates by LOD and camera-center distance, putting near tiles first. No extra sort change was needed. +- Kept all code changes in `Standalone\`. The CPU field/reference implementation is unchanged. + +## Outlier controls + +The Step 22 viewer outlier `(0,-1,1,lod2)` was an ordinary 16-cell tile with no near detail. In the isolated agreement runner, after a shader-only warm-up, it measured 23.12 μs on the first dispatch and 22.60 μs on the next. Sixteen repeats on the same output slot ranged from 22.44 to 23.92 μs. Repeating after touching that pooled slot first produced 22.48–23.36 μs. Outputs were bit-identical, and the control showed no meaningful pool-touch penalty. This runner uses an immediate D3D context, so it does not exactly match the viewer's deferred batch path. + +The final viewer present trace took 2.113 s overall and recorded 282.455 ms of GPU dispatch timestamps; its maximum dispatch was 30.928 ms at `(-1,-17,-5,lod0)`. The no-present-after-handoff control took 1.959 s and recorded 279.019 ms of GPU timestamps, with a different maximum dispatch of 24.243 ms at `(-2,-14,0,lod0)`. Slow-tile keys also moved between runs. This shows no clear present-related reduction and argues against the long tail being tied to a particular tile's deterministic field/mesh work. The tests do not isolate queue contention, residency outside the touched slot, or timestamp/scheduling artifacts, so the cause remains open. + +## GPU work measured + +On 32 ordinary far tiles, the connector-ring stage had taken 1.703 ms total (53.2 μs per tile); on 32 near tiles it had taken 1.489 ms (46.5 μs per tile), despite no connectors being present. With the guarded skip, the profiled field/classify/scan/emit stages took about 1.39 ms over the 32 far tiles and 2.73 ms over the 32 near tiles. This removes measurable work from the common path. Viewer throughput traces still average roughly 0.2 ms per submitted tile, including other kernels and scheduling overhead. + +An intermediate version skipped the ring stage on tiles with visible skirts. Numeric captures exposed missing areas in remote views. A Step 22 binary capture was run as a control and matched the established baseline. Keeping the ring dispatch for skirted tiles fixed the issue. The final capture matrix returns to the baseline signature; details are in [step23_final_capture_comparison.md](step23_final_capture_comparison.md). + +## Final verification + +- Final-build whole-near agreement: 960 tiles; zero hardware and WARP topology differences; all 229,376 protected exception cells matched; repeated GPU outputs were bit-identical. +- Final-build whole-far agreement: 1,114 eligible tiles; 152 near-transition tiles skipped; zero hardware and WARP topology differences; all 127,401 protected exception cells and 353 thin-feature cells matched; repeated GPU outputs were bit-identical. +- The auxiliary vertex-distance tolerance diagnostic still reports known high-LOD outliers documented in earlier step reports. Required field-sign, topology, protected-cell, and thin-feature differences remain zero. +- CPU W1/W8 reference hashes remained equal: density `0xdb2991c73215af4c`, mesh `0x8236c5edd7fc8e4f`, payload `0x4c48fa75a543d58e`, topology `0xbea22e76b34a4dfc`. Walkability: 5,642/5,642. +- Full-residency GPU survival passed with required field readback: 7,200/7,200 shell boundary surfaces and 10,976/10,976 far connectors. Peak private memory was 3.53 GB; sampled DXGI-local memory peaked at 2,670.8 MB, giving a conservative combined figure of about 6.14 GB. +- All ten full-residency CPU/GPU capture pairs were compared numerically. CPU-rock-to-GPU-background counts were zero in eight views and two in each of `remote_yaw180` and `remote_yaw270`, matching the Step 22 baseline. No full-size capture was opened in an image viewer. + +## Timing target and limits + +The CPU viewer completed in 1.331 s. Two `WarmupPolicy auto` runs had a matching application marker but zero GPU submissions, so they used CPU fallback and are not cached-GPU launch measurements; whole-view times were 1.909 and 1.966 s. The GPU throughput traces completed in 1.959 and 2.113 s with startup work included. The ≤1.0 s cached-launch target remains unverified and requires an owner-side run with a warm GPU queue and nonzero submissions. + +The standard app warm-up route naturally recorded `Standalone\bin\VoxelForgeGpuWarmup.state`. No cache was manually cleared or modified, and no driver settings were changed. No commit, push, or stash was made. diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_final_capture_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_final_capture_comparison.md new file mode 100644 index 0000000..cf29af1 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_final_capture_comparison.md @@ -0,0 +1,18 @@ +# Step23_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.1077% | 0.0580% | 107 | 2 | 0 | (835,204)-(886,205) | diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_ring_skip_capture_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_ring_skip_capture_comparison.md new file mode 100644 index 0000000..5c8855d --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_ring_skip_capture_comparison.md @@ -0,0 +1,18 @@ +# Step23_Ring_Skip 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.6081 | 1.8865 | 42.6368% | 0.0183% | 124 | 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.9306 | 2.4714 | 43.0219% | 0.0479% | 100 | 0 | 0 | none | +| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.9629% | 0.0708% | 55 | 0 | 0 | none | +| remote_yaw0 | 1280x800 | 4.9535 | 16.9298 | 17.6350% | 7.3604% | 125 | 1161 | 0 | (0,399)-(1207,770) | +| remote_yaw90 | 1280x800 | 2.0803 | 10.3345 | 6.8294% | 3.8046% | 96 | 3066 | 0 | (18,399)-(1279,405) | +| remote_yaw180 | 1280x800 | 3.4897 | 13.2455 | 13.5916% | 6.5046% | 102 | 2221 | 0 | (0,172)-(1279,404) | +| remote_yaw270 | 1280x800 | 3.2712 | 13.0476 | 13.0321% | 6.0290% | 107 | 1101 | 0 | (0,204)-(1279,405) | diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_step22_control_capture_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_step22_control_capture_comparison.md new file mode 100644 index 0000000..acd7ab5 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step23_step22_control_capture_comparison.md @@ -0,0 +1,18 @@ +# Step23_Step22_Control 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.1077% | 0.0580% | 107 | 2 | 0 | (835,204)-(886,205) | diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp index 16c0a42..8fd242f 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -1565,6 +1565,7 @@ namespace bool bGpuFarPreparationDeferredReported=false; bool bGpuFarFirstPreparedInputReported=false; bool bGpuWarmupCompleted=false,bGpuFarTimelineEnabled=false; + bool bGpuFarNoPresent=false,bGpuFarNoPresentReported=false; bool bGpuWarmupRoutePending=false; bool bGpuShaderWarmupQueueSucceeded=false; std::atomic bGpuShaderWarmupQueueFinished{false}; @@ -2976,8 +2977,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; double DispatchMs)const { if(!bGpuFarTimelineEnabled||DispatchMs<5.0)return; - std::printf("[GpuFarSlowDispatchTile] key=(%d,%d,%d,lod%d) dispatch_ms=%.6f cells=%d step=%d near_detail=%d protected=%llu thin=%llu solid=%llu near_proxy_samples=%u skirt_faces=0x%02x connector_rings=%u connector_vertices=%u\n", - Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,DispatchMs,Tile.InputCells, + std::printf("[GpuFarSlowDispatchTile] key=(%d,%d,%d,lod%d) dispatch_ms=%.6f output_slot=%u output_chunk=%u cells=%d step=%d near_detail=%d protected=%llu thin=%llu solid=%llu near_proxy_samples=%u skirt_faces=0x%02x connector_rings=%u connector_vertices=%u\n", + Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,DispatchMs,Tile.OutputSlot, + Tile.OutputSlot/VoxelForgeGpuFarViewer::PooledOutputSlotsPerChunk,Tile.InputCells, Tile.InputStep,Tile.bNearDetailInput?1:0, static_cast(Tile.ProtectedSamples), static_cast(Tile.ProtectedThinSamples), @@ -5170,9 +5172,21 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; const UINT PresentSyncInterval=(bDisableVSync||bNonBlockingStartupPresent)?0u:1u; const UINT PresentFlags=bNonBlockingStartupPresent?DXGI_PRESENT_DO_NOT_WAIT:0u; const auto PresentCallStart=std::chrono::steady_clock::now(); - const HRESULT PresentResult=SwapChain->Present(PresentSyncInterval,PresentFlags); + // The CPU-ready frame is the ownership handoff gate for this route. + // Keep that one presentation, then suppress presents while GPU work runs. + const bool bDidPresent=!bGpuFarNoPresent||bDeferGpuUntilCpuFrame; + const HRESULT PresentResult=bDidPresent + ?SwapChain->Present(PresentSyncInterval,PresentFlags):S_FALSE; const auto PresentCallEnd=std::chrono::steady_clock::now(); - if(GpuFarTilesSubmitted>0&&!bGpuFirstBackedPresentReported) + if(!bDidPresent&&!bGpuFarNoPresentReported) + { + bGpuFarNoPresentReported=true; + std::printf("[GpuPresentationControl] mode=no_present_after_cpu_handoff first_skipped_frame_ms=%.3f render_commands_still_submitted=1 gpu_tiles_submitted=%llu\n", + ColdTimelineMilliseconds(PresentCallEnd), + static_cast(GpuFarTilesSubmitted)); + std::fflush(stdout); + } + if(bDidPresent&&GpuFarTilesSubmitted>0&&!bGpuFirstBackedPresentReported) { bGpuFirstBackedPresentReported=true; std::printf("[GpuFirstBackedPresent] at_ms=%.3f call_ms=%.3f sync=%u flags=0x%08x hr=0x%08lx submitted_tiles=%llu batches=%llu\n", @@ -5183,7 +5197,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; static_cast(GpuFarSubmissionBatches)); std::fflush(stdout); } - if(bInitialClipmapReported&&!bWholeViewPresented) + if(bDidPresent&&bInitialClipmapReported&&!bWholeViewPresented) { bWholeViewPresented=true; std::printf("[WholeViewPresented] at_ms=%.3f present_ms=%.3f sync=%u hr=0x%08lx\n", @@ -5192,7 +5206,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; PresentSyncInterval,static_cast(PresentResult)); std::fflush(stdout); } - if(bGpuWarmupHoldReported&&!bGpuWarmupReleaseReported) + if(bDidPresent&&bGpuWarmupHoldReported&&!bGpuWarmupReleaseReported) { bGpuWarmupReleaseReported=true; std::printf("[GpuWarmupPresentRelease] at_ms=%.3f first_post_warmup_present_ms=%.3f sync=%u hr=0x%08lx\n", @@ -5201,8 +5215,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; PresentSyncInterval,static_cast(PresentResult)); std::fflush(stdout); } - const uint64 PresentOrdinal=++PresentCallCount; - if(bNonBlockingStartupPresent) + const uint64 PresentOrdinal=bDidPresent?++PresentCallCount:0; + if(bDidPresent&&bNonBlockingStartupPresent) { ++StartupNonBlockingPresentCalls; if(PresentResult==DXGI_ERROR_WAS_STILL_DRAWING)++StartupNonBlockingPresentSkips; @@ -5210,7 +5224,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::fprintf(stderr,"[PresentStartupError] hr=0x%08lx\n", static_cast(PresentResult)); } - if(PresentOrdinal<=3) + if(bDidPresent&&PresentOrdinal<=3) std::printf("[PresentProfile] ordinal=%llu at_ms=%.3f sync=%u flags=0x%08x mode=%s hr=0x%08lx call_ms=%.3f gpu_tiles_submitted=%llu gpu_batches=%llu\n", static_cast(PresentOrdinal), std::chrono::duration(PresentCallEnd-ColdProcessStart).count(), @@ -5219,7 +5233,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::chrono::duration(PresentCallEnd-PresentCallStart).count(), static_cast(GpuFarTilesSubmitted), static_cast(GpuFarSubmissionBatches)); - if(bDeferGpuUntilCpuFrame&&!StreamingBatches.empty()) + if(bDidPresent&&bDeferGpuUntilCpuFrame&&!StreamingBatches.empty()) { bCpuReadyFramePresentedBeforeGpu=true; std::printf("[CpuReadyFrameBeforeGpu] at_ms=%.3f uploaded_cpu_batches=%zu next_action=submit_gpu_far\n", @@ -7112,6 +7126,7 @@ int main(int Argc,char** Argv) else if(Arg=="--affinity-all-cpu"){} else if(Arg=="--gpu-far-no-present-hold") App.bDisableGpuWarmupPresentHold=true; + else if(Arg=="--gpu-far-no-present")App.bGpuFarNoPresent=true; else if(Arg=="--gpu-far-no-cold-drain")App.bDisableGpuColdStartDrain=true; else if(Arg=="--gpu-far-timeline")App.bGpuFarTimelineEnabled=true; else if(Arg=="--gpu-far-no-prewarm")