From 94d50edb471d43bdaa1a6a88de59918bbb7112b7 Mon Sep 17 00:00:00 2001 From: Fr0zka Date: Thu, 8 Oct 2026 02:06:49 +0200 Subject: [PATCH] wip(standalone): GPU step 24 -- warmup policy off the critical path; first launch = CPU Luna: warmup policy (marker read, hashes, adapter query) now resolves on a background thread; startup fflush(stdout) removed (a redirected flush measured 136 ms). Device-init -> generation-setup gap 0.056 ms on the miss route. Deferred-batch replay runner reproduces 85-86% of the viewer's GPU time (240 vs 279 ms); the old "10x" compared one LOD2 tile to a mixed LOD0-8 mean. Remaining sequence-dependent spikes (11-29 ms on tiles that take ~0.1 ms alone) are unexplained. Open: a marker-miss survival audit once stalled after [CpuReadyFrameBeforeGpu]. My runs outside the sandbox (Step24.exe, marker removed first, interleaved, -Timeline): CPU 1.350 / 1.281 / 1.266 s GPU marker miss 1.336 s (all-CPU fallback, generation setup at 88 ms) GPU cached 1.167 / 1.166 / 1.168 s (GPU timestamps 220 / 255 / 205 ms, max dispatch 5.6 / 22.0 / 1.0 ms; 1,134-1,236 GPU tiles) Steadier than step 23 (1.07-1.17) but not faster; <=1.0 s not reached. Audits: hashes w1/w8, walk 5,642/5,642, near 960 / far 1,114 0 diffs vs CPU and WARP, thin 353, survival 6.19 GB, ten captures 2+2 edge pixels as before. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ --- .../gpu_field_port/GpuFarViewer.h | 3 +- .../gpu_field_port/GpuFieldPort.cpp | 461 +++++++++++++++++- .../OwnerShapeWorld/gpu_field_port/RUNNERS.md | 23 + .../gpu_field_port/RunGpuFarAgreement.ps1 | 55 ++- .../gpu_field_port/RunStep15CaptureMatrix.ps1 | 4 +- .../gpu_field_port/STEP24_REPORT.md | 56 +++ .../step24_final384_capture_comparison.md | 18 + .../step24_final_capture_comparison.md | 18 + Standalone/Source/StandaloneViewer.cpp | 162 ++++-- 9 files changed, 753 insertions(+), 47 deletions(-) create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP24_REPORT.md create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final384_capture_comparison.md create mode 100644 Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final_capture_comparison.md diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h index c69370e..e35a703 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h @@ -60,7 +60,8 @@ namespace VoxelForgeGpuFarViewer std::uint32_t* indirectArgsByteOffset, ID3D11Query** completionEvent,GpuTimingQueries* timingQueries, CpuMesh* auditReadback, - double* enqueueWallMs, double* gpuDispatchMs, std::string& error); + double* enqueueWallMs, double* gpuDispatchMs, std::string& error, + bool captureGpuTiming=true); bool SubmitFarTileBatch(ID3D11Device* device,ID3D11DeviceContext* immediateContext, const std::filesystem::path& shaderPath,ID3D11Query** completionEvent, GpuTimingQueries* timingQueries,std::string& error); diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp index 083fe23..86280a9 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp @@ -1041,19 +1041,21 @@ namespace timingQueries->disjoint=disjoint.Detach(); } - void Initialize(const std::filesystem::path& shaderPath, bool warp) + void Initialize(const std::filesystem::path& shaderPath, bool warp, + bool deferredContexts=false) { const auto InitializeStart=std::chrono::steady_clock::now(); D3D_FEATURE_LEVEL requested[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0}; D3D_FEATURE_LEVEL created{}; + const UINT creationFlags=deferredContexts&&!warp?0u:D3D11_CREATE_DEVICE_SINGLETHREADED; HRESULT hr = D3D11CreateDevice(nullptr, warp ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_HARDWARE, - nullptr, D3D11_CREATE_DEVICE_SINGLETHREADED, + nullptr, creationFlags, requested, 2, D3D11_SDK_VERSION, &device, &created, &context); if (hr == E_INVALIDARG) hr = D3D11CreateDevice(nullptr, warp ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_HARDWARE, - nullptr, D3D11_CREATE_DEVICE_SINGLETHREADED, + nullptr, creationFlags, requested + 1, 1, D3D11_SDK_VERSION, &device, &created, &context); Check(hr, warp ? "D3D11CreateDevice(WARP)" : "D3D11CreateDevice(HARDWARE)"); name = warp ? "WARP" : "hardware"; @@ -3026,27 +3028,40 @@ namespace const std::filesystem::path shaderPath = root / "Research" / "OwnerShapeWorld" / "gpu_first_port" / "VoxelForgeGpuMesher.hlsl"; bool wholeFar=false; bool wholeNear=false; + bool wholeViewerPath=false; bool findThinFeature=false; bool profileGpuKernels=false; bool skirtAuditOnly=false; bool hasTileKeyOverride=false; bool pooledOutput=false; bool warmOutputPool=false; + bool deferredBatch=false; + bool deferredOnly=false; + bool deferredNoTileTimestamps=false; TileKey tileKeyOverride{}; std::uint32_t outputPoolSlots=0; std::uint32_t outputSlot=0; int clipRadiusOverride=-1; int tileLimit=0; int gpuRepeatCount=2; + int deferredBatchSize=32; + int deferredFirstBatchSize=0; + int deferredPumpBudgetMs=0; + int deferredRepeatCount=1; + std::vector deferredBatchPattern; bool hasCameraOverride=false; Vec3d cameraOverrideVoxels{}; for(int i=1;i(count)); + if(*end=='\0')break; + cursor=end+1; + if(!*cursor)throw std::runtime_error("invalid --deferred-batch-pattern value: "+pattern); + } + if(deferredBatchPattern.empty()) + throw std::runtime_error("--deferred-batch-pattern must contain positive tile counts"); + } + if(arg.starts_with("--deferred-repeats=")) + deferredRepeatCount=std::atoi(std::string(arg.substr(19)).c_str()); if(arg.starts_with("--camera=")) { float x=0.0f,y=0.0f,z=0.0f; @@ -3121,7 +3162,7 @@ namespace PrintProcessMemory("after_shape_compile"); DeviceRun hardware, warp; - hardware.Initialize(shaderPath, false); + hardware.Initialize(shaderPath, false,deferredBatch); warp.Initialize(shaderPath, true); std::printf("[GpuDevices] hardware=\"%s\" warp=Microsoft_WARP feature_level=11_0\n", hardware.name.c_str()); const std::vector probes = MakeNoiseProbes(static_cast(config.seed)); @@ -3140,9 +3181,68 @@ namespace static_cast(noiseRepeat.differingBits), noiseWarp.mean, noiseWarp.maximum, static_cast(noiseWarp.differingBits)); - std::vector selected = skirtAuditOnly - ?std::vector{}:SelectFarTiles(world, config, camera,wholeFar,wholeNear, - findThinFeature); + std::vector selected; + if(wholeViewerPath) + { + const std::unordered_set desiredSet(desired.begin(),desired.end()); + std::size_t nearCandidates=0,farCandidates=0,nearSkirtCpuFallback=0; + selected.reserve(desired.size()); + for(const TileKey& key:desired) + { + if(key.lod<=1&&key.lodstd::min(config.maxLod,8)||!world.isFarFieldTileEligible(key)) + continue; + auto input=world.exportFarFieldTile(key,error); + if(!input) + { + if(error.find("near analytic/detail region")!=std::string::npos)continue; + throw std::runtime_error("viewer-sequence far export failed: "+error); + } + if(config.generateSkirts) + { + input->visibleSkirtFaceMask=ComputeVisibleFarSkirtFaceMask(key,desiredSet); + if(input->visibleSkirtFaceMask!=0) + input->skirtDepthVoxels=static_cast(std::max(1,config.skirtCells) + *input->step); + } + selected.push_back({key,std::move(*input),0.0,0}); + ++farCandidates; + } + const auto distanceSquared=[&](const TileKey& key) + { + const auto origin=TileOrigin(key); + const double extent=static_cast(32<{} + :SelectFarTiles(world,config,camera,wholeFar,wholeNear,findThinFeature); if(skirtAuditOnly) { if(!config.generateSkirts) @@ -3170,6 +3270,16 @@ namespace tileKeyOverride.coordinate.z,tileKeyOverride.lod,selected.size()); } if (selected.empty()) throw std::runtime_error("no far-field tiles were selected"); + if(!deferredBatchPattern.empty()) + { + const std::size_t patternTileCount=std::accumulate(deferredBatchPattern.begin(), + deferredBatchPattern.end(),std::size_t{0}); + const std::size_t selectedTileCount=tileLimit>0 + ?std::min(selected.size(),static_cast(tileLimit)) + :selected.size(); + if(patternTileCount!=selectedTileCount) + throw std::runtime_error("--deferred-batch-pattern counts must sum to the selected viewer candidate count"); + } if(warmOutputPool&&!pooledOutput) throw std::runtime_error("--warm-output-pool requires --pooled-output"); if(warmOutputPool&&!profileGpuKernels) @@ -3181,6 +3291,45 @@ namespace if(outputSlot>=outputPoolSlots) throw std::runtime_error("--output-slot must be smaller than --pool-slots"); } + if(deferredBatch) + { + if(!pooledOutput)throw std::runtime_error("--deferred-batch requires --pooled-output"); + if(deferredBatchSize<1||deferredBatchSize>384) + throw std::runtime_error("--deferred-batch-size must be from 1 through 384"); + if(deferredFirstBatchSize<0||deferredFirstBatchSize>deferredBatchSize) + throw std::runtime_error("--deferred-first-batch-size must be zero or no larger than --deferred-batch-size"); + if(deferredPumpBudgetMs<0||deferredPumpBudgetMs>10000) + throw std::runtime_error("--deferred-pump-budget-ms must be from 0 through 10000"); + for(const int batchCount:deferredBatchPattern) + if(batchCount>deferredBatchSize) + throw std::runtime_error("--deferred-batch-pattern entries must not exceed --deferred-batch-size"); + if(!deferredBatchPattern.empty()&&(deferredFirstBatchSize||deferredPumpBudgetMs)) + throw std::runtime_error("--deferred-batch-pattern cannot be combined with a first-batch size or pump budget"); + if(!deferredBatchPattern.empty()&&deferredRepeatCount!=1) + throw std::runtime_error("--deferred-batch-pattern requires one repeat per tile"); + if(deferredRepeatCount<1||deferredRepeatCount>64) + throw std::runtime_error("--deferred-repeats must be from 1 through 64"); + if(tileLimit==0&&!hasTileKeyOverride&&!wholeViewerPath) + throw std::runtime_error("--deferred-batch requires --tile-limit, --tile-key, or --whole-viewer-path"); + if(outputPoolSlots-outputSlot(deferredBatchSize)) + throw std::runtime_error("the pooled output range must fit one deferred batch"); + } + if(wholeViewerPath&&!deferredBatch) + throw std::runtime_error("--whole-viewer-path requires --deferred-batch"); + if(wholeViewerPath&&!deferredOnly) + throw std::runtime_error("--whole-viewer-path requires --deferred-only"); + if(wholeViewerPath&&(wholeFar||wholeNear||skirtAuditOnly)) + throw std::runtime_error("--whole-viewer-path cannot be combined with WholeFar, WholeNear, or SkirtAudit"); + if(deferredOnly&&!deferredBatch) + throw std::runtime_error("--deferred-only requires --deferred-batch"); + if(deferredNoTileTimestamps&&!deferredBatch) + throw std::runtime_error("--deferred-no-tile-timestamps requires --deferred-batch"); + if(deferredPumpBudgetMs!=0&&!deferredBatch) + throw std::runtime_error("--deferred-pump-budget-ms requires --deferred-batch"); + if(!deferredBatchPattern.empty()&&(!deferredBatch||!wholeViewerPath||!deferredOnly)) + throw std::runtime_error("--deferred-batch-pattern requires WholeViewerPath, DeferredBatch, and DeferredOnly"); + if(deferredOnly&&skirtAuditOnly) + throw std::runtime_error("--deferred-only cannot be combined with --skirt-audit"); 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; @@ -3192,7 +3341,197 @@ namespace std::uint64_t protectedExceptionCells=0,protectedExceptionDiffGpu=0,protectedExceptionDiffWarp=0; std::uint64_t protectedRouteAirSamples=0,protectedSealSamples=0,protectedSolidOverlaySamples=0; std::uint64_t protectedThinSamples=0,thinFeatureCellsTotal=0,thinFeatureDiffGpuTotal=0,thinFeatureDiffWarpTotal=0; - for (const FarSelection& selection : selected) + struct FDeferredTileTimingSample + { + TileKey key{}; + std::uint32_t outputSlot=0; + int repeat=0; + double enqueueWallMs=0.0; + double dispatchMs=0.0; + VoxelForgeGpuFarViewer::GpuTimingQueries timing{}; + ComPtr vertices,indices,indirectArgs,connectorVertices,connectorIndices; + }; + struct FDeferredBatchSubmission + { + std::vector tiles; + ComPtr completionEvent; + VoxelForgeGpuFarViewer::GpuTimingQueries timing{}; + double recordCpuMs=0.0; + double submitCpuMs=0.0; + double dispatchMs=0.0; + std::uint64_t timestampFrequency=0; + }; + std::vector deferredPendingTiles; + std::vector deferredSubmittedBatches; + double deferredSetupMs=0.0,deferredRecordCpuMs=0.0,deferredSubmitCpuMs=0.0; + std::uint64_t deferredDispatchCount=0; + std::uint32_t deferredBatchOrdinal=0; + Clock::time_point deferredRecordStart{}; + auto releaseTimingQueries=[](VoxelForgeGpuFarViewer::GpuTimingQueries& queries) + { + if(queries.start)queries.start->Release(); + if(queries.end)queries.end->Release(); + if(queries.disjoint)queries.disjoint->Release(); + queries={}; + }; + if(deferredBatch) + { + const auto setupStart=Clock::now(); + std::uint32_t maxTileCells=1; + const std::size_t reserveTileCount=tileLimit>0 + ?std::min(selected.size(),static_cast(tileLimit)) + :selected.size(); + for(std::size_t i=0;i( + Clock::now()-warmupStart).count(); + const auto poolStart=Clock::now(); + if(!VoxelForgeGpuFarViewer::ReserveFarOutputPool(hardware.device.Get(), + hardware.context.Get(),shaderPath,outputPoolSlots,maxTileCells,deferredError)) + throw std::runtime_error("deferred batch output-pool reserve failed: "+deferredError); + const double reserveMs=std::chrono::duration( + Clock::now()-poolStart).count(); + deferredSetupMs=std::chrono::duration(Clock::now()-setupStart).count(); + deferredPendingTiles.reserve(static_cast(deferredBatchSize)); + std::printf("[GpuFarDeferredBatchSetup] batch_size=%d first_batch_size=%d pump_budget_ms=%d repeats_per_tile=%d pool_slots=%u output_slot_base=%u max_cells=%u warmup_ms=%.3f reserve_ms=%.3f total_ms=%.3f\n", + deferredBatchSize,deferredFirstBatchSize,deferredPumpBudgetMs, + deferredRepeatCount,outputPoolSlots,outputSlot,maxTileCells, + warmupMs,reserveMs,deferredSetupMs); + } + const auto submitDeferredBatch=[&]() + { + if(deferredPendingTiles.empty())return; + FDeferredBatchSubmission submission; + submission.tiles=std::move(deferredPendingTiles); + deferredPendingTiles.clear(); + submission.recordCpuMs=std::chrono::duration( + Clock::now()-deferredRecordStart).count(); + ID3D11Query* completionEvent=nullptr; + const auto submitStart=Clock::now(); + std::string submitError; + if(!VoxelForgeGpuFarViewer::SubmitFarTileBatch(hardware.device.Get(), + hardware.context.Get(),shaderPath,&completionEvent,&submission.timing,submitError)) + { + if(completionEvent)completionEvent->Release(); + releaseTimingQueries(submission.timing); + throw std::runtime_error("deferred batch submit failed: "+submitError); + } + submission.submitCpuMs=std::chrono::duration( + Clock::now()-submitStart).count(); + submission.completionEvent.Attach(completionEvent); + deferredRecordCpuMs+=submission.recordCpuMs; + deferredSubmitCpuMs+=submission.submitCpuMs; + std::printf("[GpuFarDeferredBatchSubmit] batch=%u tile_count=%zu record_cpu_ms=%.3f submit_cpu_ms=%.3f\n", + deferredBatchOrdinal++,submission.tiles.size(),submission.recordCpuMs, + submission.submitCpuMs); + deferredSubmittedBatches.push_back(std::move(submission)); + }; + auto enqueueDeferredTile=[&](const TileKey& key,const FarFieldTileInput* preparedInput) + { + const std::uint32_t outputSpan=outputPoolSlots-outputSlot; + for(int repeat=0;repeat(deferredDispatchCount)%outputSpan); + ID3D11Buffer* vertices=nullptr; + ID3D11Buffer* indices=nullptr; + ID3D11Buffer* indirectArgs=nullptr; + ID3D11Buffer* connectorVertices=nullptr; + ID3D11Buffer* connectorIndices=nullptr; + std::uint32_t indirectArgsByteOffset=0; + std::string buildError; + const auto enqueueStart=Clock::now(); + if(!VoxelForgeGpuFarViewer::BuildFarTile(hardware.device.Get(), + hardware.context.Get(),world,key,preparedInput,shaderPath, + &vertices,&indices,&indirectArgs,&connectorVertices,&connectorIndices, + sample.outputSlot,outputPoolSlots,&indirectArgsByteOffset,nullptr, + deferredNoTileTimestamps?nullptr:&sample.timing,nullptr, + &sample.enqueueWallMs,&sample.dispatchMs,buildError, + !deferredNoTileTimestamps)) + { + if(vertices)vertices->Release(); + if(indices)indices->Release(); + if(indirectArgs)indirectArgs->Release(); + if(connectorVertices)connectorVertices->Release(); + if(connectorIndices)connectorIndices->Release(); + releaseTimingQueries(sample.timing); + throw std::runtime_error("deferred tile build failed: "+buildError); + } + sample.vertices.Attach(vertices); + sample.indices.Attach(indices); + sample.indirectArgs.Attach(indirectArgs); + sample.connectorVertices.Attach(connectorVertices); + sample.connectorIndices.Attach(connectorIndices); + if(sample.enqueueWallMs<=0.0) + sample.enqueueWallMs=std::chrono::duration( + Clock::now()-enqueueStart).count(); + deferredPendingTiles.push_back(std::move(sample)); + ++deferredDispatchCount; + const int targetBatchSize=!deferredBatchPattern.empty() + ?deferredBatchPattern.at(deferredBatchOrdinal) + :(deferredFirstBatchSize>0&&deferredBatchOrdinal==0 + ?deferredFirstBatchSize:deferredBatchSize); + const bool batchFull=deferredPendingTiles.size() + >=static_cast(targetBatchSize); + const bool pumpBudgetReached=deferredPumpBudgetMs>0 + &&deferredBatchOrdinal>0 + &&std::chrono::duration(Clock::now()-deferredRecordStart).count() + >=static_cast(deferredPumpBudgetMs); + if(batchFull||pumpBudgetReached) + submitDeferredBatch(); + } + }; + if(deferredOnly) + { + for(const FarSelection& selection:selected) + { + if(tileLimit>0&&testedTiles>=static_cast(tileLimit))break; + const TileKey& key=selection.key; + FarFieldTileInput farInputStorage; + const FarFieldTileInput* farInput=&selection.input; + if((wholeFar||wholeNear)&&!skirtAuditOnly) + { + auto exported=wholeNear?world.exportGpuFieldTile(key,error) + :world.exportFarFieldTile(key,error); + if(!exported) + { + if(error.find("near analytic/detail region")!=std::string::npos) + {++nearTransitionSkipped;continue;} + throw std::runtime_error("deferred far-field export failed: "+error); + } + farInputStorage=std::move(*exported); + farInput=&farInputStorage; + } + enqueueDeferredTile(key,farInput); + ++testedTiles; + } + } + else for (const FarSelection& selection : selected) { if(tileLimit>0&&testedTiles>=static_cast(tileLimit))break; const TileKey& key=selection.key; @@ -3247,9 +3586,10 @@ 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(); + if(pooledOutput&&testedTiles==0) + hardware.EnsureFarOutputPool(outputPoolSlots,tile.cells); 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", @@ -3268,6 +3608,13 @@ namespace outputSlot,pooledOutput,true,false,profileGpuKernels); const GpuResult gpu2 = hardware.Run(tile,farInput,true,false,true,false, outputSlot,pooledOutput,true,false,profileGpuKernels); + if(deferredBatch) + { + std::printf("[GpuFarImmediateTile] key=(%d,%d,%d,lod%d) first_dispatch_ms=%.6f repeat_dispatch_ms=%.6f field_ms=%.6f\n", + key.coordinate.x,key.coordinate.y,key.coordinate.z,key.lod, + gpu1.dispatchMs,gpu2.dispatchMs,gpu1.fieldMs); + enqueueDeferredTile(key,farInput); + } bool additionalRepeatsMatch=true; for(int repeatIndex=2;repeatIndexprotectedThinSampleCount; PrintProcessMemory("after_tile_pair"); } + if(deferredBatch) + { + submitDeferredBatch(); + const auto waitStart=Clock::now(); + hardware.context->Flush(); + double deferredTileTimeSumMs=0.0,deferredBatchGpuTimeSumMs=0.0; + double deferredTileMaxMs=0.0; + std::uint64_t deferredTimestampFrequency=0; + for(std::size_t batchIndex=0;batchIndexGetData(batch.completionEvent.Get(),nullptr,0, + D3D11_ASYNC_GETDATA_DONOTFLUSH); + if(eventResult==S_FALSE) + { + if(std::chrono::duration(Clock::now()-eventStart).count()>60.0) + throw std::runtime_error("deferred batch completion event timed out"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } while(eventResult==S_FALSE); + Check(eventResult,"GetData(deferred batch completion event)"); + + const auto timingStart=Clock::now(); + while(!VoxelForgeGpuFarViewer::ResolveGpuTiming(hardware.context.Get(),batch.timing, + batch.dispatchMs,&batch.timestampFrequency)) + { + if(std::chrono::duration(Clock::now()-timingStart).count()>60.0) + throw std::runtime_error("deferred batch timestamp queries timed out"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + deferredTimestampFrequency=batch.timestampFrequency; + deferredBatchGpuTimeSumMs+=batch.dispatchMs; + double batchTileSumMs=0.0,batchTileMaxMs=0.0; + for(FDeferredTileTimingSample& sample:batch.tiles) + { + if(batch.timestampFrequency) + { + const auto tileTimingStart=Clock::now(); + while(!VoxelForgeGpuFarViewer::ResolveGpuTimestampPair( + hardware.context.Get(),sample.timing,batch.timestampFrequency, + sample.dispatchMs)) + { + if(std::chrono::duration(Clock::now()-tileTimingStart).count()>60.0) + throw std::runtime_error("deferred tile timestamp queries timed out"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + else releaseTimingQueries(sample.timing); + batchTileSumMs+=sample.dispatchMs; + batchTileMaxMs=std::max(batchTileMaxMs,sample.dispatchMs); + deferredTileTimeSumMs+=sample.dispatchMs; + deferredTileMaxMs=std::max(deferredTileMaxMs,sample.dispatchMs); + std::printf("[GpuFarDeferredTile] batch=%zu key=(%d,%d,%d,lod%d) pass=%d output_slot=%u enqueue_wall_ms=%.6f dispatch_ms=%.6f\n", + batchIndex,sample.key.coordinate.x,sample.key.coordinate.y, + sample.key.coordinate.z,sample.key.lod,sample.repeat, + sample.outputSlot,sample.enqueueWallMs,sample.dispatchMs); + } + std::printf("[GpuFarDeferredBatchTime] batch=%zu tiles=%zu command_list_gpu_ms=%.6f tile_timestamp_sum_ms=%.6f tile_mean_ms=%.6f tile_max_ms=%.6f event_wait_ms=%.3f timestamp_frequency=%llu\n", + batchIndex,batch.tiles.size(),batch.dispatchMs,batchTileSumMs, + batch.tiles.empty()?0.0:batchTileSumMs/static_cast(batch.tiles.size()), + batchTileMaxMs,std::chrono::duration(Clock::now()-eventStart).count(), + static_cast(batch.timestampFrequency)); + batch.completionEvent.Reset(); + } + const double deferredWaitMs=std::chrono::duration( + Clock::now()-waitStart).count(); + const double deferredMeanMs=deferredDispatchCount + ?deferredTileTimeSumMs/static_cast(deferredDispatchCount):0.0; + const double immediateMeanMs=testedTiles + ?gpuTotalSum/static_cast(testedTiles):0.0; + const double ratio=immediateMeanMs>0.0?deferredMeanMs/immediateMeanMs:0.0; + std::printf("[GpuFarDeferredBatchSummary] backend=viewer_deferred_context tiles=%llu batches=%zu batch_size=%d first_batch_size=%d pump_budget_ms=%d repeats_per_tile=%d tile_timestamps=%s pool_slots=%u timestamp_frequency=%llu immediate_mean_gpu_ms=%.6f deferred_mean_gpu_ms=%.6f deferred_max_gpu_ms=%.6f deferred_over_immediate=%.3f command_list_gpu_sum_ms=%.6f tile_timestamp_sum_ms=%.6f record_cpu_sum_ms=%.3f submit_cpu_sum_ms=%.3f completion_wait_ms=%.3f setup_ms=%.3f\n", + static_cast(deferredDispatchCount),deferredSubmittedBatches.size(), + deferredBatchSize,deferredFirstBatchSize,deferredPumpBudgetMs,deferredRepeatCount, + deferredNoTileTimestamps?"disabled":"enabled",outputPoolSlots, + static_cast(deferredTimestampFrequency),immediateMeanMs, + deferredMeanMs,deferredTileMaxMs,ratio,deferredBatchGpuTimeSumMs, + deferredTileTimeSumMs,deferredRecordCpuMs,deferredSubmitCpuMs,deferredWaitMs, + deferredSetupMs); + } + if(deferredOnly) + { + std::printf("[GpuFarDeferredOnlyComplete] submitted_tiles=%llu skipped_near_transition_tiles=%llu reference_agreement=not_run\n", + static_cast(deferredDispatchCount), + static_cast(nearTransitionSkipped)); + PrintProcessMemory("after_deferred_batch"); + return 0; + } std::printf("[GpuFarPortSummary] tested_tiles=%llu mode=%s skipped_near_transition_tiles=%llu cpu_export_shape_protected_prep_sum_ms=%.4f cpu_generateTile_sum_ms=%.4f cpu_reference_lattice_topology_check_sum_ms=%.4f rx7900_field_dispatch_sum_ms=%.4f rx7900_field_plus_meshing_sum_ms=%.4f warp_field_dispatch_sum_ms=%.4f topology_diff_cells_gpu=%llu topology_diff_cells_warp=%llu protected_exception_cells=%llu protected_exception_diff_gpu=%llu protected_exception_diff_warp=%llu thin_feature_cells=%llu thin_feature_diff_gpu=%llu thin_feature_diff_warp=%llu route_air_samples=%llu seal_override_samples=%llu thin_route_band_samples=%llu solid_overlay_samples=%llu whole_view_gpu_far=viewer_measurement_required viewer_direct_draw=integrated shell_survival=viewer_gpu_audit_required\n", static_cast(testedTiles),wholeFar?"whole-far-eligible":(wholeNear?"whole-near-detail":"sampled"), static_cast(nearTransitionSkipped), @@ -3524,7 +3963,8 @@ namespace VoxelForgeGpuFarViewer std::uint32_t* indirectArgsByteOffset,ID3D11Query** completionEvent, GpuTimingQueries* timingQueries, CpuMesh* auditReadback, - double* enqueueWallMs, double* gpuDispatchMs, std::string& error) + double* enqueueWallMs, double* gpuDispatchMs, std::string& error, + bool captureGpuTiming) { const auto wallStart=Clock::now(); if (vertices) *vertices = nullptr; @@ -3569,7 +4009,6 @@ namespace VoxelForgeGpuFarViewer } const std::uint32_t outputCapacity=outputCapacitySlots?outputCapacitySlots:outputSlot+1; runner.EnsureFarOutputPool(outputCapacity,tile.cells); - const bool captureGpuTiming=true; const bool batchTimingMode=completionEvent==nullptr; GpuResult result = runner.Run(tile, preparedInput,auditReadback!=nullptr,true, auditReadback!=nullptr,auditReadback!=nullptr,outputSlot,true,captureGpuTiming,batchTimingMode); diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md index 8e24b86..213182f 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RUNNERS.md @@ -78,3 +78,26 @@ Near-first dispatch ordering already exists in `GpuFarKeyLess`: queued tiles sor 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. + +## Step 24 + +- `Research\OwnerShapeWorld\gpu_field_port\BuildGpuFieldPort.ps1 -OutputName VoxelForgeGpuFarAgreementStep24.exe` and `BuildGpuFieldViewer.ps1 -OutputName VoxelForgeGpuFarStep24.exe` — build the deferred batch diagnostic and viewer. Both scripts set BelowNormal priority, affinity `0xFF00`, and `/MP8`; they retry once after `0xC0000142`. +- `RunGpuFarViewer.ps1 -Mode gpu -WarmupPolicy cold -NoColdDrain -Timeline -ExecutableName VoxelForgeGpuFarStep24.exe -LogStem step24_forced_cold` — measures the CPU handoff and deferred warmup route. Compare `viewer_device_init end_ms` with `generation_setup begin_ms`; a forced-cold route may submit no GPU tiles before whole-clipmap readiness. +- `RunGpuFarViewer.ps1 -Mode gpu -WarmupBeforeWorkers -NoColdDrain -Timeline -ExecutableName VoxelForgeGpuFarStep24.exe -LogStem step24_gpu_streaming_384` — exercises the active GPU route with a 32-tile first-batch target and later batches capped at 384. The first submitted list can be smaller when the ready queue or pump budget ends early. Require `submitted_tiles>0`, `max_tiles_per_batch<=384`, zero input failures, and memory below 8 GB. +- `RunGpuFarAgreement.ps1 -ExecutableName VoxelForgeGpuFarAgreementStep24.exe -WholeNear -LogStem step24_whole_near` and repeat with `-WholeFar` — compares CPU, hardware GPU, and WARP. Require zero topology, protected-cell, and thin-feature differences. +- Controlled deferred-context comparison: run `RunGpuFarAgreement.ps1` with `-WholeFar -TileLimit 320 -DeferredBatch -DeferredOnly -DeferredNoTileTimestamps -PoolSlots 512 -OutputSlot 128` at `-BatchSize 32`, then repeat at `-BatchSize 256` and `384`. For the full far sequence, use `-TileLimit 1114 -PoolSlots 1434 -OutputSlot 0 -BatchSize 384`. Use `-WholeNear -TileLimit 320 -PoolSlots 512 -OutputSlot 128 -BatchSize 384` for the near stream. Remove `-DeferredNoTileTimestamps` to collect per-tile timestamps. +- Replay the complete viewer GPU sequence with `-WholeViewerPath -DeferredBatch -DeferredOnly -BatchSize 384 -FirstBatchSize 32 -PoolSlots 1434 -OutputSlot 0`. Add `-DeferredNoTileTimestamps` for command-list timing only. Add `-PumpBudgetMilliseconds 4` to split later lists when CPU recording reaches the viewer's 4 ms pump budget; this approximates the viewer's pump boundaries, which can vary with CPU recording time. +- Reproduce the final viewer run's recorded six list sizes exactly with `-BatchSize 384 -BatchPattern 3,135,384,384,384,144 -PoolSlots 1434 -OutputSlot 0`. `BatchPattern` requires `WholeViewerPath` and `DeferredOnly`; the counts must sum to the selected GPU candidate count. +- `RunStep15CpuReference.ps1 -ExecutableName VoxelForgeGpuFarStep24.exe -LogPrefix step24_cpu_reference` — checks the unchanged W1/W8 hashes and 5,642/5,642 walkability. +- `RunStep15GpuSurvival.ps1 -ExecutableName VoxelForgeGpuFarStep24.exe -LogName step24_gpu_survival` — runs the full-residency audit. A natural warmup-before-workers run can establish the current application marker before retrying an audit that stalls on a marker miss; do not edit cache or driver state. +- `RunStep15CaptureMatrix.ps1 -ExecutableName VoxelForgeGpuFarStep24.exe -Prefix step24_final384 -GpuWarmupBeforeWorkers -NoColdDrain` then `py -3 Research\OwnerShapeWorld\gpu_field_port\CompareStep15Captures.py --prefix step24_final384` — captures and numerically compares the ten full-residency CPU/GPU pairs through the 32-to-384 streaming path. Full-size captures are not opened in an image viewer. + +Step 24 resolves the warmup-policy key, adapter identity, and marker read on a background thread after D3D device creation. Startup polls that result without waiting, and the explicit stdout flush before generation setup is gone. The final forced-cold log measured a 0.056 ms gap from device-init end to generation-setup begin; the initial clipmap was ready in 1.311 s on the CPU route. + +The deferred runner uses the viewer's `BeginFarTileBatch`, `BuildFarTile`, and `SubmitFarTileBatch` path. On 320 far tiles, 32-tile lists took 29.979 ms of command-list GPU time, while 256-tile lists took 8.867 ms. On 320 near tiles, 32, 128, 256, and 384 tile caps took 110.862, 106.216, 52.379, and 20.550 ms; a repeat at 384 took 20.402 ms. Per-tile timestamp queries did not explain the near-stream cost: one 384-tile batch measured 19.503 ms with tile queries versus 20.550 ms without them. + +The exact-sequence mode selects 1,434 GPU candidates (320 near and 1,114 far) and keeps the viewer's output-slot range. The active viewer log records six lists sized 3, 135, 384, 384, 384, and 144. Replaying those exact boundaries produced 237.623 ms of command-list GPU time without per-tile queries and 240.281 ms with them, compared with 279.268 ms in the viewer. The runner reproduced 85–86% of the viewer total. A 4 ms CPU-budget approximation produced 238–240 ms across 8–9 lists, so it gave a similar total but did not match the actual viewer's list split. + +The Step 23 isolated outlier `(0,-1,1,lod2)` measured 23.12 and 22.60 μs on immediate-context repeats. A Step 24 same-key control measured 25.720 and 23.520 μs on immediate repeats, then 26.280 μs through one deferred list. In the complete deferred sequence it measured 41.840–47.960 μs, about 1.6–1.8 times the Step 24 isolated result. The 10x comparison used one small LOD2 tile against the average of a mixed LOD0–8 viewer sequence, so those figures are not directly comparable. The exact six-list replay reproduced the viewer's slot-127 near spike at 14.897 ms (`(-2,-14,0,lod0)`; viewer 16.990 ms); that tile measured 0.119 ms in a one-tile immediate or deferred call. It did not reproduce the viewer's other spikes at slots 25 and 137 (11.122 and 21.194 ms); those measured 0.099 and 0.092 ms in the runner. The remaining 39–42 ms aggregate difference may come from viewer render work or GPU queue contention, but the runner does not establish which. Larger lists and the 384-tile viewer cap reduced controlled deferred overhead; the full-viewer tail remains open. + +The final auto-marker run in this environment matched the marker but queued warmup for 1.157 s; CPU workers claimed all 1,434 candidates before warmup finished, leaving zero GPU submissions. That run is not a cached-GPU timing. The ≤1.0 s cached-GPU target remains unverified here. Final CPU hashes, whole-near/far hardware/WARP topology and protected-feature checks, the GPU survival audit, and the ten-view numeric capture comparison pass. See [STEP24_REPORT.md](STEP24_REPORT.md) and [step24_final384_capture_comparison.md](step24_final384_capture_comparison.md). diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 index 1d2b582..475ae5a 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 @@ -4,15 +4,24 @@ param( [string]$LogStem='whole_far_rx7900_warp', [switch]$WholeFar, [switch]$WholeNear, + [switch]$WholeViewerPath, [switch]$FindThinFeature, [switch]$ProfileKernels, [switch]$PooledOutput, [switch]$WarmOutputPool, + [switch]$DeferredBatch, + [switch]$DeferredOnly, + [switch]$DeferredNoTileTimestamps, [int]$TileLimit=0, [string]$TileKey='', [int]$PoolSlots=0, [int]$OutputSlot=0, [int]$RepeatCount=2, + [int]$BatchSize=32, + [int]$FirstBatchSize=0, + [int]$PumpBudgetMilliseconds=0, + [string]$BatchPattern='', + [int]$DeferredRepeats=1, [int]$ClipRadius=3 ) @@ -30,7 +39,9 @@ if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExt if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU field runner not found: $ExecutablePath"} $Arguments=@("--clip-radius=$ClipRadius");if($WholeFar){$Arguments+='--whole-far'} if($WholeNear){$Arguments+='--whole-near'} -if($WholeFar-and$WholeNear){throw 'Choose either WholeFar or WholeNear, not both.'} +if($WholeViewerPath){$Arguments+='--whole-viewer-path'} +if(($WholeFar-and$WholeNear)-or($WholeViewerPath-and($WholeFar-or$WholeNear))) +{throw 'Choose WholeFar, WholeNear, or WholeViewerPath.'} 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.'} @@ -43,10 +54,48 @@ if($TileKey) $Arguments+=("--tile-key=$TileKey") } if($ProfileKernels){$Arguments+='--profile-kernels'} -if($PooledOutput) +if($DeferredBatch) { - if($PoolSlots-le 0){$PoolSlots=[Math]::Max(1,$OutputSlot+1)} + if($BatchSize-lt 1-or$BatchSize-gt 384){throw 'BatchSize must be from 1 through 384.'} + if($FirstBatchSize-lt 0-or$FirstBatchSize-gt$BatchSize){throw 'FirstBatchSize must be zero or no larger than BatchSize.'} + if($PumpBudgetMilliseconds-lt 0-or$PumpBudgetMilliseconds-gt 10000){throw 'PumpBudgetMilliseconds must be from 0 through 10000.'} + if($BatchPattern) + { + if($BatchPattern-notmatch'^\d+(,\d+)*$'){throw 'BatchPattern must be comma-separated positive tile counts.'} + $PatternCounts=@($BatchPattern.Split(',')|ForEach-Object {[int]$_}) + if(@($PatternCounts|Where-Object {$_-lt 1-or$_-gt$BatchSize}).Count-gt 0) + {throw 'BatchPattern entries must be from 1 through BatchSize.'} + if($FirstBatchSize-ne 0-or$PumpBudgetMilliseconds-ne 0-or$DeferredRepeats-ne 1) + {throw 'BatchPattern requires FirstBatchSize=0, PumpBudgetMilliseconds=0, and DeferredRepeats=1.'} + if(-not$WholeViewerPath-or-not$DeferredOnly) + {throw 'BatchPattern requires WholeViewerPath and DeferredOnly.'} + $Arguments+=("--deferred-batch-pattern=$BatchPattern") + } + if($DeferredRepeats-lt 1-or$DeferredRepeats-gt 64){throw 'DeferredRepeats must be from 1 through 64.'} + if($TileLimit-eq 0-and-not$TileKey-and-not$WholeViewerPath){throw 'DeferredBatch requires TileLimit, TileKey, or WholeViewerPath.'} + $Arguments+='--deferred-batch' + $Arguments+=("--deferred-batch-size=$BatchSize") + if($FirstBatchSize-gt 0){$Arguments+=("--deferred-first-batch-size=$FirstBatchSize")} + if($PumpBudgetMilliseconds-gt 0){$Arguments+=("--deferred-pump-budget-ms=$PumpBudgetMilliseconds")} + $Arguments+=("--deferred-repeats=$DeferredRepeats") + if($DeferredNoTileTimestamps){$Arguments+='--deferred-no-tile-timestamps'} +} +elseif($DeferredNoTileTimestamps){throw 'DeferredNoTileTimestamps requires DeferredBatch.'} +elseif($FirstBatchSize-ne 0){throw 'FirstBatchSize requires DeferredBatch.'} +elseif($PumpBudgetMilliseconds-ne 0){throw 'PumpBudgetMilliseconds requires DeferredBatch.'} +if($DeferredOnly) +{ + if(-not$DeferredBatch){throw 'DeferredOnly requires DeferredBatch.'} + $Arguments+='--deferred-only' +} +if($WholeViewerPath-and(-not$DeferredBatch-or-not$DeferredOnly)) +{throw 'WholeViewerPath requires DeferredBatch and DeferredOnly.'} +if($PooledOutput-or$DeferredBatch) +{ + $PoolSpan=if($DeferredBatch){$BatchSize}else{1} + if($PoolSlots-le 0){$PoolSlots=[Math]::Max(1,$OutputSlot+$PoolSpan)} if($OutputSlot-lt 0-or$OutputSlot-ge$PoolSlots){throw 'OutputSlot must be in the requested pool.'} + if($DeferredBatch-and$PoolSlots-$OutputSlot-lt$BatchSize){throw 'PoolSlots must leave room for BatchSize from OutputSlot.'} $Arguments+='--pooled-output' $Arguments+=("--pool-slots=$PoolSlots") $Arguments+=("--output-slot=$OutputSlot") diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunStep15CaptureMatrix.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunStep15CaptureMatrix.ps1 index 1e66c25..3d7127b 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunStep15CaptureMatrix.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunStep15CaptureMatrix.ps1 @@ -2,7 +2,8 @@ param( [string]$ExecutableName='VoxelForgeGpuFieldStep15Timeline.exe', [string]$Prefix='step15', - [switch]$GpuWarmupBeforeWorkers + [switch]$GpuWarmupBeforeWorkers, + [switch]$NoColdDrain ) $ErrorActionPreference='Stop' @@ -37,6 +38,7 @@ foreach($View in $Views) "--camera=$($View.Camera)","--$Mode-far", "--capture=Research/OwnerShapeWorld/gpu_field_port/$CaptureName") if($Mode-eq'gpu'-and$GpuWarmupBeforeWorkers){$Arguments+='--gpu-far-warmup-before-workers'} + if($Mode-eq'gpu'-and$NoColdDrain){$Arguments+='--gpu-far-no-cold-drain'} $StdoutPath=Join-Path $PSScriptRoot ($Stem+'.stdout.log') $StderrPath=Join-Path $PSScriptRoot ($Stem+'.stderr.log') $CapturePath=Join-Path $PSScriptRoot $CaptureName diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP24_REPORT.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP24_REPORT.md new file mode 100644 index 0000000..09917d9 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP24_REPORT.md @@ -0,0 +1,56 @@ +# Step 24 report: warmup handoff and deferred batch stalls + +Date: 2026-10-08 + +All implementation and report changes are under `Standalone\`. The CPU reference and shared field code in `Core\` were not changed. + +## Finding 1: the policy check no longer blocks generation setup + +The viewer now resolves the warmup launch policy on a background thread after D3D device creation. That worker hashes the shader, GPU implementation, and viewer header, queries adapter and driver identity, and reads the application warmup marker. `StartGeneration` and the render pump poll the result without waiting. Warmup-only and explicitly before-workers modes still wait because their contract requires it. The cache signature includes `GpuFarViewer.h`, so changes to its GPU interface invalidate the application marker through the normal key comparison. + +The startup stdout flushes around policy resolution and generation setup were removed. Earlier timing showed a redirected `fflush(stdout)` taking 136 ms on the CPU path; the previous marker-miss gaps were 130–155 ms. On the final build, forced-cold device initialization ended at 84.657 ms and `generation_setup` began at 84.713 ms: a 0.056 ms gap. The CPU-route initial clipmap was ready in 1.311 s. That run submitted no GPU tiles, so it measures the handoff gap and CPU fallback rather than GPU throughput. + +## Finding 2: larger command lists reduce deferred-context overhead, but do not explain every stall + +The agreement runner now has a deferred-context mode that uses the viewer's `BeginFarTileBatch`, `BuildFarTile`, and `SubmitFarTileBatch` functions. It can record per-tile timestamps or omit those queries while retaining a timestamp around each submitted command list. The hardware diagnostic creates the device without `D3D11_CREATE_DEVICE_SINGLETHREADED` when it uses deferred contexts. + +With 320 far tiles, 32-tile command lists took 29.979 ms total; 256-tile lists took 8.867 ms. On 320 near-detail tiles, command-list totals were: + +| Tiles per list | Batches | GPU time | +|---:|---:|---:| +| 32 | 10 | 110.862 ms | +| 128 | 3 | 106.216 ms | +| 256 | 2 | 52.379 ms | +| 384 | 1 | 20.550 ms | +| 384, repeat | 1 | 20.402 ms | + +The 384-tile near batch took 19.503 ms with per-tile timestamp queries enabled and 20.550 ms with them disabled. Query instrumentation is not the source of the large near-stream total. A temporary change to 256 output slots per pool chunk also failed to remove the long samples, so the final chunk size remains 128. + +The full 1,114-tile far sequence took 106.654 ms across three 384-tile lists without per-tile queries, and 108.407 ms with them. The timestamped run contained two long tiles: `(3,-5,0,lod3)` at 24.780 ms and `(2,2,-1,lod7)` at 29.585 ms. Repeating those exact keys 16 times in isolation measured 0.034 ms mean / 0.040 ms max for the LOD3 tile and 0.148 ms mean / 0.176 ms max for the LOD7 tile. The long durations depend on the surrounding command sequence, not those tiles' ordinary work. + +The full viewer still shows sequence outliers. In a 1,434-tile active GPU run, `(-1,-14,-4,lod0)` measured 11.122 ms and `(-3,-14,0,lod0)` measured 21.194 ms. Repeating those exact keys at their viewer output slots measured 0.087 ms mean / 0.114 ms max and 0.092 ms mean / 0.111 ms max. + +The new whole-viewer replay selects the same 1,434 GPU candidates (320 near, 1,114 far), uses the same output-slot range, and records through the viewer's `BeginFarTileBatch`, `BuildFarTile`, and `SubmitFarTileBatch` functions. It reports 640 near tiles with visible skirts as CPU-skirt fallback and leaves them out of the GPU selection. The active viewer log records six lists sized 3, 135, 384, 384, 384, and 144. Replaying those exact boundaries produced 237.623 ms of command-list GPU time without per-tile queries and 240.281 ms with them, compared with 279.268 ms in the viewer (194.747 μs/tile). The runner reproduced 85–86% of the viewer total. A 4 ms CPU recording budget produced a similar 238–240 ms sum across 8–9 lists, but did not match the viewer's list split. Per-tile timestamp queries barely changed the total. + +The old 10x comparison paired a 23 μs isolated LOD2 tile with the mean of a mixed LOD0–8 viewer sequence. On a Step 24 same-key test, `(0,-1,1,lod2)` measured 25.720 and 23.520 μs on immediate-context repeats and 26.280 μs through a single deferred list. In the full deferred sequence it measured 41.840–47.960 μs, a 1.6–1.8x increase over that single-tile measurement. The exact six-list replay reproduced the viewer's slot-127 near spike at 14.897 ms (`(-2,-14,0,lod0)`; viewer 16.990 ms); that tile measured 0.119 ms in a one-tile immediate or deferred call. It did not reproduce the viewer's other spikes at slots 25 and 137 (11.122 and 21.194 ms); those measured 0.099 and 0.092 ms in the runner. The remaining 39–42 ms aggregate difference may come from viewer render work or GPU queue contention, but the runner does not establish which. The full-viewer tail remains open even though the original 10x comparison was not workload-matched. + +## Cached launch status + +On the final build, the auto route reported an application-marker match and chose startup overlap. The shader warmup queue still took 1.157 s. By completion, CPU workers had claimed all 1,434 GPU candidates (`gpu_takeover_unstarted=0`), so the run submitted zero GPU tiles and reached the initial clipmap in 1.942 s. This is a CPU fallback run, not a cached-GPU measurement. + +The warmup-before-workers route did submit all 1,434 GPU tiles in six batches, with a 384-tile maximum, but reached the initial clipmap in 1.999 s including warmup. The local tests therefore do not meet or verify the ≤1.0 s cached-GPU target. No driver cache or setting was manually changed; the application updated its own warmup marker only through its normal warmup route. + +The initial Step 24 survival attempt on a marker miss stopped making progress after `[CpuReadyFrameBeforeGpu]`; its process remained below the memory limit but had no observed CPU or GPU progress for over two minutes, so that viewer was closed. After the supported warmup-before-workers route naturally recorded the current marker, the survival audit passed. This marker-miss audit stall remains a separate open edge case. + +## Correctness and resource checks + +- CPU W1 and W8 hashes match: density `0xdb2991c73215af4c`, mesh `0x8236c5edd7fc8e4f`, payload `0x4c48fa75a543d58e`, topology `0xbea22e76b34a4dfc`. Walkability is 5,642/5,642. +- Whole-near tested 960 tiles and matched all 229,376 protected exception cells on hardware and WARP with zero topology or protected-cell differences. +- Whole-far tested 1,114 eligible tiles (152 near-transition tiles skipped), matched 127,401 protected cells and 353 thin-feature cells, and reported zero hardware or WARP topology, protected, or thin-feature differences. +- The full GPU survival audit passed: 7,200/7,200 shell boundary surfaces and 10,976/10,976 connectors. Peak private memory was 3.58 GB; sampled DXGI-local memory was 2,670.8 MB, for a conservative combined 6.19 GB. +- All ten final captures completed through the no-cold-drain 32-to-384 viewer path. They were compared numerically as RGB arrays only. There are zero CPU-rock-to-GPU-background pixels in eight views and two pixels in each remote yaw 180 and 270, matching the established signature. The highest reported private-plus-DXGI-local capture total was 6.37 GB. See [step24_final384_capture_comparison.md](step24_final384_capture_comparison.md). +- Agreement, viewer, CPU-reference, and capture processes exited. No commit, push, or stash was made. + +## Files and repeat commands + +See [RUNNERS.md](RUNNERS.md) for the build and runner commands, including the deferred batch sweep, full viewer-sequence replay with exact batch-pattern and optional 4 ms recording-budget controls, and the capture script's `-NoColdDrain` option. Per-run logs use the `step24_` prefix in this directory. diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final384_capture_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final384_capture_comparison.md new file mode 100644 index 0000000..ccac043 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final384_capture_comparison.md @@ -0,0 +1,18 @@ +# Step24_Final384 full-residency capture comparison + +All ten CPU/GPU pairs were captured after `[InitialClipmapReady]`. The files were compared numerically as RGB arrays and were not opened in an image viewer. + +`CPU rock to GPU background` isolates the prior missing-coverage signature. Captures were loaded as RGB arrays only; no full-size image was opened in a viewer. + +| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel | CPU rock to GPU background | GPU rock to CPU background | Missing-pixel bounds | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---| +| chamber | 1280x800 | 0.6075 | 1.8638 | 42.6368% | 0.0183% | 123 | 0 | 0 | none | +| tunnel | 1280x800 | 0.6383 | 3.3799 | 31.7335% | 0.2772% | 130 | 0 | 0 | none | +| passage | 1280x800 | 1.6092 | 3.6878 | 50.4982% | 0.1733% | 52 | 0 | 0 | none | +| far | 1280x800 | 0.7910 | 2.1578 | 44.8325% | 0.0198% | 128 | 0 | 0 | none | +| passage_floor_closeup | 1280x800 | 0.9314 | 2.4800 | 43.0219% | 0.0500% | 100 | 0 | 0 | none | +| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.9629% | 0.0708% | 55 | 0 | 0 | none | +| remote_yaw0 | 1280x800 | 0.0416 | 1.0679 | 2.7991% | 0.0305% | 90 | 0 | 0 | none | +| remote_yaw90 | 1280x800 | 0.0100 | 0.4323 | 0.9746% | 0.0035% | 94 | 0 | 0 | none | +| remote_yaw180 | 1280x800 | 0.0470 | 1.3091 | 2.9463% | 0.0311% | 102 | 2 | 0 | (415,172)-(834,173) | +| remote_yaw270 | 1280x800 | 0.0826 | 1.6874 | 5.1076% | 0.0580% | 107 | 2 | 0 | (835,204)-(886,205) | diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final_capture_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final_capture_comparison.md new file mode 100644 index 0000000..f2695d6 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step24_final_capture_comparison.md @@ -0,0 +1,18 @@ +# Step24_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.0423 | 1.0692 | 2.8394% | 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 8fd242f..be22003 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -1556,6 +1556,12 @@ namespace bool bGpuFarWarmupOnly=false; std::string GpuWarmupEntryFilter; std::thread GpuShaderWarmupThread; + std::thread GpuWarmupPolicyThread; + std::atomic bGpuWarmupPolicyResolutionFinished{false}; + bool bGpuWarmupPolicyResolutionStarted=false; + bool bGpuWarmupLaunchPolicyResolved=false; + bool bGpuWarmupCacheMarkerMatches=false; + double GpuWarmupPolicyResolutionMs=0.0; std::shared_ptr GpuFarPreparationState; std::mutex GpuFarPreparedResultsMutex; std::deque GpuFarPreparedResults; @@ -1685,6 +1691,7 @@ namespace double BatchUploadBudgetMilliseconds=2.0; ~FApp(){ + if(GpuWarmupPolicyThread.joinable())GpuWarmupPolicyThread.join(); if(GpuShaderWarmupThread.joinable())GpuShaderWarmupThread.join(); StopGeneration(true);ReleaseMeshes(); if(SolidRasterizer)SolidRasterizer->Release();if(WireRasterizer)WireRasterizer->Release(); @@ -2061,10 +2068,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; return true; }; - MixText("gpu-far-warmup-signature-v1"); + MixText("gpu-far-warmup-signature-v2"); const std::filesystem::path ShaderImplementation=GpuFarShaderPath.parent_path() .parent_path()/"gpu_field_port"/"GpuFieldPort.cpp"; - if(!MixFile(GpuFarShaderPath,true)||!MixFile(ShaderImplementation,true))return false; + const std::filesystem::path ViewerImplementation=GpuFarShaderPath.parent_path() + .parent_path()/"gpu_field_port"/"GpuFarViewer.h"; + if(!MixFile(GpuFarShaderPath,true)||!MixFile(ShaderImplementation,true) + ||!MixFile(ViewerImplementation,true))return false; const std::filesystem::path PrecompiledDirectory=GpuFarShaderPath.parent_path() .parent_path()/"gpu_field_port"/"precompiled"; for(const char* Entry:WarmupEntries) @@ -2083,49 +2093,105 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; return true; } - void ResolveGpuWarmupLaunchPolicy() + void StartGpuWarmupPolicyResolutionAsync() { - if(!bGpuFarEnabled||!bGpuFarShaderPrewarm)return; - const bool bKeyAvailable=BuildGpuWarmupCacheKey(); - bool bCacheMarkerMatches=false; - if(bKeyAvailable&&!GpuWarmupStatePath.empty()) - { - std::ifstream Marker(GpuWarmupStatePath,std::ios::binary); - std::string SavedKey; - if(Marker&&std::getline(Marker,SavedKey)) - bCacheMarkerMatches=SavedKey==GpuWarmupCacheKey; - } + if(!bGpuFarEnabled||!bGpuFarShaderPrewarm||bGpuWarmupPolicyResolutionStarted)return; + bGpuWarmupPolicyResolutionStarted=true; + bGpuWarmupPolicyResolutionFinished.store(false,std::memory_order_relaxed); + bGpuWarmupLaunchPolicyResolved=false; + bGpuWarmupCacheMarkerMatches=false; + GpuWarmupPolicyResolutionMs=0.0; + if(bGpuFarWarmupBeforeWorkers + ||GpuWarmupLaunchPolicy==EGpuWarmupLaunchPolicy::ForceDeferred) + bGpuWarmupDeferredUntilFirstView=true; + else if(GpuWarmupLaunchPolicy==EGpuWarmupLaunchPolicy::ForceEager) + bGpuWarmupDeferredUntilFirstView=false; + else + bGpuWarmupDeferredUntilFirstView=true; + GpuWarmupPolicyThread=std::thread([this] + { + const auto ResolveStart=std::chrono::steady_clock::now(); + bool bCacheMarkerMatches=false; + try + { + const bool bKeyAvailable=BuildGpuWarmupCacheKey(); + if(bKeyAvailable&&!GpuWarmupStatePath.empty()) + { + std::ifstream Marker(GpuWarmupStatePath,std::ios::binary); + std::string SavedKey; + if(Marker&&std::getline(Marker,SavedKey)) + bCacheMarkerMatches=SavedKey==GpuWarmupCacheKey; + } + } + catch(const std::exception& Error) + { + std::fprintf(stderr,"[GpuWarmupPolicyError] %s\n",Error.what()); + } + bGpuWarmupCacheMarkerMatches=bCacheMarkerMatches; + GpuWarmupPolicyResolutionMs=std::chrono::duration( + std::chrono::steady_clock::now()-ResolveStart).count(); + bGpuWarmupPolicyResolutionFinished.store(true,std::memory_order_release); + }); + std::printf("[ColdTimeline] event=gpu_warmup_policy_async_started at_ms=%.3f default_route=%s\n", + std::chrono::duration( + std::chrono::steady_clock::now()-ColdProcessStart).count(), + bGpuWarmupDeferredUntilFirstView?"after_first_CPU_view":"startup_overlap"); + } + + bool ResolveGpuWarmupLaunchPolicy(bool bWaitForResolution=false) + { + if(!bGpuFarEnabled||!bGpuFarShaderPrewarm)return true; + if(!bGpuWarmupPolicyResolutionStarted)StartGpuWarmupPolicyResolutionAsync(); + if(bGpuWarmupLaunchPolicyResolved)return true; + if(!bGpuWarmupPolicyResolutionFinished.load(std::memory_order_acquire)) + { + if(!bWaitForResolution)return false; + if(GpuWarmupPolicyThread.joinable())GpuWarmupPolicyThread.join(); + } + else if(GpuWarmupPolicyThread.joinable())GpuWarmupPolicyThread.join(); + + if(!bGpuWarmupPolicyResolutionFinished.load(std::memory_order_acquire))return false; if(bGpuFarWarmupBeforeWorkers) { bGpuWarmupDeferredUntilFirstView=false; - std::printf("[GpuWarmupLaunchDecision] mode=before_workers cache_marker=%s key_available=%s identity=%s\n", - bCacheMarkerMatches?"match":"miss",bKeyAvailable?"yes":"no", - bKeyAvailable?GpuWarmupCacheIdentity.c_str():"unavailable"); - return; + std::printf("[GpuWarmupLaunchDecision] mode=before_workers cache_marker=%s key_available=%s identity=%s resolution_ms=%.3f\n", + bGpuWarmupCacheMarkerMatches?"match":"miss", + bGpuWarmupCacheKeyAvailable?"yes":"no", + bGpuWarmupCacheKeyAvailable?GpuWarmupCacheIdentity.c_str():"unavailable", + GpuWarmupPolicyResolutionMs); + bGpuWarmupLaunchPolicyResolved=true; + return true; } + + const bool bDeferredRouteWasAlreadyStarted=bGpuWarmupDeferredStarted; if(GpuWarmupLaunchPolicy==EGpuWarmupLaunchPolicy::ForceDeferred) bGpuWarmupDeferredUntilFirstView=true; else if(GpuWarmupLaunchPolicy==EGpuWarmupLaunchPolicy::ForceEager) bGpuWarmupDeferredUntilFirstView=false; else - bGpuWarmupDeferredUntilFirstView=!bCacheMarkerMatches; + bGpuWarmupDeferredUntilFirstView=!bGpuWarmupCacheMarkerMatches + ||bDeferredRouteWasAlreadyStarted; const char* Mode=GpuWarmupLaunchPolicy==EGpuWarmupLaunchPolicy::ForceDeferred ?"forced_cold_start":(GpuWarmupLaunchPolicy==EGpuWarmupLaunchPolicy::ForceEager ?"forced_cached_start":"auto"); - std::printf("[GpuWarmupLaunchDecision] mode=%s cache_marker=%s route=%s key_available=%s state_path=%s identity=%s\n", - Mode,bCacheMarkerMatches?"match":"miss", + std::printf("[GpuWarmupLaunchDecision] mode=%s cache_marker=%s route=%s key_available=%s state_path=%s identity=%s resolution_ms=%.3f\n", + Mode,bGpuWarmupCacheMarkerMatches?"match":"miss", bGpuWarmupDeferredUntilFirstView?"after_first_CPU_view":"startup_overlap", - bKeyAvailable?"yes":"no",GpuWarmupStatePath.string().c_str(), - bKeyAvailable?GpuWarmupCacheIdentity.c_str():"unavailable"); + bGpuWarmupCacheKeyAvailable?"yes":"no",GpuWarmupStatePath.string().c_str(), + bGpuWarmupCacheKeyAvailable?GpuWarmupCacheIdentity.c_str():"unavailable", + GpuWarmupPolicyResolutionMs); + bGpuWarmupLaunchPolicyResolved=true; + return true; } void MarkGpuWarmupCacheReady() { if(!bGpuFarShaderPrewarm||bGpuFarWarmupResourcesOnly - ||!GpuWarmupEntryFilter.empty()||!bGpuWarmupCacheKeyAvailable - ||GpuWarmupStatePath.empty())return; + ||!GpuWarmupEntryFilter.empty()||GpuWarmupStatePath.empty())return; + ResolveGpuWarmupLaunchPolicy(true); + if(!bGpuWarmupCacheKeyAvailable)return; const std::filesystem::path TemporaryPath=GpuWarmupStatePath.wstring() +L"."+std::to_wstring(GetCurrentProcessId())+L".tmp"; { @@ -2463,6 +2529,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; {std::fprintf(stderr,"World load failed: %s\n",Error.c_str());MessageBoxA(Window,Error.c_str(),"VoxelForge viewer",MB_ICONERROR);return;} std::printf("[ColdTimeline] stage=generation_setup configured_ms=%.3f\n", std::chrono::duration(std::chrono::steady_clock::now()-ColdProcessStart).count()); + ResolveGpuWarmupLaunchPolicy(false); const bool bPipelineCanStartNow=!bGpuWarmupDeferredUntilFirstView ||bGpuWarmupDeferredStarted; if(bGpuFarEnabled&&bPipelineCanStartNow&&!EnsureGpuFarPipeline(Error)) @@ -3410,6 +3477,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; void PumpGpuFarGeneration(double MaxPumpBudgetMilliseconds=8.0) { if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context)return; + ResolveGpuWarmupLaunchPolicy(false); if(bGpuWarmupDeferredUntilFirstView&&!bGpuWarmupDeferredStarted) { if(!bWholeViewPresented)return; @@ -3431,6 +3499,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::string PipelineError; if(!EnsureGpuFarPipeline(PipelineError)) {bGpuFarEnabled=false;AddGpuFarCpuFallbackTiles(DesiredTiles);return;} + if(bGpuFarShaderPrewarm&&!bGpuWarmupDeferredUntilFirstView + &&!bGpuWarmupCompleted&&!GpuShaderWarmupThread.joinable()) + StartGpuShaderWarmupAsync(); StartGpuFarPreparation(); if(!CompleteGpuShaderWarmupAndRoute())return; StartGpuFarPreparation(); @@ -3438,12 +3509,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; const bool bColdDrainBeforeWholeView=!bInitialClipmapReported&&!bGpuFarAuditEnabled &&!bDisableGpuColdStartDrain&&bGpuFarWarmupBeforeWorkers; constexpr std::size_t StartupFirstGpuBatchTarget=32; - constexpr std::size_t StartupGpuBatchTarget=256; + constexpr std::size_t StartupGpuBatchTarget=384; constexpr double StartupGpuPumpBudgetMilliseconds=16.0; const bool bWaitForStreamingGpuBatch=!bColdDrainBeforeWholeView &&!bGpuFarAuditEnabled&&!bInitialClipmapReported; - const std::size_t GpuPumpTileLimit=bWaitForStreamingGpuBatch - ?(GpuFarSubmissionBatches==0?StartupFirstGpuBatchTarget:StartupGpuBatchTarget):32; + const std::size_t GpuPumpTileLimit=bGpuFarAuditEnabled?StartupFirstGpuBatchTarget + :(GpuFarSubmissionBatches==0?StartupFirstGpuBatchTarget:StartupGpuBatchTarget); const double GpuPumpBudgetMilliseconds=std::min(MaxPumpBudgetMilliseconds, bWaitForStreamingGpuBatch?StartupGpuPumpBudgetMilliseconds:4.0); if(bWaitForStreamingGpuBatch @@ -5277,7 +5348,20 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; if(!GApp)return DefWindowProc(H,Message,W,L); switch(Message) { - case WM_SIZE:if(W!=SIZE_MINIMIZED)GApp->Resize(LOWORD(L),HIWORD(L));return 0; + case WM_SIZE: + if(W!=SIZE_MINIMIZED) + { + const UINT NewWidth=LOWORD(L),NewHeight=HIWORD(L); + const bool bResizeNeeded=static_cast(NewWidth)!=GApp->Width + ||static_cast(NewHeight)!=GApp->Height; + const auto ResizeStart=std::chrono::steady_clock::now(); + if(bResizeNeeded)GApp->Resize(NewWidth,NewHeight); + std::printf("[ColdTimeline] event=viewer_window_size width=%u height=%u resized=%s duration_ms=%.3f\n", + NewWidth,NewHeight,bResizeNeeded?"yes":"no", + std::chrono::duration( + std::chrono::steady_clock::now()-ResizeStart).count()); + } + return 0; case WM_KEYDOWN: { const bool bWasDown=(L&(1ll<<30))!=0;if(W<256)GApp->Keys[W]=true; @@ -7379,17 +7463,30 @@ int main(int Argc,char** Argv) std::printf("[ColdTimeline] stage=viewer_device_init end_ms=%.3f duration_ms=%.3f\n", std::chrono::duration(std::chrono::steady_clock::now()-ColdProcessStart).count(), std::chrono::duration(std::chrono::steady_clock::now()-DeviceInitStart).count()); - App.ResolveGpuWarmupLaunchPolicy(); + App.StartGpuWarmupPolicyResolutionAsync(); if(!App.bGpuFarWarmupOnly) { - ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window); + const auto WindowShowStart=std::chrono::steady_clock::now(); + ShowWindow(App.Window,SW_SHOW); + std::printf("[ColdTimeline] stage=viewer_window_show duration_ms=%.3f\n", + std::chrono::duration( + std::chrono::steady_clock::now()-WindowShowStart).count()); + const auto WindowUpdateStart=std::chrono::steady_clock::now(); + UpdateWindow(App.Window); + std::printf("[ColdTimeline] stage=viewer_window_update duration_ms=%.3f\n", + std::chrono::duration( + std::chrono::steady_clock::now()-WindowUpdateStart).count()); + const auto StartupBannerStart=std::chrono::steady_clock::now(); std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; G toggles legacy/detailed round walls; H toggles CPU/GPU far tiles; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; V swaps owner density/shape intervals; Esc quit.\n"); std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n", App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z); + std::printf("[ColdTimeline] stage=viewer_startup_banner duration_ms=%.3f\n", + std::chrono::duration( + std::chrono::steady_clock::now()-StartupBannerStart).count()); } - std::fflush(stdout); if(App.bGpuFarEnabled&&(App.bGpuFarWarmupOnly||App.bGpuFarWarmupBeforeWorkers)) { + App.ResolveGpuWarmupLaunchPolicy(true); std::string GpuInitError; if(!App.EnsureGpuFarPipeline(GpuInitError))return 3; } @@ -7433,6 +7530,9 @@ int main(int Argc,char** Argv) App.bGpuWarmupCompleted=true; std::printf("[GpuShaderWarmupBeforeWorkers] completed_before_generation_workers=true\n"); } + std::printf("[ColdTimeline] event=generation_setup_call at_ms=%.3f\n", + std::chrono::duration( + std::chrono::steady_clock::now()-ColdProcessStart).count()); App.StartGeneration(); MSG Message{}; while(Message.message!=WM_QUIT)