wip(standalone): GPU step 19 -- streamed GPU input export; RUNNERS.md

GPU tile inputs are published as workers finish (LOD then camera distance); first
batch at 32 ready, later batches up to 256. Export wall 224 -> ~105 ms. A camera-near
partial shape compile was tried and removed (duplicated work, missing inputs).
My runs outside the sandbox (Step19Stream.exe, marker removed first, interleaved):
  CPU 1.380 / 1.332 / 1.302 s
  GPU 1.358 (marker miss: 0 GPU tiles) / 1.409 / 1.263 / 1.246 s (marker match,
      ~1,400 GPU tiles, warm-up queue 5-6 ms)
Cached timeline (run d): shape compile 70-652 ms, idle gap 698-850 ms, export
850-954 ms, GPU tail to 1,246 ms (235 ms GPU timestamps). Run b: one 15 ms tile,
ready 1.409 s. Target <= 1.0 s not met.
Audits: hashes w1/w8, walk 5,642/5,642, near 960 / far 1,114 0 diffs vs CPU and
WARP, thin 353, survival pass, ten captures as step 18, peak 6.17 GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-07 22:09:29 +02:00
1 parent 2fea2710f7
commit 89fd9a0021
8 files changed
+273 -66

No files matched your search

+151 -65
View File
@@ -34,6 +34,7 @@
#include <iterator>
#include <limits>
#include <map>
#include <mutex>
#include <numeric>
#include <optional>
#include <sstream>
@@ -1448,6 +1449,15 @@ namespace
struct FApp
{
struct FPreparedGpuFarInputResult
{
FVoxelTileKey Key;
std::optional<VoxelForgeCore::FarFieldTileInput> Input;
std::string Error;
std::chrono::steady_clock::time_point FinishedAt{};
uint64 FinishedFrame=0;
};
HWND Window=nullptr;
ID3D11Device* Device=nullptr;
ID3D11DeviceContext* Context=nullptr;
@@ -1527,8 +1537,11 @@ namespace
std::string GpuWarmupEntryFilter;
std::thread GpuShaderWarmupThread;
std::thread GpuFarPreparationThread;
std::mutex GpuFarPreparedResultsMutex;
std::deque<FPreparedGpuFarInputResult> GpuFarPreparedResults;
std::atomic<bool> bGpuFarPreparationFinished{false};
bool bGpuFarPreparationStarted=false,bGpuFarInputsFinalized=false;
bool bGpuFarFirstPreparedInputReported=false;
bool bGpuWarmupCompleted=false,bGpuFarTimelineEnabled=false;
bool bGpuWarmupRoutePending=false;
bool bGpuShaderWarmupQueueSucceeded=false;
@@ -1719,6 +1732,7 @@ namespace
GpuFarBatches.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();GpuFarPreparedAt.clear();
{std::lock_guard Lock(GpuFarPreparedResultsMutex);GpuFarPreparedResults.clear();}
GpuFarCandidateTiles.clear();GpuFarBaseCpuFallbackTiles.clear();
GpuFarCpuFallbackTiles.clear();GpuFarNeedPreparation.clear();
bGpuFarPreparationStarted=false;bGpuFarInputsFinalized=false;
@@ -1726,6 +1740,7 @@ namespace
bGpuWarmupRoutePending=false;
GpuFarWarmupCpuClaimedTiles=0;GpuFarWarmupGpuTakeoverTiles=0;
GpuFarPreparationWallMs=0.0;GpuFarPreparationWorkerMs=0.0;
bGpuFarFirstPreparedInputReported=false;
GpuFarPumpCalls=0;GpuFarCompletionPolls=0;
GpuFarFirstPumpMs=GpuFarLastPumpMs=GpuFarFirstSubmitMs=GpuFarLastSubmitMs=-1.0;
GpuFarFirstCompletionPollMs=GpuFarLastCompletionPollMs=-1.0;
@@ -2229,8 +2244,6 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("Standalone max clip level override applied after world reload: max_level=%d.\n",
MaxClipLevelOverride);
}
// Compile one world field before streaming. The streamer already prioritizes the
// player's neighborhood, so a second near-only field would only duplicate work.
const auto ShapeSetupStart=std::chrono::steady_clock::now();
if(bShapeWorld&&!BuildShapeWorldForCamera(Error,true,false,true))return false;
std::printf("[ConfigureProfile] stage=shape_world_request duration_ms=%.3f\n",
@@ -2777,12 +2790,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
const auto PrepareStart=std::chrono::steady_clock::now();
std::printf("[ColdTimeline] stage=gpu_input_export begin_ms=%.3f tiles=%zu\n",
ColdTimelineMilliseconds(PrepareStart),NeedPreparation.size());
std::vector<std::optional<VoxelForgeCore::FarFieldTileInput>> Inputs(NeedPreparation.size());
std::vector<std::string> Errors(NeedPreparation.size());
std::vector<std::chrono::steady_clock::time_point> FinishedAt(NeedPreparation.size());
std::vector<uint64> FinishedFrame(NeedPreparation.size());
const std::size_t ThreadCount=std::min<std::size_t>(8,NeedPreparation.size());
std::atomic<std::size_t> Next{0};
std::atomic<std::size_t> Failed{0};
std::vector<double> WorkerElapsed(ThreadCount,0.0);
std::vector<std::thread> Workers;Workers.reserve(ThreadCount);
for(std::size_t Worker=0;Worker<ThreadCount;++Worker)
@@ -2796,33 +2806,39 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(Index>=NeedPreparation.size())break;
const FVoxelTileKey& Key=NeedPreparation[Index];
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
FPreparedGpuFarInputResult Result;Result.Key=Key;
try
{
Inputs[Index]=Key.Level<World->FullResClipLevels
?ShapeWorld->exportGpuFieldTile(CoreKey,Errors[Index])
:ShapeWorld->exportFarFieldTile(CoreKey,Errors[Index]);
if(Inputs[Index]&&Key.Level>=World->FullResClipLevels)
Result.Input=Key.Level<World->FullResClipLevels
?ShapeWorld->exportGpuFieldTile(CoreKey,Result.Error)
:ShapeWorld->exportFarFieldTile(CoreKey,Result.Error);
if(Result.Input&&Key.Level>=World->FullResClipLevels)
{
Inputs[Index]->visibleSkirtFaceMask=World->bGenerateSkirts
Result.Input->visibleSkirtFaceMask=World->bGenerateSkirts
&&bGpuSkirtEmitEnabled
?ComputeVisibleSkirtFaceMask(Key,DesiredTileSet):0;
// The CPU far mesher uses extent / CoarseTileCells, which can
// differ from Key.StepVoxels() when coarse tiles use a reduced
// cell count. Reuse the exported mesher step so skirt depth
// stays identical to the CPU reference.
Inputs[Index]->skirtDepthVoxels=std::max(1.0f,
static_cast<float>(World->SkirtCells))*Inputs[Index]->step;
Result.Input->skirtDepthVoxels=std::max(1.0f,
static_cast<float>(World->SkirtCells))*Result.Input->step;
if(!bGpuConnectorRepairEnabled)
{
Inputs[Index]->connectorPanels.clear();
Inputs[Index]->connectorRingCount=0;
Inputs[Index]->connectorVertexCount=0;
Result.Input->connectorPanels.clear();
Result.Input->connectorRingCount=0;
Result.Input->connectorVertexCount=0;
}
}
}
catch(const std::exception& Error) { Errors[Index]=Error.what(); }
FinishedAt[Index]=std::chrono::steady_clock::now();
FinishedFrame[Index]=ColdTimelineFrame();
catch(const std::exception& Error) { Result.Error=Error.what(); }
Result.FinishedAt=std::chrono::steady_clock::now();
Result.FinishedFrame=ColdTimelineFrame();
if(!Result.Input)Failed.fetch_add(1,std::memory_order_relaxed);
{
std::lock_guard Lock(GpuFarPreparedResultsMutex);
GpuFarPreparedResults.push_back(std::move(Result));
}
}
WorkerElapsed[Worker]=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-WorkerStart).count();
@@ -2833,29 +2849,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::chrono::steady_clock::now()-PrepareStart).count();
double WorkerSumMs=0.0;for(const double Elapsed:WorkerElapsed)WorkerSumMs+=Elapsed;
GpuFarPreparationWallMs+=PreparationMs;GpuFarPreparationWorkerMs+=WorkerSumMs;
std::size_t Failed=0;
for(std::size_t Index=0;Index<NeedPreparation.size();++Index)
{
if(Inputs[Index])
{
GpuFarPreparedInputs.emplace(NeedPreparation[Index],std::move(*Inputs[Index]));
GpuFarPreparedAt.emplace(NeedPreparation[Index],FinishedAt[Index]);
const FVoxelTileKey& Key=NeedPreparation[Index];
if(bGpuFarTimelineEnabled)
std::printf("[GpuTileTimeline] event=prepared key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
ColdTimelineMilliseconds(FinishedAt[Index]),
static_cast<unsigned long long>(FinishedFrame[Index]));
}
else
{
++Failed;
GpuFarPreparationErrors.emplace(NeedPreparation[Index],Errors[Index].empty()
?"far-field input export failed":Errors[Index]);
}
}
std::printf("[GpuFarInputPrepare] tiles=%zu workers=%zu wall_ms=%.3f worker_sum_ms=%.3f failed=%zu\n",
NeedPreparation.size(),ThreadCount,PreparationMs,WorkerSumMs,Failed);
NeedPreparation.size(),ThreadCount,PreparationMs,WorkerSumMs,
Failed.load(std::memory_order_relaxed));
std::printf("[ColdTimeline] stage=gpu_input_export end_ms=%.3f duration_ms=%.3f worker_sum_ms=%.3f\n",
ColdTimelineMilliseconds(std::chrono::steady_clock::now()),
PreparationMs,WorkerSumMs);
@@ -2866,9 +2862,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{
GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();GpuFarPreparedAt.clear();
{std::lock_guard Lock(GpuFarPreparedResultsMutex);GpuFarPreparedResults.clear();}
GpuFarCandidateTiles.clear();GpuFarBaseCpuFallbackTiles.clear();
GpuFarCpuFallbackTiles.clear();GpuFarNeedPreparation.clear();
bGpuFarPreparationStarted=false;bGpuFarInputsFinalized=false;
bGpuFarFirstPreparedInputReported=false;
bGpuFarPreparationFinished.store(false,std::memory_order_relaxed);
bGpuWarmupRoutePending=false;
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld())return;
@@ -3026,40 +3024,58 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
return;
}
bGpuFarPreparationFinished.store(false,std::memory_order_relaxed);
const std::vector<FVoxelTileKey> NeedPreparation=GpuFarNeedPreparation;
std::vector<FVoxelTileKey> NeedPreparation=GpuFarNeedPreparation;
std::stable_sort(NeedPreparation.begin(),NeedPreparation.end(),
[&](const FVoxelTileKey& A,const FVoxelTileKey& B){return GpuFarKeyLess(A,B);});
GpuFarPreparationThread=std::thread([this,NeedPreparation]()
{
try { PrepareGpuFarInputs(NeedPreparation); }
catch(const std::exception& Error)
{
std::lock_guard Lock(GpuFarPreparedResultsMutex);
for(const FVoxelTileKey& Key:NeedPreparation)
GpuFarPreparationErrors.emplace(Key,Error.what());
{
FPreparedGpuFarInputResult Result;Result.Key=Key;Result.Error=Error.what();
Result.FinishedAt=std::chrono::steady_clock::now();
Result.FinishedFrame=ColdTimelineFrame();
GpuFarPreparedResults.push_back(std::move(Result));
}
std::fprintf(stderr,"[GpuFarInputPrepareError] %s\n",Error.what());
}
catch(...)
{
std::lock_guard Lock(GpuFarPreparedResultsMutex);
for(const FVoxelTileKey& Key:NeedPreparation)
GpuFarPreparationErrors.emplace(Key,"unknown input preparation failure");
{
FPreparedGpuFarInputResult Result;Result.Key=Key;
Result.Error="unknown input preparation failure";
Result.FinishedAt=std::chrono::steady_clock::now();
Result.FinishedFrame=ColdTimelineFrame();
GpuFarPreparedResults.push_back(std::move(Result));
}
std::fprintf(stderr,"[GpuFarInputPrepareError] unknown failure\n");
}
bGpuFarPreparationFinished.store(true,std::memory_order_release);
});
}
bool GpuFarKeyLess(const FVoxelTileKey& A,const FVoxelTileKey& B)const
{
if(A.Level!=B.Level)return A.Level<B.Level;
const FIntVector AO=A.OriginVoxels(),BO=B.OriginVoxels();
const double AC=(AO.X+0.5*A.ExtentVoxels()-Position.X*4.0)*(AO.X+0.5*A.ExtentVoxels()-Position.X*4.0)
+(AO.Y+0.5*A.ExtentVoxels()-Position.Y*4.0)*(AO.Y+0.5*A.ExtentVoxels()-Position.Y*4.0)
+(AO.Z+0.5*A.ExtentVoxels()-Position.Z*4.0)*(AO.Z+0.5*A.ExtentVoxels()-Position.Z*4.0);
const double BC=(BO.X+0.5*B.ExtentVoxels()-Position.X*4.0)*(BO.X+0.5*B.ExtentVoxels()-Position.X*4.0)
+(BO.Y+0.5*B.ExtentVoxels()-Position.Y*4.0)*(BO.Y+0.5*B.ExtentVoxels()-Position.Y*4.0)
+(BO.Z+0.5*B.ExtentVoxels()-Position.Z*4.0)*(BO.Z+0.5*B.ExtentVoxels()-Position.Z*4.0);
return AC<BC;
}
void SortGpuFarQueue()
{
std::sort(GpuFarQueue.begin(),GpuFarQueue.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
if(A.Level!=B.Level)return A.Level<B.Level;
const FIntVector AO=A.OriginVoxels(),BO=B.OriginVoxels();
const double AC=(AO.X+0.5*A.ExtentVoxels()-Position.X*4.0)*(AO.X+0.5*A.ExtentVoxels()-Position.X*4.0)
+(AO.Y+0.5*A.ExtentVoxels()-Position.Y*4.0)*(AO.Y+0.5*A.ExtentVoxels()-Position.Y*4.0)
+(AO.Z+0.5*A.ExtentVoxels()-Position.Z*4.0)*(AO.Z+0.5*A.ExtentVoxels()-Position.Z*4.0);
const double BC=(BO.X+0.5*B.ExtentVoxels()-Position.X*4.0)*(BO.X+0.5*B.ExtentVoxels()-Position.X*4.0)
+(BO.Y+0.5*B.ExtentVoxels()-Position.Y*4.0)*(BO.Y+0.5*B.ExtentVoxels()-Position.Y*4.0)
+(BO.Z+0.5*B.ExtentVoxels()-Position.Z*4.0)*(BO.Z+0.5*B.ExtentVoxels()-Position.Z*4.0);
return AC<BC;
});
std::sort(GpuFarQueue.begin(),GpuFarQueue.end(),
[&](const FVoxelTileKey& A,const FVoxelTileKey& B){return GpuFarKeyLess(A,B);});
GpuFarQueued.clear();for(const FVoxelTileKey& Key:GpuFarQueue)GpuFarQueued.insert(Key);
}
@@ -3132,8 +3148,61 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
bool FinalizeGpuFarPreparation()
{
if(bGpuFarInputsFinalized)return true;
if(!bGpuFarPreparationStarted
||!bGpuFarPreparationFinished.load(std::memory_order_acquire))return false;
if(!bGpuFarPreparationStarted)return false;
std::deque<FPreparedGpuFarInputResult> ReadyResults;
{
std::lock_guard Lock(GpuFarPreparedResultsMutex);
ReadyResults.swap(GpuFarPreparedResults);
}
std::vector<FVoxelTileKey> Failed;
bool bQueueChanged=false;
for(FPreparedGpuFarInputResult& Result:ReadyResults)
{
const FVoxelTileKey& Key=Result.Key;
if(Result.Input)
{
GpuFarPreparedInputs.emplace(Key,std::move(*Result.Input));
GpuFarPreparedAt.emplace(Key,Result.FinishedAt);
GpuFarEligibleTiles.insert(Key);
if(!GpuFarTiles.contains(Key)&&GpuFarQueued.insert(Key).second)
{
GpuFarQueue.push_back(Key);
bQueueChanged=true;
}
if(bGpuFarTimelineEnabled)
std::printf("[GpuTileTimeline] event=prepared key=(%d,%d,%d,lod%d) at_ms=%.3f frame=%llu\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
ColdTimelineMilliseconds(Result.FinishedAt),
static_cast<unsigned long long>(Result.FinishedFrame));
if(!bGpuFarFirstPreparedInputReported)
{
bGpuFarFirstPreparedInputReported=true;
std::printf("[ColdTimeline] event=gpu_first_input_ready key=(%d,%d,%d,lod%d) at_ms=%.3f\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
ColdTimelineMilliseconds(Result.FinishedAt));
}
}
else
{
GpuFarPreparationErrors.emplace(Key,Result.Error.empty()
?"far-field input export failed":std::move(Result.Error));
Failed.push_back(Key);
}
}
if(bQueueChanged)SortGpuFarQueue();
if(!Failed.empty())
{
for(const FVoxelTileKey& Key:Failed)
{
const auto Error=GpuFarPreparationErrors.find(Key);
std::fprintf(stderr,"[GpuFieldInputFallback] key=(%d,%d,%d,lod%d) reason=%s\n",
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
Error==GpuFarPreparationErrors.end()?"far-field input export failed":Error->second.c_str());
}
AddGpuFarCpuFallbackTiles(Failed);
}
if(!bGpuFarPreparationFinished.load(std::memory_order_acquire))
return !GpuFarQueue.empty();
if(GpuFarPreparationThread.joinable())GpuFarPreparationThread.join();
if(!bGpuFarEnabled)
{
@@ -3145,11 +3214,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
bGpuFarInputsFinalized=true;
return false;
}
std::vector<FVoxelTileKey> Failed;
for(const FVoxelTileKey& Key:GpuFarCandidateTiles)
{
if(GpuFarTiles.contains(Key))
{GpuFarEligibleTiles.insert(Key);continue;}
if(GpuFarEligibleTiles.contains(Key)||GpuFarPreparationErrors.contains(Key))continue;
if(!GpuFarPreparedInputs.contains(Key))
{
Failed.push_back(Key);
@@ -3160,9 +3229,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
continue;
}
GpuFarEligibleTiles.insert(Key);
GpuFarQueue.push_back(Key);
if(GpuFarQueued.insert(Key).second)GpuFarQueue.push_back(Key);
}
SortGpuFarQueue();
if(!GpuFarQueue.empty())SortGpuFarQueue();
AddGpuFarCpuFallbackTiles(Failed);
bGpuFarInputsFinalized=true;
const std::size_t NearGpuTiles=static_cast<std::size_t>(std::count_if(
@@ -3201,7 +3270,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
StartGpuFarPreparation();
if(!bGpuFarEnabled||!FinalizeGpuFarPreparation()||GpuFarQueue.empty())return;
const bool bColdDrainBeforeWholeView=!bInitialClipmapReported&&!bGpuFarAuditEnabled
&&!bDisableGpuColdStartDrain;
&&!bDisableGpuColdStartDrain&&bGpuFarWarmupBeforeWorkers;
constexpr std::size_t StartupFirstGpuBatchTarget=32;
constexpr std::size_t StartupGpuBatchTarget=256;
constexpr double StartupGpuPumpBudgetMilliseconds=8.0;
const bool bWaitForStreamingGpuBatch=!bColdDrainBeforeWholeView
&&!bGpuFarAuditEnabled&&!bInitialClipmapReported;
const std::size_t GpuPumpTileLimit=bWaitForStreamingGpuBatch
?(GpuFarSubmissionBatches==0?StartupFirstGpuBatchTarget:StartupGpuBatchTarget):32;
const double GpuPumpBudgetMilliseconds=bWaitForStreamingGpuBatch
?StartupGpuPumpBudgetMilliseconds:4.0;
if(bWaitForStreamingGpuBatch
&&!bGpuFarPreparationFinished.load(std::memory_order_acquire)
&&GpuFarQueue.size()<StartupFirstGpuBatchTarget)return;
if(bWaitForStreamingGpuBatch&&GpuFarSubmissionBatches==0)
std::printf("[GpuFarStreamingPolicy] first_batch_tiles=%zu start_threshold=%zu later_batch_limit=%zu pump_budget_ms=%.1f order=lod_then_camera_distance\n",
GpuFarQueue.size(),StartupFirstGpuBatchTarget,StartupGpuBatchTarget,
StartupGpuPumpBudgetMilliseconds);
const auto PumpStart=std::chrono::steady_clock::now();
const double PumpMs=std::chrono::duration<double,std::milli>(PumpStart-ColdProcessStart).count();
if(GpuFarFirstPumpMs<0.0)GpuFarFirstPumpMs=PumpMs;
@@ -3225,7 +3310,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
const auto Start=std::chrono::steady_clock::now();
int32 Built=0;
std::vector<FVoxelTileKey> BuiltKeys;
BuiltKeys.reserve(bColdDrainBeforeWholeView?GpuFarQueue.size():32);
BuiltKeys.reserve(bColdDrainBeforeWholeView?GpuFarQueue.size():GpuPumpTileLimit);
do
{
const FVoxelTileKey Key=GpuFarQueue.front();GpuFarQueue.pop_front();GpuFarQueued.erase(Key);
@@ -3721,8 +3806,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
}
}
while(!GpuFarQueue.empty()&&(bColdDrainBeforeWholeView
||(Built<32&&std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-Start).count()<4.0)));
||(Built<GpuPumpTileLimit&&std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-Start).count()
<GpuPumpBudgetMilliseconds)));
if(Built>0)
{
if(bDeferredBatch)