wip(standalone): GPU step 5 -- far GPU work 646 -> 112 ms; hybrid whole view equal to CPU
Whole far field on the RX 7900 XTX: 112 ms total (connector rings 61 ms). Cold hybrid 2.303 s vs all-CPU 2.314 s (within noise): the far field is no longer on the wall-clock critical path. 0 sign/topology differences vs CPU and WARP incl. 127,401 protected and 353 thin-feature cells; repeat GPU runs identical; far survival passes on GPU meshes; CPU hashes unchanged. Open: CPU/GPU vertex distance mean 1.15 mm but max 64.56 cm, 247 tiles over the quantisation bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
247682a8e0
commit
0e730cdbba
9 files changed
+697
-260
No files matched your search
@@ -20,6 +20,7 @@
|
||||
#include <dxgi.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <complex>
|
||||
#include <cstdio>
|
||||
@@ -35,6 +36,7 @@
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -2227,6 +2229,55 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
return ResidentTiles.contains(Key);
|
||||
}
|
||||
|
||||
void PrepareGpuFarInputs(std::vector<FVoxelTileKey> NeedPreparation)
|
||||
{
|
||||
if(NeedPreparation.empty())return;
|
||||
const auto PrepareStart=std::chrono::steady_clock::now();
|
||||
std::vector<std::optional<VoxelForgeCore::FarFieldTileInput>> Inputs(NeedPreparation.size());
|
||||
std::vector<std::string> Errors(NeedPreparation.size());
|
||||
const std::size_t ThreadCount=std::min<std::size_t>(8,NeedPreparation.size());
|
||||
std::atomic<std::size_t> Next{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)
|
||||
{
|
||||
Workers.emplace_back([&,Worker]
|
||||
{
|
||||
const auto WorkerStart=std::chrono::steady_clock::now();
|
||||
for(;;)
|
||||
{
|
||||
const std::size_t Index=Next.fetch_add(1,std::memory_order_relaxed);
|
||||
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};
|
||||
try { Inputs[Index]=ShapeWorld->exportFarFieldTile(CoreKey,Errors[Index]); }
|
||||
catch(const std::exception& Error) { Errors[Index]=Error.what(); }
|
||||
}
|
||||
WorkerElapsed[Worker]=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-WorkerStart).count();
|
||||
});
|
||||
}
|
||||
for(std::thread& Worker:Workers)Worker.join();
|
||||
const double PreparationMs=std::chrono::duration<double,std::milli>(
|
||||
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]));
|
||||
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);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
void ScheduleGpuFarTiles(const std::vector<FVoxelTileKey>& Keys)
|
||||
{
|
||||
GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
||||
@@ -2270,52 +2321,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
for(const FVoxelTileKey& Key:Eligible)
|
||||
if(!GpuFarTiles.contains(Key))NeedPreparation.push_back(Key);
|
||||
if(!NeedPreparation.empty())
|
||||
{
|
||||
const auto PrepareStart=std::chrono::steady_clock::now();
|
||||
std::vector<std::optional<VoxelForgeCore::FarFieldTileInput>> Inputs(NeedPreparation.size());
|
||||
std::vector<std::string> Errors(NeedPreparation.size());
|
||||
const std::size_t ThreadCount=std::min<std::size_t>(8,NeedPreparation.size());
|
||||
std::atomic<std::size_t> Next{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)
|
||||
{
|
||||
Workers.emplace_back([&,Worker]
|
||||
{
|
||||
const auto WorkerStart=std::chrono::steady_clock::now();
|
||||
for(;;)
|
||||
{
|
||||
const std::size_t Index=Next.fetch_add(1,std::memory_order_relaxed);
|
||||
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};
|
||||
try { Inputs[Index]=ShapeWorld->exportFarFieldTile(CoreKey,Errors[Index]); }
|
||||
catch(const std::exception& Error) { Errors[Index]=Error.what(); }
|
||||
}
|
||||
WorkerElapsed[Worker]=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-WorkerStart).count();
|
||||
});
|
||||
}
|
||||
for(std::thread& Worker:Workers)Worker.join();
|
||||
const double PreparationMs=std::chrono::duration<double,std::milli>(
|
||||
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]));
|
||||
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);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
PrepareGpuFarInputs(std::move(NeedPreparation));
|
||||
for(const FVoxelTileKey& Key:Eligible)
|
||||
{
|
||||
GpuFarEligibleTiles.insert(Key);
|
||||
@@ -3170,7 +3176,10 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
const UINT Stride=sizeof(FGpuVertex),Offset=0;Context->IASetVertexBuffers(0,1,&L.VertexBuffer,&Stride,&Offset);
|
||||
Context->IASetIndexBuffer(L.IndexBuffer,DXGI_FORMAT_R32_UINT,0);Context->DrawIndexed(L.IndexCount,0,0);
|
||||
}
|
||||
if(bGpuFarEnabled&&ShapeWorld&&ShapeWorld->hasFarWorld())
|
||||
// Avoid issuing hundreds of tiny indirect draw calls while the
|
||||
// initial far batches are still being built. Once the clipmap is
|
||||
// ready the pooled GPU meshes draw directly as before.
|
||||
if(bGpuFarEnabled&&bInitialClipmapReported&&ShapeWorld&&ShapeWorld->hasFarWorld())
|
||||
{
|
||||
C.Padding[0]=0.01f;
|
||||
Context->UpdateSubresource(Constants,0,nullptr,&C,0,0);
|
||||
|
||||
Reference in new issue
Block a user