wip(standalone): GPU step 4 -- batched far generation, connector panels on GPU; far survival passes
Up to 32 tiles per deferred command list, one polled event per batch, no per-tile waits; CPU upload/enqueue 723 -> 46 ms. GPU far meshes pass far survival (7,200 strata boundaries, 10,976 connectors, 0 missing/holes). But hybrid cold 4.36 s vs CPU 2.29 s: whole-tile GPU timestamps sum to 646 ms (0.58 ms/tile; step 3's 0.03 ms covered part of the work only), divergent per-thread shape loops; CPU far-input export 1.6 worker-s. CPU 44/44 hashes unchanged. 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
782f195bdb
commit
247682a8e0
18 files changed
+1496
-168
No files matched your search
@@ -205,6 +205,20 @@ namespace VoxelForgeCore
|
|||||||
float z = 0.0f, halfWidth = 8.0f;
|
float z = 0.0f, halfWidth = 8.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sparse connector repair panels for the GPU far renderer. Each record describes
|
||||||
|
// the tile-clipped axial interval and output ranges; signs and source radii are
|
||||||
|
// evaluated by the GPU from the far shape field.
|
||||||
|
struct FarFieldConnectorPanel
|
||||||
|
{
|
||||||
|
float start[3]{}, radius = 0.0f;
|
||||||
|
float end[3]{}, tMin = 0.0f;
|
||||||
|
float tMax = 1.0f;
|
||||||
|
std::uint32_t ringCount = 0, ringOffset = 0, vertexOffset = 0;
|
||||||
|
std::uint32_t axialSampleStart = 0, axialSampleCount = 0;
|
||||||
|
std::uint32_t padding[2]{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FarFieldConnectorPanel) == 64);
|
||||||
|
|
||||||
struct FarFieldTileInput
|
struct FarFieldTileInput
|
||||||
{
|
{
|
||||||
Vec3i origin{};
|
Vec3i origin{};
|
||||||
@@ -222,6 +236,8 @@ namespace VoxelForgeCore
|
|||||||
std::vector<std::uint32_t> protectedSolid;
|
std::vector<std::uint32_t> protectedSolid;
|
||||||
std::vector<std::uint32_t> protectedThin;
|
std::vector<std::uint32_t> protectedThin;
|
||||||
std::vector<FarFieldStratumBoundary> stratumBoundaries;
|
std::vector<FarFieldStratumBoundary> stratumBoundaries;
|
||||||
|
std::vector<FarFieldConnectorPanel> connectorPanels;
|
||||||
|
std::uint32_t connectorRingCount = 0, connectorVertexCount = 0;
|
||||||
std::uint64_t protectedSampleCount = 0;
|
std::uint64_t protectedSampleCount = 0;
|
||||||
std::uint64_t protectedSolidSampleCount = 0;
|
std::uint64_t protectedSolidSampleCount = 0;
|
||||||
std::uint64_t protectedSealSampleCount = 0;
|
std::uint64_t protectedSealSampleCount = 0;
|
||||||
|
|||||||
@@ -471,7 +471,6 @@ public:
|
|||||||
{Error="far field export requires a built far world and a bounded coarse tile";return false;}
|
{Error="far field export requires a built far world and a bounded coarse tile";return false;}
|
||||||
const int32 Dim=Cells+3;
|
const int32 Dim=Cells+3;
|
||||||
const size_t SampleCount=static_cast<size_t>(Dim)*Dim*Dim;
|
const size_t SampleCount=static_cast<size_t>(Dim)*Dim*Dim;
|
||||||
std::vector<std::vector<uint32>> PerSample(SampleCount);
|
|
||||||
std::vector<uint8> Used(Primitives.size(),0);
|
std::vector<uint8> Used(Primitives.size(),0);
|
||||||
Out={};Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim;
|
Out={};Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim;
|
||||||
if(LayerRanges.empty())
|
if(LayerRanges.empty())
|
||||||
@@ -505,6 +504,76 @@ public:
|
|||||||
Out.protectedSolid.assign((SampleCount+31u)/32u,0u);
|
Out.protectedSolid.assign((SampleCount+31u)/32u,0u);
|
||||||
Out.protectedThin.assign((SampleCount+31u)/32u,0u);
|
Out.protectedThin.assign((SampleCount+31u)/32u,0u);
|
||||||
|
|
||||||
|
const auto& Connectors=GetFarConnectorSegments();
|
||||||
|
// CPU repair panels are only used by the survival path at LOD6 and above.
|
||||||
|
// Keep this sparse repair out of LOD2..5, where the base GPU surface has
|
||||||
|
// sufficient resolution and the CPU path emits no panel geometry.
|
||||||
|
if(Cells*Step>=(CHUNK_SIZE<<6))
|
||||||
|
{
|
||||||
|
std::vector<uint32> ConnectorCandidates;
|
||||||
|
FindFarConnectorCandidates(
|
||||||
|
FIntVector(Origin.X-64,Origin.Y-64,Origin.Z-64),
|
||||||
|
FIntVector(Origin.X+Cells*Step+64,Origin.Y+Cells*Step+64,
|
||||||
|
Origin.Z+Cells*Step+64),ConnectorCandidates);
|
||||||
|
const double TileMin[3]={static_cast<double>(Origin.X),static_cast<double>(Origin.Y),
|
||||||
|
static_cast<double>(Origin.Z)};
|
||||||
|
const double TileMax[3]={TileMin[0]+Cells*Step,TileMin[1]+Cells*Step,
|
||||||
|
TileMin[2]+Cells*Step};
|
||||||
|
for(const uint32 ConnectorIndex:ConnectorCandidates)
|
||||||
|
{
|
||||||
|
if(ConnectorIndex>=Connectors.size())continue;
|
||||||
|
const FFarConnectorSegment& Connector=Connectors[ConnectorIndex];
|
||||||
|
const double Start[3]={Connector.Start.X,Connector.Start.Y,Connector.Start.Z};
|
||||||
|
const double Delta[3]={Connector.End.X-Connector.Start.X,
|
||||||
|
Connector.End.Y-Connector.Start.Y,Connector.End.Z-Connector.Start.Z};
|
||||||
|
double TMin=0.0,TMax=1.0;bool Intersects=true;
|
||||||
|
for(int32 Axis=0;Axis<3;++Axis)
|
||||||
|
{
|
||||||
|
if(std::abs(Delta[Axis])<1.0e-9)
|
||||||
|
{if(Start[Axis]<TileMin[Axis]||Start[Axis]>TileMax[Axis])Intersects=false;continue;}
|
||||||
|
double A=(TileMin[Axis]-Start[Axis])/Delta[Axis];
|
||||||
|
double B=(TileMax[Axis]-Start[Axis])/Delta[Axis];
|
||||||
|
if(A>B)std::swap(A,B);
|
||||||
|
TMin=std::max(TMin,A);TMax=std::min(TMax,B);
|
||||||
|
if(TMin>TMax){Intersects=false;break;}
|
||||||
|
}
|
||||||
|
const double Length=std::sqrt(Delta[0]*Delta[0]+Delta[1]*Delta[1]+Delta[2]*Delta[2]);
|
||||||
|
if(!Intersects||Length<=1.0e-6||TMax-TMin<=1.0e-7)continue;
|
||||||
|
const double ShapeMargin=static_cast<double>(Connector.Radius)+64.0;
|
||||||
|
for(uint32 ShapeId=0;ShapeId<Primitives.size();++ShapeId)
|
||||||
|
{
|
||||||
|
const FPrimitive& Shape=Primitives[ShapeId];bool Overlaps=true;
|
||||||
|
for(int32 Axis=0;Axis<3;++Axis)
|
||||||
|
{
|
||||||
|
const double Lo=std::min(Start[Axis],Start[Axis]+Delta[Axis])-ShapeMargin;
|
||||||
|
const double Hi=std::max(Start[Axis],Start[Axis]+Delta[Axis])+ShapeMargin;
|
||||||
|
if(Shape.Max[Axis]<Lo||Shape.Min[Axis]>Hi){Overlaps=false;break;}
|
||||||
|
}
|
||||||
|
if(Overlaps)Used[ShapeId]=1;
|
||||||
|
}
|
||||||
|
const uint32 AxialSampleCount=std::max<uint32>(1,static_cast<uint32>(std::ceil(Length/128.0)));
|
||||||
|
const double FirstValue=std::ceil(TMin*AxialSampleCount-0.5-1.0e-8);
|
||||||
|
const double LastValue=std::floor(TMax*AxialSampleCount-0.5+1.0e-8);
|
||||||
|
const uint32 FirstSample=static_cast<uint32>(std::clamp(FirstValue,0.0,
|
||||||
|
static_cast<double>(AxialSampleCount)));
|
||||||
|
const uint32 SampleCount=LastValue>=FirstValue?static_cast<uint32>(std::max(0.0,
|
||||||
|
std::min(LastValue,static_cast<double>(AxialSampleCount-1))-FirstValue+1.0)):0;
|
||||||
|
const uint32 RingCount=SampleCount+2;
|
||||||
|
const uint64 VertexCount=static_cast<uint64>(RingCount-1)*24u;
|
||||||
|
if(Out.connectorVertexCount+VertexCount>std::numeric_limits<uint32>::max())
|
||||||
|
{Error="far connector panel output exceeds the 32-bit tile limit";return false;}
|
||||||
|
VoxelForgeCore::FarFieldConnectorPanel Panel{};
|
||||||
|
for(int32 Axis=0;Axis<3;++Axis)
|
||||||
|
{Panel.start[Axis]=static_cast<float>(Start[Axis]);Panel.end[Axis]=static_cast<float>(Start[Axis]+Delta[Axis]);}
|
||||||
|
Panel.radius=Connector.Radius;Panel.tMin=static_cast<float>(TMin);Panel.tMax=static_cast<float>(TMax);
|
||||||
|
Panel.ringCount=RingCount;Panel.ringOffset=Out.connectorRingCount;
|
||||||
|
Panel.vertexOffset=Out.connectorVertexCount;
|
||||||
|
Panel.axialSampleStart=FirstSample;Panel.axialSampleCount=AxialSampleCount;
|
||||||
|
Out.connectorRingCount+=Panel.ringCount;Out.connectorVertexCount+=static_cast<uint32>(VertexCount);
|
||||||
|
Out.connectorPanels.push_back(Panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size_t SampleIndex=0;
|
size_t SampleIndex=0;
|
||||||
for(int32 Z=-1;Z<=Cells+1;++Z)
|
for(int32 Z=-1;Z<=Cells+1;++Z)
|
||||||
for(int32 Y=-1;Y<=Cells+1;++Y)
|
for(int32 Y=-1;Y<=Cells+1;++Y)
|
||||||
@@ -561,9 +630,17 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto Found=SpatialBins.find(PointBin(WX,WY,WZ));
|
const auto Found=SpatialBins.find(PointBin(WX,WY,WZ));
|
||||||
if(Found==SpatialBins.end())continue;
|
if(Found==SpatialBins.end())
|
||||||
auto& Ids=PerSample[SampleIndex];
|
{
|
||||||
Ids.assign(Found->second.begin(),Found->second.end());
|
Out.sampleShapeRanges[SampleIndex]={static_cast<uint32>(Out.sampleShapeIds.size()),0};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& Ids=Found->second;
|
||||||
|
if(Out.sampleShapeIds.size()+Ids.size()>std::numeric_limits<uint32>::max())
|
||||||
|
{Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;}
|
||||||
|
Out.sampleShapeRanges[SampleIndex]={static_cast<uint32>(Out.sampleShapeIds.size()),
|
||||||
|
static_cast<uint32>(Ids.size())};
|
||||||
|
Out.sampleShapeIds.insert(Out.sampleShapeIds.end(),Ids.begin(),Ids.end());
|
||||||
for(const uint32 Id:Ids)if(Id<Used.size())Used[Id]=1;
|
for(const uint32 Id:Ids)if(Id<Used.size())Used[Id]=1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,22 +684,11 @@ public:
|
|||||||
G.floorCurveEndBlend=static_cast<float>(S.floorCurveEndBlend);
|
G.floorCurveEndBlend=static_cast<float>(S.floorCurveEndBlend);
|
||||||
Out.shapes.push_back(G);
|
Out.shapes.push_back(G);
|
||||||
}
|
}
|
||||||
uint64 CandidateCount=0;
|
for(uint32& Id:Out.sampleShapeIds)
|
||||||
for(const auto& Ids:PerSample)CandidateCount+=Ids.size();
|
|
||||||
if(CandidateCount>std::numeric_limits<uint32>::max())
|
|
||||||
{Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;}
|
|
||||||
Out.sampleShapeIds.reserve(static_cast<size_t>(CandidateCount));
|
|
||||||
for(size_t I=0;I<PerSample.size();++I)
|
|
||||||
{
|
|
||||||
const auto& Ids=PerSample[I];
|
|
||||||
Out.sampleShapeRanges[I]={static_cast<uint32>(Out.sampleShapeIds.size()),
|
|
||||||
static_cast<uint32>(Ids.size())};
|
|
||||||
for(const uint32 Id:Ids)
|
|
||||||
{
|
{
|
||||||
if(Id>=Remap.size()||Remap[Id]==std::numeric_limits<uint32>::max())
|
if(Id>=Remap.size()||Remap[Id]==std::numeric_limits<uint32>::max())
|
||||||
{Error="far tile shape remap is inconsistent";return false;}
|
{Error="far tile shape remap is inconsistent";return false;}
|
||||||
Out.sampleShapeIds.push_back(Remap[Id]);
|
Id=Remap[Id];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ $BuildHostProcess = Get-Process -Id $PID
|
|||||||
$BuildHostProcess.PriorityClass = 'BelowNormal'
|
$BuildHostProcess.PriorityClass = 'BelowNormal'
|
||||||
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00
|
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00
|
||||||
$Root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
|
$Root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
|
||||||
$BuildDir = Join-Path $Root 'Build\VoxelForgeGpuFarHybridViewer'
|
$BuildStem = [IO.Path]::GetFileNameWithoutExtension($OutputName)
|
||||||
|
$BuildDir = Join-Path $Root ('Build\'+$BuildStem)
|
||||||
$BinDir = Join-Path $Root 'bin'
|
$BinDir = Join-Path $Root 'bin'
|
||||||
|
$ShaderSource = Join-Path $Root 'Research\OwnerShapeWorld\gpu_first_port\VoxelForgeGpuMesher.hlsl'
|
||||||
|
$PrecompiledShaderDir = Join-Path $Root 'Research\OwnerShapeWorld\gpu_field_port\precompiled'
|
||||||
[IO.Directory]::CreateDirectory($BuildDir) | Out-Null
|
[IO.Directory]::CreateDirectory($BuildDir) | Out-Null
|
||||||
[IO.Directory]::CreateDirectory($BinDir) | Out-Null
|
[IO.Directory]::CreateDirectory($BinDir) | Out-Null
|
||||||
|
|
||||||
@@ -110,6 +113,22 @@ if ($Target -eq 'All') {
|
|||||||
$LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
|
$LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
|
||||||
$LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ })
|
$LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ })
|
||||||
[IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false))
|
[IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false))
|
||||||
|
if (-not (Test-Path -LiteralPath $ShaderSource -PathType Leaf)) { throw "GPU shader source is missing: $ShaderSource" }
|
||||||
|
$FxCRoot = 'C:\Program Files (x86)\Windows Kits\10\bin'
|
||||||
|
$FxC = Get-ChildItem -LiteralPath $FxCRoot -Filter fxc.exe -Recurse -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.FullName -match '\\x64\\fxc\.exe$' } |
|
||||||
|
Sort-Object -Property FullName -Descending | Select-Object -First 1
|
||||||
|
if ($FxC) {
|
||||||
|
[IO.Directory]::CreateDirectory($PrecompiledShaderDir) | Out-Null
|
||||||
|
foreach ($Entry in @('EvaluateFarFieldCS','ClassifyCS','ClassifyFarCS','ScanGroupsCS','EmitCS','NoiseProbeCS','BuildFarConnectorRingsCS','EmitFarConnectorPanelsCS')) {
|
||||||
|
$ShaderBinary = Join-Path $PrecompiledShaderDir ($Entry+'.cso')
|
||||||
|
$CommandLine = '"{0}" /nologo /T cs_5_0 /E {1} /O3 /Gis /Ges /Fo "{2}" "{3}"' -f $FxC.FullName,$Entry,$ShaderBinary,$ShaderSource
|
||||||
|
Invoke-FieldViewerBuildCommand $CommandLine "precompile HLSL entry $Entry"
|
||||||
|
}
|
||||||
|
Write-Host "Precompiled viewer compute shaders into $PrecompiledShaderDir"
|
||||||
|
} else {
|
||||||
|
Write-Warning 'fxc.exe was not found; the viewer will fall back to one-time runtime shader compilation.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ViewerRsp = Join-Path $BuildDir 'viewer-link.rsp'
|
$ViewerRsp = Join-Path $BuildDir 'viewer-link.rsp'
|
||||||
|
|||||||
@@ -15,14 +15,39 @@ namespace VoxelForgeGpuFarViewer
|
|||||||
{
|
{
|
||||||
struct CpuVertex { float position[3]{}, normal[3]{}; };
|
struct CpuVertex { float position[3]{}, normal[3]{}; };
|
||||||
struct CpuMesh { std::vector<CpuVertex> vertices; std::vector<std::uint32_t> indices; };
|
struct CpuMesh { std::vector<CpuVertex> vertices; std::vector<std::uint32_t> indices; };
|
||||||
|
struct GpuTimingQueries
|
||||||
|
{
|
||||||
|
ID3D11Query* start=nullptr;
|
||||||
|
ID3D11Query* end=nullptr;
|
||||||
|
ID3D11Query* disjoint=nullptr;
|
||||||
|
};
|
||||||
|
bool ReserveFarOutputPool(ID3D11Device* device,ID3D11DeviceContext* context,
|
||||||
|
const std::filesystem::path& shaderPath,std::uint32_t tileSlots,std::uint32_t cells,
|
||||||
|
std::string& error);
|
||||||
|
bool QueryFarGpuMemory(ID3D11Device* device,ID3D11DeviceContext* context,
|
||||||
|
const std::filesystem::path& shaderPath,std::uint64_t& usageBytes,
|
||||||
|
std::uint64_t& budgetBytes,std::string& error);
|
||||||
|
bool BeginFarTileBatch(ID3D11Device* device,ID3D11DeviceContext* immediateContext,
|
||||||
|
const std::filesystem::path& shaderPath,std::string& error);
|
||||||
// Returned D3D11 buffers carry one reference each; the caller releases them.
|
// Returned D3D11 buffers carry one reference each; the caller releases them.
|
||||||
// Vertex records keep the 32-byte compute layout (position/normal/uv). The
|
// Vertex records keep the 32-byte compute layout (position/normal/uv). The
|
||||||
// first 24 bytes match the standalone viewer's input layout.
|
// first 24 bytes match the standalone viewer's input layout.
|
||||||
bool BuildFarTile(ID3D11Device* device, ID3D11DeviceContext* context,
|
bool BuildFarTile(ID3D11Device* device, ID3D11DeviceContext* context,
|
||||||
VoxelForgeCore::World& world, const VoxelForgeCore::TileKey& key,
|
VoxelForgeCore::World& world, const VoxelForgeCore::TileKey& key,
|
||||||
|
const VoxelForgeCore::FarFieldTileInput* preparedInput,
|
||||||
const std::filesystem::path& shaderPath,
|
const std::filesystem::path& shaderPath,
|
||||||
ID3D11Buffer** vertices, ID3D11Buffer** indices, ID3D11Buffer** indirectArgs,
|
ID3D11Buffer** vertices, ID3D11Buffer** indices, ID3D11Buffer** indirectArgs,
|
||||||
ID3D11Query** completionEvent,
|
ID3D11Buffer** connectorVertices,ID3D11Buffer** connectorIndices,
|
||||||
|
std::uint32_t outputSlot, std::uint32_t outputCapacitySlots,
|
||||||
|
std::uint32_t* indirectArgsByteOffset,
|
||||||
|
ID3D11Query** completionEvent,GpuTimingQueries* timingQueries,
|
||||||
CpuMesh* auditReadback,
|
CpuMesh* auditReadback,
|
||||||
double* enqueueWallMs, double* gpuDispatchMs, std::string& error);
|
double* enqueueWallMs, double* gpuDispatchMs, std::string& error);
|
||||||
|
bool SubmitFarTileBatch(ID3D11Device* device,ID3D11DeviceContext* immediateContext,
|
||||||
|
const std::filesystem::path& shaderPath,ID3D11Query** completionEvent,
|
||||||
|
GpuTimingQueries* timingQueries,std::string& error);
|
||||||
|
bool ResolveGpuTiming(ID3D11DeviceContext* context,GpuTimingQueries& queries,
|
||||||
|
double& dispatchMs,std::uint64_t* timestampFrequency=nullptr);
|
||||||
|
bool ResolveGpuTimestampPair(ID3D11DeviceContext* context,GpuTimingQueries& queries,
|
||||||
|
std::uint64_t timestampFrequency,double& dispatchMs);
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -3,7 +3,8 @@ param(
|
|||||||
[ValidateSet('gpu','cpu','audit')][string]$Mode='gpu',
|
[ValidateSet('gpu','cpu','audit')][string]$Mode='gpu',
|
||||||
[string]$LogStem='gpu_far_viewer',
|
[string]$LogStem='gpu_far_viewer',
|
||||||
[int]$ClipRadius=3,
|
[int]$ClipRadius=3,
|
||||||
[string]$CaptureName=''
|
[string]$CaptureName='',
|
||||||
|
[string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe'
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference='Stop'
|
$ErrorActionPreference='Stop'
|
||||||
@@ -12,7 +13,9 @@ $HostProcess=Get-Process -Id $PID
|
|||||||
$HostProcess.PriorityClass='BelowNormal'
|
$HostProcess.PriorityClass='BelowNormal'
|
||||||
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
|
$HostProcess.ProcessorAffinity=[IntPtr]0xFF00
|
||||||
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
|
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
|
||||||
$ExecutablePath=Join-Path $StandaloneRoot 'bin\VoxelForgeGpuFarHybridViewer.exe'
|
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
|
||||||
|
{throw 'ExecutableName must be an executable filename, not a path.'}
|
||||||
|
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
|
||||||
$StdoutPath=Join-Path $PSScriptRoot ($LogStem+'.stdout.log')
|
$StdoutPath=Join-Path $PSScriptRoot ($LogStem+'.stdout.log')
|
||||||
$StderrPath=Join-Path $PSScriptRoot ($LogStem+'.stderr.log')
|
$StderrPath=Join-Path $PSScriptRoot ($LogStem+'.stderr.log')
|
||||||
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU far viewer not found: $ExecutablePath"}
|
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU far viewer not found: $ExecutablePath"}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# GPU far streaming, step 4
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
The GPU far viewer now records up to 32 tiles into a deferred D3D11 command list, executes one list per update batch, and polls one completion event per batch. The normal path has no per-tile GPU wait or readback. Persistent pooled mesh buffers remain in use, including a separate connector-panel pool drawn with the base far mesh. **This removes most of the CPU submission overhead, but the measured whole-view cold time is still slower than all-CPU.**
|
||||||
|
|
||||||
|
The viewer keeps CPU generation for LOD0/1 and 152 transition tiles. GPU field evaluation and marching cubes cover the other 1,114 far tiles. Press **H** to switch between CPU-far and GPU-far.
|
||||||
|
|
||||||
|
## Connector and topology evidence
|
||||||
|
|
||||||
|
The final viewer binary passed `RunGpuFarViewer.ps1 -Mode audit -ClipRadius 3`. The GPU mesh survival audit reported:
|
||||||
|
|
||||||
|
- 7,200/7,200 strata-boundary surfaces, with zero missing, extra, or hole counts.
|
||||||
|
- 10,976/10,976 far connectors audited; 178,351 connector samples and 710,047 rays, with zero missing samples and zero connector holes.
|
||||||
|
- `status=PASS` and `mesh_source=GPU field_readback=required`.
|
||||||
|
|
||||||
|
The audit reports 49,585 connector-ray `extra` hits; those are tolerated by the existing repair-panel survival criteria. The missing and hole counts, which gate survival, are zero.
|
||||||
|
|
||||||
|
The batch refactor only changes viewer submission and timing. It does not change field evaluation, sign overrides, or marching-cubes cases. The prior step-3 whole-far agreement covered all 1,114 tiles: zero ordinary sign differences, zero cell-configuration differences against the RX 7900 XTX and WARP, zero protected/thin topology differences, and repeat RX output was bit-identical. Those agreement results are recorded in [the step-3 report](REPORT.md); they were not rerun for this submission-only change.
|
||||||
|
|
||||||
|
`CompareFarCpuHashes.ps1` still reports 44/44 hash fields equal with zero differences against the retained W8 reference logs. That script compares the saved `566d1f3` reference log to the saved current-path log; it is not a fresh CPU-path hash run.
|
||||||
|
|
||||||
|
## Cold timings
|
||||||
|
|
||||||
|
Commands, all with `--workers=8`, BelowNormal priority, affinity `0xFF00`, and clip radius 3:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
RunGpuFarViewer.ps1 -Mode cpu -LogStem step4_memory_cpu_A -ClipRadius 3 -ExecutableName VoxelForgeGpuFarStep4Viewer.exe
|
||||||
|
RunGpuFarViewer.ps1 -Mode gpu -LogStem step4_memory_gpu_A -ClipRadius 3 -ExecutableName VoxelForgeGpuFarStep4Viewer.exe
|
||||||
|
RunGpuFarViewer.ps1 -Mode gpu -LogStem step4_memory_gpu_B -ClipRadius 3 -ExecutableName VoxelForgeGpuFarStep4Viewer.exe
|
||||||
|
RunGpuFarViewer.ps1 -Mode cpu -LogStem step4_memory_cpu_B -ClipRadius 3 -ExecutableName VoxelForgeGpuFarStep4Viewer.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
| Cold clipmap | A | B | Mean |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| All CPU, 2,226 tiles | 2.189 s | 2.387 s | 2.288 s |
|
||||||
|
| CPU near + GPU far, 1,114 GPU tiles | 4.359 s | 4.361 s | 4.360 s |
|
||||||
|
|
||||||
|
The hybrid is 2.072 s slower on average (1.91x). CPU near/transition work took 3.214 s and 3.114 s wall, despite 1.566 s and 1.698 s of worker time summed across workers. The all-CPU runs took 2.093 s and 2.154 s wall for the full 2,226 tiles. This near-stream timing remains a major unexplained contributor to the hybrid result.
|
||||||
|
|
||||||
|
CPU tile-worker time fell from a 4.745 s mean to 1.632 s, saving 3.113 worker-seconds. Parallel far-input export used another 1.598 worker-seconds on average, leaving about 1.515 worker-seconds net before main-thread upload/enqueue and pool work. This worker-time reduction did not reduce whole-view wall time.
|
||||||
|
|
||||||
|
The batch design reduced summed CPU upload/enqueue time from about 723 ms before batching to 46.5 ms and 45.0 ms in the final runs. Each normal run used 37 submissions for 1,114 tiles, at most 32 tiles per submission. The command list still contains per-tile compute dispatch sequences; this is command-list batching rather than a single multi-tile shader dispatch.
|
||||||
|
|
||||||
|
## GPU timing and 90 Hz slicing
|
||||||
|
|
||||||
|
Two query levels are reported separately. Per-tile timestamp pairs bracket each tile's field, classify, emit, and connector compute sequence. A separate timestamp set brackets command-list execution, including its deferred resource updates.
|
||||||
|
|
||||||
|
| Measurement across 1,114 GPU tiles | Run A | Run B | Mean |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| Tile dispatch timestamp sum | 640.7 ms | 650.5 ms | 645.6 ms |
|
||||||
|
| Mean dispatch time per tile | 0.575 ms | 0.584 ms | 0.580 ms |
|
||||||
|
| Slowest tile dispatch | 3.924 ms | 3.990 ms | 3.990 ms |
|
||||||
|
| Command-list execution timestamp sum | 762.4 ms | 817.3 ms | 789.8 ms |
|
||||||
|
| Slowest command-list span (up to 32 tiles) | 57.6 ms | 62.5 ms | 60.1 ms |
|
||||||
|
|
||||||
|
For 0.5 ms of compute per 90 Hz frame, the measured dispatch sum needs about 1,292 frame slices (14.4 s). For 1 ms per frame it needs about 646 slices (7.2 s). At the mean tile cost, that is 0.86 tiles per 0.5 ms slice or 1.72 tiles per 1 ms slice. These are budget estimates from summed dispatch timestamps, not a VR frame-pacing run; a tile cannot be divided by the current scheduler, and the slowest tile alone took about 4 ms. The current 32-tile batches are not frame-budgeted: their largest measured execution span was 62.5 ms.
|
||||||
|
|
||||||
|
## Memory and limits
|
||||||
|
|
||||||
|
The final timed runs peaked at 0.94 GiB and 0.85 GiB working set, and 4.66 GiB and 4.60 GiB private process memory. DXGI sampled local GPU-memory peaks of 3,689.8 MiB and 3,690.9 MiB (3.60 GiB) against a reported 23,749 MiB (23.2 GiB) budget. The higher run's working set plus local GPU residency was about 4.54 GiB. Private-byte totals and DXGI residency can overlap because the driver accounts for resource backing separately; they are reported separately rather than added as if disjoint.
|
||||||
|
|
||||||
|
No `0xC0000142` occurred. The final viewer and audit runners exited with code 0. The audit mode uses immediate-context readback and is intentionally synchronous, so its 41.6 s cold time and 1.50 s query sum are not comparable to the normal no-readback viewer path.
|
||||||
|
|
||||||
|
## GTX 1060-class estimate
|
||||||
|
|
||||||
|
Using the user's 4.4 TFLOPS figure and AMD's published 61.4 TFLOPS FP32 peak for the RX 7900 XTX, simple throughput scaling gives roughly a 14x ratio ([AMD RX 7900 XTX specifications](https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7900xtx.html)). Applied to these measurements, that estimates about 8.1 ms average GPU dispatch per far tile and about 9.0 s summed dispatch time for 1,114 tiles. Pascal's slower 32-bit integer multiply and this shader's divergent shape loops make **roughly 9–15 s for the far dispatch work** a more practical, still very coarse estimate. This is not a GTX 1060 measurement. It implies no whole-view speedup over the measured 2.29 s CPU path.
|
||||||
|
|
||||||
|
## Logs and implementation
|
||||||
|
|
||||||
|
- Final interleaved timings: `step4_memory_cpu_A/B.stdout.log`, `step4_memory_gpu_A/B.stdout.log`.
|
||||||
|
- Final GPU connector audit: `step4_final_gpu_survival_audit.stdout.log`.
|
||||||
|
- Batching and timestamp code: [GpuFarViewer.h](GpuFarViewer.h), [GpuFieldPort.cpp](GpuFieldPort.cpp), and [StandaloneViewer.cpp](../../../Source/StandaloneViewer.cpp).
|
||||||
|
- Connector repair compute stages: [VoxelForgeGpuMesher.hlsl](../gpu_first_port/VoxelForgeGpuMesher.hlsl).
|
||||||
|
|
||||||
|
The CPU reference path was left unchanged. All work and generated output remain under `Standalone/`; nothing was committed, pushed, or stashed.
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param([int]$ClipRadius=3)
|
param([int]$ClipRadius=3,[string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe')
|
||||||
|
|
||||||
$ErrorActionPreference='Stop'
|
$ErrorActionPreference='Stop'
|
||||||
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
|
$StandaloneRoot=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
|
||||||
$ExecutablePath=Join-Path $StandaloneRoot 'bin\VoxelForgeGpuFarHybridViewer.exe'
|
if([IO.Path]::GetFileName($ExecutableName)-ne$ExecutableName-or[IO.Path]::GetExtension($ExecutableName)-ne'.exe')
|
||||||
|
{throw 'ExecutableName must be an executable filename, not a path.'}
|
||||||
|
$ExecutablePath=Join-Path $StandaloneRoot ('bin\'+$ExecutableName)
|
||||||
$StdoutPath=Join-Path $PSScriptRoot 'owner_hybrid_viewer.stdout.log'
|
$StdoutPath=Join-Path $PSScriptRoot 'owner_hybrid_viewer.stdout.log'
|
||||||
$StderrPath=Join-Path $PSScriptRoot 'owner_hybrid_viewer.stderr.log'
|
$StderrPath=Join-Path $PSScriptRoot 'owner_hybrid_viewer.stderr.log'
|
||||||
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU far viewer not found: $ExecutablePath"}
|
if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU far viewer not found: $ExecutablePath"}
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -25,7 +25,19 @@ cbuffer TileConstants : register(b0)
|
|||||||
int OriginZ;
|
int OriginZ;
|
||||||
int StepVoxels;
|
int StepVoxels;
|
||||||
float VoxelSizeCm;
|
float VoxelSizeCm;
|
||||||
float3 ConstantPad;
|
uint OutputBaseVertex;
|
||||||
|
uint OutputArgsByteOffset;
|
||||||
|
uint ConstantPad;
|
||||||
|
};
|
||||||
|
|
||||||
|
cbuffer FarConnectorConstants : register(b2)
|
||||||
|
{
|
||||||
|
uint FarConnectorCount;
|
||||||
|
uint FarConnectorRingCount;
|
||||||
|
uint FarConnectorVertexCount;
|
||||||
|
uint FarConnectorPanelVertexCapacity;
|
||||||
|
uint FarShapeCount;
|
||||||
|
uint3 FarConnectorPadding;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int3 CornerOffset[8] = {
|
static const int3 CornerOffset[8] = {
|
||||||
@@ -127,6 +139,25 @@ RWStructuredBuffer<uint> CellLocalOffsets : register(u1);
|
|||||||
RWStructuredBuffer<uint> GroupVertexCounts : register(u2);
|
RWStructuredBuffer<uint> GroupVertexCounts : register(u2);
|
||||||
|
|
||||||
struct FarFieldSampleRange { uint offset; uint count; };
|
struct FarFieldSampleRange { uint offset; uint count; };
|
||||||
|
struct FarFieldConnectorPanel
|
||||||
|
{
|
||||||
|
float3 start; float radius;
|
||||||
|
float3 end; float tMin;
|
||||||
|
float tMax;
|
||||||
|
uint ringCount;
|
||||||
|
uint ringOffset;
|
||||||
|
uint vertexOffset;
|
||||||
|
uint axialSampleStart;
|
||||||
|
uint axialSampleCount;
|
||||||
|
uint2 padding;
|
||||||
|
};
|
||||||
|
struct FarConnectorRing
|
||||||
|
{
|
||||||
|
float4 corners[4];
|
||||||
|
float4 normals[4];
|
||||||
|
float t;
|
||||||
|
float3 padding;
|
||||||
|
};
|
||||||
StructuredBuffer<VF_FieldPrimitive> FarShapes : register(t10);
|
StructuredBuffer<VF_FieldPrimitive> FarShapes : register(t10);
|
||||||
StructuredBuffer<FarFieldSampleRange> FarSampleShapeRanges : register(t11);
|
StructuredBuffer<FarFieldSampleRange> FarSampleShapeRanges : register(t11);
|
||||||
StructuredBuffer<uint> FarSampleShapeIds : register(t12);
|
StructuredBuffer<uint> FarSampleShapeIds : register(t12);
|
||||||
@@ -134,6 +165,12 @@ StructuredBuffer<uint> FarProtectedAir : register(t13);
|
|||||||
StructuredBuffer<uint> FarProtectedSolid : register(t14);
|
StructuredBuffer<uint> FarProtectedSolid : register(t14);
|
||||||
RWStructuredBuffer<float> FarFieldDensity : register(u7);
|
RWStructuredBuffer<float> FarFieldDensity : register(u7);
|
||||||
StructuredBuffer<float> GpuSignDensity : register(t15);
|
StructuredBuffer<float> GpuSignDensity : register(t15);
|
||||||
|
StructuredBuffer<FarFieldConnectorPanel> FarConnectorPanels : register(t8);
|
||||||
|
StructuredBuffer<FarConnectorRing> FarConnectorRingInput : register(t9);
|
||||||
|
RWStructuredBuffer<FarConnectorRing> FarConnectorRingOutput : register(u7);
|
||||||
|
RWByteAddressBuffer FarConnectorDrawArgs : register(u4);
|
||||||
|
RWByteAddressBuffer FarConnectorVertexBytes : register(u5);
|
||||||
|
RWByteAddressBuffer FarConnectorIndexBytes : register(u6);
|
||||||
|
|
||||||
cbuffer FarFieldConstants : register(b1)
|
cbuffer FarFieldConstants : register(b1)
|
||||||
{
|
{
|
||||||
@@ -196,6 +233,205 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID)
|
|||||||
FarFieldDensity[index] = density;
|
FarFieldDensity[index] = density;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float FarRockDistanceAt(float3 position)
|
||||||
|
{
|
||||||
|
float cave = 100000.0f;
|
||||||
|
float overlay = -100000.0f;
|
||||||
|
[loop]
|
||||||
|
for (uint shapeId = 0; shapeId < FarShapeCount; ++shapeId)
|
||||||
|
{
|
||||||
|
const VF_FieldPrimitive shape = FarShapes[shapeId];
|
||||||
|
if ((shape.flags & VF_FIELD_PRIMITIVE_CARVE) != 0)
|
||||||
|
{
|
||||||
|
if (position.x < shape.minVoxel[0] - 6 || position.x > shape.maxVoxel[0] + 6
|
||||||
|
|| position.y < shape.minVoxel[1] - 6 || position.y > shape.maxVoxel[1] + 6
|
||||||
|
|| position.z < shape.minVoxel[2] - 6 || position.z > shape.maxVoxel[2] + 6)
|
||||||
|
continue;
|
||||||
|
float distance = VF_FieldPrimitiveDistance(shape, position.x, position.y, position.z);
|
||||||
|
if ((shape.flags & VF_FIELD_PRIMITIVE_HAS_FLOOR) != 0)
|
||||||
|
{
|
||||||
|
const float floorZ = VF_FieldPrimitiveFloorAt(shape, position.x, position.y);
|
||||||
|
distance = VF_FieldSmoothMax(distance, floorZ - position.z, shape.floorBlendRadius);
|
||||||
|
}
|
||||||
|
cave = VF_FieldSmoothMin(cave, distance, shape.blendRadius);
|
||||||
|
}
|
||||||
|
else if (position.x >= shape.minVoxel[0] && position.x <= shape.maxVoxel[0]
|
||||||
|
&& position.y >= shape.minVoxel[1] && position.y <= shape.maxVoxel[1]
|
||||||
|
&& position.z >= shape.minVoxel[2] && position.z <= shape.maxVoxel[2])
|
||||||
|
{
|
||||||
|
overlay = max(overlay, -VF_FieldPrimitiveDistance(shape,
|
||||||
|
position.x, position.y, position.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float base = min(position.z - FarStrataLo, FarStrataHi - position.z);
|
||||||
|
return VF_FieldSmoothMin(base, max(cave, overlay), FarStrataBlend);
|
||||||
|
}
|
||||||
|
|
||||||
|
float FarConnectorRadius(float3 center, float3 direction, float nominalRadius)
|
||||||
|
{
|
||||||
|
// The coarse far field is a signed-distance composition. Two bounded
|
||||||
|
// distance steps follow its connector wall closely without a CPU readback
|
||||||
|
// or a long radial scan for each sparse panel ring.
|
||||||
|
const float maxOffset=max(16.0f,nominalRadius*0.75f);
|
||||||
|
const float minRadius=max(0.0f,nominalRadius-maxOffset);
|
||||||
|
const float maxRadius=nominalRadius+maxOffset;
|
||||||
|
float radius=nominalRadius;
|
||||||
|
[unroll]
|
||||||
|
for(uint refine=0;refine<3;++refine)
|
||||||
|
{
|
||||||
|
const float distance=FarRockDistanceAt(center+direction*radius);
|
||||||
|
const float nextDistance=FarRockDistanceAt(center+direction*(radius+4.0f));
|
||||||
|
const float slope=(nextDistance-distance)*0.25f;
|
||||||
|
const float correction=abs(slope)>0.125f?distance/slope:distance;
|
||||||
|
radius=clamp(radius-correction,minRadius,maxRadius);
|
||||||
|
}
|
||||||
|
const float residual=FarRockDistanceAt(center+direction*radius);
|
||||||
|
if(abs(residual)>1.0f)
|
||||||
|
{
|
||||||
|
const float searchMin=max(0.0f,nominalRadius-maxOffset-8.0f);
|
||||||
|
const float searchMax=nominalRadius+maxOffset;
|
||||||
|
float previous=searchMin;
|
||||||
|
float previousDistance=FarRockDistanceAt(center+direction*previous);
|
||||||
|
float bestRadius=radius;
|
||||||
|
float bestDifference=100000.0f;
|
||||||
|
[loop]
|
||||||
|
for(uint sample=1;sample<=32;++sample)
|
||||||
|
{
|
||||||
|
const float distance=min(searchMin+sample*8.0f,searchMax);
|
||||||
|
if(distance<=previous+1.0e-5f)break;
|
||||||
|
const float currentDistance=FarRockDistanceAt(center+direction*distance);
|
||||||
|
if((previousDistance>=0.0f)!=(currentDistance>=0.0f))
|
||||||
|
{
|
||||||
|
float lo=previous,hi=distance;
|
||||||
|
const bool previousSolid=previousDistance>=0.0f;
|
||||||
|
[unroll]
|
||||||
|
for(uint refine=0;refine<8;++refine)
|
||||||
|
{
|
||||||
|
const float mid=(lo+hi)*0.5f;
|
||||||
|
if((FarRockDistanceAt(center+direction*mid)>=0.0f)==previousSolid)lo=mid;
|
||||||
|
else hi=mid;
|
||||||
|
}
|
||||||
|
const float crossing=(lo+hi)*0.5f;
|
||||||
|
const float difference=abs(crossing-nominalRadius);
|
||||||
|
if(difference<=maxOffset&&difference<bestDifference)
|
||||||
|
{bestRadius=crossing;bestDifference=difference;}
|
||||||
|
}
|
||||||
|
previous=distance;previousDistance=currentDistance;
|
||||||
|
if(distance>=searchMax-1.0e-5f)break;
|
||||||
|
}
|
||||||
|
radius=bestRadius;
|
||||||
|
}
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
[numthreads(64,1,1)]
|
||||||
|
void BuildFarConnectorRingsCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||||
|
{
|
||||||
|
const uint ringId = dispatchId.x;
|
||||||
|
if (ringId == 0)
|
||||||
|
{
|
||||||
|
const uint maxVerticesPerTile = CellCount * 15;
|
||||||
|
const uint slot = maxVerticesPerTile > 0 ? OutputBaseVertex / maxVerticesPerTile : 0;
|
||||||
|
const uint panelBaseVertex = slot * FarConnectorPanelVertexCapacity;
|
||||||
|
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 20, FarConnectorVertexCount);
|
||||||
|
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 24, FarConnectorVertexCount > 0 ? 1 : 0);
|
||||||
|
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 28, panelBaseVertex);
|
||||||
|
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 32, 0);
|
||||||
|
FarConnectorDrawArgs.Store(OutputArgsByteOffset + 36, 0);
|
||||||
|
}
|
||||||
|
if (ringId >= FarConnectorRingCount) return;
|
||||||
|
|
||||||
|
FarFieldConnectorPanel connector = FarConnectorPanels[0];
|
||||||
|
[loop]
|
||||||
|
for (uint i = 0; i < FarConnectorCount; ++i)
|
||||||
|
{
|
||||||
|
const FarFieldConnectorPanel candidate = FarConnectorPanels[i];
|
||||||
|
if (ringId >= candidate.ringOffset && ringId < candidate.ringOffset + candidate.ringCount)
|
||||||
|
{
|
||||||
|
connector = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float3 start = connector.start;
|
||||||
|
const float3 delta = connector.end - start;
|
||||||
|
const float connectorLength = length(delta);
|
||||||
|
const float3 axis = delta / max(connectorLength, 1.0e-8f);
|
||||||
|
float3 u = cross(axis, float3(0,0,1));
|
||||||
|
if (dot(u,u) < 0.01f) u = cross(axis, float3(0,1,0));
|
||||||
|
u = normalize(u);
|
||||||
|
const float3 v = normalize(cross(axis,u));
|
||||||
|
const uint localRing = ringId - connector.ringOffset;
|
||||||
|
float t;
|
||||||
|
if (localRing == 0) t = connector.tMin;
|
||||||
|
else if (localRing + 1 == connector.ringCount) t = connector.tMax;
|
||||||
|
else t = ((float)(connector.axialSampleStart + localRing - 1) + 0.5f)
|
||||||
|
/ max(1.0f, (float)connector.axialSampleCount);
|
||||||
|
const float3 center = start + delta * t;
|
||||||
|
const float ru = FarConnectorRadius(center,u,connector.radius);
|
||||||
|
const float rmu = FarConnectorRadius(center,-u,connector.radius);
|
||||||
|
const float rv = FarConnectorRadius(center,v,connector.radius);
|
||||||
|
const float rmv = FarConnectorRadius(center,-v,connector.radius);
|
||||||
|
FarConnectorRing ring;
|
||||||
|
ring.corners[0] = float4(center + u*ru + v*rv, 0);
|
||||||
|
ring.corners[1] = float4(center - u*rmu + v*rv, 0);
|
||||||
|
ring.corners[2] = float4(center - u*rmu - v*rmv, 0);
|
||||||
|
ring.corners[3] = float4(center + u*ru - v*rmv, 0);
|
||||||
|
ring.normals[0] = float4(-normalize(u+v),0);
|
||||||
|
ring.normals[1] = float4(-normalize(-u+v),0);
|
||||||
|
ring.normals[2] = float4(-normalize(-u-v),0);
|
||||||
|
ring.normals[3] = float4(-normalize(u-v),0);
|
||||||
|
ring.t = t;
|
||||||
|
ring.padding = 0;
|
||||||
|
FarConnectorRingOutput[ringId] = ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
[numthreads(64,1,1)]
|
||||||
|
void EmitFarConnectorPanelsCS(uint3 dispatchId : SV_DispatchThreadID)
|
||||||
|
{
|
||||||
|
const uint vertexId = dispatchId.x;
|
||||||
|
if (vertexId >= FarConnectorVertexCount) return;
|
||||||
|
FarFieldConnectorPanel connector = FarConnectorPanels[0];
|
||||||
|
[loop]
|
||||||
|
for (uint i = 0; i < FarConnectorCount; ++i)
|
||||||
|
{
|
||||||
|
const FarFieldConnectorPanel candidate = FarConnectorPanels[i];
|
||||||
|
const uint candidateCount = (candidate.ringCount - 1) * 24;
|
||||||
|
if (vertexId >= candidate.vertexOffset && vertexId < candidate.vertexOffset + candidateCount)
|
||||||
|
{
|
||||||
|
connector = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const uint localVertex = vertexId - connector.vertexOffset;
|
||||||
|
const uint interval = localVertex / 24;
|
||||||
|
const uint cornerInQuad = localVertex % 6;
|
||||||
|
const uint side = (localVertex / 6) % 4;
|
||||||
|
const uint nextSide = (side + 1) & 3;
|
||||||
|
const uint ringA = connector.ringOffset + interval;
|
||||||
|
const uint ringB = ringA + 1;
|
||||||
|
uint ringIndex, cornerIndex;
|
||||||
|
if (cornerInQuad == 0) { ringIndex=ringA; cornerIndex=side; }
|
||||||
|
else if (cornerInQuad == 1) { ringIndex=ringB; cornerIndex=side; }
|
||||||
|
else if (cornerInQuad == 2) { ringIndex=ringA; cornerIndex=nextSide; }
|
||||||
|
else if (cornerInQuad == 3) { ringIndex=ringA; cornerIndex=nextSide; }
|
||||||
|
else if (cornerInQuad == 4) { ringIndex=ringB; cornerIndex=side; }
|
||||||
|
else { ringIndex=ringB; cornerIndex=nextSide; }
|
||||||
|
const FarConnectorRing ring = FarConnectorRingInput[ringIndex];
|
||||||
|
GpuVertex vertex;
|
||||||
|
vertex.position = ring.corners[cornerIndex].xyz * VoxelSizeCm;
|
||||||
|
vertex.normal = ring.normals[cornerIndex].xyz;
|
||||||
|
const float connectorLength = length(connector.end - connector.start);
|
||||||
|
vertex.uv = float2(ring.t * connectorLength / 256.0f, (float)side / 4.0f);
|
||||||
|
const uint maxVerticesPerTile = CellCount * 15;
|
||||||
|
const uint slot = maxVerticesPerTile > 0 ? OutputBaseVertex / maxVerticesPerTile : 0;
|
||||||
|
const uint base = slot * FarConnectorPanelVertexCapacity;
|
||||||
|
const uint byteAddress = (base + vertexId) * 32;
|
||||||
|
FarConnectorVertexBytes.Store3(byteAddress, asuint(vertex.position));
|
||||||
|
FarConnectorVertexBytes.Store3(byteAddress + 12, asuint(vertex.normal));
|
||||||
|
FarConnectorVertexBytes.Store2(byteAddress + 24, asuint(vertex.uv));
|
||||||
|
FarConnectorIndexBytes.Store((base + vertexId) * 4, base + vertexId);
|
||||||
|
}
|
||||||
|
|
||||||
groupshared uint ScanValues[256];
|
groupshared uint ScanValues[256];
|
||||||
|
|
||||||
void ClassifyAndScan(uint index, uint3 groupId, uint3 groupThreadId, uint caseIndex)
|
void ClassifyAndScan(uint index, uint3 groupId, uint3 groupThreadId, uint caseIndex)
|
||||||
@@ -283,11 +519,16 @@ void ScanGroupsCS(uint3 dispatchId : SV_DispatchThreadID)
|
|||||||
GroupOffsets[i] = offset;
|
GroupOffsets[i] = offset;
|
||||||
offset += InputGroupVertexCounts[i];
|
offset += InputGroupVertexCounts[i];
|
||||||
}
|
}
|
||||||
DrawArgs.Store(0, offset);
|
DrawArgs.Store(OutputArgsByteOffset + 0, offset);
|
||||||
DrawArgs.Store(4, 1);
|
DrawArgs.Store(OutputArgsByteOffset + 4, 1);
|
||||||
DrawArgs.Store(8, 0);
|
DrawArgs.Store(OutputArgsByteOffset + 8, OutputBaseVertex);
|
||||||
DrawArgs.Store(12, 0);
|
DrawArgs.Store(OutputArgsByteOffset + 12, 0);
|
||||||
DrawArgs.Store(16, 0);
|
DrawArgs.Store(OutputArgsByteOffset + 16, 0);
|
||||||
|
DrawArgs.Store(OutputArgsByteOffset + 20, 0);
|
||||||
|
DrawArgs.Store(OutputArgsByteOffset + 24, 0);
|
||||||
|
DrawArgs.Store(OutputArgsByteOffset + 28, 0);
|
||||||
|
DrawArgs.Store(OutputArgsByteOffset + 32, 0);
|
||||||
|
DrawArgs.Store(OutputArgsByteOffset + 36, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
StructuredBuffer<float> Density : register(t2);
|
StructuredBuffer<float> Density : register(t2);
|
||||||
@@ -306,7 +547,7 @@ void EmitCS(uint3 dispatchId : SV_DispatchThreadID)
|
|||||||
const uint index = dispatchId.x;
|
const uint index = dispatchId.x;
|
||||||
if (index >= CellCount) return;
|
if (index >= CellCount) return;
|
||||||
const uint caseIndex = Cases[index];
|
const uint caseIndex = Cases[index];
|
||||||
const uint base = LocalOffsets[index] + PrefixGroups[index / 256];
|
const uint base = OutputBaseVertex + LocalOffsets[index] + PrefixGroups[index / 256];
|
||||||
const uint3 cell = uint3(index % CellsPerAxis,
|
const uint3 cell = uint3(index % CellsPerAxis,
|
||||||
(index / CellsPerAxis) % CellsPerAxis,
|
(index / CellsPerAxis) % CellsPerAxis,
|
||||||
index / (CellsPerAxis * CellsPerAxis));
|
index / (CellsPerAxis * CellsPerAxis));
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@@ -1352,10 +1353,24 @@ namespace
|
|||||||
ID3D11Buffer* VertexBuffer=nullptr;
|
ID3D11Buffer* VertexBuffer=nullptr;
|
||||||
ID3D11Buffer* IndexBuffer=nullptr;
|
ID3D11Buffer* IndexBuffer=nullptr;
|
||||||
ID3D11Buffer* IndirectArgs=nullptr;
|
ID3D11Buffer* IndirectArgs=nullptr;
|
||||||
|
ID3D11Buffer* ConnectorVertexBuffer=nullptr;
|
||||||
|
ID3D11Buffer* ConnectorIndexBuffer=nullptr;
|
||||||
ID3D11Query* CompletionEvent=nullptr;
|
ID3D11Query* CompletionEvent=nullptr;
|
||||||
|
VoxelForgeGpuFarViewer::GpuTimingQueries TimingQueries{};
|
||||||
uint64 VertexCapacityBytes=0;
|
uint64 VertexCapacityBytes=0;
|
||||||
|
uint32 OutputSlot=0,IndirectArgsByteOffset=0,BatchIndex=UINT32_MAX;
|
||||||
double GpuDispatchMs=0.0;
|
double GpuDispatchMs=0.0;
|
||||||
bool bGpuComplete=false;
|
bool bGpuComplete=false;
|
||||||
|
bool bTimingResolved=false;
|
||||||
|
};
|
||||||
|
struct FGpuFarBatch
|
||||||
|
{
|
||||||
|
ID3D11Query* CompletionEvent=nullptr;
|
||||||
|
VoxelForgeGpuFarViewer::GpuTimingQueries TimingQueries{};
|
||||||
|
uint32 TileCount=0;
|
||||||
|
uint64 TimestampFrequency=0;
|
||||||
|
double GpuDispatchMs=0.0;
|
||||||
|
bool bGpuComplete=false,bTimingResolved=false;
|
||||||
};
|
};
|
||||||
|
|
||||||
uint64 HashStandaloneFile(const std::string& Path)
|
uint64 HashStandaloneFile(const std::string& Path)
|
||||||
@@ -1439,6 +1454,7 @@ namespace
|
|||||||
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
|
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
|
||||||
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
|
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
|
||||||
std::unordered_map<FVoxelTileKey,FGpuFarTile,FStandaloneTileKeyHash> GpuFarTiles;
|
std::unordered_map<FVoxelTileKey,FGpuFarTile,FStandaloneTileKeyHash> GpuFarTiles;
|
||||||
|
std::vector<FGpuFarBatch> GpuFarBatches;
|
||||||
std::deque<FVoxelTileKey> GpuFarQueue;
|
std::deque<FVoxelTileKey> GpuFarQueue;
|
||||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> GpuFarQueued;
|
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> GpuFarQueued;
|
||||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> GpuFarEligibleTiles;
|
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> GpuFarEligibleTiles;
|
||||||
@@ -1474,6 +1490,16 @@ namespace
|
|||||||
uint64 GpuFarTilesSubmitted=0;
|
uint64 GpuFarTilesSubmitted=0;
|
||||||
double GpuFarGpuDispatchMs=0.0;
|
double GpuFarGpuDispatchMs=0.0;
|
||||||
double GpuFarGpuDispatchMaxMs=0.0;
|
double GpuFarGpuDispatchMaxMs=0.0;
|
||||||
|
double GpuFarSubmissionSpanMs=0.0,GpuFarSubmissionSpanMaxMs=0.0;
|
||||||
|
uint64 GpuFarLocalMemoryPeakBytes=0,GpuFarLocalMemoryBudgetBytes=0;
|
||||||
|
double GpuFarPoolReserveMs=0.0;
|
||||||
|
uint64 GpuFarSubmissionBatches=0;
|
||||||
|
uint32 GpuFarMaxTilesPerSubmissionBatch=0;
|
||||||
|
uint32 GpuFarOutputCapacitySlots=0,GpuFarNextOutputSlot=0;
|
||||||
|
std::vector<uint32> GpuFarFreeOutputSlots;
|
||||||
|
std::unordered_map<FVoxelTileKey,VoxelForgeCore::FarFieldTileInput,FStandaloneTileKeyHash> GpuFarPreparedInputs;
|
||||||
|
std::unordered_map<FVoxelTileKey,std::string,FStandaloneTileKeyHash> GpuFarPreparationErrors;
|
||||||
|
double GpuFarPreparationWallMs=0.0,GpuFarPreparationWorkerMs=0.0;
|
||||||
VoxelForgeCore::DetailVariant ShapeDetailVariant=VoxelForgeCore::DetailVariant::coherent;
|
VoxelForgeCore::DetailVariant ShapeDetailVariant=VoxelForgeCore::DetailVariant::coherent;
|
||||||
bool bWireframe=false;
|
bool bWireframe=false;
|
||||||
bool bCaptured=false;
|
bool bCaptured=false;
|
||||||
@@ -1531,6 +1557,41 @@ namespace
|
|||||||
if(SwapChain)SwapChain->Release();if(Context)Context->Release();if(Device)Device->Release();
|
if(SwapChain)SwapChain->Release();if(Context)Context->Release();if(Device)Device->Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ReleaseGpuFarTile(FGpuFarTile& Tile)
|
||||||
|
{
|
||||||
|
if(Tile.VertexBuffer)Tile.VertexBuffer->Release();
|
||||||
|
if(Tile.IndexBuffer)Tile.IndexBuffer->Release();
|
||||||
|
if(Tile.IndirectArgs)Tile.IndirectArgs->Release();
|
||||||
|
if(Tile.ConnectorVertexBuffer)Tile.ConnectorVertexBuffer->Release();
|
||||||
|
if(Tile.ConnectorIndexBuffer)Tile.ConnectorIndexBuffer->Release();
|
||||||
|
if(Tile.CompletionEvent)Tile.CompletionEvent->Release();
|
||||||
|
if(Tile.TimingQueries.start)Tile.TimingQueries.start->Release();
|
||||||
|
if(Tile.TimingQueries.end)Tile.TimingQueries.end->Release();
|
||||||
|
if(Tile.TimingQueries.disjoint)Tile.TimingQueries.disjoint->Release();
|
||||||
|
Tile={};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReleaseGpuFarBatch(FGpuFarBatch& Batch)
|
||||||
|
{
|
||||||
|
if(Batch.CompletionEvent)Batch.CompletionEvent->Release();
|
||||||
|
if(Batch.TimingQueries.start)Batch.TimingQueries.start->Release();
|
||||||
|
if(Batch.TimingQueries.end)Batch.TimingQueries.end->Release();
|
||||||
|
if(Batch.TimingQueries.disjoint)Batch.TimingQueries.disjoint->Release();
|
||||||
|
Batch={};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SampleGpuFarMemory()
|
||||||
|
{
|
||||||
|
if(!bGpuFarEnabled||!Device||!Context)return;
|
||||||
|
uint64 Usage=0,Budget=0;std::string Error;
|
||||||
|
if(VoxelForgeGpuFarViewer::QueryFarGpuMemory(Device,Context,GpuFarShaderPath,
|
||||||
|
Usage,Budget,Error))
|
||||||
|
{
|
||||||
|
GpuFarLocalMemoryPeakBytes=std::max(GpuFarLocalMemoryPeakBytes,Usage);
|
||||||
|
GpuFarLocalMemoryBudgetBytes=Budget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ReleaseMeshes()
|
void ReleaseMeshes()
|
||||||
{
|
{
|
||||||
for(auto& Pair:StreamingBatches)
|
for(auto& Pair:StreamingBatches)
|
||||||
@@ -1540,16 +1601,20 @@ namespace
|
|||||||
}
|
}
|
||||||
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
|
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
|
||||||
for(auto& Pair:GpuFarTiles)
|
for(auto& Pair:GpuFarTiles)
|
||||||
{
|
ReleaseGpuFarTile(Pair.second);
|
||||||
if(Pair.second.VertexBuffer)Pair.second.VertexBuffer->Release();
|
GpuFarTiles.clear();
|
||||||
if(Pair.second.IndexBuffer)Pair.second.IndexBuffer->Release();
|
for(FGpuFarBatch& Batch:GpuFarBatches)ReleaseGpuFarBatch(Batch);
|
||||||
if(Pair.second.IndirectArgs)Pair.second.IndirectArgs->Release();
|
GpuFarBatches.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
||||||
if(Pair.second.CompletionEvent)Pair.second.CompletionEvent->Release();
|
GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
|
||||||
}
|
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();
|
||||||
GpuFarTiles.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
GpuFarPreparationWallMs=0.0;GpuFarPreparationWorkerMs=0.0;
|
||||||
GpuFarAudit.reset();bGpuFarAuditReported=false;
|
GpuFarAudit.reset();bGpuFarAuditReported=false;
|
||||||
GpuFarTilesSubmitted=0;GpuFarEnqueueWallSeconds=0.0;GpuFarGpuDispatchMs=0.0;
|
GpuFarTilesSubmitted=0;GpuFarEnqueueWallSeconds=0.0;GpuFarGpuDispatchMs=0.0;
|
||||||
GpuFarGpuDispatchMaxMs=0.0;bGpuFarFirstTileReported=false;
|
GpuFarGpuDispatchMaxMs=0.0;GpuFarSubmissionSpanMs=0.0;GpuFarSubmissionSpanMaxMs=0.0;
|
||||||
|
GpuFarLocalMemoryPeakBytes=0;GpuFarLocalMemoryBudgetBytes=0;
|
||||||
|
bGpuFarFirstTileReported=false;
|
||||||
|
GpuFarPoolReserveMs=0.0;
|
||||||
|
GpuFarSubmissionBatches=0;GpuFarMaxTilesPerSubmissionBatch=0;
|
||||||
DirtyBatchSet.clear();DesiredTiles.clear();DesiredTileSet.clear();CrossingWork.clear();
|
DirtyBatchSet.clear();DesiredTiles.clear();DesiredTileSet.clear();CrossingWork.clear();
|
||||||
DeferredLeaverTiles.clear();
|
DeferredLeaverTiles.clear();
|
||||||
for(int32 I=0;I<=MaxLevels;++I)
|
for(int32 I=0;I<=MaxLevels;++I)
|
||||||
@@ -2104,13 +2169,59 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
auto It=GpuFarTiles.find(Key);
|
auto It=GpuFarTiles.find(Key);
|
||||||
if(It==GpuFarTiles.end())return false;
|
if(It==GpuFarTiles.end())return false;
|
||||||
FGpuFarTile& Tile=It->second;
|
FGpuFarTile& Tile=It->second;
|
||||||
if(Tile.bGpuComplete)return true;
|
if(Tile.BatchIndex!=UINT32_MAX&&Tile.BatchIndex<GpuFarBatches.size())
|
||||||
if(Tile.CompletionEvent&&Context
|
{
|
||||||
|
FGpuFarBatch& Batch=GpuFarBatches[Tile.BatchIndex];
|
||||||
|
if(!Batch.bGpuComplete&&Batch.CompletionEvent&&Context
|
||||||
|
&&Context->GetData(Batch.CompletionEvent,nullptr,0,D3D11_ASYNC_GETDATA_DONOTFLUSH)==S_OK)
|
||||||
|
{
|
||||||
|
Batch.bGpuComplete=true;
|
||||||
|
Batch.CompletionEvent->Release();Batch.CompletionEvent=nullptr;
|
||||||
|
}
|
||||||
|
if(Batch.bGpuComplete&&!Batch.bTimingResolved)
|
||||||
|
{
|
||||||
|
if(VoxelForgeGpuFarViewer::ResolveGpuTiming(Context,Batch.TimingQueries,
|
||||||
|
Batch.GpuDispatchMs,&Batch.TimestampFrequency))
|
||||||
|
{
|
||||||
|
Batch.bTimingResolved=true;
|
||||||
|
GpuFarSubmissionSpanMs+=Batch.GpuDispatchMs;
|
||||||
|
GpuFarSubmissionSpanMaxMs=std::max(GpuFarSubmissionSpanMaxMs,Batch.GpuDispatchMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Batch.bGpuComplete&&Batch.bTimingResolved&&!Tile.bTimingResolved
|
||||||
|
&&Batch.TimestampFrequency)
|
||||||
|
{
|
||||||
|
double TileDispatchMs=0.0;
|
||||||
|
if(VoxelForgeGpuFarViewer::ResolveGpuTimestampPair(Context,Tile.TimingQueries,
|
||||||
|
Batch.TimestampFrequency,TileDispatchMs))
|
||||||
|
{
|
||||||
|
Tile.GpuDispatchMs=TileDispatchMs;Tile.bTimingResolved=true;
|
||||||
|
GpuFarGpuDispatchMs+=TileDispatchMs;
|
||||||
|
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,TileDispatchMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Batch.bGpuComplete;
|
||||||
|
}
|
||||||
|
if(!Tile.bGpuComplete&&Tile.CompletionEvent&&Context
|
||||||
&&Context->GetData(Tile.CompletionEvent,nullptr,0,D3D11_ASYNC_GETDATA_DONOTFLUSH)==S_OK)
|
&&Context->GetData(Tile.CompletionEvent,nullptr,0,D3D11_ASYNC_GETDATA_DONOTFLUSH)==S_OK)
|
||||||
{
|
{
|
||||||
Tile.bGpuComplete=true;
|
Tile.bGpuComplete=true;
|
||||||
Tile.CompletionEvent->Release();Tile.CompletionEvent=nullptr;
|
Tile.CompletionEvent->Release();Tile.CompletionEvent=nullptr;
|
||||||
}
|
}
|
||||||
|
if(Tile.bGpuComplete&&!Tile.bTimingResolved)
|
||||||
|
{
|
||||||
|
double AsyncDispatchMs=0.0;
|
||||||
|
if(VoxelForgeGpuFarViewer::ResolveGpuTiming(Context,Tile.TimingQueries,AsyncDispatchMs))
|
||||||
|
{
|
||||||
|
if(Tile.GpuDispatchMs==0.0)
|
||||||
|
{
|
||||||
|
Tile.GpuDispatchMs=AsyncDispatchMs;
|
||||||
|
GpuFarGpuDispatchMs+=AsyncDispatchMs;
|
||||||
|
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,AsyncDispatchMs);
|
||||||
|
}
|
||||||
|
Tile.bTimingResolved=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return Tile.bGpuComplete;
|
return Tile.bGpuComplete;
|
||||||
}
|
}
|
||||||
return ResidentTiles.contains(Key);
|
return ResidentTiles.contains(Key);
|
||||||
@@ -2119,20 +2230,98 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
void ScheduleGpuFarTiles(const std::vector<FVoxelTileKey>& Keys)
|
void ScheduleGpuFarTiles(const std::vector<FVoxelTileKey>& Keys)
|
||||||
{
|
{
|
||||||
GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear();
|
||||||
|
GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear();
|
||||||
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld())return;
|
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld())return;
|
||||||
std::vector<FVoxelTileKey> CpuTiles;CpuTiles.reserve(Keys.size());
|
std::vector<FVoxelTileKey> CpuTiles;CpuTiles.reserve(Keys.size());
|
||||||
|
std::vector<FVoxelTileKey> Eligible;Eligible.reserve(Keys.size());
|
||||||
for(const FVoxelTileKey& Key:Keys)
|
for(const FVoxelTileKey& Key:Keys)
|
||||||
{
|
{
|
||||||
if(Key.Level<World->FullResClipLevels){CpuTiles.push_back(Key);continue;}
|
if(Key.Level<World->FullResClipLevels){CpuTiles.push_back(Key);continue;}
|
||||||
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
|
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
|
||||||
if(ShapeWorld->isFarFieldTileEligible(CoreKey))
|
if(ShapeWorld->isFarFieldTileEligible(CoreKey))
|
||||||
|
{
|
||||||
|
Eligible.push_back(Key);
|
||||||
|
}
|
||||||
|
else CpuTiles.push_back(Key);
|
||||||
|
}
|
||||||
|
if(Eligible.size()>GpuFarOutputCapacitySlots)
|
||||||
|
{
|
||||||
|
for(auto& Pair:GpuFarTiles)ReleaseGpuFarTile(Pair.second);
|
||||||
|
GpuFarTiles.clear();GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0;
|
||||||
|
GpuFarOutputCapacitySlots=static_cast<uint32>(Eligible.size());
|
||||||
|
}
|
||||||
|
if(!Eligible.empty()&&GpuFarOutputCapacitySlots==0)
|
||||||
|
GpuFarOutputCapacitySlots=static_cast<uint32>(Eligible.size());
|
||||||
|
if(!Eligible.empty())
|
||||||
|
{
|
||||||
|
std::string PoolError;const auto ReserveStart=std::chrono::steady_clock::now();
|
||||||
|
if(!VoxelForgeGpuFarViewer::ReserveFarOutputPool(Device,Context,GpuFarShaderPath,
|
||||||
|
GpuFarOutputCapacitySlots,static_cast<uint32>(World->CoarseTileCells),PoolError))
|
||||||
|
{
|
||||||
|
std::fprintf(stderr,"[GpuFarPoolError] %s\n",PoolError.c_str());
|
||||||
|
bGpuFarEnabled=false;GpuFarOutputCapacitySlots=0;
|
||||||
|
CpuTiles=Keys;Eligible.clear();GpuFarTiles.clear();
|
||||||
|
}
|
||||||
|
GpuFarPoolReserveMs+=std::chrono::duration<double,std::milli>(
|
||||||
|
std::chrono::steady_clock::now()-ReserveStart).count();
|
||||||
|
SampleGpuFarMemory();
|
||||||
|
}
|
||||||
|
std::vector<FVoxelTileKey> NeedPreparation;NeedPreparation.reserve(Eligible.size());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
for(const FVoxelTileKey& Key:Eligible)
|
||||||
{
|
{
|
||||||
GpuFarEligibleTiles.insert(Key);
|
GpuFarEligibleTiles.insert(Key);
|
||||||
if(!GpuFarTiles.contains(Key))
|
if(!GpuFarTiles.contains(Key))
|
||||||
{GpuFarQueue.push_back(Key);GpuFarQueued.insert(Key);}
|
{GpuFarQueue.push_back(Key);GpuFarQueued.insert(Key);}
|
||||||
}
|
}
|
||||||
else CpuTiles.push_back(Key);
|
|
||||||
}
|
|
||||||
if(Streamer)Streamer->SetDesiredTileFilter(CpuTiles);
|
if(Streamer)Streamer->SetDesiredTileFilter(CpuTiles);
|
||||||
const std::size_t CpuTransitionFallbacks=static_cast<std::size_t>(std::count_if(
|
const std::size_t CpuTransitionFallbacks=static_cast<std::size_t>(std::count_if(
|
||||||
Keys.begin(),Keys.end(),[&](const FVoxelTileKey& Key)
|
Keys.begin(),Keys.end(),[&](const FVoxelTileKey& Key)
|
||||||
@@ -2145,27 +2334,67 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
{
|
{
|
||||||
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context
|
if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld()||!Device||!Context
|
||||||
||GpuFarQueue.empty())return;
|
||GpuFarQueue.empty())return;
|
||||||
|
const bool bDeferredBatch=!bGpuFarAuditEnabled;
|
||||||
|
if(bDeferredBatch)
|
||||||
|
{
|
||||||
|
std::string BatchError;
|
||||||
|
if(!VoxelForgeGpuFarViewer::BeginFarTileBatch(Device,Context,GpuFarShaderPath,BatchError))
|
||||||
|
{
|
||||||
|
std::fprintf(stderr,"[GpuFarBatchError] begin: %s\n",BatchError.c_str());
|
||||||
|
bGpuFarEnabled=false;return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto Start=std::chrono::steady_clock::now();
|
const auto Start=std::chrono::steady_clock::now();
|
||||||
int32 Built=0;
|
int32 Built=0;
|
||||||
|
std::vector<FVoxelTileKey> BuiltKeys;BuiltKeys.reserve(32);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
const FVoxelTileKey Key=GpuFarQueue.front();GpuFarQueue.pop_front();GpuFarQueued.erase(Key);
|
const FVoxelTileKey Key=GpuFarQueue.front();GpuFarQueue.pop_front();GpuFarQueued.erase(Key);
|
||||||
if(!DesiredTileSet.contains(Key)||GpuFarTiles.contains(Key))continue;
|
if(!DesiredTileSet.contains(Key)||GpuFarTiles.contains(Key))continue;
|
||||||
FGpuFarTile Tile;
|
FGpuFarTile Tile;
|
||||||
|
if(!GpuFarFreeOutputSlots.empty())
|
||||||
|
{Tile.OutputSlot=GpuFarFreeOutputSlots.back();GpuFarFreeOutputSlots.pop_back();}
|
||||||
|
else if(GpuFarNextOutputSlot<GpuFarOutputCapacitySlots)
|
||||||
|
Tile.OutputSlot=GpuFarNextOutputSlot++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::fprintf(stderr,"[GpuFarPoolError] no free far-output slot for queued tile\n");
|
||||||
|
bGpuFarEnabled=false;break;
|
||||||
|
}
|
||||||
VoxelForgeGpuFarViewer::CpuMesh AuditMesh;
|
VoxelForgeGpuFarViewer::CpuMesh AuditMesh;
|
||||||
std::string Error;double EnqueueMs=0.0,GpuDispatchMs=0.0;
|
std::string Error;double EnqueueMs=0.0,GpuDispatchMs=0.0;
|
||||||
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
|
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level};
|
||||||
if(!VoxelForgeGpuFarViewer::BuildFarTile(Device,Context,*ShapeWorld,CoreKey,
|
const auto PreparedInputIt=GpuFarPreparedInputs.find(Key);
|
||||||
GpuFarShaderPath,&Tile.VertexBuffer,&Tile.IndexBuffer,&Tile.IndirectArgs,
|
const VoxelForgeCore::FarFieldTileInput* PreparedInput=PreparedInputIt==GpuFarPreparedInputs.end()
|
||||||
&Tile.CompletionEvent,
|
?nullptr:&PreparedInputIt->second;
|
||||||
(bGpuFarAuditEnabled&&Key.Level>=6)?&AuditMesh:nullptr,
|
if(!PreparedInput)
|
||||||
&EnqueueMs,&GpuDispatchMs,Error))
|
|
||||||
{
|
{
|
||||||
std::fprintf(stderr,"[GpuFarTileError] key=(%d,%d,%d,lod%d): %s\n",
|
const auto PrepError=GpuFarPreparationErrors.find(Key);
|
||||||
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,Error.c_str());
|
std::fprintf(stderr,"[GpuFarInputError] key=(%d,%d,%d,lod%d): %s\n",
|
||||||
|
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,
|
||||||
|
PrepError==GpuFarPreparationErrors.end()?"prepared far input is missing":PrepError->second.c_str());
|
||||||
|
GpuFarFreeOutputSlots.push_back(Tile.OutputSlot);
|
||||||
bGpuFarEnabled=false;
|
bGpuFarEnabled=false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if(!VoxelForgeGpuFarViewer::BuildFarTile(Device,Context,*ShapeWorld,CoreKey,
|
||||||
|
PreparedInput,
|
||||||
|
GpuFarShaderPath,&Tile.VertexBuffer,&Tile.IndexBuffer,&Tile.IndirectArgs,
|
||||||
|
&Tile.ConnectorVertexBuffer,&Tile.ConnectorIndexBuffer,
|
||||||
|
Tile.OutputSlot,GpuFarOutputCapacitySlots,&Tile.IndirectArgsByteOffset,
|
||||||
|
bDeferredBatch?nullptr:&Tile.CompletionEvent,
|
||||||
|
&Tile.TimingQueries,
|
||||||
|
(bGpuFarAuditEnabled&&Key.Level>=6)?&AuditMesh:nullptr,
|
||||||
|
&EnqueueMs,&GpuDispatchMs,Error))
|
||||||
|
{
|
||||||
|
GpuFarFreeOutputSlots.push_back(Tile.OutputSlot);
|
||||||
|
std::fprintf(stderr,"[GpuFarTileError] key=(%d,%d,%d,lod%d): %s\n",
|
||||||
|
Key.Coord.X,Key.Coord.Y,Key.Coord.Z,Key.Level,Error.c_str());
|
||||||
|
if(PreparedInputIt!=GpuFarPreparedInputs.end())GpuFarPreparedInputs.erase(PreparedInputIt);
|
||||||
|
bGpuFarEnabled=false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(PreparedInputIt!=GpuFarPreparedInputs.end())GpuFarPreparedInputs.erase(PreparedInputIt);
|
||||||
if(bGpuFarAuditEnabled&&GpuFarAudit&&Key.Level>=6)
|
if(bGpuFarAuditEnabled&&GpuFarAudit&&Key.Level>=6)
|
||||||
{
|
{
|
||||||
FStandaloneCpuTile AuditTile;AuditTile.Key=Key;
|
FStandaloneCpuTile AuditTile;AuditTile.Key=Key;
|
||||||
@@ -2182,10 +2411,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
Tile.VertexCapacityBytes=Cells*Cells*Cells*15u*32u;
|
Tile.VertexCapacityBytes=Cells*Cells*Cells*15u*32u;
|
||||||
Tile.GpuDispatchMs=GpuDispatchMs;
|
Tile.GpuDispatchMs=GpuDispatchMs;
|
||||||
GpuFarEnqueueWallSeconds+=EnqueueMs/1000.0;
|
GpuFarEnqueueWallSeconds+=EnqueueMs/1000.0;
|
||||||
|
if(GpuDispatchMs>0.0)
|
||||||
|
{
|
||||||
GpuFarGpuDispatchMs+=GpuDispatchMs;
|
GpuFarGpuDispatchMs+=GpuDispatchMs;
|
||||||
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,GpuDispatchMs);
|
GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,GpuDispatchMs);
|
||||||
|
Tile.bTimingResolved=true;
|
||||||
|
}
|
||||||
++GpuFarTilesSubmitted;++Built;
|
++GpuFarTilesSubmitted;++Built;
|
||||||
GpuFarTiles.emplace(Key,Tile);
|
GpuFarTiles.emplace(Key,Tile);
|
||||||
|
BuiltKeys.push_back(Key);
|
||||||
if(!bGpuFarFirstTileReported)
|
if(!bGpuFarFirstTileReported)
|
||||||
{
|
{
|
||||||
bGpuFarFirstTileReported=true;
|
bGpuFarFirstTileReported=true;
|
||||||
@@ -2195,8 +2429,42 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
std::fflush(stdout);
|
std::fflush(stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while(Built<1&& !GpuFarQueue.empty()
|
while(Built<32&&!GpuFarQueue.empty()
|
||||||
&&std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-Start).count()<1.0);
|
&&std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-Start).count()<4.0);
|
||||||
|
if(Built>0)
|
||||||
|
{
|
||||||
|
if(bDeferredBatch)
|
||||||
|
{
|
||||||
|
FGpuFarBatch Batch;Batch.TileCount=static_cast<uint32>(Built);
|
||||||
|
std::string BatchError;
|
||||||
|
if(!VoxelForgeGpuFarViewer::SubmitFarTileBatch(Device,Context,GpuFarShaderPath,
|
||||||
|
&Batch.CompletionEvent,&Batch.TimingQueries,BatchError))
|
||||||
|
{
|
||||||
|
std::fprintf(stderr,"[GpuFarBatchError] submit: %s\n",BatchError.c_str());
|
||||||
|
bGpuFarEnabled=false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const uint32 BatchIndex=static_cast<uint32>(GpuFarBatches.size());
|
||||||
|
GpuFarBatches.push_back(Batch);
|
||||||
|
for(const FVoxelTileKey& Key:BuiltKeys)
|
||||||
|
{
|
||||||
|
auto It=GpuFarTiles.find(Key);
|
||||||
|
if(It!=GpuFarTiles.end())It->second.BatchIndex=BatchIndex;
|
||||||
|
}
|
||||||
|
++GpuFarSubmissionBatches;
|
||||||
|
GpuFarMaxTilesPerSubmissionBatch=std::max<uint32>(GpuFarMaxTilesPerSubmissionBatch,
|
||||||
|
static_cast<uint32>(Built));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++GpuFarSubmissionBatches;
|
||||||
|
GpuFarMaxTilesPerSubmissionBatch=std::max<uint32>(GpuFarMaxTilesPerSubmissionBatch,
|
||||||
|
static_cast<uint32>(Built));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SampleGpuFarMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ToggleFarRenderer()
|
void ToggleFarRenderer()
|
||||||
@@ -2423,10 +2691,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
for(auto It=GpuFarTiles.begin();It!=GpuFarTiles.end();)
|
for(auto It=GpuFarTiles.begin();It!=GpuFarTiles.end();)
|
||||||
{
|
{
|
||||||
if(DesiredTileSet.contains(It->first)){++It;continue;}
|
if(DesiredTileSet.contains(It->first)){++It;continue;}
|
||||||
if(It->second.VertexBuffer)It->second.VertexBuffer->Release();
|
GpuFarFreeOutputSlots.push_back(It->second.OutputSlot);
|
||||||
if(It->second.IndexBuffer)It->second.IndexBuffer->Release();
|
ReleaseGpuFarTile(It->second);
|
||||||
if(It->second.IndirectArgs)It->second.IndirectArgs->Release();
|
|
||||||
if(It->second.CompletionEvent)It->second.CompletionEvent->Release();
|
|
||||||
It=GpuFarTiles.erase(It);
|
It=GpuFarTiles.erase(It);
|
||||||
}
|
}
|
||||||
ScheduleGpuFarTiles(DesiredTiles);
|
ScheduleGpuFarTiles(DesiredTiles);
|
||||||
@@ -2786,11 +3052,19 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
|
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
|
||||||
if(bGpuFarEnabled)
|
if(bGpuFarEnabled)
|
||||||
{
|
{
|
||||||
|
SampleGpuFarMemory();
|
||||||
const double AverageGpuMs=GpuFarTilesSubmitted
|
const double AverageGpuMs=GpuFarTilesSubmitted
|
||||||
?GpuFarGpuDispatchMs/static_cast<double>(GpuFarTilesSubmitted):0.0;
|
?GpuFarGpuDispatchMs/static_cast<double>(GpuFarTilesSubmitted):0.0;
|
||||||
std::printf("[GpuFarTiming] submitted_tiles=%llu cpu_export_upload_enqueue_wall_ms=%.3f gpu_timestamp_total_ms=%.3f gpu_timestamp_mean_per_one_tile_frame_ms=%.6f gpu_timestamp_max_tile_ms=%.6f frame_budget_90hz_ms=11.111\n",
|
std::printf("[GpuFarTiming] submitted_tiles=%llu submission_batches=%llu max_tiles_per_batch=%u pooled_output_reserve_wall_ms=%.3f cpu_input_prepare_wall_ms=%.3f cpu_input_worker_sum_ms=%.3f cpu_upload_enqueue_sum_ms=%.3f gpu_dispatch_timestamp_total_ms=%.3f gpu_dispatch_mean_per_tile_ms=%.6f gpu_dispatch_max_tile_ms=%.6f gpu_submission_span_total_ms=%.3f gpu_submission_span_max_batch_ms=%.3f frame_budget_90hz_ms=11.111\n",
|
||||||
static_cast<unsigned long long>(GpuFarTilesSubmitted),
|
static_cast<unsigned long long>(GpuFarTilesSubmitted),
|
||||||
GpuFarEnqueueWallSeconds*1000.0,GpuFarGpuDispatchMs,AverageGpuMs,GpuFarGpuDispatchMaxMs);
|
static_cast<unsigned long long>(GpuFarSubmissionBatches),GpuFarMaxTilesPerSubmissionBatch,
|
||||||
|
GpuFarPoolReserveMs,GpuFarPreparationWallMs,GpuFarPreparationWorkerMs,
|
||||||
|
GpuFarEnqueueWallSeconds*1000.0,
|
||||||
|
GpuFarGpuDispatchMs,AverageGpuMs,GpuFarGpuDispatchMaxMs,
|
||||||
|
GpuFarSubmissionSpanMs,GpuFarSubmissionSpanMaxMs);
|
||||||
|
std::printf("[GpuFarMemory] dxgi_local_sampled_peak_mb=%.1f dxgi_local_budget_mb=%.1f\n",
|
||||||
|
GpuFarLocalMemoryPeakBytes/(1024.0*1024.0),
|
||||||
|
GpuFarLocalMemoryBudgetBytes/(1024.0*1024.0));
|
||||||
}
|
}
|
||||||
ReportTileTimings();std::fflush(stdout);
|
ReportTileTimings();std::fflush(stdout);
|
||||||
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
|
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
|
||||||
@@ -2908,7 +3182,14 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
const UINT Stride=32,Offset=0;
|
const UINT Stride=32,Offset=0;
|
||||||
Context->IASetVertexBuffers(0,1,&Tile.VertexBuffer,&Stride,&Offset);
|
Context->IASetVertexBuffers(0,1,&Tile.VertexBuffer,&Stride,&Offset);
|
||||||
Context->IASetIndexBuffer(Tile.IndexBuffer,DXGI_FORMAT_R32_UINT,0);
|
Context->IASetIndexBuffer(Tile.IndexBuffer,DXGI_FORMAT_R32_UINT,0);
|
||||||
Context->DrawIndexedInstancedIndirect(Tile.IndirectArgs,0);
|
Context->DrawIndexedInstancedIndirect(Tile.IndirectArgs,Tile.IndirectArgsByteOffset);
|
||||||
|
if(Tile.ConnectorVertexBuffer&&Tile.ConnectorIndexBuffer)
|
||||||
|
{
|
||||||
|
Context->IASetVertexBuffers(0,1,&Tile.ConnectorVertexBuffer,&Stride,&Offset);
|
||||||
|
Context->IASetIndexBuffer(Tile.ConnectorIndexBuffer,DXGI_FORMAT_R32_UINT,0);
|
||||||
|
Context->DrawIndexedInstancedIndirect(Tile.IndirectArgs,
|
||||||
|
Tile.IndirectArgsByteOffset+5u*sizeof(uint32));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CaptureRenderedFrame();
|
CaptureRenderedFrame();
|
||||||
|
|||||||
Reference in new issue
Block a user