diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 index 69a97c1..14fcf3a 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarAgreement.ps1 @@ -1,5 +1,5 @@ [CmdletBinding()] -param([string]$ExecutableName='VoxelForgeGpuFarAgreement.exe',[string]$LogStem='whole_far_rx7900_warp',[switch]$WholeFar,[switch]$WholeNear,[int]$ClipRadius=3) +param([string]$ExecutableName='VoxelForgeGpuFarAgreement.exe',[string]$LogStem='whole_far_rx7900_warp',[switch]$WholeFar,[switch]$WholeNear,[switch]$FindThinFeature,[int]$ClipRadius=3) $ErrorActionPreference='Stop' if([IO.Path]::GetFileName($LogStem)-ne$LogStem){throw 'LogStem must be a filename stem.'} @@ -16,6 +16,8 @@ if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU field $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($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'} 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 c6b7a10..6f4978a 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 @@ -5,7 +5,8 @@ param( [int]$ClipRadius=3, [string]$CaptureName='', [string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe', - [switch]$WarmupBeforeWorkers + [switch]$WarmupBeforeWorkers, + [ValidateSet('auto','cold','cached')][string]$WarmupPolicy='auto' ) $ErrorActionPreference='Stop' @@ -24,6 +25,8 @@ $Arguments=@("--clip-radius=$ClipRadius",'--workers=8','--quit-after-clipmap') if($Mode-eq'gpu'){$Arguments+='--gpu-far'} elseif($Mode-eq'cpu'){$Arguments+='--cpu-far'} else{$Arguments+='--gpu-far-audit'} +if($Mode-eq'gpu' -and $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($CaptureName) { @@ -34,6 +37,7 @@ if($CaptureName) } $Command="`"$ExecutablePath`" $($Arguments -join ' ')" Write-Output "VIEWER_COMMAND mode=$Mode $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/STEP18_REPORT.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP18_REPORT.md new file mode 100644 index 0000000..d40f7df --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP18_REPORT.md @@ -0,0 +1,41 @@ +# GPU field port — Step 18 report + +Date: 2026-10-07. The viewer and agreement runner were built from `Standalone\` with `/MP8`. Viewer and audit processes ran BelowNormal with affinity `0xFF00`; memory stayed below 8 GB private plus GPU-local memory. No `0xC0000142` retry was needed. All viewers exited. No driver or cache was manually inspected, cleared, or edited; shader warm-up ran through the application as usual. No full-size image was opened in an image viewer. + +## Startup policy + +The viewer now keeps an app-owned warm-up record at `Standalone\bin\VoxelForgeGpuWarmup.state`. Its key covers the adapter LUID and IDs, driver version, the HLSL source, the GPU runner source, and the production shader binaries. It is a history hint; it does not inspect or prove the contents of the driver cache. + +With plain `--gpu-far`, a missing or mismatched record defers GPU pipeline creation and warm-up until the initial CPU view has been presented. A completed full shader warm-up records the key. A matching record selects the early warm-up route. The regular asynchronous warm-up thread now runs at Normal relative priority within the BelowNormal process; audit and before-worker modes retain their previous priority policy. The step 17 exported-mesher skirt-depth fix is unchanged. + +`RunGpuFarViewer.ps1` accepts `-WarmupPolicy auto|cold|cached`: + +- `auto` follows the app record. +- `cold` forces the CPU-first deferred route. +- `cached` forces the early route, whether or not the app record matches. + +## Launch measurements + +The interleaved CPU controls were 1.357 s and 1.355 s to `[InitialClipmapReady]` (median 1.356 s). A forced-cold launch reached readiness in 1.427 s and presented the CPU view at 1.477 s; its 726 ms warm-up started after presentation. The initial view had zero GPU submissions, and the handoff audit accounted for all 1,434 candidates as CPU-owned. + +The sandbox did not retain a fast driver warm-up across processes. Forced-eager runs queued warm-up in 1,101 ms and 1,023 ms and reached readiness in 2.188 s and 2.212 s. A plain `auto` launch saw the app marker match, selected the early route, then queued in 1,049 ms and reached readiness in 2.143 s, again with zero GPU submissions. Those runs are cold-driver evidence, not a measurement of the owner's real cached path. The outside-sandbox `-WarmupPolicy cached` run is still needed to confirm the ≤1.26 s target. + +## Correctness and residency audits + +- CPU W1 and W8 hashes matched: `0xdb2991c73215af4c`, `0x8236c5edd7fc8e4f`, `0x4c48fa75a543d58e`, `0xbea22e76b34a4dfc`. Walkability passed **5,642/5,642**. +- Whole-near agreement passed on **960 tiles**, with zero topology differences on hardware and WARP and zero differences across **229,376** protected-exception cells. +- Whole-far agreement passed on **1,114 tiles** (152 transition tiles skipped), with zero topology differences on hardware and WARP, zero differences across **127,401** protected-exception cells, and zero differences across **353** thin-feature cells. +- The selected thin-feature tile `(-1,0,-1,lod3)` audited **18** thin-feature cells and **35** protected-exception cells, with zero topology differences on hardware and WARP. The one-to-one LOD3 tile control `(6,2,0,lod3)` matched **680/680** surface triangles and **64/64** visible skirt triangles, with zero unmatched triangles and 0.001892 cm maximum surface vertex error. The regular GPU, direct-draw, readback-copy, meter-copy, and CPU-mesh draw controls had pixel-identical captures to CPU. +- GPU survival passed with `mesh_source=GPU` and required field readback. Peak private memory was **3.53 GB** and DXGI-local memory was **2,674.8 MB**, totaling **6.14 GB** conservatively. +- All ten full-residency CPU/GPU view pairs completed. Numeric RGB comparison found zero CPU-rock-to-GPU-background pixels in eight views, two in `remote_yaw180`, and two in `remote_yaw270`, matching the step 17 capture result. Peak combined memory was **6.17 GB**. The comparison is [step18_final_capture_comparison.md](step18_final_capture_comparison.md). +- The tile comparison is [step18_tilecontrol_comparison.md](step18_tilecontrol_comparison.md); whole agreement logs are `step18_whole_near_agreement.stdout.log`, `step18_whole_far_agreement.stdout.log`, and `step18_thin_feature_audit.stdout.log`. CPU reference and survival logs use the `step18_` prefix. + +## Diagnostic note + +The optional `gpu_skirts_off` and `gpu_skirts_connectors_off` controls make the pooled readback checker report `indirect_args_valid=0`; their CPU/GPU triangle audit and skirt draw-argument summary still pass with skirts intentionally absent. Their captures show the expected coverage loss from disabling skirts. The normal GPU path, direct draw, and step 17 skirt-depth coverage control pass. + +No commit, push, or stash was made. + +## Note back + +The ordered goals, measured-versus-hypothesis labels, launch table, and explicit operating constraints are the most useful parts of the brief. The report and recent history prevented rediscovering why the skirt fix was needed. A compact current-runner command list with pass criteria is the main thing I still had to reconstruct from older scripts. The repeated operating rules are useful here because they apply to every audit run. diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step18_final_capture_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step18_final_capture_comparison.md new file mode 100644 index 0000000..0832b74 --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step18_final_capture_comparison.md @@ -0,0 +1,18 @@ +# Step18_Final full-residency capture comparison + +All ten CPU/GPU pairs were captured after `[InitialClipmapReady]`. The files were compared numerically as RGB arrays and were not opened in an image viewer. + +`CPU rock to GPU background` isolates the prior missing-coverage signature. Captures were loaded as RGB arrays only; no full-size image was opened in a viewer. + +| View | Size | RGB MAE | RGB RMSE | Changed pixels | Pixels max channel >32 | Max channel | CPU rock to GPU background | GPU rock to CPU background | Missing-pixel bounds | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---| +| chamber | 1280x800 | 0.6075 | 1.8638 | 42.6368% | 0.0183% | 123 | 0 | 0 | none | +| tunnel | 1280x800 | 0.6383 | 3.3799 | 31.7335% | 0.2772% | 130 | 0 | 0 | none | +| passage | 1280x800 | 1.6092 | 3.6878 | 50.4982% | 0.1733% | 52 | 0 | 0 | none | +| far | 1280x800 | 0.7910 | 2.1578 | 44.8325% | 0.0198% | 128 | 0 | 0 | none | +| passage_floor_closeup | 1280x800 | 0.9314 | 2.4800 | 43.0219% | 0.0500% | 100 | 0 | 0 | none | +| tunnel_bend | 1280x800 | 0.8129 | 2.1599 | 40.9629% | 0.0708% | 55 | 0 | 0 | none | +| remote_yaw0 | 1280x800 | 0.0416 | 1.0679 | 2.7991% | 0.0305% | 90 | 0 | 0 | none | +| remote_yaw90 | 1280x800 | 0.0100 | 0.4323 | 0.9746% | 0.0035% | 94 | 0 | 0 | none | +| remote_yaw180 | 1280x800 | 0.0470 | 1.3091 | 2.9463% | 0.0311% | 102 | 2 | 0 | (415,172)-(834,173) | +| remote_yaw270 | 1280x800 | 0.0826 | 1.6874 | 5.1076% | 0.0580% | 107 | 2 | 0 | (835,204)-(886,205) | diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/step18_tilecontrol_comparison.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step18_tilecontrol_comparison.md new file mode 100644 index 0000000..dc559ec --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/step18_tilecontrol_comparison.md @@ -0,0 +1,17 @@ +# Step 17 LOD3 tile control comparison + +The `(6,2,0,lod3)` target was the only drawn tile, with the full desired clipmap retained. Captures were loaded as RGB arrays and compared numerically; full-size images were not opened in a viewer. + +`CPU rock to GPU background` counts pixels where the CPU target is rock `(96,86,68)` and the control is background `(14,19,27)`, the prior missing-coverage signature. + +| Control | Size | RGB MAE | RGB RMSE | Changed pixels | CPU rock to GPU background | Max channel | +|---|---:|---:|---:|---:|---:|---:| +| GPU indirect | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 | +| GPU direct | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 | +| GPU readback copy | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 | +| GPU meter copy | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 | +| CPU mesh GPU path | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 | +| Depth disabled | 1280x800 | 0.0097 | 0.4705 | 0.1337% | 0 | 65 | +| Skirts disabled | 1280x800 | 1.6339 | 10.5157 | 2.5859% | 26055 | 82 | +| Connectors disabled | 1280x800 | 0.0000 | 0.0000 | 0.0000% | 0 | 0 | +| Skirts and connectors disabled | 1280x800 | 1.6339 | 10.5157 | 2.5859% | 26055 | 82 | diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp index 0d82834..8b351d3 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -1395,6 +1395,13 @@ namespace bool bGpuComplete=false,bTimingResolved=false; }; + enum class EGpuWarmupLaunchPolicy + { + Auto, + ForceDeferred, + ForceEager + }; + uint64 HashStandaloneFile(const std::string& Path) { std::ifstream File(Path,std::ios::binary); @@ -1510,6 +1517,11 @@ namespace bool bGpuFarShaderPrewarm=false,bDisableGpuWarmupPresentHold=false; bool bGpuFarWarmupResourcesOnly=false; bool bGpuFarWarmupNormalPriority=false,bGpuFarWarmupBeforeWorkers=false; + EGpuWarmupLaunchPolicy GpuWarmupLaunchPolicy=EGpuWarmupLaunchPolicy::Auto; + bool bGpuWarmupDeferredUntilFirstView=false,bGpuWarmupDeferredStarted=false; + bool bGpuWarmupCacheKeyAvailable=false; + std::filesystem::path GpuWarmupStatePath; + std::string GpuWarmupCacheKey,GpuWarmupCacheIdentity; bool bGpuFarWarmupKernelFences=false; bool bGpuFarWarmupOnly=false; std::string GpuWarmupEntryFilter; @@ -1952,6 +1964,156 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; return true; } + bool BuildGpuWarmupCacheKey() + { + GpuWarmupCacheKey.clear(); + GpuWarmupCacheIdentity.clear(); + if(!Device||GpuFarShaderPath.empty())return false; + + IDXGIDevice* DxgiDevice=nullptr; + if(FAILED(Device->QueryInterface(__uuidof(IDXGIDevice), + reinterpret_cast(&DxgiDevice)))||!DxgiDevice)return false; + IDXGIAdapter* Adapter=nullptr; + const HRESULT AdapterResult=DxgiDevice->GetAdapter(&Adapter); + DxgiDevice->Release(); + if(FAILED(AdapterResult)||!Adapter)return false; + + DXGI_ADAPTER_DESC AdapterDesc{}; + const HRESULT DescResult=Adapter->GetDesc(&AdapterDesc); + LARGE_INTEGER DriverVersion{}; + const HRESULT DriverResult=Adapter->CheckInterfaceSupport( + __uuidof(IDXGIDevice),&DriverVersion); + Adapter->Release(); + if(FAILED(DescResult)||FAILED(DriverResult))return false; + + static constexpr std::array WarmupEntries={ + "EvaluateFarFieldCS","EvaluateNearFieldCS","ClassifyCS","ClassifyFarCS", + "ScanGroupsCS","EmitCS","ApplyNearFieldDetailCS","EmitNearCS", + "BuildFarConnectorRingsCS","EmitFarConnectorPanelsCS","ClassifyFarSkirtsCS", + "ScanFarSkirtsCS","EmitFarSkirtsCS","NoiseProbeCS"}; + uint64 ShaderSetHash=14695981039346656037ull; + const auto MixText=[&](std::string_view Text) + { + for(const unsigned char Byte:Text) + {ShaderSetHash^=Byte;ShaderSetHash*=1099511628211ull;} + ShaderSetHash^=0xffu;ShaderSetHash*=1099511628211ull; + }; + const auto MixFile=[&](const std::filesystem::path& Path,bool bRequired) + { + std::error_code Ec; + const bool bExists=std::filesystem::is_regular_file(Path,Ec)&&!Ec; + MixText(Path.filename().string()); + if(!bExists) + { + MixText("missing"); + return !bRequired; + } + const uint64 FileHash=HashStandaloneFile(Path.string()); + if(FileHash==0)return false; + const uint64 FileSize=std::filesystem::file_size(Path,Ec); + if(Ec)return false; + MixText(std::to_string(FileSize)); + MixText(std::to_string(FileHash)); + return true; + }; + + MixText("gpu-far-warmup-signature-v1"); + 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 PrecompiledDirectory=GpuFarShaderPath.parent_path() + .parent_path()/"gpu_field_port"/"precompiled"; + for(const char* Entry:WarmupEntries) + if(!MixFile(PrecompiledDirectory/(std::string(Entry)+".cso"),false))return false; + + std::ostringstream Identity; + Identity<<"adapter_luid="<(AdapterDesc.AdapterLuid.HighPart)<<":"<(DriverVersion.QuadPart) + <<" shader_set=0x"<(Error)); + return; + } + std::printf("[GpuWarmupCacheState] result=recorded path=%s identity=%s\n", + GpuWarmupStatePath.string().c_str(),GpuWarmupCacheIdentity.c_str()); + } + bool SetGpuWarmupContextProtection(bool bProtected) { if(!Context)return false; @@ -1975,18 +2137,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::fprintf(stderr,"[GpuWarmupContextProtection] enable_failed; async immediate-context protection is unavailable\n"); GpuShaderWarmupThread=std::thread([this]() { - // Keep cold driver compilation from stealing CPU time from the CPU fallback - // workers. Cached shader warm-up is only a few milliseconds, so this does not - // delay the normal GPU route when the driver cache is warm. const bool bPreserveAuditPriority=bGpuFarWarmupBeforeWorkers||bGpuFarAuditEnabled; const int WarmupPriority=bGpuFarWarmupNormalPriority?THREAD_PRIORITY_HIGHEST: - (bPreserveAuditPriority?THREAD_PRIORITY_BELOW_NORMAL:THREAD_PRIORITY_IDLE); + (bPreserveAuditPriority?THREAD_PRIORITY_BELOW_NORMAL:THREAD_PRIORITY_NORMAL); const bool bPrioritySet=SetThreadPriority(GetCurrentThread(),WarmupPriority)!=0; - std::printf("[GpuShaderWarmupThread] priority=%s process=BelowNormal thread_relative=%s\n", - bPrioritySet?(bGpuFarWarmupNormalPriority?"Normal": - (bPreserveAuditPriority?"BelowNormal":"Idle")):"set_failed", - bGpuFarWarmupNormalPriority?"Highest": - (bPreserveAuditPriority?"BelowNormal":"Idle")); + const char* RelativePriority=bGpuFarWarmupNormalPriority?"Highest": + (bPreserveAuditPriority?"BelowNormal":"Normal"); + std::printf("[GpuShaderWarmupThread] priority=%s process=BelowNormal thread_relative=%s purpose=%s\n", + bPrioritySet?RelativePriority:"set_failed",RelativePriority, + bGpuWarmupDeferredUntilFirstView?"post_first_CPU_view": + (bPreserveAuditPriority?"audit_or_before_workers":"cache_candidate_startup")); std::fflush(stdout); bGpuShaderWarmupQueueSucceeded=VoxelForgeGpuFarViewer::QueueFarShaderWarmup( Device,Context,GpuFarShaderPath,!bGpuFarWarmupResourcesOnly,GpuWarmupEntryFilter, @@ -1995,7 +2155,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; bGpuShaderWarmupQueueFinished.store(true,std::memory_order_release); }); std::printf("[GpuShaderWarmup] queue_thread=started overlap=%s\n", - bGpuFarWarmupBeforeWorkers?"none_before_generation_workers":"CPU_world_configuration"); + bGpuWarmupDeferredUntilFirstView?"after_first_CPU_view": + (bGpuFarWarmupBeforeWorkers?"none_before_generation_workers":"CPU_world_configuration")); std::fflush(stdout); } @@ -2260,9 +2421,12 @@ 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()); - if(bGpuFarEnabled&&!EnsureGpuFarPipeline(Error)) + const bool bPipelineCanStartNow=!bGpuWarmupDeferredUntilFirstView + ||bGpuWarmupDeferredStarted; + if(bGpuFarEnabled&&bPipelineCanStartNow&&!EnsureGpuFarPipeline(Error)) {MessageBoxA(Window,Error.c_str(),"VoxelForge GPU pipeline",MB_ICONERROR);return;} - if(bGpuFarShaderPrewarm&&!bGpuFarWarmupBeforeWorkers) + if(bGpuFarShaderPrewarm&&!bGpuFarWarmupBeforeWorkers + &&!bGpuWarmupDeferredUntilFirstView) StartGpuShaderWarmupAsync(); const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0); BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels); @@ -2925,6 +3089,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; return false; } bGpuWarmupCompleted=true; + MarkGpuWarmupCacheReady(); SetGpuWarmupContextProtection(false); std::printf("[GpuShaderWarmupComplete] completion_wall_ms=%.3f\n", std::chrono::duration( @@ -3011,6 +3176,24 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; void PumpGpuFarGeneration() { if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context)return; + if(bGpuWarmupDeferredUntilFirstView&&!bGpuWarmupDeferredStarted) + { + if(!bWholeViewPresented)return; + std::string DeferredPipelineError; + if(!EnsureGpuFarPipeline(DeferredPipelineError)) + { + std::fprintf(stderr,"[GpuPipelineInitError] deferred_after_first_CPU_view: %s\n", + DeferredPipelineError.c_str()); + bGpuFarEnabled=false;bGpuWarmupDeferredStarted=true; + AddGpuFarCpuFallbackTiles(DesiredTiles); + return; + } + bGpuWarmupDeferredStarted=true; + std::printf("[GpuWarmupDeferredStart] at_ms=%.3f first_CPU_view_presented=true\n", + std::chrono::duration( + std::chrono::steady_clock::now()-ColdProcessStart).count()); + StartGpuShaderWarmupAsync(); + } std::string PipelineError; if(!EnsureGpuFarPipeline(PipelineError)) {bGpuFarEnabled=false;AddGpuFarCpuFallbackTiles(DesiredTiles);return;} @@ -4663,7 +4846,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75; std::chrono::duration(GpuPumpEnd-CpuPumpEnd).count()); return; } - if(bGpuFarEnabled&&bGpuFarShaderPrewarm&&!bGpuWarmupCompleted + if(bGpuFarEnabled&&bGpuFarShaderPrewarm&&!bGpuWarmupDeferredUntilFirstView + &&!bGpuWarmupCompleted &&!bGpuShaderWarmupQueueFinished.load(std::memory_order_acquire)) { if(!bGpuWarmupPresentSkipReported) @@ -6544,8 +6728,12 @@ int main(int Argc,char** Argv) wchar_t ExecutablePath[MAX_PATH]{}; const DWORD ExecutablePathLength=GetModuleFileNameW(nullptr,ExecutablePath,MAX_PATH); if(ExecutablePathLength>0&&ExecutablePathLength(std::chrono::steady_clock::now()-ColdProcessStart).count(), std::chrono::duration(std::chrono::steady_clock::now()-DeviceInitStart).count()); + App.ResolveGpuWarmupLaunchPolicy(); if(!App.bGpuFarWarmupOnly) { ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window); @@ -6898,6 +7091,7 @@ int main(int Argc,char** Argv) if(!VoxelForgeGpuFarViewer::CompleteFarShaderWarmup(App.Device,App.Context, App.GpuFarShaderPath,WarmupError)) {std::fprintf(stderr,"[GpuShaderWarmupError] completion: %s\n",WarmupError.c_str());return 5;} + App.MarkGpuWarmupCacheReady(); uint64 WarmupDxgiUsage=0,WarmupDxgiBudget=0; if(VoxelForgeGpuFarViewer::QueryFarGpuMemory(App.Device,App.Context,App.GpuFarShaderPath, WarmupDxgiUsage,WarmupDxgiBudget,WarmupError)) @@ -6922,6 +7116,7 @@ int main(int Argc,char** Argv) if(!VoxelForgeGpuFarViewer::CompleteFarShaderWarmup(App.Device,App.Context, App.GpuFarShaderPath,WarmupError)) {std::fprintf(stderr,"[GpuShaderWarmupError] completion: %s\n",WarmupError.c_str());return 5;} + App.MarkGpuWarmupCacheReady(); App.SetGpuWarmupContextProtection(false); App.bGpuWarmupCompleted=true; std::printf("[GpuShaderWarmupBeforeWorkers] completed_before_generation_workers=true\n");