diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 4582906..db2341e 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -205,6 +205,20 @@ namespace VoxelForgeCore 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 { Vec3i origin{}; @@ -222,6 +236,8 @@ namespace VoxelForgeCore std::vector protectedSolid; std::vector protectedThin; std::vector stratumBoundaries; + std::vector connectorPanels; + std::uint32_t connectorRingCount = 0, connectorVertexCount = 0; std::uint64_t protectedSampleCount = 0; std::uint64_t protectedSolidSampleCount = 0; std::uint64_t protectedSealSampleCount = 0; diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index c9f8eaf..31524a5 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -471,7 +471,6 @@ public: {Error="far field export requires a built far world and a bounded coarse tile";return false;} const int32 Dim=Cells+3; const size_t SampleCount=static_cast(Dim)*Dim*Dim; - std::vector> PerSample(SampleCount); std::vector Used(Primitives.size(),0); Out={};Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim; if(LayerRanges.empty()) @@ -505,6 +504,76 @@ public: Out.protectedSolid.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 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(Origin.X),static_cast(Origin.Y), + static_cast(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]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(Connector.Radius)+64.0; + for(uint32 ShapeId=0;ShapeIdHi){Overlaps=false;break;} + } + if(Overlaps)Used[ShapeId]=1; + } + const uint32 AxialSampleCount=std::max(1,static_cast(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(std::clamp(FirstValue,0.0, + static_cast(AxialSampleCount))); + const uint32 SampleCount=LastValue>=FirstValue?static_cast(std::max(0.0, + std::min(LastValue,static_cast(AxialSampleCount-1))-FirstValue+1.0)):0; + const uint32 RingCount=SampleCount+2; + const uint64 VertexCount=static_cast(RingCount-1)*24u; + if(Out.connectorVertexCount+VertexCount>std::numeric_limits::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(Start[Axis]);Panel.end[Axis]=static_cast(Start[Axis]+Delta[Axis]);} + Panel.radius=Connector.Radius;Panel.tMin=static_cast(TMin);Panel.tMax=static_cast(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(VertexCount); + Out.connectorPanels.push_back(Panel); + } + } + size_t SampleIndex=0; for(int32 Z=-1;Z<=Cells+1;++Z) for(int32 Y=-1;Y<=Cells+1;++Y) @@ -561,9 +630,17 @@ public: } } const auto Found=SpatialBins.find(PointBin(WX,WY,WZ)); - if(Found==SpatialBins.end())continue; - auto& Ids=PerSample[SampleIndex]; - Ids.assign(Found->second.begin(),Found->second.end()); + if(Found==SpatialBins.end()) + { + Out.sampleShapeRanges[SampleIndex]={static_cast(Out.sampleShapeIds.size()),0}; + continue; + } + const auto& Ids=Found->second; + if(Out.sampleShapeIds.size()+Ids.size()>std::numeric_limits::max()) + {Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;} + Out.sampleShapeRanges[SampleIndex]={static_cast(Out.sampleShapeIds.size()), + static_cast(Ids.size())}; + Out.sampleShapeIds.insert(Out.sampleShapeIds.end(),Ids.begin(),Ids.end()); for(const uint32 Id:Ids)if(Id(S.floorCurveEndBlend); Out.shapes.push_back(G); } - uint64 CandidateCount=0; - for(const auto& Ids:PerSample)CandidateCount+=Ids.size(); - if(CandidateCount>std::numeric_limits::max()) - {Error="far tile candidate list exceeds the GPU 32-bit offset limit";return false;} - Out.sampleShapeIds.reserve(static_cast(CandidateCount)); - for(size_t I=0;I(Out.sampleShapeIds.size()), - static_cast(Ids.size())}; - for(const uint32 Id:Ids) - { - if(Id>=Remap.size()||Remap[Id]==std::numeric_limits::max()) - {Error="far tile shape remap is inconsistent";return false;} - Out.sampleShapeIds.push_back(Remap[Id]); - } + if(Id>=Remap.size()||Remap[Id]==std::numeric_limits::max()) + {Error="far tile shape remap is inconsistent";return false;} + Id=Remap[Id]; } return true; } diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/BuildGpuFieldViewer.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/BuildGpuFieldViewer.ps1 index e2f66ee..5478e05 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/BuildGpuFieldViewer.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/BuildGpuFieldViewer.ps1 @@ -9,8 +9,11 @@ $BuildHostProcess = Get-Process -Id $PID $BuildHostProcess.PriorityClass = 'BelowNormal' $BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00 $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' +$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($BinDir) | Out-Null @@ -110,6 +113,22 @@ if ($Target -eq 'All') { $LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) }) $LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ }) [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' diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h index 7eaa417..3a79739 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFarViewer.h @@ -15,14 +15,39 @@ namespace VoxelForgeGpuFarViewer { struct CpuVertex { float position[3]{}, normal[3]{}; }; struct CpuMesh { std::vector vertices; std::vector 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. // Vertex records keep the 32-byte compute layout (position/normal/uv). The // first 24 bytes match the standalone viewer's input layout. bool BuildFarTile(ID3D11Device* device, ID3D11DeviceContext* context, VoxelForgeCore::World& world, const VoxelForgeCore::TileKey& key, + const VoxelForgeCore::FarFieldTileInput* preparedInput, const std::filesystem::path& shaderPath, 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, 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); } diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp index a601fce..e1a9cae 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/GpuFieldPort.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ using int32 = std::int32_t; #include #include #include +#include "GpuFarViewer.h" using Microsoft::WRL::ComPtr; using Clock = std::chrono::steady_clock; @@ -98,6 +100,21 @@ namespace }; static_assert(sizeof(MidpointRecord) == 64); + struct FarConnectorRingRecord + { + float corners[4][4]{}; + float normals[4][4]{}; + float t=0.0f,padding[3]{}; + }; + static_assert(sizeof(FarConnectorRingRecord)==144); + + struct FarConnectorConstants + { + std::uint32_t connectorCount=0,ringCount=0,vertexCount=0,panelVertexCapacity=0; + std::uint32_t shapeCount=0,padding[3]{}; + }; + static_assert(sizeof(FarConnectorConstants)==32); + struct GpuVertex { float position[3]; @@ -110,7 +127,8 @@ namespace { std::uint32_t cells, sampleDim, cellCount, groupCount; std::int32_t originX, originY, originZ, stepVoxels; - float voxelSizeCm, pad[3]{}; + float voxelSizeCm; + std::uint32_t outputBaseVertex=0, outputArgsByteOffset=0, pad=0; }; static_assert(sizeof(TileConstants) == 48); @@ -159,9 +177,12 @@ namespace std::vector cases; std::vector fieldDensity; ComPtr vertexBuffer, indexBuffer, indirectArgs; + ComPtr connectorVertexBuffer,connectorIndexBuffer; + ComPtr startTimestamp,endTimestamp,disjointTimestamp; std::array drawArgs{}; bool sequentialIndices = false; bool indexedIndirectArgsValid = false; + std::uint32_t outputBaseVertex = 0, outputArgsByteOffset = 0; bool hasAdapterMemoryInfo = false; std::uint64_t adapterMemoryBeforeBytes = 0; std::uint64_t adapterMemoryAfterBytes = 0; @@ -455,39 +476,133 @@ namespace struct DeviceRun { + struct FarOutputChunk + { + ComPtr vertices, indices, args; + ComPtr vertexUav, indexUav, argsUav; + ComPtr connectorVertices,connectorIndices; + ComPtr connectorVertexUav,connectorIndexUav; + }; + struct DynamicInput + { + ComPtr buffer; + ComPtr srv; + std::size_t capacity=0,stride=0; + }; + struct FarScratch + { + std::uint32_t sampleCount=0,cellCount=0,groupCount=0; + ComPtr signs,tri,density,midpointIds,midpointRecords; + ComPtr cases,localOffsets,groupCounts,groupOffsets; + ComPtr tileConstants,farConstants; + ComPtr connectorConstants,connectorRings; + ComPtr signSrv,triSrv,densitySrv,midpointIdSrv,midpointRecordSrv; + ComPtr caseSrv,localOffsetSrv,groupCountSrv,groupOffsetSrv; + ComPtr connectorRingSrv; + ComPtr densityUav,caseUav,localOffsetUav,groupCountUav,groupOffsetUav; + ComPtr connectorRingUav; + DynamicInput shapes,ranges,ids,protectedAir,protectedSolid; + DynamicInput connectors; + std::uint32_t connectorRingCapacity=0; + } farScratch; ComPtr device; ComPtr context; + ComPtr immediateContext,deferredContext; ComPtr fieldEval, classify, fieldClassify, scan, emit, noiseProbe; + ComPtr connectorRings,connectorPanels; ComPtr memoryAdapter; std::string name; UINT64 timestampFrequency = 0; + std::vector farOutputChunks; + std::uint32_t farOutputCapacitySlots=0,farOutputCells=0; + std::uint32_t farOutputSlotsPerChunk=128,farMaxVerticesPerTile=0; + static constexpr std::uint32_t FarPanelVertexCapacity=32768; void CompileShaders(const std::filesystem::path& shaderPath) { - ComPtr code = CompileShader(shaderPath, "EvaluateFarFieldCS"); + const auto Load=[&](const char* entry) + { + const std::filesystem::path binary=shaderPath.parent_path().parent_path() + /"gpu_field_port"/"precompiled"/(std::string(entry)+".cso"); + ComPtr code; + if(std::filesystem::exists(binary) + &&SUCCEEDED(D3DReadFileToBlob(binary.c_str(),&code)))return code; + return CompileShader(shaderPath,entry); + }; + ComPtr code = Load("EvaluateFarFieldCS"); Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &fieldEval), "CreateComputeShader(EvaluateFarFieldCS)"); - code = CompileShader(shaderPath, "ClassifyCS"); + code = Load("ClassifyCS"); Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &classify), "CreateComputeShader(ClassifyCS)"); - code = CompileShader(shaderPath, "ClassifyFarCS"); + code = Load("ClassifyFarCS"); Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &fieldClassify), "CreateComputeShader(ClassifyFarCS)"); - code = CompileShader(shaderPath, "ScanGroupsCS"); + code = Load("ScanGroupsCS"); Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &scan), "CreateComputeShader(ScanGroupsCS)"); - code = CompileShader(shaderPath, "EmitCS"); + code = Load("EmitCS"); Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &emit), "CreateComputeShader(EmitCS)"); - code = CompileShader(shaderPath, "NoiseProbeCS"); + code = Load("NoiseProbeCS"); Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &noiseProbe), "CreateComputeShader(NoiseProbeCS)"); + code = Load("BuildFarConnectorRingsCS"); + Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &connectorRings), "CreateComputeShader(BuildFarConnectorRingsCS)"); + code = Load("EmitFarConnectorPanelsCS"); + Check(device->CreateComputeShader(code->GetBufferPointer(), code->GetBufferSize(), nullptr, &connectorPanels), "CreateComputeShader(EmitFarConnectorPanelsCS)"); } void InitializeExternal(ID3D11Device* inDevice, ID3D11DeviceContext* inContext, const std::filesystem::path& shaderPath) { device = inDevice; - context = inContext; - if (!device || !context) throw std::runtime_error("GPU far viewer requires a live D3D11 device/context"); + immediateContext = inContext; + context = immediateContext; + if (!device || !immediateContext) throw std::runtime_error("GPU far viewer requires a live D3D11 device/context"); + Check(device->CreateDeferredContext(0,&deferredContext),"CreateDeferredContext(far tile batches)"); name = "viewer-device"; + ComPtr dxgiDevice; + ComPtr adapter; + if(SUCCEEDED(device.As(&dxgiDevice))&&SUCCEEDED(dxgiDevice->GetAdapter(&adapter))) + adapter.As(&memoryAdapter); CompileShaders(shaderPath); } + void BeginFarTileBatch() + { + if(!deferredContext)throw std::runtime_error("far tile deferred context is unavailable"); + context=deferredContext; + } + + void EndFarTileBatch() + { + context=immediateContext; + } + + void SubmitFarTileBatch(ID3D11Query** completionEvent, + VoxelForgeGpuFarViewer::GpuTimingQueries* timingQueries) + { + if(!completionEvent||!timingQueries||!deferredContext||!immediateContext) + throw std::runtime_error("far tile batch submission arguments are invalid"); + *completionEvent=nullptr;*timingQueries=VoxelForgeGpuFarViewer::GpuTimingQueries{}; + ComPtr commands; + Check(deferredContext->FinishCommandList(FALSE,&commands),"FinishCommandList(far tile batch)"); + context=immediateContext; + D3D11_QUERY_DESC timestampDesc{D3D11_QUERY_TIMESTAMP,0}; + D3D11_QUERY_DESC disjointDesc{D3D11_QUERY_TIMESTAMP_DISJOINT,0}; + D3D11_QUERY_DESC eventDesc{D3D11_QUERY_EVENT,0}; + ComPtr start,end,disjoint,event; + Check(device->CreateQuery(×tampDesc,&start),"CreateQuery(far batch start)"); + Check(device->CreateQuery(×tampDesc,&end),"CreateQuery(far batch end)"); + Check(device->CreateQuery(&disjointDesc,&disjoint),"CreateQuery(far batch disjoint)"); + Check(device->CreateQuery(&eventDesc,&event),"CreateQuery(far batch event)"); + immediateContext->Begin(disjoint.Get()); + immediateContext->End(start.Get()); + immediateContext->ExecuteCommandList(commands.Get(),TRUE); + immediateContext->End(end.Get()); + immediateContext->End(disjoint.Get()); + immediateContext->End(event.Get()); + *completionEvent=event.Detach(); + timingQueries->start=start.Detach(); + timingQueries->end=end.Detach(); + timingQueries->disjoint=disjoint.Detach(); + } + void Initialize(const std::filesystem::path& shaderPath, bool warp) { D3D_FEATURE_LEVEL requested[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0}; @@ -604,6 +719,149 @@ namespace return result; } + void EnsureFarOutputPool(std::uint32_t tileSlots,std::uint32_t cells) + { + if(tileSlots==0||cells==0)throw std::runtime_error("far output pool dimensions must be non-zero"); + if(tileSlots<=farOutputCapacitySlots&&cells==farOutputCells)return; + const std::uint64_t maxVertices64=static_cast(cells)*cells*cells*15ull; + if(maxVertices64>std::numeric_limits::max()) + throw std::runtime_error("far output tile vertex capacity exceeds 32-bit limits"); + farMaxVerticesPerTile=static_cast(maxVertices64); + farOutputCapacitySlots=std::max(tileSlots,farOutputCapacitySlots); + farOutputCells=cells; + const std::uint32_t chunkCount=(farOutputCapacitySlots+farOutputSlotsPerChunk-1)/farOutputSlotsPerChunk; + std::vector chunks; + chunks.reserve(chunkCount); + for(std::uint32_t chunkIndex=0;chunkIndex(slots)*farMaxVerticesPerTile*sizeof(GpuVertex); + const std::uint64_t indexBytes=static_cast(slots)*farMaxVerticesPerTile*sizeof(std::uint32_t); + const std::uint64_t argsBytes=static_cast(slots)*10u*sizeof(std::uint32_t); + if(vertexBytes>std::numeric_limits::max()||indexBytes>std::numeric_limits::max()) + throw std::runtime_error("far output chunk exceeds the D3D11 buffer size limit"); + FarOutputChunk chunk; + chunk.vertices=RawMeshBuffer(static_cast(vertexBytes), + D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_VERTEX_BUFFER); + chunk.indices=RawMeshBuffer(static_cast(indexBytes), + D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_INDEX_BUFFER); + chunk.args=RawMeshBuffer(static_cast(argsBytes),D3D11_BIND_UNORDERED_ACCESS, + D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS|D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS); + chunk.vertexUav=RawUav(chunk.vertices.Get(),static_cast(vertexBytes/sizeof(std::uint32_t))); + chunk.indexUav=RawUav(chunk.indices.Get(),static_cast(indexBytes/sizeof(std::uint32_t))); + chunk.argsUav=RawUav(chunk.args.Get(),static_cast(argsBytes/sizeof(std::uint32_t))); + const std::uint64_t connectorVertexBytes=static_cast(slots) + *FarPanelVertexCapacity*sizeof(GpuVertex); + const std::uint64_t connectorIndexBytes=static_cast(slots) + *FarPanelVertexCapacity*sizeof(std::uint32_t); + chunk.connectorVertices=RawMeshBuffer(static_cast(connectorVertexBytes), + D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_VERTEX_BUFFER); + chunk.connectorIndices=RawMeshBuffer(static_cast(connectorIndexBytes), + D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_INDEX_BUFFER); + chunk.connectorVertexUav=RawUav(chunk.connectorVertices.Get(), + static_cast(connectorVertexBytes/sizeof(std::uint32_t))); + chunk.connectorIndexUav=RawUav(chunk.connectorIndices.Get(), + static_cast(connectorIndexBytes/sizeof(std::uint32_t))); + chunks.push_back(std::move(chunk)); + } + farOutputChunks=std::move(chunks); + } + + template + ComPtr UpdateDynamicInput(DynamicInput& input, + const T* data,std::size_t count,const char* label) + { + const std::size_t used=std::max(1,count); + if(input.stride&&input.stride!=sizeof(T))throw std::runtime_error("dynamic input stride changed"); + if(used>input.capacity) + { + std::size_t capacity=1; + while(capacitystd::numeric_limits::max()) + throw std::runtime_error(std::string(label)+" exceeds the D3D11 buffer size limit"); + D3D11_BUFFER_DESC desc{};desc.ByteWidth=static_cast(capacity*sizeof(T)); + desc.Usage=D3D11_USAGE_DYNAMIC;desc.BindFlags=D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags=D3D11_CPU_ACCESS_WRITE;desc.MiscFlags=D3D11_RESOURCE_MISC_BUFFER_STRUCTURED; + desc.StructureByteStride=sizeof(T); + ComPtr buffer;Check(device->CreateBuffer(&desc,nullptr,&buffer),label); + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{};srvDesc.Format=DXGI_FORMAT_UNKNOWN; + srvDesc.ViewDimension=D3D11_SRV_DIMENSION_BUFFER; + srvDesc.Buffer.NumElements=static_cast(capacity); + ComPtr srv; + Check(device->CreateShaderResourceView(buffer.Get(),&srvDesc,&srv),"CreateShaderResourceView(dynamic far input)"); + input.buffer=std::move(buffer);input.srv=std::move(srv);input.capacity=capacity;input.stride=sizeof(T); + } + D3D11_MAPPED_SUBRESOURCE mapped{}; + Check(context->Map(input.buffer.Get(),0,D3D11_MAP_WRITE_DISCARD,0,&mapped),label); + if(count)std::memcpy(mapped.pData,data,count*sizeof(T)); + else std::memset(mapped.pData,0,sizeof(T)); + context->Unmap(input.buffer.Get(),0); + return input.srv; + } + + void EnsureFarScratch(const TileData& tile) + { + const std::uint32_t cells=tile.cells*tile.cells*tile.cells; + const std::uint32_t groups=(cells+255u)/256u; + const std::uint32_t samples=static_cast(tile.dim*tile.dim*tile.dim); + if(farScratch.sampleCount==samples&&farScratch.cellCount==cells)return; + FarScratch scratch{}; + scratch.sampleCount=samples;scratch.cellCount=cells;scratch.groupCount=groups; + const std::uint32_t dummySign=0; + scratch.signs=Structured(&dummySign,1,D3D11_BIND_SHADER_RESOURCE); + std::array triData{}; + for(std::size_t c=0;c<256;++c) + for(std::size_t s=0;s<16;++s)triData[c*16+s]=MCTriTable[c][s]; + scratch.tri=Structured(triData.data(),triData.size(),D3D11_BIND_SHADER_RESOURCE); + scratch.density=Structured(nullptr,samples,D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS); + std::vector invalidIds(cells,InvalidIndex); + scratch.midpointIds=Structured(invalidIds.data(),invalidIds.size(),D3D11_BIND_SHADER_RESOURCE); + const MidpointRecord zeroRecord{}; + scratch.midpointRecords=Structured(&zeroRecord,1,D3D11_BIND_SHADER_RESOURCE); + scratch.cases=Structured(nullptr,cells,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + scratch.localOffsets=Structured(nullptr,cells,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + scratch.groupCounts=Structured(nullptr,groups,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + scratch.groupOffsets=Structured(nullptr,groups,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + scratch.signSrv=Srv(scratch.signs.Get(),1); + scratch.triSrv=Srv(scratch.tri.Get(),256u*16u); + scratch.densitySrv=Srv(scratch.density.Get(),samples); + scratch.midpointIdSrv=Srv(scratch.midpointIds.Get(),cells); + scratch.midpointRecordSrv=Srv(scratch.midpointRecords.Get(),1); + scratch.caseSrv=Srv(scratch.cases.Get(),cells); + scratch.localOffsetSrv=Srv(scratch.localOffsets.Get(),cells); + scratch.groupCountSrv=Srv(scratch.groupCounts.Get(),groups); + scratch.groupOffsetSrv=Srv(scratch.groupOffsets.Get(),groups); + scratch.densityUav=Uav(scratch.density.Get(),samples); + scratch.caseUav=Uav(scratch.cases.Get(),cells); + scratch.localOffsetUav=Uav(scratch.localOffsets.Get(),cells); + scratch.groupCountUav=Uav(scratch.groupCounts.Get(),groups); + scratch.groupOffsetUav=Uav(scratch.groupOffsets.Get(),groups); + const auto DynamicConstant=[&](UINT bytes,const char* label) + { + D3D11_BUFFER_DESC desc{};desc.ByteWidth=bytes;desc.Usage=D3D11_USAGE_DYNAMIC; + desc.BindFlags=D3D11_BIND_CONSTANT_BUFFER;desc.CPUAccessFlags=D3D11_CPU_ACCESS_WRITE; + ComPtr buffer;Check(device->CreateBuffer(&desc,nullptr,&buffer),label);return buffer; + }; + scratch.tileConstants=DynamicConstant(sizeof(TileConstants),"CreateBuffer(tile constants pool)"); + scratch.farConstants=DynamicConstant(sizeof(FarFieldConstants),"CreateBuffer(far constants pool)"); + scratch.connectorConstants=DynamicConstant(sizeof(FarConnectorConstants), + "CreateBuffer(far connector constants pool)"); + farScratch=std::move(scratch); + } + + void EnsureConnectorRingScratch(std::uint32_t ringCount) + { + const std::uint32_t needed=std::max(1,ringCount); + if(needed<=farScratch.connectorRingCapacity)return; + std::uint32_t capacity=1;while(capacity(nullptr,capacity, + D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS); + farScratch.connectorRingSrv=Srv(farScratch.connectorRings.Get(),capacity); + farScratch.connectorRingUav=Uav(farScratch.connectorRings.Get(),capacity); + farScratch.connectorRingCapacity=capacity; + } + ComPtr RawUav(ID3D11Buffer* buffer, UINT dwordCount) { D3D11_UNORDERED_ACCESS_VIEW_DESC desc{}; @@ -642,7 +900,9 @@ namespace GpuResult Run(const TileData& tile, const FarFieldTileInput* farField = nullptr, bool readback = true, bool retainOutputBuffers = false, - bool waitForGpu = true, bool useIndirectOutputCount = false) + bool waitForGpu = true, bool useIndirectOutputCount = false, + std::uint32_t outputSlot = 0, bool usePooledFarOutput = false, + bool captureGpuTiming = true,bool batchTimingMode=false) { using namespace std::chrono; const auto wallStart = Clock::now(); @@ -662,6 +922,19 @@ namespace } (void)edge; const std::size_t maxVertices = static_cast(cellCount) * 15; + std::uint32_t outputBaseVertex=0,outputArgsByteOffset=0; + FarOutputChunk* pooledChunk=nullptr; + if(usePooledFarOutput) + { + if(!farField||farOutputCells!=tile.cells||outputSlot>=farOutputCapacitySlots + ||farOutputChunks.empty()) + throw std::runtime_error("far output pool was not reserved for this tile slot"); + const std::uint32_t chunkIndex=outputSlot/farOutputSlotsPerChunk; + const std::uint32_t localSlot=outputSlot%farOutputSlotsPerChunk; + pooledChunk=&farOutputChunks[chunkIndex]; + outputBaseVertex=localSlot*farMaxVerticesPerTile; + outputArgsByteOffset=localSlot*10u*sizeof(std::uint32_t); + } if (farField && (farField->sampleDim != static_cast(tile.dim) || farField->cells != static_cast(tile.cells) || farField->step != static_cast(tile.step))) @@ -690,114 +963,224 @@ namespace + std::max(1, tile.expectedVertexCount) * sizeof(GpuVertex) + std::max(1, tile.expectedVertexCount) * sizeof(std::uint32_t) + 5 * sizeof(std::uint32_t); - auto signs = Structured(tile.signs.data(), tile.signs.size(), D3D11_BIND_SHADER_RESOURCE); - auto triBuffer = Structured(tri.data(), triCount, D3D11_BIND_SHADER_RESOURCE); - auto density = Structured(farField ? static_cast(nullptr) : tile.density.data(), - tile.density.size(), D3D11_BIND_SHADER_RESOURCE - | (farField ? D3D11_BIND_UNORDERED_ACCESS : 0)); - auto midpointIds = Structured(tile.midpointIds.data(), tile.midpointIds.size(), D3D11_BIND_SHADER_RESOURCE); - const MidpointRecord zeroRecord{}; - auto midpointRecords = Structured(tile.midpointRecords.empty() ? &zeroRecord : tile.midpointRecords.data(), - std::max(1, tile.midpointRecords.size()), D3D11_BIND_SHADER_RESOURCE); - auto cases = Structured(nullptr, cellCount, - D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE); - auto localOffsets = Structured(nullptr, cellCount, - D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE); - auto groupCounts = Structured(nullptr, groupCount, - D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE); - auto groupOffsets = Structured(nullptr, groupCount, - D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE); - auto args = RawMeshBuffer(5 * sizeof(std::uint32_t), D3D11_BIND_UNORDERED_ACCESS, - D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS | D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS); - auto vertices = RawMeshBuffer(static_cast(maxVertices * sizeof(GpuVertex)), - D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER); - auto indices = RawMeshBuffer(static_cast(maxVertices * sizeof(std::uint32_t)), - D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_INDEX_BUFFER); - const FarFieldPrimitiveGpu zeroShape{}; - const FarFieldSampleRange zeroRange{}; - const std::uint32_t zeroId=0,zeroWord=0; - auto farShapes = farField ? Structured(farField->shapes.empty()?&zeroShape:farField->shapes.data(), - std::max(1,farField->shapes.size()),D3D11_BIND_SHADER_RESOURCE) - : ComPtr{}; - auto farRanges = farField ? Structured(farField->sampleShapeRanges.empty()?&zeroRange:farField->sampleShapeRanges.data(), - std::max(1,farField->sampleShapeRanges.size()),D3D11_BIND_SHADER_RESOURCE) - : ComPtr{}; - auto farIds = farField ? Structured(farField->sampleShapeIds.empty()?&zeroId:farField->sampleShapeIds.data(), - std::max(1,farField->sampleShapeIds.size()),D3D11_BIND_SHADER_RESOURCE) - : ComPtr{}; - auto farProtected = farField ? Structured(farField->protectedAir.empty()?&zeroWord:farField->protectedAir.data(), - std::max(1,farField->protectedAir.size()),D3D11_BIND_SHADER_RESOURCE) - : ComPtr{}; - auto farProtectedSolid = farField ? Structured(farField->protectedSolid.empty()?&zeroWord:farField->protectedSolid.data(), - std::max(1,farField->protectedSolid.size()),D3D11_BIND_SHADER_RESOURCE) - : ComPtr{}; - auto signSrv = Srv(signs.Get(), tile.signs.size()); - auto triSrv = Srv(triBuffer.Get(), triCount); - auto densitySrv = Srv(density.Get(), tile.density.size()); - auto farShapeSrv = farField ? Srv(farShapes.Get(),std::max(1,farField->shapes.size())) - : ComPtr{}; - auto farRangeSrv = farField ? Srv(farRanges.Get(),std::max(1,farField->sampleShapeRanges.size())) - : ComPtr{}; - auto farIdSrv = farField ? Srv(farIds.Get(),std::max(1,farField->sampleShapeIds.size())) - : ComPtr{}; - auto farProtectedSrv = farField ? Srv(farProtected.Get(),std::max(1,farField->protectedAir.size())) - : ComPtr{}; - auto farProtectedSolidSrv = farField ? Srv(farProtectedSolid.Get(),std::max(1,farField->protectedSolid.size())) - : ComPtr{}; - auto midpointIdSrv = Srv(midpointIds.Get(), tile.midpointIds.size()); - auto midpointRecordSrv = Srv(midpointRecords.Get(), std::max(1, tile.midpointRecords.size())); - auto caseSrv = Srv(cases.Get(), cellCount); - auto localOffsetSrv = Srv(localOffsets.Get(), cellCount); - auto groupCountSrv = Srv(groupCounts.Get(), groupCount); - auto groupOffsetSrv = Srv(groupOffsets.Get(), groupCount); - auto caseUav = Uav(cases.Get(), cellCount); - auto localOffsetUav = Uav(localOffsets.Get(), cellCount); - auto groupCountUav = Uav(groupCounts.Get(), groupCount); - auto groupOffsetUav = Uav(groupOffsets.Get(), groupCount); - auto densityUav = farField ? Uav(density.Get(),tile.density.size()) - : ComPtr{}; - auto argsUav = RawUav(args.Get(), 5); - auto vertexUav = RawUav(vertices.Get(), static_cast(maxVertices * sizeof(GpuVertex) / sizeof(std::uint32_t))); - auto indexUav = RawUav(indices.Get(), static_cast(maxVertices)); + ComPtr signs,triBuffer,density,midpointIds,midpointRecords; + ComPtr cases,localOffsets,groupCounts,groupOffsets; + ComPtr signSrv,triSrv,densitySrv,farShapeSrv,farRangeSrv,farIdSrv; + ComPtr farProtectedSrv,farProtectedSolidSrv; + ComPtr farConnectorSrv,farConnectorRingSrv; + ComPtr midpointIdSrv,midpointRecordSrv,caseSrv,localOffsetSrv; + ComPtr groupCountSrv,groupOffsetSrv; + ComPtr caseUav,localOffsetUav,groupCountUav,groupOffsetUav,densityUav; + ComPtr farConnectorRingUav; + ComPtr farConnectorConstantsBuffer; + ComPtr farConnectorRingBuffer; + if(pooledChunk) + { + EnsureFarScratch(tile); + FarScratch& scratch=farScratch; + signs=scratch.signs;triBuffer=scratch.tri;density=scratch.density; + midpointIds=scratch.midpointIds;midpointRecords=scratch.midpointRecords; + cases=scratch.cases;localOffsets=scratch.localOffsets; + groupCounts=scratch.groupCounts;groupOffsets=scratch.groupOffsets; + signSrv=scratch.signSrv;triSrv=scratch.triSrv;densitySrv=scratch.densitySrv; + midpointIdSrv=scratch.midpointIdSrv;midpointRecordSrv=scratch.midpointRecordSrv; + caseSrv=scratch.caseSrv;localOffsetSrv=scratch.localOffsetSrv; + groupCountSrv=scratch.groupCountSrv;groupOffsetSrv=scratch.groupOffsetSrv; + densityUav=scratch.densityUav;caseUav=scratch.caseUav; + localOffsetUav=scratch.localOffsetUav;groupCountUav=scratch.groupCountUav; + groupOffsetUav=scratch.groupOffsetUav; + farShapeSrv=UpdateDynamicInput(scratch.shapes, + farField->shapes.empty()?nullptr:farField->shapes.data(),farField->shapes.size(),"Map(far shapes)"); + farRangeSrv=UpdateDynamicInput(scratch.ranges, + farField->sampleShapeRanges.data(),farField->sampleShapeRanges.size(),"Map(far ranges)"); + farIdSrv=UpdateDynamicInput(scratch.ids, + farField->sampleShapeIds.empty()?nullptr:farField->sampleShapeIds.data(),farField->sampleShapeIds.size(),"Map(far shape ids)"); + farProtectedSrv=UpdateDynamicInput(scratch.protectedAir, + farField->protectedAir.data(),farField->protectedAir.size(),"Map(far protected air)"); + farProtectedSolidSrv=UpdateDynamicInput(scratch.protectedSolid, + farField->protectedSolid.data(),farField->protectedSolid.size(),"Map(far protected solid)"); + farConnectorSrv=UpdateDynamicInput(scratch.connectors, + farField->connectorPanels.empty()?nullptr:farField->connectorPanels.data(), + farField->connectorPanels.size(),"Map(far connector panels)"); + EnsureConnectorRingScratch(farField->connectorRingCount); + farConnectorRingBuffer=scratch.connectorRings; + farConnectorRingSrv=scratch.connectorRingSrv; + farConnectorRingUav=scratch.connectorRingUav; + } + else + { + signs=Structured(tile.signs.data(),tile.signs.size(),D3D11_BIND_SHADER_RESOURCE); + triBuffer=Structured(tri.data(),triCount,D3D11_BIND_SHADER_RESOURCE); + density=Structured(farField?static_cast(nullptr):tile.density.data(), + tile.density.size(),D3D11_BIND_SHADER_RESOURCE|(farField?D3D11_BIND_UNORDERED_ACCESS:0)); + midpointIds=Structured(tile.midpointIds.data(),tile.midpointIds.size(),D3D11_BIND_SHADER_RESOURCE); + const MidpointRecord zeroRecord{}; + midpointRecords=Structured(tile.midpointRecords.empty()?&zeroRecord:tile.midpointRecords.data(), + std::max(1,tile.midpointRecords.size()),D3D11_BIND_SHADER_RESOURCE); + cases=Structured(nullptr,cellCount,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + localOffsets=Structured(nullptr,cellCount,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + groupCounts=Structured(nullptr,groupCount,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + groupOffsets=Structured(nullptr,groupCount,D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_SHADER_RESOURCE); + signSrv=Srv(signs.Get(),tile.signs.size()); + triSrv=Srv(triBuffer.Get(),triCount); + densitySrv=Srv(density.Get(),tile.density.size()); + midpointIdSrv=Srv(midpointIds.Get(),tile.midpointIds.size()); + midpointRecordSrv=Srv(midpointRecords.Get(),std::max(1,tile.midpointRecords.size())); + caseSrv=Srv(cases.Get(),cellCount); + localOffsetSrv=Srv(localOffsets.Get(),cellCount); + groupCountSrv=Srv(groupCounts.Get(),groupCount); + groupOffsetSrv=Srv(groupOffsets.Get(),groupCount); + caseUav=Uav(cases.Get(),cellCount);localOffsetUav=Uav(localOffsets.Get(),cellCount); + groupCountUav=Uav(groupCounts.Get(),groupCount);groupOffsetUav=Uav(groupOffsets.Get(),groupCount); + densityUav=farField?Uav(density.Get(),tile.density.size()):ComPtr{}; + if(farField) + { + const FarFieldPrimitiveGpu zeroShape{};const FarFieldSampleRange zeroRange{}; + const std::uint32_t zeroId=0,zeroWord=0; + auto farShapes=Structured(farField->shapes.empty()?&zeroShape:farField->shapes.data(), + std::max(1,farField->shapes.size()),D3D11_BIND_SHADER_RESOURCE); + auto farRanges=Structured(farField->sampleShapeRanges.empty()?&zeroRange:farField->sampleShapeRanges.data(), + std::max(1,farField->sampleShapeRanges.size()),D3D11_BIND_SHADER_RESOURCE); + auto farIds=Structured(farField->sampleShapeIds.empty()?&zeroId:farField->sampleShapeIds.data(), + std::max(1,farField->sampleShapeIds.size()),D3D11_BIND_SHADER_RESOURCE); + auto farProtected=Structured(farField->protectedAir.empty()?&zeroWord:farField->protectedAir.data(), + std::max(1,farField->protectedAir.size()),D3D11_BIND_SHADER_RESOURCE); + auto farProtectedSolid=Structured(farField->protectedSolid.empty()?&zeroWord:farField->protectedSolid.data(), + std::max(1,farField->protectedSolid.size()),D3D11_BIND_SHADER_RESOURCE); + farShapeSrv=Srv(farShapes.Get(),std::max(1,farField->shapes.size())); + farRangeSrv=Srv(farRanges.Get(),std::max(1,farField->sampleShapeRanges.size())); + farIdSrv=Srv(farIds.Get(),std::max(1,farField->sampleShapeIds.size())); + farProtectedSrv=Srv(farProtected.Get(),std::max(1,farField->protectedAir.size())); + farProtectedSolidSrv=Srv(farProtectedSolid.Get(),std::max(1,farField->protectedSolid.size())); + const FarFieldConnectorPanel zeroPanel{}; + auto farConnectors=Structured(farField->connectorPanels.empty()?&zeroPanel: + farField->connectorPanels.data(),std::max(1, + farField->connectorPanels.size()),D3D11_BIND_SHADER_RESOURCE); + farConnectorSrv=Srv(farConnectors.Get(), + std::max(1,farField->connectorPanels.size())); + const std::uint32_t ringCapacity=std::max(1,farField->connectorRingCount); + farConnectorRingBuffer=Structured(nullptr,ringCapacity, + D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS); + farConnectorRingSrv=Srv(farConnectorRingBuffer.Get(),ringCapacity); + farConnectorRingUav=Uav(farConnectorRingBuffer.Get(),ringCapacity); + } + } + ComPtr args,vertices,indices; + ComPtr argsUav,vertexUav,indexUav; + ComPtr connectorVertices,connectorIndices; + ComPtr connectorVertexUav,connectorIndexUav; + const std::uint32_t connectorPanelCapacity=pooledChunk?FarPanelVertexCapacity: + std::max(1,farField?farField->connectorVertexCount:0); + if(farField&&farField->connectorVertexCount>connectorPanelCapacity) + throw std::runtime_error("far connector repair output exceeds the pooled panel capacity"); + if(pooledChunk) + { + args=pooledChunk->args;vertices=pooledChunk->vertices;indices=pooledChunk->indices; + argsUav=pooledChunk->argsUav;vertexUav=pooledChunk->vertexUav;indexUav=pooledChunk->indexUav; + connectorVertices=pooledChunk->connectorVertices;connectorIndices=pooledChunk->connectorIndices; + connectorVertexUav=pooledChunk->connectorVertexUav;connectorIndexUav=pooledChunk->connectorIndexUav; + } + else + { + args = RawMeshBuffer(10 * sizeof(std::uint32_t), D3D11_BIND_UNORDERED_ACCESS, + D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS | D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS); + vertices = RawMeshBuffer(static_cast(maxVertices * sizeof(GpuVertex)), + D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER); + indices = RawMeshBuffer(static_cast(maxVertices * sizeof(std::uint32_t)), + D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_INDEX_BUFFER); + argsUav=RawUav(args.Get(),10); + vertexUav=RawUav(vertices.Get(),static_cast(maxVertices*sizeof(GpuVertex)/sizeof(std::uint32_t))); + indexUav=RawUav(indices.Get(),static_cast(maxVertices)); + connectorVertices=RawMeshBuffer(connectorPanelCapacity*sizeof(GpuVertex), + D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_VERTEX_BUFFER); + connectorIndices=RawMeshBuffer(connectorPanelCapacity*sizeof(std::uint32_t), + D3D11_BIND_UNORDERED_ACCESS|D3D11_BIND_INDEX_BUFFER); + connectorVertexUav=RawUav(connectorVertices.Get(),connectorPanelCapacity*sizeof(GpuVertex)/sizeof(std::uint32_t)); + connectorIndexUav=RawUav(connectorIndices.Get(),connectorPanelCapacity); + } D3D11_BUFFER_DESC cbDesc{}; cbDesc.ByteWidth = sizeof(TileConstants); cbDesc.Usage = D3D11_USAGE_DEFAULT; cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; TileConstants constants{tile.cells, tile.dim, cellCount, groupCount, - tile.origin[0], tile.origin[1], tile.origin[2], static_cast(tile.step), 25.0f,{}}; - D3D11_SUBRESOURCE_DATA cbData{}; cbData.pSysMem = &constants; + tile.origin[0], tile.origin[1], tile.origin[2], static_cast(tile.step), + 25.0f,outputBaseVertex,outputArgsByteOffset,0}; + result.outputBaseVertex=outputBaseVertex; + result.outputArgsByteOffset=outputArgsByteOffset; ComPtr cb; - Check(device->CreateBuffer(&cbDesc, &cbData, &cb), "CreateBuffer(constants)"); ComPtr farCb; if (farField) { const FarFieldConstants farConstants{farField->strataLo,farField->strataHi, farField->strataBlend,static_cast(tile.density.size())}; - D3D11_BUFFER_DESC farDesc{};farDesc.ByteWidth=sizeof(FarFieldConstants); - farDesc.Usage=D3D11_USAGE_DEFAULT;farDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER; - D3D11_SUBRESOURCE_DATA farData{};farData.pSysMem=&farConstants; - Check(device->CreateBuffer(&farDesc,&farData,&farCb),"CreateBuffer(far constants)"); + const FarConnectorConstants connectorConstants{ + static_cast(farField->connectorPanels.size()), + farField->connectorRingCount,farField->connectorVertexCount,connectorPanelCapacity, + static_cast(farField->shapes.size()),{0,0,0}}; + if(pooledChunk) + { + cb=farScratch.tileConstants;farCb=farScratch.farConstants; + D3D11_MAPPED_SUBRESOURCE tileMapped{}; + Check(context->Map(cb.Get(),0,D3D11_MAP_WRITE_DISCARD,0,&tileMapped),"Map(tile constants)"); + std::memcpy(tileMapped.pData,&constants,sizeof(constants));context->Unmap(cb.Get(),0); + D3D11_MAPPED_SUBRESOURCE farMapped{}; + Check(context->Map(farCb.Get(),0,D3D11_MAP_WRITE_DISCARD,0,&farMapped),"Map(far constants)"); + std::memcpy(farMapped.pData,&farConstants,sizeof(farConstants));context->Unmap(farCb.Get(),0); + farConnectorConstantsBuffer=farScratch.connectorConstants; + D3D11_MAPPED_SUBRESOURCE connectorMapped{}; + Check(context->Map(farConnectorConstantsBuffer.Get(),0,D3D11_MAP_WRITE_DISCARD,0, + &connectorMapped),"Map(far connector constants)"); + std::memcpy(connectorMapped.pData,&connectorConstants,sizeof(connectorConstants)); + context->Unmap(farConnectorConstantsBuffer.Get(),0); + } + else + { + D3D11_SUBRESOURCE_DATA cbData{};cbData.pSysMem=&constants; + Check(device->CreateBuffer(&cbDesc,&cbData,&cb),"CreateBuffer(constants)"); + D3D11_BUFFER_DESC farDesc{};farDesc.ByteWidth=sizeof(FarFieldConstants); + farDesc.Usage=D3D11_USAGE_DEFAULT;farDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER; + D3D11_SUBRESOURCE_DATA farData{};farData.pSysMem=&farConstants; + Check(device->CreateBuffer(&farDesc,&farData,&farCb),"CreateBuffer(far constants)"); + D3D11_BUFFER_DESC connectorDesc{};connectorDesc.ByteWidth=sizeof(FarConnectorConstants); + connectorDesc.Usage=D3D11_USAGE_DEFAULT;connectorDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER; + D3D11_SUBRESOURCE_DATA connectorData{};connectorData.pSysMem=&connectorConstants; + Check(device->CreateBuffer(&connectorDesc,&connectorData,&farConnectorConstantsBuffer), + "CreateBuffer(far connector constants)"); + } + } + else + { + D3D11_SUBRESOURCE_DATA cbData{};cbData.pSysMem=&constants; + Check(device->CreateBuffer(&cbDesc,&cbData,&cb),"CreateBuffer(constants)"); } D3D11_QUERY_DESC timestampDesc{D3D11_QUERY_TIMESTAMP, 0}; D3D11_QUERY_DESC disjointDesc{D3D11_QUERY_TIMESTAMP_DISJOINT, 0}; ComPtr startQuery, endQuery, disjointQuery; ComPtr fieldStartQuery,fieldEndQuery; - Check(device->CreateQuery(×tampDesc, &startQuery), "CreateQuery(start)"); - Check(device->CreateQuery(×tampDesc, &endQuery), "CreateQuery(end)"); - Check(device->CreateQuery(&disjointDesc, &disjointQuery), "CreateQuery(disjoint)"); - if(farField) + if(captureGpuTiming) { - Check(device->CreateQuery(×tampDesc,&fieldStartQuery),"CreateQuery(field start)"); - Check(device->CreateQuery(×tampDesc,&fieldEndQuery),"CreateQuery(field end)"); + Check(device->CreateQuery(×tampDesc, &startQuery), "CreateQuery(start)"); + Check(device->CreateQuery(×tampDesc, &endQuery), "CreateQuery(end)"); + if(!batchTimingMode) + Check(device->CreateQuery(&disjointDesc, &disjointQuery), "CreateQuery(disjoint)"); + if(farField&&!batchTimingMode) + { + Check(device->CreateQuery(×tampDesc,&fieldStartQuery),"CreateQuery(field start)"); + Check(device->CreateQuery(×tampDesc,&fieldEndQuery),"CreateQuery(field end)"); + } } context->CSSetConstantBuffers(0, 1, cb.GetAddressOf()); - context->Begin(disjointQuery.Get()); - context->End(startQuery.Get()); + if(captureGpuTiming&&!batchTimingMode) + { + context->Begin(disjointQuery.Get()); + context->End(startQuery.Get()); + } + else if(captureGpuTiming)context->End(startQuery.Get()); if(farField) { - context->End(fieldStartQuery.Get()); + if(captureGpuTiming&&!batchTimingMode)context->End(fieldStartQuery.Get()); context->CSSetConstantBuffers(1,1,farCb.GetAddressOf()); context->CSSetShader(fieldEval.Get(),nullptr,0); ID3D11ShaderResourceView* fieldSrvs[15]{}; @@ -812,7 +1195,7 @@ namespace ID3D11UnorderedAccessView* clearUavs[8]{}; context->CSSetShaderResources(0,16,clearSrvs); context->CSSetUnorderedAccessViews(0,8,clearUavs,nullptr); - context->End(fieldEndQuery.Get()); + if(captureGpuTiming&&!batchTimingMode)context->End(fieldEndQuery.Get()); } context->CSSetShader(farField ? fieldClassify.Get() : classify.Get(), nullptr, 0); ID3D11ShaderResourceView* classifySrvs[16]{}; @@ -847,8 +1230,38 @@ namespace context->Dispatch(groupCount, 1, 1); context->CSSetShaderResources(0, 16, nullSrvs); context->CSSetUnorderedAccessViews(0, 7, nullUavs, nullptr); - context->End(endQuery.Get()); - context->End(disjointQuery.Get()); + + if(farField) + { + ID3D11Buffer* connectorCbs[3]={cb.Get(),farCb.Get(),farConnectorConstantsBuffer.Get()}; + context->CSSetConstantBuffers(0,3,connectorCbs); + context->CSSetShader(connectorRings.Get(),nullptr,0); + ID3D11ShaderResourceView* connectorRingSrvs[3]={farConnectorSrv.Get(),nullptr,farShapeSrv.Get()}; + context->CSSetShaderResources(8,3,connectorRingSrvs); + ID3D11UnorderedAccessView* connectorRingUavs[8]{}; + connectorRingUavs[4]=argsUav.Get();connectorRingUavs[7]=farConnectorRingUav.Get(); + context->CSSetUnorderedAccessViews(0,8,connectorRingUavs,nullptr); + context->Dispatch(std::max(1,(farField->connectorRingCount+63u)/64u),1,1); + context->CSSetShaderResources(8,3,nullSrvs); + context->CSSetUnorderedAccessViews(0,8,nullUavs,nullptr); + if(farField->connectorVertexCount) + { + context->CSSetShader(connectorPanels.Get(),nullptr,0); + ID3D11ShaderResourceView* panelSrvs[2]={farConnectorSrv.Get(),farConnectorRingSrv.Get()}; + context->CSSetShaderResources(8,2,panelSrvs); + ID3D11UnorderedAccessView* panelUavs[7]{}; + panelUavs[5]=connectorVertexUav.Get();panelUavs[6]=connectorIndexUav.Get(); + context->CSSetUnorderedAccessViews(0,7,panelUavs,nullptr); + context->Dispatch((farField->connectorVertexCount+63u)/64u,1,1); + context->CSSetShaderResources(8,2,nullSrvs); + context->CSSetUnorderedAccessViews(0,7,nullUavs,nullptr); + } + } + if(captureGpuTiming) + { + context->End(endQuery.Get()); + if(!batchTimingMode)context->End(disjointQuery.Get()); + } if (readback) { @@ -861,7 +1274,11 @@ namespace * sizeof(GpuVertex))); auto indexStaging = Staging(static_cast(readbackCapacity * sizeof(std::uint32_t))); - auto argsStaging = Staging(5 * sizeof(std::uint32_t)); + auto argsStaging = Staging(10 * sizeof(std::uint32_t)); + auto connectorVertexStaging=farField&&farField->connectorVertexCount + ?Staging(farField->connectorVertexCount*sizeof(GpuVertex)):ComPtr{}; + auto connectorIndexStaging=farField&&farField->connectorVertexCount + ?Staging(farField->connectorVertexCount*sizeof(std::uint32_t)):ComPtr{}; if (QueryLocalMemory(localUsage, localBudget)) { result.hasAdapterMemoryInfo = true; @@ -874,21 +1291,45 @@ namespace if (copyBytes != 0) { const D3D11_BOX box{0, 0, 0, copyBytes, 1, 1}; + const UINT64 vertexStart=static_cast(outputBaseVertex)*sizeof(GpuVertex); + D3D11_BOX vertexSource{static_cast(vertexStart),0,0, + static_cast(vertexStart+copyBytes),1,1}; context->CopySubresourceRegion(vertexStaging.Get(), 0, 0, 0, 0, - vertices.Get(), 0, &box); + vertices.Get(), 0, &vertexSource); const D3D11_BOX indexBox{0, 0, 0, (useIndirectOutputCount?readbackCapacity:tile.expectedVertexCount)*sizeof(std::uint32_t),1,1}; + D3D11_BOX indexSource=indexBox; + indexSource.left=outputBaseVertex*sizeof(std::uint32_t); + indexSource.right=indexSource.left+indexBox.right; context->CopySubresourceRegion(indexStaging.Get(), 0, 0, 0, 0, - indices.Get(), 0, &indexBox); + indices.Get(), 0, &indexSource); + } + const D3D11_BOX argsSource{outputArgsByteOffset,0,0, + outputArgsByteOffset+10u*sizeof(std::uint32_t),1,1}; + context->CopySubresourceRegion(argsStaging.Get(),0,0,0,0,args.Get(),0,&argsSource); + std::uint32_t connectorBase=0; + if(farField&&farField->connectorVertexCount) + { + connectorBase=pooledChunk?outputBaseVertex/farMaxVerticesPerTile*FarPanelVertexCapacity:0; + const UINT vertexBytes=farField->connectorVertexCount*sizeof(GpuVertex); + const UINT indexBytes=farField->connectorVertexCount*sizeof(std::uint32_t); + const D3D11_BOX connectorVertexSource{connectorBase*sizeof(GpuVertex),0,0, + connectorBase*sizeof(GpuVertex)+vertexBytes,1,1}; + const D3D11_BOX connectorIndexSource{connectorBase*sizeof(std::uint32_t),0,0, + connectorBase*sizeof(std::uint32_t)+indexBytes,1,1}; + context->CopySubresourceRegion(connectorVertexStaging.Get(),0,0,0,0, + connectorVertices.Get(),0,&connectorVertexSource); + context->CopySubresourceRegion(connectorIndexStaging.Get(),0,0,0,0, + connectorIndices.Get(),0,&connectorIndexSource); } - context->CopyResource(argsStaging.Get(), args.Get()); if(farField)context->CopyResource(fieldStaging.Get(),density.Get()); context->Flush(); + if(!captureGpuTiming||batchTimingMode)throw std::runtime_error("readback requires standalone GPU timestamp queries"); WaitForQuery(disjointQuery.Get()); const D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint = ReadQuery(disjointQuery.Get(), "GetData(disjoint)"); const UINT64 start = ReadQuery(startQuery.Get(), "GetData(start timestamp)"); const UINT64 end = ReadQuery(endQuery.Get(), "GetData(end timestamp)"); - result.dispatchMs = disjoint.Disjoint ? 0.0 : + result.dispatchMs = disjoint.Disjoint ? 0.0 : 1000.0 * static_cast(end - start) / static_cast(disjoint.Frequency); D3D11_MAPPED_SUBRESOURCE mapped{}; if(farField) @@ -907,13 +1348,18 @@ namespace static_cast(mapped.pData) + cellCount); context->Unmap(caseStaging.Get(), 0); Check(context->Map(argsStaging.Get(), 0, D3D11_MAP_READ, 0, &mapped), "Map(args)"); - std::copy_n(static_cast(mapped.pData), 5, result.drawArgs.begin()); + std::array allArgs{}; + std::copy_n(static_cast(mapped.pData),10,allArgs.begin()); + std::copy_n(allArgs.begin(),5,result.drawArgs.begin()); context->Unmap(argsStaging.Get(), 0); result.indexedIndirectArgsValid = (useIndirectOutputCount ?result.drawArgs[0]<=maxVertices :result.drawArgs[0] == tile.expectedVertexCount) - && result.drawArgs[1] == 1 && result.drawArgs[2] == 0 + && result.drawArgs[1] == 1 && result.drawArgs[2] == outputBaseVertex && result.drawArgs[3] == 0 && result.drawArgs[4] == 0; + result.indexedIndirectArgsValid &= allArgs[5]==(farField?farField->connectorVertexCount:0) + &&allArgs[6]==(farField&&farField->connectorVertexCount?1u:0u) + &&allArgs[7]==connectorBase&&allArgs[8]==connectorBase&&allArgs[9]==0; const std::uint32_t outputCount = std::min(result.drawArgs[0], static_cast(maxVertices)); // The staging buffer contains the GPU output. Only retain the prefix named by @@ -926,12 +1372,27 @@ namespace const auto* indexData = static_cast(mapped.pData); result.sequentialIndices = useIndirectOutputCount||outputCount == tile.expectedVertexCount; for (std::uint32_t i = 0; i < outputCount; ++i) - result.sequentialIndices &= indexData[i] == i; + result.sequentialIndices &= indexData[i] == outputBaseVertex+i; context->Unmap(indexStaging.Get(), 0); + if(farField&&farField->connectorVertexCount) + { + Check(context->Map(connectorVertexStaging.Get(),0,D3D11_MAP_READ,0,&mapped), + "Map(far connector panel vertices)"); + const auto* connectorData=static_cast(mapped.pData); + result.vertices.insert(result.vertices.end(),connectorData, + connectorData+farField->connectorVertexCount); + context->Unmap(connectorVertexStaging.Get(),0); + Check(context->Map(connectorIndexStaging.Get(),0,D3D11_MAP_READ,0,&mapped), + "Map(far connector panel indices)"); + const auto* connectorIndexData=static_cast(mapped.pData); + for(std::uint32_t i=0;iconnectorVertexCount;++i) + result.sequentialIndices &= connectorIndexData[i]==connectorBase+i; + context->Unmap(connectorIndexStaging.Get(),0); + } } else { - if(waitForGpu) + if(waitForGpu&&captureGpuTiming&&!batchTimingMode) { context->Flush(); WaitForQuery(disjointQuery.Get()); @@ -948,12 +1409,20 @@ namespace /static_cast(disjoint.Frequency); } } + else if(captureGpuTiming) + { + result.startTimestamp=std::move(startQuery); + result.endTimestamp=std::move(endQuery); + if(!batchTimingMode)result.disjointTimestamp=std::move(disjointQuery); + } } if (retainOutputBuffers) { result.vertexBuffer = vertices; result.indexBuffer = indices; result.indirectArgs = args; + result.connectorVertexBuffer=connectorVertices; + result.connectorIndexBuffer=connectorIndices; } result.wallMs = duration(Clock::now() - wallStart).count(); return result; @@ -1651,15 +2120,81 @@ namespace } } -#include "GpuFarViewer.h" - namespace VoxelForgeGpuFarViewer { + namespace + { + std::unique_ptr FarViewerRunner; + ID3D11Device* FarViewerRunnerDevice=nullptr; + + DeviceRun& GetFarViewerRunner(ID3D11Device* device,ID3D11DeviceContext* context, + const std::filesystem::path& shaderPath) + { + if(!FarViewerRunner||FarViewerRunnerDevice!=device) + { + FarViewerRunner=std::make_unique(); + FarViewerRunner->InitializeExternal(device,context,shaderPath); + FarViewerRunnerDevice=device; + } + return *FarViewerRunner; + } + } + + bool ReserveFarOutputPool(ID3D11Device* device,ID3D11DeviceContext* context, + const std::filesystem::path& shaderPath,std::uint32_t tileSlots,std::uint32_t cells, + std::string& error) + { + try + { + GetFarViewerRunner(device,context,shaderPath).EnsureFarOutputPool(tileSlots,cells); + return true; + } + catch(const std::exception& e){error=e.what();return false;} + } + + bool QueryFarGpuMemory(ID3D11Device* device,ID3D11DeviceContext* context, + const std::filesystem::path& shaderPath,std::uint64_t& usageBytes, + std::uint64_t& budgetBytes,std::string& error) + { + try + { + return GetFarViewerRunner(device,context,shaderPath).QueryLocalMemory(usageBytes,budgetBytes); + } + catch(const std::exception& e){error=e.what();return false;} + } + + bool BeginFarTileBatch(ID3D11Device* device,ID3D11DeviceContext* context, + const std::filesystem::path& shaderPath,std::string& error) + { + try + { + GetFarViewerRunner(device,context,shaderPath).BeginFarTileBatch(); + return true; + } + catch(const std::exception& e){error=e.what();return false;} + } + + bool SubmitFarTileBatch(ID3D11Device* device,ID3D11DeviceContext* context, + const std::filesystem::path& shaderPath,ID3D11Query** completionEvent, + GpuTimingQueries* timingQueries,std::string& error) + { + try + { + GetFarViewerRunner(device,context,shaderPath).SubmitFarTileBatch(completionEvent,timingQueries); + return true; + } + catch(const std::exception& e){error=e.what();return false;} + } + bool BuildFarTile(ID3D11Device* device, ID3D11DeviceContext* context, VoxelForgeCore::World& world, const VoxelForgeCore::TileKey& key, + const FarFieldTileInput* preparedInput, const std::filesystem::path& shaderPath, 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, double* enqueueWallMs, double* gpuDispatchMs, std::string& error) { @@ -1667,21 +2202,19 @@ namespace VoxelForgeGpuFarViewer if (vertices) *vertices = nullptr; if (indices) *indices = nullptr; if (indirectArgs) *indirectArgs = nullptr; + if (connectorVertices) *connectorVertices=nullptr; + if (connectorIndices) *connectorIndices=nullptr; + if (indirectArgsByteOffset) *indirectArgsByteOffset = 0; if (completionEvent) *completionEvent = nullptr; + if (timingQueries) *timingQueries={}; if (enqueueWallMs) *enqueueWallMs = 0.0; if (gpuDispatchMs) *gpuDispatchMs = 0.0; try { - if (!vertices || !indices || !indirectArgs || !completionEvent) + if (!vertices || !indices || !indirectArgs || !connectorVertices || !connectorIndices + ||(auditReadback&&!completionEvent)) throw std::runtime_error("far tile output pointers are required"); - static std::unique_ptr runner; - static ID3D11Device* runnerDevice = nullptr; - if (!runner || runnerDevice != device) - { - runner = std::make_unique(); - runner->InitializeExternal(device, context, shaderPath); - runnerDevice = device; - } + DeviceRun& runner=GetFarViewerRunner(device,context,shaderPath); const WorldConfiguration& config = world.configuration(); std::uint32_t cells = 0; @@ -1699,10 +2232,19 @@ namespace VoxelForgeGpuFarViewer tile.signs.resize((sampleCount + 31u) / 32u); tile.midpointIds.assign(cellCount, InvalidIndex); - auto farInput = world.exportFarFieldTile(key, error); - if (!farInput) return false; - GpuResult result = runner->Run(tile, &*farInput,auditReadback!=nullptr,true, - true,auditReadback!=nullptr); + std::optional localFarInput; + if(!preparedInput) + { + localFarInput=world.exportFarFieldTile(key,error); + if(!localFarInput)return false; + preparedInput=&*localFarInput; + } + const std::uint32_t outputCapacity=outputCapacitySlots?outputCapacitySlots:outputSlot+1; + runner.EnsureFarOutputPool(outputCapacity,tile.cells); + const bool captureGpuTiming=true; + const bool batchTimingMode=completionEvent==nullptr; + GpuResult result = runner.Run(tile, preparedInput,auditReadback!=nullptr,true, + auditReadback!=nullptr,auditReadback!=nullptr,outputSlot,true,captureGpuTiming,batchTimingMode); if(auditReadback) { auditReadback->vertices.resize(result.vertices.size()); @@ -1717,14 +2259,26 @@ namespace VoxelForgeGpuFarViewer auditReadback->indices.resize(result.vertices.size()); std::iota(auditReadback->indices.begin(),auditReadback->indices.end(),0u); } - D3D11_QUERY_DESC eventDesc{D3D11_QUERY_EVENT,0}; - ComPtr completion; - Check(device->CreateQuery(&eventDesc,&completion),"CreateQuery(far tile completion)"); - context->End(completion.Get()); + if(completionEvent) + { + D3D11_QUERY_DESC eventDesc{D3D11_QUERY_EVENT,0}; + ComPtr completion; + Check(device->CreateQuery(&eventDesc,&completion),"CreateQuery(far completion)"); + context->End(completion.Get()); + *completionEvent=completion.Detach(); + } *vertices = result.vertexBuffer.Detach(); *indices = result.indexBuffer.Detach(); *indirectArgs = result.indirectArgs.Detach(); - *completionEvent = completion.Detach(); + *connectorVertices=result.connectorVertexBuffer.Detach(); + *connectorIndices=result.connectorIndexBuffer.Detach(); + if(indirectArgsByteOffset)*indirectArgsByteOffset=result.outputArgsByteOffset; + if(timingQueries&&result.startTimestamp) + { + timingQueries->start=result.startTimestamp.Detach(); + timingQueries->end=result.endTimestamp.Detach(); + timingQueries->disjoint=result.disjointTimestamp.Detach(); + } if (enqueueWallMs) *enqueueWallMs = std::chrono::duration(Clock::now()-wallStart).count(); if (gpuDispatchMs) *gpuDispatchMs = result.dispatchMs; return true; @@ -1735,6 +2289,51 @@ namespace VoxelForgeGpuFarViewer return false; } } + + bool ResolveGpuTiming(ID3D11DeviceContext* context,GpuTimingQueries& queries,double& dispatchMs, + std::uint64_t* timestampFrequency) + { + dispatchMs=0.0; + if(timestampFrequency)*timestampFrequency=0; + if(!context||!queries.start||!queries.end||!queries.disjoint)return true; + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint{};UINT64 start=0,end=0; + HRESULT hr=context->GetData(queries.disjoint,&disjoint,sizeof(disjoint),D3D11_ASYNC_GETDATA_DONOTFLUSH); + if(hr==S_FALSE)return false; + Check(hr,"GetData(async timestamp disjoint)"); + hr=context->GetData(queries.start,&start,sizeof(start),D3D11_ASYNC_GETDATA_DONOTFLUSH); + if(hr==S_FALSE)return false; + Check(hr,"GetData(async start timestamp)"); + hr=context->GetData(queries.end,&end,sizeof(end),D3D11_ASYNC_GETDATA_DONOTFLUSH); + if(hr==S_FALSE)return false; + Check(hr,"GetData(async end timestamp)"); + if(timestampFrequency&&!disjoint.Disjoint)*timestampFrequency=disjoint.Frequency; + if(!disjoint.Disjoint&&disjoint.Frequency) + dispatchMs=1000.0*static_cast(end-start)/static_cast(disjoint.Frequency); + queries.start->Release();queries.start=nullptr; + queries.end->Release();queries.end=nullptr; + queries.disjoint->Release();queries.disjoint=nullptr; + return true; + } + + bool ResolveGpuTimestampPair(ID3D11DeviceContext* context,GpuTimingQueries& queries, + std::uint64_t timestampFrequency,double& dispatchMs) + { + dispatchMs=0.0; + if(!context||!queries.start||!queries.end||!timestampFrequency)return true; + UINT64 start=0,end=0; + HRESULT hr=context->GetData(queries.start,&start,sizeof(start),D3D11_ASYNC_GETDATA_DONOTFLUSH); + if(hr==S_FALSE)return false; + Check(hr,"GetData(async tile start timestamp)"); + hr=context->GetData(queries.end,&end,sizeof(end),D3D11_ASYNC_GETDATA_DONOTFLUSH); + if(hr==S_FALSE)return false; + Check(hr,"GetData(async tile end timestamp)"); + if(end>=start)dispatchMs=1000.0*static_cast(end-start) + /static_cast(timestampFrequency); + queries.start->Release();queries.start=nullptr; + queries.end->Release();queries.end=nullptr; + if(queries.disjoint){queries.disjoint->Release();queries.disjoint=nullptr;} + return true; + } } #ifndef VF_GPU_FIELD_PORT_NO_MAIN diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 index c94ce61..9b9fec4 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/RunGpuFarViewer.ps1 @@ -3,7 +3,8 @@ param( [ValidateSet('gpu','cpu','audit')][string]$Mode='gpu', [string]$LogStem='gpu_far_viewer', [int]$ClipRadius=3, - [string]$CaptureName='' + [string]$CaptureName='', + [string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe' ) $ErrorActionPreference='Stop' @@ -12,7 +13,9 @@ $HostProcess=Get-Process -Id $PID $HostProcess.PriorityClass='BelowNormal' $HostProcess.ProcessorAffinity=[IntPtr]0xFF00 $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') $StderrPath=Join-Path $PSScriptRoot ($LogStem+'.stderr.log') if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU far viewer not found: $ExecutablePath"} diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP4_REPORT.md b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP4_REPORT.md new file mode 100644 index 0000000..3f89cfa --- /dev/null +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/STEP4_REPORT.md @@ -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. diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/StartGpuFarViewer.ps1 b/Standalone/Research/OwnerShapeWorld/gpu_field_port/StartGpuFarViewer.ps1 index 4da3e0e..2f0b683 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_field_port/StartGpuFarViewer.ps1 +++ b/Standalone/Research/OwnerShapeWorld/gpu_field_port/StartGpuFarViewer.ps1 @@ -1,9 +1,11 @@ [CmdletBinding()] -param([int]$ClipRadius=3) +param([int]$ClipRadius=3,[string]$ExecutableName='VoxelForgeGpuFarHybridViewer.exe') $ErrorActionPreference='Stop' $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' $StderrPath=Join-Path $PSScriptRoot 'owner_hybrid_viewer.stderr.log' if(-not(Test-Path -LiteralPath $ExecutablePath -PathType Leaf)){throw "GPU far viewer not found: $ExecutablePath"} diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/BuildFarConnectorRingsCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/BuildFarConnectorRingsCS.cso new file mode 100644 index 0000000..4f774ca Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/BuildFarConnectorRingsCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ClassifyCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ClassifyCS.cso new file mode 100644 index 0000000..96933c6 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ClassifyCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ClassifyFarCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ClassifyFarCS.cso new file mode 100644 index 0000000..f9fac43 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ClassifyFarCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EmitCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EmitCS.cso new file mode 100644 index 0000000..96ecfeb Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EmitCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EmitFarConnectorPanelsCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EmitFarConnectorPanelsCS.cso new file mode 100644 index 0000000..58f2b37 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EmitFarConnectorPanelsCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EvaluateFarFieldCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EvaluateFarFieldCS.cso new file mode 100644 index 0000000..2b26a09 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/EvaluateFarFieldCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/NoiseProbeCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/NoiseProbeCS.cso new file mode 100644 index 0000000..784d619 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/NoiseProbeCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ScanGroupsCS.cso b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ScanGroupsCS.cso new file mode 100644 index 0000000..c351350 Binary files /dev/null and b/Standalone/Research/OwnerShapeWorld/gpu_field_port/precompiled/ScanGroupsCS.cso differ diff --git a/Standalone/Research/OwnerShapeWorld/gpu_first_port/VoxelForgeGpuMesher.hlsl b/Standalone/Research/OwnerShapeWorld/gpu_first_port/VoxelForgeGpuMesher.hlsl index 2f252fa..598c55b 100644 --- a/Standalone/Research/OwnerShapeWorld/gpu_first_port/VoxelForgeGpuMesher.hlsl +++ b/Standalone/Research/OwnerShapeWorld/gpu_first_port/VoxelForgeGpuMesher.hlsl @@ -25,7 +25,19 @@ cbuffer TileConstants : register(b0) int OriginZ; int StepVoxels; 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] = { @@ -127,6 +139,25 @@ RWStructuredBuffer CellLocalOffsets : register(u1); RWStructuredBuffer GroupVertexCounts : register(u2); 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 FarShapes : register(t10); StructuredBuffer FarSampleShapeRanges : register(t11); StructuredBuffer FarSampleShapeIds : register(t12); @@ -134,6 +165,12 @@ StructuredBuffer FarProtectedAir : register(t13); StructuredBuffer FarProtectedSolid : register(t14); RWStructuredBuffer FarFieldDensity : register(u7); StructuredBuffer GpuSignDensity : register(t15); +StructuredBuffer FarConnectorPanels : register(t8); +StructuredBuffer FarConnectorRingInput : register(t9); +RWStructuredBuffer FarConnectorRingOutput : register(u7); +RWByteAddressBuffer FarConnectorDrawArgs : register(u4); +RWByteAddressBuffer FarConnectorVertexBytes : register(u5); +RWByteAddressBuffer FarConnectorIndexBytes : register(u6); cbuffer FarFieldConstants : register(b1) { @@ -196,6 +233,205 @@ void EvaluateFarFieldCS(uint3 dispatchId : SV_DispatchThreadID) 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=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]; void ClassifyAndScan(uint index, uint3 groupId, uint3 groupThreadId, uint caseIndex) @@ -283,11 +519,16 @@ void ScanGroupsCS(uint3 dispatchId : SV_DispatchThreadID) GroupOffsets[i] = offset; offset += InputGroupVertexCounts[i]; } - DrawArgs.Store(0, offset); - DrawArgs.Store(4, 1); - DrawArgs.Store(8, 0); - DrawArgs.Store(12, 0); - DrawArgs.Store(16, 0); + DrawArgs.Store(OutputArgsByteOffset + 0, offset); + DrawArgs.Store(OutputArgsByteOffset + 4, 1); + DrawArgs.Store(OutputArgsByteOffset + 8, OutputBaseVertex); + DrawArgs.Store(OutputArgsByteOffset + 12, 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 Density : register(t2); @@ -306,7 +547,7 @@ void EmitCS(uint3 dispatchId : SV_DispatchThreadID) const uint index = dispatchId.x; if (index >= CellCount) return; 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, (index / CellsPerAxis) % CellsPerAxis, index / (CellsPerAxis * CellsPerAxis)); diff --git a/Standalone/Source/StandaloneViewer.cpp b/Standalone/Source/StandaloneViewer.cpp index 77292dc..6fd9b02 100644 --- a/Standalone/Source/StandaloneViewer.cpp +++ b/Standalone/Source/StandaloneViewer.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -1352,10 +1353,24 @@ namespace ID3D11Buffer* VertexBuffer=nullptr; ID3D11Buffer* IndexBuffer=nullptr; ID3D11Buffer* IndirectArgs=nullptr; + ID3D11Buffer* ConnectorVertexBuffer=nullptr; + ID3D11Buffer* ConnectorIndexBuffer=nullptr; ID3D11Query* CompletionEvent=nullptr; + VoxelForgeGpuFarViewer::GpuTimingQueries TimingQueries{}; uint64 VertexCapacityBytes=0; + uint32 OutputSlot=0,IndirectArgsByteOffset=0,BatchIndex=UINT32_MAX; double GpuDispatchMs=0.0; 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) @@ -1439,6 +1454,7 @@ namespace std::unordered_map,FStandaloneBatchKeyHash> BatchMembers; std::unordered_map StreamingBatches; std::unordered_map GpuFarTiles; + std::vector GpuFarBatches; std::deque GpuFarQueue; std::unordered_set GpuFarQueued; std::unordered_set GpuFarEligibleTiles; @@ -1474,6 +1490,16 @@ namespace uint64 GpuFarTilesSubmitted=0; double GpuFarGpuDispatchMs=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 GpuFarFreeOutputSlots; + std::unordered_map GpuFarPreparedInputs; + std::unordered_map GpuFarPreparationErrors; + double GpuFarPreparationWallMs=0.0,GpuFarPreparationWorkerMs=0.0; VoxelForgeCore::DetailVariant ShapeDetailVariant=VoxelForgeCore::DetailVariant::coherent; bool bWireframe=false; bool bCaptured=false; @@ -1531,6 +1557,41 @@ namespace 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() { for(auto& Pair:StreamingBatches) @@ -1540,16 +1601,20 @@ namespace } StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear(); for(auto& Pair:GpuFarTiles) - { - if(Pair.second.VertexBuffer)Pair.second.VertexBuffer->Release(); - if(Pair.second.IndexBuffer)Pair.second.IndexBuffer->Release(); - if(Pair.second.IndirectArgs)Pair.second.IndirectArgs->Release(); - if(Pair.second.CompletionEvent)Pair.second.CompletionEvent->Release(); - } - GpuFarTiles.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear(); + ReleaseGpuFarTile(Pair.second); + GpuFarTiles.clear(); + for(FGpuFarBatch& Batch:GpuFarBatches)ReleaseGpuFarBatch(Batch); + GpuFarBatches.clear();GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear(); + GpuFarFreeOutputSlots.clear();GpuFarNextOutputSlot=0; + GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear(); + GpuFarPreparationWallMs=0.0;GpuFarPreparationWorkerMs=0.0; GpuFarAudit.reset();bGpuFarAuditReported=false; 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(); DeferredLeaverTiles.clear(); 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); if(It==GpuFarTiles.end())return false; FGpuFarTile& Tile=It->second; - if(Tile.bGpuComplete)return true; - if(Tile.CompletionEvent&&Context + if(Tile.BatchIndex!=UINT32_MAX&&Tile.BatchIndexGetData(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) { Tile.bGpuComplete=true; 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 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& Keys) { GpuFarQueue.clear();GpuFarQueued.clear();GpuFarEligibleTiles.clear(); + GpuFarPreparedInputs.clear();GpuFarPreparationErrors.clear(); if(!bGpuFarEnabled||!ShapeWorld||!ShapeWorld->hasFarWorld())return; std::vector CpuTiles;CpuTiles.reserve(Keys.size()); + std::vector Eligible;Eligible.reserve(Keys.size()); for(const FVoxelTileKey& Key:Keys) { if(Key.LevelFullResClipLevels){CpuTiles.push_back(Key);continue;} const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level}; if(ShapeWorld->isFarFieldTileEligible(CoreKey)) { - GpuFarEligibleTiles.insert(Key); - if(!GpuFarTiles.contains(Key)) - {GpuFarQueue.push_back(Key);GpuFarQueued.insert(Key);} + 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(Eligible.size()); + } + if(!Eligible.empty()&&GpuFarOutputCapacitySlots==0) + GpuFarOutputCapacitySlots=static_cast(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(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( + std::chrono::steady_clock::now()-ReserveStart).count(); + SampleGpuFarMemory(); + } + std::vector 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> Inputs(NeedPreparation.size()); + std::vector Errors(NeedPreparation.size()); + const std::size_t ThreadCount=std::min(8,NeedPreparation.size()); + std::atomic Next{0}; + std::vector WorkerElapsed(ThreadCount,0.0); + std::vector Workers;Workers.reserve(ThreadCount); + for(std::size_t Worker=0;Worker=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( + std::chrono::steady_clock::now()-WorkerStart).count(); + }); + } + for(std::thread& Worker:Workers)Worker.join(); + const double PreparationMs=std::chrono::duration( + 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;IndexSetDesiredTileFilter(CpuTiles); const std::size_t CpuTransitionFallbacks=static_cast(std::count_if( 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 ||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(); int32 Built=0; + std::vector BuiltKeys;BuiltKeys.reserve(32); do { const FVoxelTileKey Key=GpuFarQueue.front();GpuFarQueue.pop_front();GpuFarQueued.erase(Key); if(!DesiredTileSet.contains(Key)||GpuFarTiles.contains(Key))continue; FGpuFarTile Tile; + if(!GpuFarFreeOutputSlots.empty()) + {Tile.OutputSlot=GpuFarFreeOutputSlots.back();GpuFarFreeOutputSlots.pop_back();} + else if(GpuFarNextOutputSlot=6)?&AuditMesh:nullptr, - &EnqueueMs,&GpuDispatchMs,Error)) + const auto PreparedInputIt=GpuFarPreparedInputs.find(Key); + const VoxelForgeCore::FarFieldTileInput* PreparedInput=PreparedInputIt==GpuFarPreparedInputs.end() + ?nullptr:&PreparedInputIt->second; + if(!PreparedInput) { - 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()); + const auto PrepError=GpuFarPreparationErrors.find(Key); + 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; 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) { 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.GpuDispatchMs=GpuDispatchMs; GpuFarEnqueueWallSeconds+=EnqueueMs/1000.0; - GpuFarGpuDispatchMs+=GpuDispatchMs; - GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,GpuDispatchMs); + if(GpuDispatchMs>0.0) + { + GpuFarGpuDispatchMs+=GpuDispatchMs; + GpuFarGpuDispatchMaxMs=std::max(GpuFarGpuDispatchMaxMs,GpuDispatchMs); + Tile.bTimingResolved=true; + } ++GpuFarTilesSubmitted;++Built; GpuFarTiles.emplace(Key,Tile); + BuiltKeys.push_back(Key); if(!bGpuFarFirstTileReported) { 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); } } - while(Built<1&& !GpuFarQueue.empty() - &&std::chrono::duration(std::chrono::steady_clock::now()-Start).count()<1.0); + while(Built<32&&!GpuFarQueue.empty() + &&std::chrono::duration(std::chrono::steady_clock::now()-Start).count()<4.0); + if(Built>0) + { + if(bDeferredBatch) + { + FGpuFarBatch Batch;Batch.TileCount=static_cast(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(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(GpuFarMaxTilesPerSubmissionBatch, + static_cast(Built)); + } + } + else + { + ++GpuFarSubmissionBatches; + GpuFarMaxTilesPerSubmissionBatch=std::max(GpuFarMaxTilesPerSubmissionBatch, + static_cast(Built)); + } + } + SampleGpuFarMemory(); } 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();) { if(DesiredTileSet.contains(It->first)){++It;continue;} - if(It->second.VertexBuffer)It->second.VertexBuffer->Release(); - if(It->second.IndexBuffer)It->second.IndexBuffer->Release(); - if(It->second.IndirectArgs)It->second.IndirectArgs->Release(); - if(It->second.CompletionEvent)It->second.CompletionEvent->Release(); + GpuFarFreeOutputSlots.push_back(It->second.OutputSlot); + ReleaseGpuFarTile(It->second); It=GpuFarTiles.erase(It); } 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(TotalIndices),StreamingBatches.size()); if(bGpuFarEnabled) { + SampleGpuFarMemory(); const double AverageGpuMs=GpuFarTilesSubmitted ?GpuFarGpuDispatchMs/static_cast(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(GpuFarTilesSubmitted), - GpuFarEnqueueWallSeconds*1000.0,GpuFarGpuDispatchMs,AverageGpuMs,GpuFarGpuDispatchMaxMs); + static_cast(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); 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; Context->IASetVertexBuffers(0,1,&Tile.VertexBuffer,&Stride,&Offset); 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();