From 41e9f3c4260c60aa293ae2487534bfc1f22242e4 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 25 Sep 2025 16:32:18 +0100 Subject: [PATCH] Add a demo project with extremely high overlap/sparseness buffers * For stressing memory overhead in buffer initial states --- .../test/demos/d3d12/d3d12_memory_overlap.cpp | 247 ++++++++++++++++++ util/test/demos/d3d12/d3d12_test.cpp | 2 +- util/test/demos/demos.vcxproj | 1 + util/test/demos/demos.vcxproj.filters | 3 + util/test/tests/D3D12/D3D12_Memory_Overlap.py | 13 + 5 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 util/test/demos/d3d12/d3d12_memory_overlap.cpp create mode 100644 util/test/tests/D3D12/D3D12_Memory_Overlap.py diff --git a/util/test/demos/d3d12/d3d12_memory_overlap.cpp b/util/test/demos/d3d12/d3d12_memory_overlap.cpp new file mode 100644 index 000000000..f60d52bf8 --- /dev/null +++ b/util/test/demos/d3d12/d3d12_memory_overlap.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2025 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "d3d12_test.h" + +RD_TEST(D3D12_Memory_Overlap, D3D12GraphicsTest) +{ + static constexpr const char *Description = + "Test that allocates a lot of 'virtual' memory that doesn't require much physical memory - " + "overlapping resources and sparse buffers that are mostly unmapped."; + + std::string pixel = R"EOSHADER( + +StructuredBuffer buffers[8000] : register(t0); + +float4 main() : SV_Target0 +{ + uint bufIdx = 0; + uint byteOffset = 0; + + for(int i=0; i < 20; i++) + { + uint a = buffers[bufIdx][byteOffset/4]; + uint b = buffers[bufIdx][(byteOffset/4) + 1]; + + if(a == 999999 && b == 999999) + return float4(0, 1, 0, 1); + + bufIdx = a; + byteOffset = b; + } + + return float4(1, 0, 0, 1); +} + +)EOSHADER"; + + int main() + { + // initialise, create window, create device, etc + if(!Init()) + return 3; + + ID3DBlobPtr vsblob = Compile(D3DDefaultVertex, "main", "vs_6_0"); + ID3DBlobPtr psblob = Compile(pixel, "main", "ps_6_0"); + + ID3D12RootSignaturePtr sig = MakeSig({ + tableParam(D3D12_SHADER_VISIBILITY_PIXEL, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 0, 8000, 0), + }); + + ID3D12PipelineStatePtr pso = MakePSO().RootSig(sig).InputLayout().VS(vsblob).PS(psblob); + + // 256MB heap gives us plenty of unique possibilities for buffers + + D3D12_HEAP_DESC heapDesc; + heapDesc.SizeInBytes = 256 * 1024 * 1024; + heapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; + heapDesc.Alignment = 0; + heapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; + heapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapDesc.Properties.CreationNodeMask = 1; + heapDesc.Properties.VisibleNodeMask = 1; + + D3D12_RESOURCE_DESC resDesc; + resDesc.Alignment = 0; + resDesc.DepthOrArraySize = 1; + resDesc.Width = heapDesc.SizeInBytes; + resDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + resDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + resDesc.Format = DXGI_FORMAT_UNKNOWN; + resDesc.Height = 1; + resDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + resDesc.MipLevels = 1; + resDesc.SampleDesc.Count = 1; + resDesc.SampleDesc.Quality = 0; + + D3D12_RESOURCE_ALLOCATION_INFO info = dev->GetResourceAllocationInfo(0, 1, &resDesc); + + ID3D12HeapPtr heap; + CHECK_HR(dev->CreateHeap(&heapDesc, __uuidof(ID3D12Heap), (void **)&heap)); + + heapDesc.SizeInBytes = 128 * 1024 * 1024; + ID3D12HeapPtr sparseHeap; + CHECK_HR(dev->CreateHeap(&heapDesc, __uuidof(ID3D12Heap), (void **)&sparseHeap)); + + std::vector bufs; + + // create a max-size buffer at many possible offsets. Saving the contents of each buffer + // separately will result in a huge expansion of needed memory. + // We cut down the number of resources a bit since tracking overlaps is expensive and this makes + // the test run slower - no real application will have this amount of overlapping + UINT64 offs = 0; + while(resDesc.Width > 0) + { + ID3D12ResourcePtr buf; + CHECK_HR(dev->CreatePlacedResource(heap, offs, &resDesc, D3D12_RESOURCE_STATE_COMMON, NULL, + __uuidof(ID3D12Resource), (void **)&buf)); + bufs.push_back(buf); + + resDesc.Width -= D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT * 2; + offs += D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT * 2; + } + + const uint32_t firstSparse = (uint32_t)bufs.size(); + + // create a few sparse buffers with only tiny amounts mapped - again saving the buffer contents + // instead of backing memory will expand memory use + resDesc.Width = 1536 * 1024 * 1024; + + std::vector sparsebufs; + + for(int i = 0; i < 20; i++) + { + ID3D12ResourcePtr buf; + dev->CreateReservedResource(&resDesc, D3D12_RESOURCE_STATE_COMMON, NULL, + __uuidof(ID3D12Resource), (void **)&buf); + sparsebufs.push_back(buf); + bufs.push_back(buf); + } + + // we don't test textures currently as we save those entirely :)... + + for(size_t i = 0; i < bufs.size(); i++) + { + MakeSRV(bufs[i]).Format(DXGI_FORMAT_R32_UINT).CreateGPU(int(i)); + } + + // we chase around a few buffers to ensure their data is correct. Starting from buffer 0, offset + // 0 each time we read a pair of uints + uint32_t data[] = { + 3, + 128, + + 10, + 1024, + + firstSparse, + 1024 * 1024 + 64, + + firstSparse + 10, + 7 * 1024 * 1024 + 512, + + // success value + 999999, + 999999, + }; + + ID3D12ResourcePtr uploadBuf = MakeBuffer().Upload().Data(data); + + // need to map the + D3D12_TILED_RESOURCE_COORDINATE start = { + (1024 * 1024) / 65536, + }; + D3D12_TILE_REGION_SIZE size = { + 1, FALSE, 1, 1, 1, + }; + UINT heapRangeStart = (92 * 1024 * 1024 + 128 * 1024) / 65536; + UINT rangeTileCount = 1; + queue->UpdateTileMappings(sparsebufs[0], 1, &start, &size, sparseHeap, 1, NULL, &heapRangeStart, + &rangeTileCount, D3D12_TILE_MAPPING_FLAG_NONE); + + start.X = (7 * 1024 * 1024) / 65536; + heapRangeStart = (100 * 1024 * 1024 + 768 * 1024) / 65536; + queue->UpdateTileMappings(sparsebufs[10], 1, &start, &size, sparseHeap, 1, NULL, + &heapRangeStart, &rangeTileCount, D3D12_TILE_MAPPING_FLAG_NONE); + + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + uint32_t bufIdx = 0; + uint32_t byteOffset = 0; + + uint32_t srcOffs = 0; + while(bufIdx != 999999) + { + cmd->CopyBufferRegion(bufs[bufIdx], byteOffset, uploadBuf, srcOffs, 8); + bufIdx = data[srcOffs / sizeof(uint32_t)]; + byteOffset = data[srcOffs / sizeof(uint32_t) + 1]; + srcOffs += 8; + } + + cmd->Close(); + + Submit({cmd}); + } + + while(Running()) + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + + Reset(cmd); + + cmd->SetDescriptorHeaps(1, &m_CBVUAVSRV.GetInterfacePtr()); + + ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + ClearRenderTargetView(cmd, BBRTV, {0.2f, 0.2f, 0.2f, 1.0f}); + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + IASetVertexBuffer(cmd, DefaultTriVB, sizeof(DefaultA2V), 0); + cmd->SetPipelineState(pso); + cmd->SetGraphicsRootSignature(sig); + cmd->SetGraphicsRootDescriptorTable(0, m_CBVUAVSRV->GetGPUDescriptorHandleForHeapStart()); + + SetMainWindowViewScissor(cmd); + + OMSetRenderTargets(cmd, {BBRTV}, {}); + + cmd->DrawInstanced(3, 1, 0, 0); + + FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + + cmd->Close(); + + SubmitAndPresent({cmd}); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/d3d12/d3d12_test.cpp b/util/test/demos/d3d12/d3d12_test.cpp index 6d16cb2e0..f12916886 100644 --- a/util/test/demos/d3d12/d3d12_test.cpp +++ b/util/test/demos/d3d12/d3d12_test.cpp @@ -588,7 +588,7 @@ void D3D12GraphicsTest::PostDeviceCreate() m_Sampler->SetName(L"Sampler heap"); - desc.NumDescriptors = 1030; + desc.NumDescriptors = 8192; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; CHECK_HR(dev->CreateDescriptorHeap(&desc, __uuidof(ID3D12DescriptorHeap), (void **)&m_CBVUAVSRV)); diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 570ad26d7..675a902de 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -204,6 +204,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 923972402..e711dae44 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -736,6 +736,9 @@ D3D12\demos + + D3D12\demos + diff --git a/util/test/tests/D3D12/D3D12_Memory_Overlap.py b/util/test/tests/D3D12/D3D12_Memory_Overlap.py new file mode 100644 index 000000000..3b06df0d4 --- /dev/null +++ b/util/test/tests/D3D12/D3D12_Memory_Overlap.py @@ -0,0 +1,13 @@ +import renderdoc as rd +import rdtest + + +class D3D12_Memory_Overlap(rdtest.TestCase): + demos_test_name = 'D3D12_Memory_Overlap' + + def check_capture(self): + last_action: rd.ActionDescription = self.get_last_action() + + self.controller.SetFrameEvent(last_action.eventId, True) + + self.check_triangle(out=last_action.copyDestination)