| // | |
| // Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. | |
| // | |
| // 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 "Common.h" | |
| #include "Tests.h" | |
| #include <thread> | |
| // Define to the same value as you did for D3D12MemAlloc.cpp. | |
| #ifndef D3D12MA_DEBUG_MARGIN | |
| #define D3D12MA_DEBUG_MARGIN 0 | |
| #endif | |
| extern ID3D12GraphicsCommandList* BeginCommandList(); | |
| extern DXGI_ADAPTER_DESC1 g_AdapterDesc; | |
| extern void EndCommandList(ID3D12GraphicsCommandList* cmdList); | |
| enum CONFIG_TYPE | |
| { | |
| CONFIG_TYPE_MINIMUM, | |
| CONFIG_TYPE_SMALL, | |
| CONFIG_TYPE_AVERAGE, | |
| CONFIG_TYPE_LARGE, | |
| CONFIG_TYPE_MAXIMUM, | |
| CONFIG_TYPE_COUNT | |
| }; | |
| enum class FREE_ORDER { FORWARD, BACKWARD, RANDOM, COUNT }; | |
| static const char* CODE_DESCRIPTION = "D3D12MA Tests"; | |
| static constexpr UINT64 KILOBYTE = 1024; | |
| static constexpr UINT64 MEGABYTE = 1024 * KILOBYTE; | |
| static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_AVERAGE; | |
| static const char* FREE_ORDER_NAMES[] = { "FORWARD", "BACKWARD", "RANDOM", }; | |
| static void CurrentTimeToStr(std::string& out) | |
| { | |
| time_t rawTime; time(&rawTime); | |
| struct tm timeInfo; localtime_s(&timeInfo, &rawTime); | |
| char timeStr[128]; | |
| strftime(timeStr, _countof(timeStr), "%c", &timeInfo); | |
| out = timeStr; | |
| } | |
| static float ToFloatSeconds(duration d) | |
| { | |
| return std::chrono::duration_cast<std::chrono::duration<float>>(d).count(); | |
| } | |
| static const char* AlgorithmToStr(D3D12MA::POOL_FLAGS algorithm) | |
| { | |
| switch (algorithm) | |
| { | |
| case D3D12MA::POOL_FLAG_ALGORITHM_LINEAR: | |
| return "Linear"; | |
| case 0: | |
| return "TLSF"; | |
| default: | |
| assert(0); | |
| return ""; | |
| } | |
| } | |
| static const char* VirtualAlgorithmToStr(D3D12MA::VIRTUAL_BLOCK_FLAGS algorithm) | |
| { | |
| switch (algorithm) | |
| { | |
| case D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR: | |
| return "Linear"; | |
| case 0: | |
| return "TLSF"; | |
| default: | |
| assert(0); | |
| return ""; | |
| } | |
| } | |
| static const wchar_t* DefragmentationAlgorithmToStr(UINT32 algorithm) | |
| { | |
| switch (algorithm) | |
| { | |
| case D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: | |
| return L"Balanced"; | |
| case D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FAST: | |
| return L"Fast"; | |
| case D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FULL: | |
| return L"Full"; | |
| case 0: | |
| return L"Default"; | |
| default: | |
| assert(0); | |
| return L""; | |
| } | |
| } | |
| struct ResourceWithAllocation | |
| { | |
| ComPtr<ID3D12Resource> resource; | |
| ComPtr<D3D12MA::Allocation> allocation; | |
| UINT64 size = UINT64_MAX; | |
| UINT dataSeed = 0; | |
| void Reset() | |
| { | |
| resource.Reset(); | |
| allocation.Reset(); | |
| size = UINT64_MAX; | |
| dataSeed = 0; | |
| } | |
| }; | |
| template<typename D3D12_RESOURCE_DESC_T> | |
| static void FillResourceDescForBuffer(D3D12_RESOURCE_DESC_T& outResourceDesc, UINT64 size) | |
| { | |
| outResourceDesc = {}; | |
| outResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; | |
| outResourceDesc.Alignment = 0; | |
| outResourceDesc.Width = size; | |
| outResourceDesc.Height = 1; | |
| outResourceDesc.DepthOrArraySize = 1; | |
| outResourceDesc.MipLevels = 1; | |
| outResourceDesc.Format = DXGI_FORMAT_UNKNOWN; | |
| outResourceDesc.SampleDesc.Count = 1; | |
| outResourceDesc.SampleDesc.Quality = 0; | |
| outResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; | |
| outResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; | |
| } | |
| static void FillData(void* outPtr, const UINT64 sizeInBytes, UINT seed) | |
| { | |
| UINT* outValues = (UINT*)outPtr; | |
| const UINT64 sizeInValues = sizeInBytes / sizeof(UINT); | |
| UINT value = seed; | |
| for(UINT i = 0; i < sizeInValues; ++i) | |
| { | |
| outValues[i] = value++; | |
| } | |
| } | |
| static void FillAllocationsData(const ComPtr<D3D12MA::Allocation>* allocs, size_t allocCount, UINT seed) | |
| { | |
| std::for_each(allocs, allocs + allocCount, [seed](const ComPtr<D3D12MA::Allocation>& alloc) | |
| { | |
| D3D12_RANGE range = {}; | |
| void* ptr; | |
| CHECK_HR(alloc->GetResource()->Map(0, &range, &ptr)); | |
| FillData(ptr, alloc->GetSize(), seed); | |
| alloc->GetResource()->Unmap(0, nullptr); | |
| }); | |
| } | |
| static void FillAllocationsDataGPU(const TestContext& ctx, const ComPtr<D3D12MA::Allocation>* allocs, size_t allocCount, UINT seed) | |
| { | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAGS::ALLOCATION_FLAG_COMMITTED; | |
| std::vector<D3D12_RESOURCE_BARRIER> barriers; | |
| std::vector<ComPtr<D3D12MA::Allocation>> uploadAllocs; | |
| barriers.reserve(allocCount); | |
| uploadAllocs.reserve(allocCount); | |
| // Move resource into right state | |
| D3D12_RESOURCE_BARRIER barrier = {}; | |
| barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; | |
| barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; | |
| barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; | |
| barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; | |
| ID3D12GraphicsCommandList* cl = BeginCommandList(); | |
| std::for_each(allocs, allocs + allocCount, [&](const ComPtr<D3D12MA::Allocation>& alloc) | |
| { | |
| // Copy only buffers for now | |
| D3D12_RESOURCE_DESC resDesc = alloc->GetResource()->GetDesc(); | |
| if (resDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) | |
| { | |
| ComPtr<D3D12MA::Allocation> uploadAlloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, &uploadAlloc, IID_NULL, nullptr)); | |
| D3D12_RANGE range = {}; | |
| void* ptr; | |
| CHECK_HR(uploadAlloc->GetResource()->Map(0, &range, &ptr)); | |
| FillData(ptr, resDesc.Width, seed); | |
| uploadAlloc->GetResource()->Unmap(0, nullptr); | |
| cl->CopyResource(alloc->GetResource(), uploadAlloc->GetResource()); | |
| uploadAllocs.emplace_back(std::move(uploadAlloc)); | |
| } | |
| barrier.Transition.pResource = alloc->GetResource(); | |
| barrier.Transition.StateAfter = (D3D12_RESOURCE_STATES)(uintptr_t)alloc->GetPrivateData(); | |
| barriers.emplace_back(barrier); | |
| }); | |
| cl->ResourceBarrier(static_cast<UINT>(allocCount), barriers.data()); | |
| EndCommandList(cl); | |
| } | |
| static bool ValidateData(const void* ptr, const UINT64 sizeInBytes, UINT seed) | |
| { | |
| const UINT* values = (const UINT*)ptr; | |
| const UINT64 sizeInValues = sizeInBytes / sizeof(UINT); | |
| UINT value = seed; | |
| for(UINT i = 0; i < sizeInValues; ++i) | |
| { | |
| if(values[i] != value++) | |
| { | |
| //CHECK_BOOL(0 && "ValidateData failed."); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| static bool ValidateDataZero(const void* ptr, const UINT64 sizeInBytes) | |
| { | |
| const UINT* values = (const UINT*)ptr; | |
| const UINT64 sizeInValues = sizeInBytes / sizeof(UINT); | |
| for(UINT i = 0; i < sizeInValues; ++i) | |
| { | |
| if(values[i] != 0) | |
| { | |
| //CHECK_BOOL(0 && "ValidateData failed."); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| static void ValidateAllocationsData(const ComPtr<D3D12MA::Allocation>* allocs, size_t allocCount, UINT seed) | |
| { | |
| std::for_each(allocs, allocs + allocCount, [seed](const ComPtr<D3D12MA::Allocation>& alloc) | |
| { | |
| D3D12_RANGE range = {}; | |
| void* ptr; | |
| CHECK_HR(alloc->GetResource()->Map(0, &range, &ptr)); | |
| CHECK_BOOL(ValidateData(ptr, alloc->GetSize(), seed)); | |
| alloc->GetResource()->Unmap(0, nullptr); | |
| }); | |
| } | |
| static void ValidateAllocationsDataGPU(const TestContext& ctx, const ComPtr<D3D12MA::Allocation>* allocs, size_t allocCount, UINT seed) | |
| { | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_READBACK; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAGS::ALLOCATION_FLAG_COMMITTED; | |
| std::vector<D3D12_RESOURCE_BARRIER> barriers; | |
| std::vector<ComPtr<D3D12MA::Allocation>> downloadAllocs; | |
| barriers.reserve(allocCount); | |
| downloadAllocs.reserve(allocCount); | |
| // Move resource into right state | |
| D3D12_RESOURCE_BARRIER barrier = {}; | |
| barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; | |
| barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; | |
| barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; | |
| barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; | |
| ID3D12GraphicsCommandList* cl = BeginCommandList(); | |
| size_t resCount = allocCount; | |
| std::for_each(allocs, allocs + allocCount, [&](const ComPtr<D3D12MA::Allocation>& alloc) | |
| { | |
| // Check only buffers for now | |
| D3D12_RESOURCE_DESC resDesc = alloc->GetResource()->GetDesc(); | |
| if (resDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) | |
| { | |
| ComPtr<D3D12MA::Allocation> downloadAlloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, | |
| nullptr, &downloadAlloc, IID_NULL, nullptr)); | |
| barrier.Transition.pResource = alloc->GetResource(); | |
| barrier.Transition.StateBefore = (D3D12_RESOURCE_STATES)(uintptr_t)alloc->GetPrivateData(); | |
| barriers.emplace_back(barrier); | |
| downloadAllocs.emplace_back(std::move(downloadAlloc)); | |
| } | |
| else | |
| --resCount; | |
| }); | |
| cl->ResourceBarrier(static_cast<UINT>(resCount), barriers.data()); | |
| for (size_t i = 0, j = 0; i < resCount; ++j) | |
| { | |
| if (allocs[j]->GetResource()->GetDesc().Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) | |
| { | |
| cl->CopyResource(downloadAllocs.at(i)->GetResource(), allocs[j]->GetResource()); | |
| barriers.at(i).Transition.StateAfter = barriers.at(i).Transition.StateBefore; | |
| barriers.at(i).Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; | |
| ++i; | |
| } | |
| } | |
| cl->ResourceBarrier(static_cast<UINT>(resCount), barriers.data()); | |
| EndCommandList(cl); | |
| for (auto& alloc : downloadAllocs) | |
| { | |
| D3D12_RANGE range = {}; | |
| void* ptr; | |
| CHECK_HR(alloc->GetResource()->Map(0, &range, &ptr)); | |
| CHECK_BOOL(ValidateData(ptr, alloc->GetResource()->GetDesc().Width, seed)); | |
| alloc->GetResource()->Unmap(0, nullptr); | |
| } | |
| } | |
| static void SaveStatsStringToFile(const TestContext& ctx, const wchar_t* dstFilePath, BOOL detailed = TRUE) | |
| { | |
| WCHAR* s = nullptr; | |
| ctx.allocator->BuildStatsString(&s, detailed); | |
| SaveFile(dstFilePath, s, wcslen(s) * sizeof(WCHAR)); | |
| ctx.allocator->FreeStatsString(s); | |
| } | |
| static void TestDebugMargin(const TestContext& ctx) | |
| { | |
| using namespace D3D12MA; | |
| if(D3D12MA_DEBUG_MARGIN == 0) | |
| { | |
| return; | |
| } | |
| wprintf(L"Test D3D12MA_DEBUG_MARGIN = %u\n", (uint32_t)D3D12MA_DEBUG_MARGIN); | |
| ALLOCATION_DESC allocDesc = {}; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD; | |
| for(size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex) | |
| { | |
| switch(algorithmIndex) | |
| { | |
| case 0: poolDesc.Flags = POOL_FLAG_NONE; break; | |
| case 1: poolDesc.Flags = POOL_FLAG_ALGORITHM_LINEAR; break; | |
| default: assert(0); | |
| } | |
| ComPtr<Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| allocDesc.CustomPool = pool.Get(); | |
| // Create few buffers of different size. | |
| const size_t BUF_COUNT = 10; | |
| ComPtr<Allocation> buffers[BUF_COUNT]; | |
| for(size_t allocIndex = 0; allocIndex < 10; ++allocIndex) | |
| { | |
| const bool isLast = allocIndex == BUF_COUNT - 1; | |
| FillResourceDescForBuffer(resDesc, (UINT64)(allocIndex + 1) * 0x10000); | |
| CHECK_HR(ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, | |
| &buffers[allocIndex], | |
| IID_NULL, nullptr)); | |
| } | |
| // JSON dump | |
| wchar_t* json = nullptr; | |
| ctx.allocator->BuildStatsString(&json, TRUE); | |
| int I = 1; // Put breakpoint here to manually inspect json in a debugger. | |
| // Check if their offsets preserve margin between them. | |
| std::sort(buffers, buffers + BUF_COUNT, [](const ComPtr<Allocation>& lhs, const ComPtr<Allocation>& rhs) -> bool | |
| { | |
| if(lhs->GetHeap() != rhs->GetHeap()) | |
| { | |
| return lhs->GetHeap() < rhs->GetHeap(); | |
| } | |
| return lhs->GetOffset() < rhs->GetOffset(); | |
| }); | |
| for(size_t i = 1; i < BUF_COUNT; ++i) | |
| { | |
| if(buffers[i]->GetHeap() == buffers[i - 1]->GetHeap()) | |
| { | |
| const UINT64 allocStart = buffers[i]->GetOffset(); | |
| const UINT64 prevAllocEnd = buffers[i - 1]->GetOffset() + buffers[i - 1]->GetSize(); | |
| CHECK_BOOL(allocStart >= prevAllocEnd + D3D12MA_DEBUG_MARGIN); | |
| } | |
| } | |
| ctx.allocator->FreeStatsString(json); | |
| } | |
| } | |
| static void TestDebugMarginNotInVirtualAllocator(const TestContext& ctx) | |
| { | |
| wprintf(L"Test D3D12MA_DEBUG_MARGIN not applied to virtual allocator\n"); | |
| using namespace D3D12MA; | |
| constexpr size_t ALLOCATION_COUNT = 10; | |
| for(size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex) | |
| { | |
| VIRTUAL_BLOCK_DESC blockDesc = {}; | |
| blockDesc.Size = ALLOCATION_COUNT * MEGABYTE; | |
| switch(algorithmIndex) | |
| { | |
| case 0: blockDesc.Flags = VIRTUAL_BLOCK_FLAG_NONE; break; | |
| case 1: blockDesc.Flags = VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR; break; | |
| default: assert(0); | |
| } | |
| ComPtr<VirtualBlock> block; | |
| CHECK_HR(CreateVirtualBlock(&blockDesc, &block)); | |
| // Fill the entire block | |
| VirtualAllocation allocs[ALLOCATION_COUNT]; | |
| for(size_t i = 0; i < ALLOCATION_COUNT; ++i) | |
| { | |
| VIRTUAL_ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.Size = 1 * MEGABYTE; | |
| CHECK_HR(block->Allocate(&allocDesc, &allocs[i], nullptr)); | |
| } | |
| block->Clear(); | |
| } | |
| } | |
| static void TestJson(const TestContext& ctx) | |
| { | |
| wprintf(L"Test JSON\n"); | |
| std::vector<ComPtr<D3D12MA::Pool>> pools; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocs; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| resDesc.Alignment = 0; | |
| resDesc.MipLevels = 1; | |
| resDesc.SampleDesc.Count = 1; | |
| resDesc.SampleDesc.Quality = 0; | |
| D3D12_RESOURCE_ALLOCATION_INFO allocInfo = {}; | |
| allocInfo.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; | |
| allocInfo.SizeInBytes = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; | |
| // Select if using custom pool or default | |
| for (UINT8 poolType = 0; poolType < 2; ++poolType) | |
| { | |
| // Select different heaps | |
| for (UINT8 heapType = 0; heapType < 5; ++heapType) | |
| { | |
| D3D12_RESOURCE_STATES state; | |
| D3D12_CPU_PAGE_PROPERTY cpuPageType = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; | |
| D3D12_MEMORY_POOL memoryPool = D3D12_MEMORY_POOL_UNKNOWN; | |
| switch (heapType) | |
| { | |
| case 0: | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| state = D3D12_RESOURCE_STATE_COMMON; | |
| break; | |
| case 1: | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| state = D3D12_RESOURCE_STATE_GENERIC_READ; | |
| break; | |
| case 2: | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_READBACK; | |
| state = D3D12_RESOURCE_STATE_COPY_DEST; | |
| break; | |
| case 3: | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_CUSTOM; | |
| state = D3D12_RESOURCE_STATE_COMMON; | |
| cpuPageType = D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE; | |
| memoryPool = ctx.allocator->IsUMA() ? D3D12_MEMORY_POOL_L0 : D3D12_MEMORY_POOL_L1; | |
| break; | |
| case 4: | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_CUSTOM; | |
| state = D3D12_RESOURCE_STATE_GENERIC_READ; | |
| cpuPageType = D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE; | |
| memoryPool = D3D12_MEMORY_POOL_L0; | |
| break; | |
| } | |
| // Skip custom heaps for default pools | |
| if (poolType == 0 && heapType > 2) | |
| continue; | |
| const bool texturesPossible = heapType == 0 || heapType == 3; | |
| // Select different resource region types | |
| for (UINT8 resType = 0; resType < 3; ++resType) | |
| { | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| D3D12_RESOURCE_FLAGS resFlags = D3D12_RESOURCE_FLAG_NONE; | |
| if (texturesPossible) | |
| { | |
| switch (resType) | |
| { | |
| case 1: | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES; | |
| break; | |
| case 2: | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; | |
| resFlags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; | |
| break; | |
| } | |
| } | |
| switch (poolType) | |
| { | |
| case 0: | |
| allocDesc.CustomPool = nullptr; | |
| break; | |
| case 1: | |
| { | |
| ComPtr<D3D12MA::Pool> pool; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapFlags = allocDesc.ExtraHeapFlags; | |
| poolDesc.HeapProperties.Type = allocDesc.HeapType; | |
| poolDesc.HeapProperties.CPUPageProperty = cpuPageType; | |
| poolDesc.HeapProperties.MemoryPoolPreference = memoryPool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| allocDesc.CustomPool = pool.Get(); | |
| pools.emplace_back(std::move(pool)); | |
| break; | |
| } | |
| } | |
| // Select different allocation flags | |
| for (UINT8 allocFlag = 0; allocFlag < 2; ++allocFlag) | |
| { | |
| switch (allocFlag) | |
| { | |
| case 0: | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; | |
| break; | |
| case 1: | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| break; | |
| } | |
| // Select different alloc types (block, buffer, texture, etc.) | |
| for (UINT8 allocType = 0; allocType < 5; ++allocType) | |
| { | |
| // Select different data stored in the allocation | |
| for (UINT8 data = 0; data < 4; ++data) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| if (texturesPossible && resType != 0) | |
| { | |
| resDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| switch (allocType % 3) | |
| { | |
| case 0: | |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE1D; | |
| resDesc.Width = 512; | |
| resDesc.Height = 1; | |
| resDesc.DepthOrArraySize = 1; | |
| resDesc.Flags = resFlags; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); | |
| break; | |
| case 1: | |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resDesc.Width = 1024; | |
| resDesc.Height = 512; | |
| resDesc.DepthOrArraySize = 1; | |
| resDesc.Flags = resFlags; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); | |
| break; | |
| case 2: | |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE3D; | |
| resDesc.Width = 512; | |
| resDesc.Height = 256; | |
| resDesc.DepthOrArraySize = 128; | |
| resDesc.Flags = resFlags; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| switch (allocType % 2) | |
| { | |
| case 0: | |
| CHECK_HR(ctx.allocator->AllocateMemory(&allocDesc, &allocInfo, &alloc)); | |
| break; | |
| case 1: | |
| FillResourceDescForBuffer(resDesc, 1024); | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, state, nullptr, &alloc, IID_NULL, nullptr)); | |
| break; | |
| } | |
| } | |
| switch (data) | |
| { | |
| case 1: | |
| alloc->SetPrivateData((void*)16112007); | |
| break; | |
| case 2: | |
| alloc->SetName(L"SHEPURD"); | |
| break; | |
| case 3: | |
| alloc->SetPrivateData((void*)26012010); | |
| alloc->SetName(L"JOKER"); | |
| break; | |
| } | |
| allocs.emplace_back(std::move(alloc)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| SaveStatsStringToFile(ctx, L"JSON_D3D12.json"); | |
| } | |
| static void TestCommittedResourcesAndJson(const TestContext& ctx) | |
| { | |
| wprintf(L"Test committed resources and JSON\n"); | |
| const UINT count = 4; | |
| const UINT64 bufSize = 32ull * 1024; | |
| const wchar_t* names[count] = { | |
| L"Resource\nFoo\r\nBar", | |
| L"Resource \"'&<>?#@!&-=_+[]{};:,./\\", | |
| nullptr, | |
| L"", | |
| }; | |
| ResourceWithAllocation resources[count]; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, bufSize); | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| const bool receiveExplicitResource = i < 2; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| NULL, | |
| &resources[i].allocation, | |
| __uuidof(ID3D12Resource), | |
| receiveExplicitResource ? (void**)&resources[i].resource : NULL)); | |
| if(receiveExplicitResource) | |
| { | |
| ID3D12Resource* res = resources[i].resource.Get(); | |
| CHECK_BOOL(res && res == resources[i].allocation->GetResource()); | |
| const ULONG refCountAfterAdd = res->AddRef(); | |
| CHECK_BOOL(refCountAfterAdd == 3); | |
| res->Release(); | |
| } | |
| // Make sure it has implicit heap. | |
| CHECK_BOOL( resources[i].allocation->GetHeap() == NULL && resources[i].allocation->GetOffset() == 0 ); | |
| resources[i].allocation->SetName(names[i]); | |
| } | |
| // Check names. | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| const wchar_t* const allocName = resources[i].allocation->GetName(); | |
| if(allocName) | |
| { | |
| CHECK_BOOL( wcscmp(allocName, names[i]) == 0 ); | |
| } | |
| else | |
| { | |
| CHECK_BOOL(names[i] == NULL); | |
| } | |
| } | |
| WCHAR* jsonString; | |
| ctx.allocator->BuildStatsString(&jsonString, TRUE); | |
| CHECK_BOOL(wcsstr(jsonString, L"\"Resource\\nFoo\\r\\nBar\"") != NULL); | |
| CHECK_BOOL(wcsstr(jsonString, L"\"Resource \\\"'&<>?#@!&-=_+[]{};:,.\\/\\\\\"") != NULL); | |
| CHECK_BOOL(wcsstr(jsonString, L"\"\"") != NULL); | |
| ctx.allocator->FreeStatsString(jsonString); | |
| } | |
| static void TestCustomHeapFlags(const TestContext& ctx) | |
| { | |
| wprintf(L"Test custom heap flags\n"); | |
| // 1. Just memory heap with custom flags | |
| { | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES | | |
| D3D12_HEAP_FLAG_SHARED; // Extra flag. | |
| D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo = {}; | |
| resAllocInfo.SizeInBytes = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; | |
| resAllocInfo.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; | |
| ResourceWithAllocation res; | |
| CHECK_HR( ctx.allocator->AllocateMemory(&allocDesc, &resAllocInfo, &res.allocation) ); | |
| // Must be created as separate allocation. | |
| CHECK_BOOL( res.allocation->GetOffset() == 0 ); | |
| } | |
| // 2. Committed resource with custom flags | |
| { | |
| D3D12_RESOURCE_DESC resourceDesc = {}; | |
| resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resourceDesc.Alignment = 0; | |
| resourceDesc.Width = 1920; | |
| resourceDesc.Height = 1080; | |
| resourceDesc.DepthOrArraySize = 1; | |
| resourceDesc.MipLevels = 1; | |
| resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resourceDesc.SampleDesc.Count = 1; | |
| resourceDesc.SampleDesc.Quality = 0; | |
| resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; | |
| resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_SHARED | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER; // Extra flags. | |
| ResourceWithAllocation res; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| NULL, | |
| &res.allocation, | |
| IID_PPV_ARGS(&res.resource)) ); | |
| // Must be created as committed. | |
| CHECK_BOOL( res.allocation->GetHeap() == NULL ); | |
| } | |
| } | |
| static void TestPlacedResources(const TestContext& ctx) | |
| { | |
| wprintf(L"Test placed resources\n"); | |
| const bool alwaysCommitted = (ctx.allocatorFlags & D3D12MA::ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0; | |
| const UINT count = 4; | |
| const UINT64 bufSize = 32ull * 1024; | |
| ResourceWithAllocation resources[count]; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, bufSize); | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &resources[i].allocation, | |
| IID_PPV_ARGS(&resources[i].resource)) ); | |
| // Make sure it doesn't have implicit heap. | |
| if(!alwaysCommitted) | |
| { | |
| CHECK_BOOL( resources[i].allocation->GetHeap() != NULL ); | |
| } | |
| } | |
| // Make sure at least some of the resources belong to the same heap, but their memory ranges don't overlap. | |
| bool sameHeapFound = false; | |
| for(size_t i = 0; i < count; ++i) | |
| { | |
| for(size_t j = i + 1; j < count; ++j) | |
| { | |
| const ResourceWithAllocation& resI = resources[i]; | |
| const ResourceWithAllocation& resJ = resources[j]; | |
| if(resI.allocation->GetHeap() != NULL && | |
| resI.allocation->GetHeap() == resJ.allocation->GetHeap()) | |
| { | |
| sameHeapFound = true; | |
| CHECK_BOOL(resI.allocation->GetOffset() + resI.allocation->GetSize() <= resJ.allocation->GetOffset() || | |
| resJ.allocation->GetOffset() + resJ.allocation->GetSize() <= resI.allocation->GetOffset()); | |
| } | |
| } | |
| } | |
| if(!alwaysCommitted) | |
| { | |
| CHECK_BOOL(sameHeapFound); | |
| } | |
| // Additionally create a texture to see if no error occurs due to bad handling of Resource Tier. | |
| resourceDesc = {}; | |
| resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resourceDesc.Alignment = 0; | |
| resourceDesc.Width = 1024; | |
| resourceDesc.Height = 1024; | |
| resourceDesc.DepthOrArraySize = 1; | |
| resourceDesc.MipLevels = 1; | |
| resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resourceDesc.SampleDesc.Count = 1; | |
| resourceDesc.SampleDesc.Quality = 0; | |
| resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; | |
| ResourceWithAllocation textureRes; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| NULL, | |
| &textureRes.allocation, | |
| IID_PPV_ARGS(&textureRes.resource)) ); | |
| // Additionally create an MSAA render target to see if no error occurs due to bad handling of Resource Tier. | |
| resourceDesc = {}; | |
| resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resourceDesc.Alignment = 0; | |
| resourceDesc.Width = 1920; | |
| resourceDesc.Height = 1080; | |
| resourceDesc.DepthOrArraySize = 1; | |
| resourceDesc.MipLevels = 1; | |
| resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resourceDesc.SampleDesc.Count = 2; | |
| resourceDesc.SampleDesc.Quality = 0; | |
| resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; | |
| ResourceWithAllocation renderTargetRes; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_RENDER_TARGET, | |
| NULL, | |
| &renderTargetRes.allocation, | |
| IID_PPV_ARGS(&renderTargetRes.resource)) ); | |
| } | |
| static void TestOtherComInterface(const TestContext& ctx) | |
| { | |
| wprintf(L"Test other COM interface\n"); | |
| D3D12_RESOURCE_DESC resDesc; | |
| FillResourceDescForBuffer(resDesc, 0x10000); | |
| for(uint32_t i = 0; i < 2; ++i) | |
| { | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| if(i == 1) | |
| { | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| } | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| ComPtr<ID3D12Pageable> pageable; | |
| CHECK_HR(ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resDesc, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| nullptr, // pOptimizedClearValue | |
| &alloc, | |
| IID_PPV_ARGS(&pageable))); | |
| // Do something with the interface to make sure it's valid. | |
| ComPtr<ID3D12Device> device; | |
| CHECK_HR(pageable->GetDevice(IID_PPV_ARGS(&device))); | |
| CHECK_BOOL(device.Get() == ctx.device); | |
| } | |
| } | |
| static void TestCustomPools(const TestContext& ctx) | |
| { | |
| wprintf(L"Test custom pools\n"); | |
| // # Fetch global stats 1 | |
| D3D12MA::TotalStatistics globalStatsBeg = {}; | |
| ctx.allocator->CalculateStatistics(&globalStatsBeg); | |
| // # Create pool, 1..2 blocks of 11 MB | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.BlockSize = 11 * MEGABYTE; | |
| poolDesc.MinBlockCount = 1; | |
| poolDesc.MaxBlockCount = 2; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR( ctx.allocator->CreatePool(&poolDesc, &pool) ); | |
| // # Validate stats for empty pool | |
| D3D12MA::DetailedStatistics poolStats = {}; | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL( poolStats.Stats.BlockCount == 1 ); | |
| CHECK_BOOL( poolStats.Stats.AllocationCount == 0 ); | |
| CHECK_BOOL( poolStats.Stats.AllocationBytes == 0 ); | |
| CHECK_BOOL( poolStats.Stats.BlockBytes - poolStats.Stats.AllocationBytes == | |
| poolStats.Stats.BlockCount * poolDesc.BlockSize ); | |
| // # SetName and GetName | |
| static const wchar_t* NAME = L"Custom pool name 1"; | |
| pool->SetName(NAME); | |
| CHECK_BOOL( wcscmp(pool->GetName(), NAME) == 0 ); | |
| // # Create buffers 2x 5 MB | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| allocDesc.ExtraHeapFlags = (D3D12_HEAP_FLAGS)0xCDCDCDCD; // Should be ignored. | |
| allocDesc.HeapType = (D3D12_HEAP_TYPE)0xCDCDCDCD; // Should be ignored. | |
| const UINT64 BUFFER_SIZE = 5 * MEGABYTE; | |
| D3D12_RESOURCE_DESC resDesc; | |
| FillResourceDescForBuffer(resDesc, BUFFER_SIZE); | |
| ComPtr<D3D12MA::Allocation> allocs[4]; | |
| for(uint32_t i = 0; i < 2; ++i) | |
| { | |
| CHECK_HR( ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, // pOptimizedClearValue | |
| &allocs[i], | |
| __uuidof(ID3D12Resource), NULL) ); // riidResource, ppvResource | |
| } | |
| // # Validate pool stats now | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL( poolStats.Stats.BlockCount == 1 ); | |
| CHECK_BOOL( poolStats.Stats.AllocationCount == 2 ); | |
| CHECK_BOOL( poolStats.Stats.AllocationBytes == 2 * BUFFER_SIZE ); | |
| CHECK_BOOL( poolStats.Stats.BlockBytes - poolStats.Stats.AllocationBytes == | |
| poolDesc.BlockSize - poolStats.Stats.AllocationBytes ); | |
| // # Check that global stats are updated as well | |
| D3D12MA::TotalStatistics globalStatsCurr = {}; | |
| ctx.allocator->CalculateStatistics(&globalStatsCurr); | |
| CHECK_BOOL( globalStatsCurr.Total.Stats.AllocationCount == | |
| globalStatsBeg.Total.Stats.AllocationCount + poolStats.Stats.AllocationCount ); | |
| CHECK_BOOL( globalStatsCurr.Total.Stats.BlockCount == | |
| globalStatsBeg.Total.Stats.BlockCount + poolStats.Stats.BlockCount ); | |
| CHECK_BOOL( globalStatsCurr.Total.Stats.AllocationBytes == | |
| globalStatsBeg.Total.Stats.AllocationBytes + poolStats.Stats.AllocationBytes ); | |
| // # NEVER_ALLOCATE and COMMITTED should fail | |
| // (Committed allocations not allowed in this pool because BlockSize != 0.) | |
| for(uint32_t i = 0; i < 2; ++i) | |
| { | |
| allocDesc.Flags = i == 0 ? | |
| D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE: | |
| D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| const HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, // pOptimizedClearValue | |
| &alloc, | |
| __uuidof(ID3D12Resource), NULL); // riidResource, ppvResource | |
| CHECK_BOOL( FAILED(hr) ); | |
| } | |
| // # 3 more buffers. 3rd should fail. | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; | |
| for(uint32_t i = 2; i < 5; ++i) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, // pOptimizedClearValue | |
| &alloc, | |
| __uuidof(ID3D12Resource), NULL); // riidResource, ppvResource | |
| if(i < 4) | |
| { | |
| CHECK_HR( hr ); | |
| allocs[i] = std::move(alloc); | |
| } | |
| else | |
| { | |
| CHECK_BOOL( FAILED(hr) ); | |
| } | |
| } | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL( poolStats.Stats.BlockCount == 2 ); | |
| CHECK_BOOL( poolStats.Stats.AllocationCount == 4 ); | |
| CHECK_BOOL( poolStats.Stats.AllocationBytes == 4 * BUFFER_SIZE ); | |
| CHECK_BOOL( poolStats.Stats.BlockBytes - poolStats.Stats.AllocationBytes == | |
| poolStats.Stats.BlockCount * poolDesc.BlockSize - poolStats.Stats.AllocationBytes ); | |
| // # Make room, AllocateMemory, CreateAliasingResource | |
| allocs[3].Reset(); | |
| allocs[0].Reset(); | |
| D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo = {}; | |
| resAllocInfo.SizeInBytes = 5 * MEGABYTE; | |
| resAllocInfo.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; | |
| CHECK_HR( ctx.allocator->AllocateMemory(&allocDesc, &resAllocInfo, &allocs[0]) ); | |
| resDesc.Width = 1 * MEGABYTE; | |
| ComPtr<ID3D12Resource> res; | |
| CHECK_HR( ctx.allocator->CreateAliasingResource(allocs[0].Get(), | |
| 0, // AllocationLocalOffset | |
| &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, // pOptimizedClearValue | |
| IID_PPV_ARGS(&res)) ); | |
| // JSON dump | |
| wchar_t* json = nullptr; | |
| ctx.allocator->BuildStatsString(&json, TRUE); | |
| ctx.allocator->FreeStatsString(json); | |
| } | |
| static void TestPoolsAndAllocationParameters(const TestContext& ctx) | |
| { | |
| wprintf(L"Test pools and allocation parameters\n"); | |
| ComPtr<D3D12MA::Pool> pool1, pool2; | |
| std::vector<ComPtr<D3D12MA::Allocation>> bufs; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| uint32_t totalNewAllocCount = 0, totalNewBlockCount = 0; | |
| D3D12MA::TotalStatistics statsBeg, statsEnd; | |
| ctx.allocator->CalculateStatistics(&statsBeg); | |
| HRESULT hr; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| // poolTypeI: | |
| // 0 = default pool | |
| // 1 = custom pool, default (flexible) block size and block count | |
| // 2 = custom pool, fixed block size and limited block count | |
| for(size_t poolTypeI = 0; poolTypeI < 3; ++poolTypeI) | |
| { | |
| if(poolTypeI == 0) | |
| { | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.CustomPool = nullptr; | |
| } | |
| else if(poolTypeI == 1) | |
| { | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| hr = ctx.allocator->CreatePool(&poolDesc, &pool1); | |
| CHECK_HR(hr); | |
| allocDesc.CustomPool = pool1.Get(); | |
| } | |
| else if(poolTypeI == 2) | |
| { | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.MaxBlockCount = 1; | |
| poolDesc.BlockSize = 2 * MEGABYTE + MEGABYTE / 2; // 2.5 MB | |
| hr = ctx.allocator->CreatePool(&poolDesc, &pool2); | |
| CHECK_HR(hr); | |
| allocDesc.CustomPool = pool2.Get(); | |
| } | |
| uint32_t poolAllocCount = 0, poolBlockCount = 0; | |
| D3D12_RESOURCE_DESC resDesc; | |
| FillResourceDescForBuffer(resDesc, MEGABYTE); | |
| // Default parameters | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; | |
| hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, &alloc, IID_NULL, nullptr); | |
| CHECK_BOOL(SUCCEEDED(hr) && alloc && alloc->GetResource()); | |
| ID3D12Heap* const defaultAllocHeap = alloc->GetHeap(); | |
| const UINT64 defaultAllocOffset = alloc->GetOffset(); | |
| bufs.push_back(std::move(alloc)); | |
| ++poolAllocCount; | |
| // COMMITTED. Should not try pool2 as it may assert on invalid call. | |
| if(poolTypeI != 2) | |
| { | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, &alloc, IID_NULL, nullptr); | |
| CHECK_BOOL(SUCCEEDED(hr) && alloc && alloc->GetResource()); | |
| CHECK_BOOL(alloc->GetOffset() == 0); // Committed | |
| CHECK_BOOL(alloc->GetHeap() == nullptr); // Committed | |
| bufs.push_back(std::move(alloc)); | |
| ++poolAllocCount; | |
| } | |
| // NEVER_ALLOCATE #1 | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE; | |
| hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, &alloc, IID_NULL, nullptr); | |
| CHECK_BOOL(SUCCEEDED(hr) && alloc && alloc->GetResource()); | |
| CHECK_BOOL(alloc->GetHeap() == defaultAllocHeap); // Same memory block as default one. | |
| CHECK_BOOL(alloc->GetOffset() != defaultAllocOffset); | |
| bufs.push_back(std::move(alloc)); | |
| ++poolAllocCount; | |
| // NEVER_ALLOCATE #2. Should fail in pool2 as it has no space. | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE; | |
| hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, &alloc, IID_NULL, nullptr); | |
| if(poolTypeI == 2) | |
| CHECK_BOOL(FAILED(hr)); | |
| else | |
| { | |
| CHECK_BOOL(SUCCEEDED(hr) && alloc && alloc->GetResource()); | |
| bufs.push_back(std::move(alloc)); | |
| ++poolAllocCount; | |
| } | |
| // Pool stats | |
| switch(poolTypeI) | |
| { | |
| case 0: poolBlockCount = 1; break; // At least 1 added for dedicated allocation. | |
| case 1: poolBlockCount = 2; break; // 1 for custom pool block and 1 for dedicated allocation. | |
| case 2: poolBlockCount = 1; break; // Only custom pool, no dedicated allocation. | |
| } | |
| if(poolTypeI > 0) | |
| { | |
| D3D12MA::DetailedStatistics poolStats = {}; | |
| (poolTypeI == 2 ? pool2 : pool1)->CalculateStatistics(&poolStats); | |
| CHECK_BOOL(poolStats.Stats.AllocationCount == poolAllocCount); | |
| CHECK_BOOL(poolStats.Stats.AllocationBytes == poolAllocCount * MEGABYTE); | |
| CHECK_BOOL(poolStats.Stats.BlockCount == poolBlockCount); | |
| } | |
| totalNewAllocCount += poolAllocCount; | |
| totalNewBlockCount += poolBlockCount; | |
| } | |
| ctx.allocator->CalculateStatistics(&statsEnd); | |
| CHECK_BOOL(statsEnd.Total.Stats.AllocationCount == | |
| statsBeg.Total.Stats.AllocationCount + totalNewAllocCount); | |
| CHECK_BOOL(statsEnd.Total.Stats.BlockCount >= | |
| statsBeg.Total.Stats.BlockCount + totalNewBlockCount); | |
| CHECK_BOOL(statsEnd.Total.Stats.AllocationBytes == | |
| statsBeg.Total.Stats.AllocationBytes + totalNewAllocCount * MEGABYTE); | |
| } | |
| static void TestCustomPool_MinAllocationAlignment(const TestContext& ctx) | |
| { | |
| wprintf(L"Test custom pool MinAllocationAlignment\n"); | |
| const UINT64 BUFFER_SIZE = 32; | |
| constexpr size_t BUFFER_COUNT = 4; | |
| const UINT64 MIN_ALIGNMENT = 128 * 1024; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.MinAllocationAlignment = MIN_ALIGNMENT; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR( ctx.allocator->CreatePool(&poolDesc, &pool) ); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| D3D12_RESOURCE_DESC resDesc; | |
| FillResourceDescForBuffer(resDesc, BUFFER_SIZE); | |
| ComPtr<D3D12MA::Allocation> allocs[BUFFER_COUNT]; | |
| for(size_t i = 0; i < BUFFER_COUNT; ++i) | |
| { | |
| CHECK_HR( ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, // pOptimizedClearValue | |
| &allocs[i], | |
| IID_NULL, NULL) ); // riidResource, ppvResource | |
| CHECK_BOOL(allocs[i]->GetOffset() % MIN_ALIGNMENT == 0); | |
| } | |
| } | |
| static void TestCustomPool_Committed(const TestContext& ctx) | |
| { | |
| wprintf(L"Test custom pool committed\n"); | |
| const UINT64 BUFFER_SIZE = 32; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR( ctx.allocator->CreatePool(&poolDesc, &pool) ); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| D3D12_RESOURCE_DESC resDesc; | |
| FillResourceDescForBuffer(resDesc, BUFFER_SIZE); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR( ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| NULL, // pOptimizedClearValue | |
| &alloc, | |
| IID_NULL, NULL) ); // riidResource, ppvResource | |
| CHECK_BOOL(alloc->GetHeap() == NULL); | |
| CHECK_BOOL(alloc->GetResource() != NULL); | |
| CHECK_BOOL(alloc->GetOffset() == 0); | |
| } | |
| static HRESULT TestCustomHeap(const TestContext& ctx, const D3D12_HEAP_PROPERTIES& heapProps) | |
| { | |
| D3D12MA::TotalStatistics globalStatsBeg = {}; | |
| ctx.allocator->CalculateStatistics(&globalStatsBeg); | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties = heapProps; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.BlockSize = 10 * MEGABYTE; | |
| poolDesc.MinBlockCount = 1; | |
| poolDesc.MaxBlockCount = 1; | |
| const UINT64 BUFFER_SIZE = 1 * MEGABYTE; | |
| ComPtr<D3D12MA::Pool> pool; | |
| HRESULT hr = ctx.allocator->CreatePool(&poolDesc, &pool); | |
| if(SUCCEEDED(hr)) | |
| { | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| D3D12_RESOURCE_DESC resDesc; | |
| FillResourceDescForBuffer(resDesc, BUFFER_SIZE); | |
| // Pool already allocated a block. We don't expect CreatePlacedResource to fail. | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR( ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| NULL, // pOptimizedClearValue | |
| &alloc, | |
| __uuidof(ID3D12Resource), NULL) ); // riidResource, ppvResource | |
| D3D12MA::TotalStatistics globalStatsCurr = {}; | |
| ctx.allocator->CalculateStatistics(&globalStatsCurr); | |
| // Make sure it is accounted only in CUSTOM heap not any of the standard heaps. | |
| CHECK_BOOL(memcmp(&globalStatsCurr.HeapType[0], &globalStatsBeg.HeapType[0], sizeof(D3D12MA::DetailedStatistics)) == 0); | |
| CHECK_BOOL(memcmp(&globalStatsCurr.HeapType[1], &globalStatsBeg.HeapType[1], sizeof(D3D12MA::DetailedStatistics)) == 0); | |
| CHECK_BOOL(memcmp(&globalStatsCurr.HeapType[2], &globalStatsBeg.HeapType[2], sizeof(D3D12MA::DetailedStatistics)) == 0); | |
| CHECK_BOOL( globalStatsCurr.HeapType[3].Stats.AllocationCount == globalStatsBeg.HeapType[3].Stats.AllocationCount + 1 ); | |
| CHECK_BOOL( globalStatsCurr.HeapType[3].Stats.BlockCount == globalStatsBeg.HeapType[3].Stats.BlockCount + 1 ); | |
| CHECK_BOOL( globalStatsCurr.HeapType[3].Stats.AllocationBytes == globalStatsBeg.HeapType[3].Stats.AllocationBytes + BUFFER_SIZE ); | |
| CHECK_BOOL( globalStatsCurr.Total.Stats.AllocationCount == globalStatsBeg.Total.Stats.AllocationCount + 1 ); | |
| CHECK_BOOL( globalStatsCurr.Total.Stats.BlockCount == globalStatsBeg.Total.Stats.BlockCount + 1 ); | |
| CHECK_BOOL( globalStatsCurr.Total.Stats.AllocationBytes == globalStatsBeg.Total.Stats.AllocationBytes + BUFFER_SIZE ); | |
| // Map and write some data. | |
| if(heapProps.CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE || | |
| heapProps.CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_BACK) | |
| { | |
| ID3D12Resource* const res = alloc->GetResource(); | |
| UINT* mappedPtr = nullptr; | |
| const D3D12_RANGE readRange = {0, 0}; | |
| CHECK_HR(res->Map(0, &readRange, (void**)&mappedPtr)); | |
| *mappedPtr = 0xDEADC0DE; | |
| res->Unmap(0, nullptr); | |
| } | |
| } | |
| return hr; | |
| } | |
| static void TestCustomHeaps(const TestContext& ctx) | |
| { | |
| wprintf(L"Test custom heap\n"); | |
| D3D12_HEAP_PROPERTIES heapProps = {}; | |
| // Use custom pool but the same as READBACK, which should be always available. | |
| heapProps.Type = D3D12_HEAP_TYPE_CUSTOM; | |
| heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_BACK; | |
| heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_L0; // System memory | |
| HRESULT hr = TestCustomHeap(ctx, heapProps); | |
| CHECK_HR(hr); | |
| } | |
| static void TestStandardCustomCommittedPlaced(const TestContext& ctx) | |
| { | |
| wprintf(L"Test standard, custom, committed, placed\n"); | |
| static const D3D12_HEAP_TYPE heapType = D3D12_HEAP_TYPE_DEFAULT; | |
| static const UINT64 bufferSize = 1024; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = heapType; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| D3D12MA::TotalStatistics statsBeg = {}; | |
| D3D12MA::DetailedStatistics poolStatInfoBeg = {}; | |
| ctx.allocator->CalculateStatistics(&statsBeg); | |
| pool->CalculateStatistics(&poolStatInfoBeg); | |
| size_t poolAllocCount = 0; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| FillResourceDescForBuffer(resDesc, bufferSize); | |
| for(uint32_t standardCustomI = 0; standardCustomI < 2; ++standardCustomI) | |
| { | |
| const bool useCustomPool = standardCustomI > 0; | |
| for(uint32_t flagsI = 0; flagsI < 3; ++flagsI) | |
| { | |
| const bool useCommitted = flagsI > 0; | |
| const bool neverAllocate = flagsI > 1; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| if(useCustomPool) | |
| { | |
| allocDesc.CustomPool = pool.Get(); | |
| allocDesc.HeapType = (D3D12_HEAP_TYPE)0xCDCDCDCD; // Should be ignored. | |
| allocDesc.ExtraHeapFlags = (D3D12_HEAP_FLAGS)0xCDCDCDCD; // Should be ignored. | |
| } | |
| else | |
| allocDesc.HeapType = heapType; | |
| if(useCommitted) | |
| allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| if(neverAllocate) | |
| allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE; | |
| ComPtr<D3D12MA::Allocation> allocPtr = NULL; | |
| HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| NULL, // pOptimizedClearValue | |
| &allocPtr, IID_NULL, NULL); | |
| CHECK_BOOL(SUCCEEDED(hr) == (allocPtr != NULL)); | |
| if(allocPtr) | |
| { | |
| allocations.push_back(allocPtr); | |
| if(useCustomPool) | |
| ++poolAllocCount; | |
| } | |
| bool expectSuccess = !neverAllocate; // NEVER_ALLOCATE should always fail with COMMITTED. | |
| CHECK_BOOL(expectSuccess == SUCCEEDED(hr)); | |
| if(SUCCEEDED(hr) && useCommitted) | |
| { | |
| CHECK_BOOL(allocPtr->GetHeap() == NULL); // Committed allocation has implicit heap. | |
| } | |
| } | |
| } | |
| D3D12MA::TotalStatistics statsEnd = {}; | |
| D3D12MA::DetailedStatistics poolStatInfoEnd = {}; | |
| ctx.allocator->CalculateStatistics(&statsEnd); | |
| pool->CalculateStatistics(&poolStatInfoEnd); | |
| CHECK_BOOL(statsEnd.Total.Stats.AllocationCount == statsBeg.Total.Stats.AllocationCount + allocations.size()); | |
| CHECK_BOOL(statsEnd.Total.Stats.AllocationBytes >= statsBeg.Total.Stats.AllocationBytes + allocations.size() * bufferSize); | |
| CHECK_BOOL(statsEnd.HeapType[0].Stats.AllocationCount == statsBeg.HeapType[0].Stats.AllocationCount + allocations.size()); | |
| CHECK_BOOL(statsEnd.HeapType[0].Stats.AllocationBytes >= statsBeg.HeapType[0].Stats.AllocationBytes + allocations.size() * bufferSize); | |
| CHECK_BOOL(poolStatInfoEnd.Stats.AllocationCount == poolStatInfoBeg.Stats.AllocationCount + poolAllocCount); | |
| CHECK_BOOL(poolStatInfoEnd.Stats.AllocationBytes >= poolStatInfoBeg.Stats.AllocationBytes + poolAllocCount * bufferSize); | |
| } | |
| static void TestAliasingMemory(const TestContext& ctx) | |
| { | |
| wprintf(L"Test aliasing memory\n"); | |
| D3D12_RESOURCE_DESC resDesc1 = {}; | |
| resDesc1.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resDesc1.Alignment = 0; | |
| resDesc1.Width = 1920; | |
| resDesc1.Height = 1080; | |
| resDesc1.DepthOrArraySize = 1; | |
| resDesc1.MipLevels = 1; | |
| resDesc1.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resDesc1.SampleDesc.Count = 1; | |
| resDesc1.SampleDesc.Quality = 0; | |
| resDesc1.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resDesc1.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; | |
| D3D12_RESOURCE_DESC resDesc2 = {}; | |
| resDesc2.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resDesc2.Alignment = 0; | |
| resDesc2.Width = 1024; | |
| resDesc2.Height = 1024; | |
| resDesc2.DepthOrArraySize = 1; | |
| resDesc2.MipLevels = 0; | |
| resDesc2.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resDesc2.SampleDesc.Count = 1; | |
| resDesc2.SampleDesc.Quality = 0; | |
| resDesc2.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resDesc2.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; | |
| const D3D12_RESOURCE_ALLOCATION_INFO allocInfo1 = | |
| ctx.device->GetResourceAllocationInfo(0, 1, &resDesc1); | |
| const D3D12_RESOURCE_ALLOCATION_INFO allocInfo2 = | |
| ctx.device->GetResourceAllocationInfo(0, 1, &resDesc2); | |
| D3D12_RESOURCE_ALLOCATION_INFO finalAllocInfo = {}; | |
| finalAllocInfo.Alignment = std::max(allocInfo1.Alignment, allocInfo2.Alignment); | |
| finalAllocInfo.SizeInBytes = std::max(allocInfo1.SizeInBytes, allocInfo2.SizeInBytes); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR( ctx.allocator->AllocateMemory(&allocDesc, &finalAllocInfo, &alloc) ); | |
| CHECK_BOOL(alloc != NULL && alloc->GetHeap() != NULL); | |
| ComPtr<ID3D12Resource> res1; | |
| CHECK_HR( ctx.allocator->CreateAliasingResource( | |
| alloc.Get(), | |
| 0, // AllocationLocalOffset | |
| &resDesc1, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| NULL, // pOptimizedClearValue | |
| IID_PPV_ARGS(&res1)) ); | |
| CHECK_BOOL(res1 != NULL); | |
| ComPtr<ID3D12Resource> res2; | |
| CHECK_HR( ctx.allocator->CreateAliasingResource( | |
| alloc.Get(), | |
| 0, // AllocationLocalOffset | |
| &resDesc2, | |
| D3D12_RESOURCE_STATE_COMMON, | |
| NULL, // pOptimizedClearValue | |
| IID_PPV_ARGS(&res2)) ); | |
| CHECK_BOOL(res2 != NULL); | |
| // You can use res1 and res2, but not at the same time! | |
| } | |
| static void TestAliasingImplicitCommitted(const TestContext& ctx) | |
| { | |
| wprintf(L"Test aliasing implicit dedicated\n"); | |
| // The buffer will be large enough to be allocated as committed. | |
| // We still need it to have an explicit heap to be able to alias. | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| FillResourceDescForBuffer(resDesc, 300 * MEGABYTE); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_CAN_ALIAS; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, NULL, | |
| &alloc, IID_NULL, NULL)); | |
| CHECK_BOOL(alloc != NULL && alloc->GetHeap() != NULL); | |
| resDesc.Width = 200 * MEGABYTE; | |
| ComPtr<ID3D12Resource> aliasingRes; | |
| CHECK_HR(ctx.allocator->CreateAliasingResource(alloc.Get(), | |
| 0, // AllocationLocalOffset | |
| &resDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&aliasingRes))); | |
| CHECK_BOOL(aliasingRes != NULL); | |
| } | |
| static void TestPoolMsaaTextureAsCommitted(const TestContext& ctx) | |
| { | |
| wprintf(L"Test MSAA texture always as committed in pool\n"); | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.Flags = D3D12MA::POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resDesc.Width = 1024; | |
| resDesc.Height = 512; | |
| resDesc.DepthOrArraySize = 1; | |
| resDesc.MipLevels = 1; | |
| resDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resDesc.SampleDesc.Count = 2; | |
| resDesc.SampleDesc.Quality = 0; | |
| resDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_RENDER_TARGET, nullptr, &alloc, IID_NULL, nullptr)); | |
| // Committed allocation should not have explicit heap | |
| CHECK_BOOL(alloc->GetHeap() == nullptr); | |
| } | |
| static void TestMapping(const TestContext& ctx) | |
| { | |
| wprintf(L"Test mapping\n"); | |
| const UINT count = 10; | |
| const UINT64 bufSize = 32ull * 1024; | |
| ResourceWithAllocation resources[count]; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, bufSize); | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &resources[i].allocation, | |
| IID_PPV_ARGS(&resources[i].resource)) ); | |
| void* mappedPtr = NULL; | |
| CHECK_HR( resources[i].resource->Map(0, &EMPTY_RANGE, &mappedPtr) ); | |
| FillData(mappedPtr, bufSize, i); | |
| // Unmap every other buffer. Leave others mapped. | |
| if((i % 2) != 0) | |
| { | |
| resources[i].resource->Unmap(0, NULL); | |
| } | |
| } | |
| } | |
| static inline bool StatisticsEqual(const D3D12MA::DetailedStatistics& lhs, const D3D12MA::DetailedStatistics& rhs) | |
| { | |
| return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; | |
| } | |
| static void CheckStatistics(const D3D12MA::DetailedStatistics& stats) | |
| { | |
| CHECK_BOOL(stats.Stats.AllocationBytes <= stats.Stats.BlockBytes); | |
| if(stats.Stats.AllocationBytes > 0) | |
| { | |
| CHECK_BOOL(stats.Stats.AllocationCount > 0); | |
| CHECK_BOOL(stats.AllocationSizeMin <= stats.AllocationSizeMax); | |
| } | |
| if(stats.UnusedRangeCount > 0) | |
| { | |
| CHECK_BOOL(stats.UnusedRangeSizeMax > 0); | |
| CHECK_BOOL(stats.UnusedRangeSizeMin <= stats.UnusedRangeSizeMax); | |
| } | |
| } | |
| static void TestStats(const TestContext& ctx) | |
| { | |
| wprintf(L"Test stats\n"); | |
| D3D12MA::TotalStatistics begStats = {}; | |
| ctx.allocator->CalculateStatistics(&begStats); | |
| const UINT count = 10; | |
| const UINT64 bufSize = 64ull * 1024; | |
| ResourceWithAllocation resources[count]; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, bufSize); | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| if(i == count / 2) | |
| allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &resources[i].allocation, | |
| IID_PPV_ARGS(&resources[i].resource)) ); | |
| } | |
| D3D12MA::TotalStatistics endStats = {}; | |
| ctx.allocator->CalculateStatistics(&endStats); | |
| CHECK_BOOL(endStats.Total.Stats.BlockCount >= begStats.Total.Stats.BlockCount); | |
| CHECK_BOOL(endStats.Total.Stats.AllocationCount == begStats.Total.Stats.AllocationCount + count); | |
| CHECK_BOOL(endStats.Total.Stats.AllocationBytes == begStats.Total.Stats.AllocationBytes + count * bufSize); | |
| CHECK_BOOL(endStats.Total.AllocationSizeMin <= bufSize); | |
| CHECK_BOOL(endStats.Total.AllocationSizeMax >= bufSize); | |
| CHECK_BOOL(endStats.HeapType[1].Stats.BlockCount >= begStats.HeapType[1].Stats.BlockCount); | |
| CHECK_BOOL(endStats.HeapType[1].Stats.AllocationCount >= begStats.HeapType[1].Stats.AllocationCount + count); | |
| CHECK_BOOL(endStats.HeapType[1].Stats.AllocationBytes >= begStats.HeapType[1].Stats.AllocationBytes + count * bufSize); | |
| CHECK_BOOL(endStats.HeapType[1].AllocationSizeMin <= bufSize); | |
| CHECK_BOOL(endStats.HeapType[1].AllocationSizeMax >= bufSize); | |
| CHECK_BOOL(StatisticsEqual(begStats.HeapType[0], endStats.HeapType[0])); | |
| CHECK_BOOL(StatisticsEqual(begStats.HeapType[2], endStats.HeapType[2])); | |
| CheckStatistics(endStats.Total); | |
| CheckStatistics(endStats.HeapType[0]); | |
| CheckStatistics(endStats.HeapType[1]); | |
| CheckStatistics(endStats.HeapType[2]); | |
| D3D12MA::Budget localBudget = {}, nonLocalBudget = {}; | |
| ctx.allocator->GetBudget(&localBudget, &nonLocalBudget); | |
| CHECK_BOOL(localBudget.Stats.AllocationBytes <= localBudget.Stats.BlockBytes); | |
| CHECK_BOOL(endStats.HeapType[3].Stats.BlockCount == 0); // No allocation from D3D12_HEAP_TYPE_CUSTOM in this test. | |
| if(!ctx.allocator->IsUMA()) | |
| { | |
| // Discrete GPU | |
| CHECK_BOOL(localBudget.Stats.AllocationBytes == endStats.HeapType[0].Stats.AllocationBytes); | |
| CHECK_BOOL(localBudget.Stats.BlockBytes == endStats.HeapType[0].Stats.BlockBytes); | |
| CHECK_BOOL(nonLocalBudget.Stats.AllocationBytes <= nonLocalBudget.Stats.BlockBytes); | |
| CHECK_BOOL(nonLocalBudget.Stats.AllocationBytes == endStats.HeapType[1].Stats.AllocationBytes + endStats.HeapType[2].Stats.AllocationBytes); | |
| CHECK_BOOL(nonLocalBudget.Stats.BlockBytes == | |
| endStats.HeapType[1].Stats.BlockBytes + endStats.HeapType[2].Stats.BlockBytes); | |
| } | |
| else | |
| { | |
| // Integrated GPU - all memory is local | |
| CHECK_BOOL(localBudget.Stats.AllocationBytes == endStats.HeapType[0].Stats.AllocationBytes + | |
| endStats.HeapType[1].Stats.AllocationBytes + | |
| endStats.HeapType[2].Stats.AllocationBytes); | |
| CHECK_BOOL(localBudget.Stats.BlockBytes == endStats.HeapType[0].Stats.BlockBytes + | |
| endStats.HeapType[1].Stats.BlockBytes + | |
| endStats.HeapType[2].Stats.BlockBytes); | |
| CHECK_BOOL(nonLocalBudget.Stats.AllocationBytes == 0); | |
| CHECK_BOOL(nonLocalBudget.Stats.BlockBytes == 0); | |
| } | |
| } | |
| static void TestTransfer(const TestContext& ctx) | |
| { | |
| wprintf(L"Test mapping\n"); | |
| const UINT count = 10; | |
| const UINT64 bufSize = 32ull * 1024; | |
| ResourceWithAllocation resourcesUpload[count]; | |
| ResourceWithAllocation resourcesDefault[count]; | |
| ResourceWithAllocation resourcesReadback[count]; | |
| D3D12MA::ALLOCATION_DESC allocDescUpload = {}; | |
| allocDescUpload.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| D3D12MA::ALLOCATION_DESC allocDescDefault = {}; | |
| allocDescDefault.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| D3D12MA::ALLOCATION_DESC allocDescReadback = {}; | |
| allocDescReadback.HeapType = D3D12_HEAP_TYPE_READBACK; | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, bufSize); | |
| // Create 3 sets of resources. | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescUpload, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &resourcesUpload[i].allocation, | |
| IID_PPV_ARGS(&resourcesUpload[i].resource)) ); | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescDefault, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| NULL, | |
| &resourcesDefault[i].allocation, | |
| IID_PPV_ARGS(&resourcesDefault[i].resource)) ); | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescReadback, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| NULL, | |
| &resourcesReadback[i].allocation, | |
| IID_PPV_ARGS(&resourcesReadback[i].resource)) ); | |
| } | |
| // Map and fill data in UPLOAD. | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( resourcesUpload[i].resource->Map(0, &EMPTY_RANGE, &mappedPtr) ); | |
| FillData(mappedPtr, bufSize, i); | |
| // Unmap every other resource, leave others mapped. | |
| if((i % 2) != 0) | |
| { | |
| resourcesUpload[i].resource->Unmap(0, NULL); | |
| } | |
| } | |
| // Transfer from UPLOAD to DEFAULT, from there to READBACK. | |
| ID3D12GraphicsCommandList* cmdList = BeginCommandList(); | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| cmdList->CopyBufferRegion(resourcesDefault[i].resource.Get(), 0, resourcesUpload[i].resource.Get(), 0, bufSize); | |
| } | |
| D3D12_RESOURCE_BARRIER barriers[count] = {}; | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| barriers[i].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; | |
| barriers[i].Transition.pResource = resourcesDefault[i].resource.Get(); | |
| barriers[i].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; | |
| barriers[i].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; | |
| barriers[i].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; | |
| } | |
| cmdList->ResourceBarrier(count, barriers); | |
| for(UINT i = 0; i < count; ++i) | |
| { | |
| cmdList->CopyBufferRegion(resourcesReadback[i].resource.Get(), 0, resourcesDefault[i].resource.Get(), 0, bufSize); | |
| } | |
| EndCommandList(cmdList); | |
| // Validate READBACK buffers. | |
| for(UINT i = count; i--; ) | |
| { | |
| const D3D12_RANGE mapRange = {0, bufSize}; | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( resourcesReadback[i].resource->Map(0, &mapRange, &mappedPtr) ); | |
| CHECK_BOOL( ValidateData(mappedPtr, bufSize, i) ); | |
| // Unmap every 3rd resource, leave others mapped. | |
| if((i % 3) != 0) | |
| { | |
| resourcesReadback[i].resource->Unmap(0, &EMPTY_RANGE); | |
| } | |
| } | |
| } | |
| static void TestZeroInitialized(const TestContext& ctx) | |
| { | |
| wprintf(L"Test zero initialized\n"); | |
| const UINT64 bufSize = 128ull * 1024; | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, bufSize); | |
| // # Create upload buffer and fill it with data. | |
| D3D12MA::ALLOCATION_DESC allocDescUpload = {}; | |
| allocDescUpload.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| ResourceWithAllocation bufUpload; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescUpload, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &bufUpload.allocation, | |
| IID_PPV_ARGS(&bufUpload.resource)) ); | |
| { | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( bufUpload.resource->Map(0, &EMPTY_RANGE, &mappedPtr) ); | |
| FillData(mappedPtr, bufSize, 5236245); | |
| bufUpload.resource->Unmap(0, NULL); | |
| } | |
| // # Create readback buffer | |
| D3D12MA::ALLOCATION_DESC allocDescReadback = {}; | |
| allocDescReadback.HeapType = D3D12_HEAP_TYPE_READBACK; | |
| ResourceWithAllocation bufReadback; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescReadback, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_DEST, | |
| NULL, | |
| &bufReadback.allocation, | |
| IID_PPV_ARGS(&bufReadback.resource)) ); | |
| auto CheckBufferData = [&](const ResourceWithAllocation& buf) | |
| { | |
| const bool shouldBeZero = buf.allocation->WasZeroInitialized() != FALSE; | |
| { | |
| ID3D12GraphicsCommandList* cmdList = BeginCommandList(); | |
| cmdList->CopyBufferRegion(bufReadback.resource.Get(), 0, buf.resource.Get(), 0, bufSize); | |
| EndCommandList(cmdList); | |
| } | |
| bool isZero = false; | |
| { | |
| const D3D12_RANGE readRange{0, bufSize}; // I could pass pReadRange = NULL but it generates D3D Debug layer warning: EXECUTION WARNING #930: MAP_INVALID_NULLRANGE | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( bufReadback.resource->Map(0, &readRange, &mappedPtr) ); | |
| isZero = ValidateDataZero(mappedPtr, bufSize); | |
| bufReadback.resource->Unmap(0, &EMPTY_RANGE); | |
| } | |
| wprintf(L"Should be zero: %u, is zero: %u\n", shouldBeZero ? 1 : 0, isZero ? 1 : 0); | |
| if(shouldBeZero) | |
| { | |
| CHECK_BOOL(isZero); | |
| } | |
| }; | |
| // # Test 1: Committed resource. Should always be zero initialized. | |
| { | |
| D3D12MA::ALLOCATION_DESC allocDescDefault = {}; | |
| allocDescDefault.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDescDefault.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| ResourceWithAllocation bufDefault; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescDefault, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_SOURCE, | |
| NULL, | |
| &bufDefault.allocation, | |
| IID_PPV_ARGS(&bufDefault.resource)) ); | |
| wprintf(L" Committed: "); | |
| CheckBufferData(bufDefault); | |
| CHECK_BOOL( bufDefault.allocation->WasZeroInitialized() ); | |
| } | |
| // # Test 2: (Probably) placed resource. | |
| ResourceWithAllocation bufDefault; | |
| for(uint32_t i = 0; i < 2; ++i) | |
| { | |
| // 1. Create buffer | |
| D3D12MA::ALLOCATION_DESC allocDescDefault = {}; | |
| allocDescDefault.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDescDefault, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_COPY_SOURCE, | |
| NULL, | |
| &bufDefault.allocation, | |
| IID_PPV_ARGS(&bufDefault.resource)) ); | |
| // 2. Check it | |
| wprintf(L" Normal #%u: ", i); | |
| CheckBufferData(bufDefault); | |
| // 3. Upload some data to it | |
| { | |
| ID3D12GraphicsCommandList* cmdList = BeginCommandList(); | |
| D3D12_RESOURCE_BARRIER barrier = {}; | |
| barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; | |
| barrier.Transition.pResource = bufDefault.resource.Get(); | |
| barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; | |
| barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; | |
| barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; | |
| cmdList->ResourceBarrier(1, &barrier); | |
| cmdList->CopyBufferRegion(bufDefault.resource.Get(), 0, bufUpload.resource.Get(), 0, bufSize); | |
| EndCommandList(cmdList); | |
| } | |
| // 4. Delete it | |
| bufDefault.Reset(); | |
| } | |
| } | |
| static void TestMultithreading(const TestContext& ctx) | |
| { | |
| wprintf(L"Test multithreading\n"); | |
| const UINT threadCount = 32; | |
| const UINT bufSizeMin = 1024ull; | |
| const UINT bufSizeMax = 1024ull * 1024; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| // Launch threads. | |
| std::thread threads[threadCount]; | |
| for(UINT threadIndex = 0; threadIndex < threadCount; ++threadIndex) | |
| { | |
| auto threadFunc = [&, threadIndex]() | |
| { | |
| RandomNumberGenerator rand(threadIndex); | |
| std::vector<ResourceWithAllocation> resources; | |
| resources.reserve(256); | |
| // Create starting number of buffers. | |
| const UINT bufToCreateCount = 32; | |
| for(UINT bufIndex = 0; bufIndex < bufToCreateCount; ++bufIndex) | |
| { | |
| ResourceWithAllocation res = {}; | |
| res.dataSeed = (threadIndex << 16) | bufIndex; | |
| res.size = AlignUp<UINT>(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 16); | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, res.size); | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &res.allocation, | |
| IID_PPV_ARGS(&res.resource)) ); | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( res.resource->Map(0, &EMPTY_RANGE, &mappedPtr) ); | |
| FillData(mappedPtr, res.size, res.dataSeed); | |
| // Unmap some of them, leave others mapped. | |
| if(rand.GenerateBool()) | |
| { | |
| res.resource->Unmap(0, NULL); | |
| } | |
| resources.push_back(std::move(res)); | |
| } | |
| Sleep(20); | |
| // Make a number of random allocate and free operations. | |
| const UINT operationCount = 128; | |
| for(UINT operationIndex = 0; operationIndex < operationCount; ++operationIndex) | |
| { | |
| const bool removePossible = !resources.empty(); | |
| const bool remove = removePossible && rand.GenerateBool(); | |
| if(remove) | |
| { | |
| const UINT indexToRemove = rand.Generate() % resources.size(); | |
| resources.erase(resources.begin() + indexToRemove); | |
| } | |
| else // Create new buffer. | |
| { | |
| ResourceWithAllocation res = {}; | |
| res.dataSeed = (threadIndex << 16) | operationIndex; | |
| res.size = AlignUp<UINT>(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 16); | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, res.size); | |
| CHECK_HR( ctx.allocator->CreateResource( | |
| &allocDesc, | |
| &resourceDesc, | |
| D3D12_RESOURCE_STATE_GENERIC_READ, | |
| NULL, | |
| &res.allocation, | |
| IID_PPV_ARGS(&res.resource)) ); | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( res.resource->Map(0, NULL, &mappedPtr) ); | |
| FillData(mappedPtr, res.size, res.dataSeed); | |
| // Unmap some of them, leave others mapped. | |
| if(rand.GenerateBool()) | |
| { | |
| res.resource->Unmap(0, NULL); | |
| } | |
| resources.push_back(std::move(res)); | |
| } | |
| } | |
| Sleep(20); | |
| // Validate data in all remaining buffers while deleting them. | |
| for(size_t resIndex = resources.size(); resIndex--; ) | |
| { | |
| void* mappedPtr = nullptr; | |
| CHECK_HR( resources[resIndex].resource->Map(0, NULL, &mappedPtr) ); | |
| ValidateData(mappedPtr, resources[resIndex].size, resources[resIndex].dataSeed); | |
| // Unmap some of them, leave others mapped. | |
| if((resIndex % 3) == 1) | |
| { | |
| resources[resIndex].resource->Unmap(0, &EMPTY_RANGE); | |
| } | |
| resources.pop_back(); | |
| } | |
| }; | |
| threads[threadIndex] = std::thread(threadFunc); | |
| } | |
| // Wait for threads to finish. | |
| for(UINT threadIndex = threadCount; threadIndex--; ) | |
| { | |
| threads[threadIndex].join(); | |
| } | |
| } | |
| static bool IsProtectedResourceSessionSupported(const TestContext& ctx) | |
| { | |
| D3D12_FEATURE_DATA_PROTECTED_RESOURCE_SESSION_SUPPORT support = {}; | |
| CHECK_HR(ctx.device->CheckFeatureSupport( | |
| D3D12_FEATURE_PROTECTED_RESOURCE_SESSION_SUPPORT, &support, sizeof support)); | |
| return support.Support > D3D12_PROTECTED_RESOURCE_SESSION_SUPPORT_FLAG_NONE; | |
| } | |
| static void TestLinearAllocator(const TestContext& ctx) | |
| { | |
| wprintf(L"Test linear allocator\n"); | |
| RandomNumberGenerator rand{ 645332 }; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.Flags = D3D12MA::POOL_FLAG_ALGORITHM_LINEAR; | |
| poolDesc.BlockSize = 64 * KILOBYTE * 300; // Alignment of buffers is always 64KB | |
| poolDesc.MinBlockCount = poolDesc.MaxBlockCount = 1; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12_RESOURCE_DESC buffDesc = {}; | |
| FillResourceDescForBuffer(buffDesc, 0); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| constexpr size_t maxBufCount = 100; | |
| struct BufferInfo | |
| { | |
| ComPtr<ID3D12Resource> Buffer; | |
| ComPtr<D3D12MA::Allocation> Allocation; | |
| }; | |
| std::vector<BufferInfo> buffInfo; | |
| constexpr UINT64 bufSizeMin = 16; | |
| constexpr UINT64 bufSizeMax = 1024; | |
| UINT64 prevOffset = 0; | |
| // Test one-time free. | |
| for (size_t i = 0; i < 2; ++i) | |
| { | |
| // Allocate number of buffers of varying size that surely fit into this block. | |
| UINT64 bufSumSize = 0; | |
| UINT64 allocSumSize = 0; | |
| for (size_t i = 0; i < maxBufCount; ++i) | |
| { | |
| buffDesc.Width = AlignUp<UINT64>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| CHECK_BOOL(i == 0 || offset > prevOffset); | |
| prevOffset = offset; | |
| bufSumSize += buffDesc.Width; | |
| allocSumSize += newBuffInfo.Allocation->GetSize(); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| // Validate pool stats. | |
| D3D12MA::DetailedStatistics stats; | |
| pool->CalculateStatistics(&stats); | |
| CHECK_BOOL(stats.Stats.BlockBytes - stats.Stats.AllocationBytes == poolDesc.BlockSize - allocSumSize); | |
| CHECK_BOOL(allocSumSize >= bufSumSize); | |
| CHECK_BOOL(stats.Stats.AllocationCount == buffInfo.size()); | |
| // Destroy the buffers in random order. | |
| while (!buffInfo.empty()) | |
| { | |
| const size_t indexToDestroy = rand.Generate() % buffInfo.size(); | |
| buffInfo.erase(buffInfo.begin() + indexToDestroy); | |
| } | |
| } | |
| // Test stack. | |
| { | |
| // Allocate number of buffers of varying size that surely fit into this block. | |
| for (size_t i = 0; i < maxBufCount; ++i) | |
| { | |
| buffDesc.Width = AlignUp<UINT64>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| CHECK_BOOL(i == 0 || offset > prevOffset); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| prevOffset = offset; | |
| } | |
| // Destroy few buffers from top of the stack. | |
| for (size_t i = 0; i < maxBufCount / 5; ++i) | |
| buffInfo.pop_back(); | |
| // Create some more | |
| for (size_t i = 0; i < maxBufCount / 5; ++i) | |
| { | |
| buffDesc.Width = AlignUp<UINT64>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| CHECK_BOOL(i == 0 || offset > prevOffset); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| prevOffset = offset; | |
| } | |
| // Destroy the buffers in reverse order. | |
| while (!buffInfo.empty()) | |
| buffInfo.pop_back(); | |
| } | |
| // Test ring buffer. | |
| { | |
| // Allocate number of buffers that surely fit into this block. | |
| buffDesc.Width = bufSizeMax; | |
| for (size_t i = 0; i < maxBufCount; ++i) | |
| { | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| CHECK_BOOL(i == 0 || offset > prevOffset); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| prevOffset = offset; | |
| } | |
| // Free and allocate new buffers so many times that we make sure we wrap-around at least once. | |
| const size_t buffersPerIter = maxBufCount / 10 - 1; | |
| const size_t iterCount = poolDesc.BlockSize / buffDesc.Width / buffersPerIter * 2; | |
| for (size_t iter = 0; iter < iterCount; ++iter) | |
| { | |
| buffInfo.erase(buffInfo.begin(), buffInfo.begin() + buffersPerIter); | |
| for (size_t bufPerIter = 0; bufPerIter < buffersPerIter; ++bufPerIter) | |
| { | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| } | |
| // Allocate buffers until we reach out-of-memory. | |
| UINT32 debugIndex = 0; | |
| while (true) | |
| { | |
| BufferInfo newBuffInfo; | |
| HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer)); | |
| ++debugIndex; | |
| if (SUCCEEDED(hr)) | |
| { | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| else | |
| { | |
| CHECK_BOOL(hr == E_OUTOFMEMORY); | |
| break; | |
| } | |
| } | |
| // Destroy the buffers in random order. | |
| while (!buffInfo.empty()) | |
| { | |
| const size_t indexToDestroy = rand.Generate() % buffInfo.size(); | |
| buffInfo.erase(buffInfo.begin() + indexToDestroy); | |
| } | |
| } | |
| // Test double stack. | |
| { | |
| // Allocate number of buffers of varying size that surely fit into this block, alternate from bottom/top. | |
| UINT64 prevOffsetLower = 0; | |
| UINT64 prevOffsetUpper = poolDesc.BlockSize; | |
| for (size_t i = 0; i < maxBufCount; ++i) | |
| { | |
| const bool upperAddress = (i % 2) != 0; | |
| if (upperAddress) | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS; | |
| else | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; | |
| buffDesc.Width = AlignUp<UINT64>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| if (upperAddress) | |
| { | |
| CHECK_BOOL(offset < prevOffsetUpper); | |
| prevOffsetUpper = offset; | |
| } | |
| else | |
| { | |
| CHECK_BOOL(offset >= prevOffsetLower); | |
| prevOffsetLower = offset; | |
| } | |
| CHECK_BOOL(prevOffsetLower < prevOffsetUpper); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| // Destroy few buffers from top of the stack. | |
| for (size_t i = 0; i < maxBufCount / 5; ++i) | |
| buffInfo.pop_back(); | |
| // Create some more | |
| for (size_t i = 0; i < maxBufCount / 5; ++i) | |
| { | |
| const bool upperAddress = (i % 2) != 0; | |
| if (upperAddress) | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS; | |
| else | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; | |
| buffDesc.Width = AlignUp<UINT64>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| // Destroy the buffers in reverse order. | |
| while (!buffInfo.empty()) | |
| buffInfo.pop_back(); | |
| // Create buffers on both sides until we reach out of memory. | |
| prevOffsetLower = 0; | |
| prevOffsetUpper = poolDesc.BlockSize; | |
| for (size_t i = 0; true; ++i) | |
| { | |
| const bool upperAddress = (i % 2) != 0; | |
| if (upperAddress) | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS; | |
| else | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE; | |
| buffDesc.Width = AlignUp<UINT64>(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16); | |
| BufferInfo newBuffInfo; | |
| HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer)); | |
| if (SUCCEEDED(hr)) | |
| { | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| if (upperAddress) | |
| { | |
| CHECK_BOOL(offset < prevOffsetUpper); | |
| prevOffsetUpper = offset; | |
| } | |
| else | |
| { | |
| CHECK_BOOL(offset >= prevOffsetLower); | |
| prevOffsetLower = offset; | |
| } | |
| CHECK_BOOL(prevOffsetLower < prevOffsetUpper); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| else | |
| break; | |
| } | |
| // Destroy the buffers in random order. | |
| while (!buffInfo.empty()) | |
| { | |
| const size_t indexToDestroy = rand.Generate() % buffInfo.size(); | |
| buffInfo.erase(buffInfo.begin() + indexToDestroy); | |
| } | |
| // Create buffers on upper side only, constant size, until we reach out of memory. | |
| prevOffsetUpper = poolDesc.BlockSize; | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS; | |
| buffDesc.Width = bufSizeMax; | |
| while (true) | |
| { | |
| BufferInfo newBuffInfo; | |
| HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer)); | |
| if (SUCCEEDED(hr)) | |
| { | |
| const UINT64 offset = newBuffInfo.Allocation->GetOffset(); | |
| CHECK_BOOL(offset < prevOffsetUpper); | |
| prevOffsetUpper = offset; | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| else | |
| break; | |
| } | |
| // Destroy the buffers in reverse order. | |
| while (!buffInfo.empty()) | |
| { | |
| const BufferInfo& currBufInfo = buffInfo.back(); | |
| buffInfo.pop_back(); | |
| } | |
| } | |
| } | |
| static void TestLinearAllocatorMultiBlock(const TestContext& ctx) | |
| { | |
| wprintf(L"Test linear allocator multi block\n"); | |
| RandomNumberGenerator rand{ 345673 }; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.Flags = D3D12MA::POOL_FLAG_ALGORITHM_LINEAR; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12_RESOURCE_DESC buffDesc = {}; | |
| FillResourceDescForBuffer(buffDesc, 1024 * 1024); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| struct BufferInfo | |
| { | |
| ComPtr<ID3D12Resource> Buffer; | |
| ComPtr<D3D12MA::Allocation> Allocation; | |
| }; | |
| std::vector<BufferInfo> buffInfo; | |
| // Test one-time free. | |
| { | |
| // Allocate buffers until we move to a second block. | |
| ID3D12Heap* lastHeap = nullptr; | |
| while (true) | |
| { | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| ID3D12Heap* heap = newBuffInfo.Allocation->GetHeap(); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| if (lastHeap && heap != lastHeap) | |
| { | |
| break; | |
| } | |
| lastHeap = heap; | |
| } | |
| CHECK_BOOL(buffInfo.size() > 2); | |
| // Make sure that pool has now two blocks. | |
| D3D12MA::DetailedStatistics poolStats = {}; | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL(poolStats.Stats.BlockCount == 2); | |
| // Destroy all the buffers in random order. | |
| while (!buffInfo.empty()) | |
| { | |
| const size_t indexToDestroy = rand.Generate() % buffInfo.size(); | |
| buffInfo.erase(buffInfo.begin() + indexToDestroy); | |
| } | |
| // Make sure that pool has now at most one block. | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL(poolStats.Stats.BlockCount <= 1); | |
| } | |
| // Test stack. | |
| { | |
| // Allocate buffers until we move to a second block. | |
| ID3D12Heap* lastHeap = nullptr; | |
| while (true) | |
| { | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| ID3D12Heap* heap = newBuffInfo.Allocation->GetHeap(); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| if (lastHeap && heap != lastHeap) | |
| { | |
| break; | |
| } | |
| lastHeap = heap; | |
| } | |
| CHECK_BOOL(buffInfo.size() > 2); | |
| // Add few more buffers. | |
| for (UINT32 i = 0; i < 5; ++i) | |
| { | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| } | |
| // Make sure that pool has now two blocks. | |
| D3D12MA::DetailedStatistics poolStats = {}; | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL(poolStats.Stats.BlockCount == 2); | |
| // Delete half of buffers, LIFO. | |
| for (size_t i = 0, countToDelete = buffInfo.size() / 2; i < countToDelete; ++i) | |
| buffInfo.pop_back(); | |
| // Add one more buffer. | |
| BufferInfo newBuffInfo; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| // Make sure that pool has now one block. | |
| pool->CalculateStatistics(&poolStats); | |
| CHECK_BOOL(poolStats.Stats.BlockCount == 1); | |
| // Delete all the remaining buffers, LIFO. | |
| while (!buffInfo.empty()) | |
| buffInfo.pop_back(); | |
| } | |
| } | |
| static void ManuallyTestLinearAllocator(const TestContext& ctx) | |
| { | |
| wprintf(L"Manually test linear allocator\n"); | |
| RandomNumberGenerator rand{ 645332 }; | |
| D3D12MA::TotalStatistics origStats; | |
| ctx.allocator->CalculateStatistics(&origStats); | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.Flags = D3D12MA::POOL_FLAG_ALGORITHM_LINEAR; | |
| poolDesc.BlockSize = 6 * 64 * KILOBYTE; | |
| poolDesc.MinBlockCount = poolDesc.MaxBlockCount = 1; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12_RESOURCE_DESC buffDesc = {}; | |
| FillResourceDescForBuffer(buffDesc, 0); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| struct BufferInfo | |
| { | |
| ComPtr<ID3D12Resource> Buffer; | |
| ComPtr<D3D12MA::Allocation> Allocation; | |
| }; | |
| std::vector<BufferInfo> buffInfo; | |
| BufferInfo newBuffInfo; | |
| // Test double stack. | |
| { | |
| /* | |
| Lower: Buffer 32 B, Buffer 1024 B, Buffer 32 B | |
| Upper: Buffer 16 B, Buffer 1024 B, Buffer 128 B | |
| Totally: | |
| 1 block allocated | |
| 393Â 216 DirectX 12 bytes | |
| 6 new allocations | |
| 2256 bytes in allocations (384 KB according to alignment) | |
| */ | |
| buffDesc.Width = 32; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| buffDesc.Width = 1024; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| buffDesc.Width = 32; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS; | |
| buffDesc.Width = 128; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| buffDesc.Width = 1024; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| buffDesc.Width = 16; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &buffDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &newBuffInfo.Allocation, IID_PPV_ARGS(&newBuffInfo.Buffer))); | |
| buffInfo.push_back(std::move(newBuffInfo)); | |
| D3D12MA::TotalStatistics currStats; | |
| ctx.allocator->CalculateStatistics(&currStats); | |
| D3D12MA::DetailedStatistics poolStats; | |
| pool->CalculateStatistics(&poolStats); | |
| WCHAR* statsStr = nullptr; | |
| ctx.allocator->BuildStatsString(&statsStr, FALSE); | |
| // PUT BREAKPOINT HERE TO CHECK. | |
| // Inspect: currStats versus origStats, poolStats, statsStr. | |
| int I = 0; | |
| ctx.allocator->FreeStatsString(statsStr); | |
| // Destroy the buffers in reverse order. | |
| while (!buffInfo.empty()) | |
| buffInfo.pop_back(); | |
| } | |
| } | |
| static void BenchmarkAlgorithmsCase(const TestContext& ctx, | |
| FILE* file, | |
| D3D12MA::POOL_FLAGS algorithm, | |
| bool empty, | |
| FREE_ORDER freeOrder) | |
| { | |
| RandomNumberGenerator rand{ 16223 }; | |
| const UINT64 bufSize = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; | |
| const size_t maxBufCapacity = 10000; | |
| const UINT32 iterationCount = 10; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.BlockSize = bufSize * maxBufCapacity; | |
| poolDesc.Flags |= algorithm; | |
| poolDesc.MinBlockCount = poolDesc.MaxBlockCount = 1; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12_RESOURCE_ALLOCATION_INFO allocInfo = {}; | |
| allocInfo.SizeInBytes = bufSize; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| std::vector<ComPtr<D3D12MA::Allocation>> baseAllocations; | |
| const size_t allocCount = maxBufCapacity / 3; | |
| if (!empty) | |
| { | |
| // Make allocations up to 1/3 of pool size. | |
| for (UINT64 i = 0; i < allocCount; ++i) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->AllocateMemory(&allocDesc, &allocInfo, &alloc)); | |
| baseAllocations.push_back(std::move(alloc)); | |
| } | |
| // Delete half of them, choose randomly. | |
| size_t allocsToDelete = baseAllocations.size() / 2; | |
| for (size_t i = 0; i < allocsToDelete; ++i) | |
| { | |
| const size_t index = (size_t)rand.Generate() % baseAllocations.size(); | |
| baseAllocations.erase(baseAllocations.begin() + index); | |
| } | |
| } | |
| // BENCHMARK | |
| std::vector<ComPtr<D3D12MA::Allocation>> testAllocations; | |
| duration allocTotalDuration = duration::zero(); | |
| duration freeTotalDuration = duration::zero(); | |
| for (uint32_t iterationIndex = 0; iterationIndex < iterationCount; ++iterationIndex) | |
| { | |
| testAllocations.reserve(allocCount); | |
| // Allocations | |
| time_point allocTimeBeg = std::chrono::high_resolution_clock::now(); | |
| for (size_t i = 0; i < allocCount; ++i) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->AllocateMemory(&allocDesc, &allocInfo, &alloc)); | |
| testAllocations.push_back(std::move(alloc)); | |
| } | |
| allocTotalDuration += std::chrono::high_resolution_clock::now() - allocTimeBeg; | |
| // Deallocations | |
| switch (freeOrder) | |
| { | |
| case FREE_ORDER::FORWARD: | |
| // Leave testAllocations unchanged. | |
| break; | |
| case FREE_ORDER::BACKWARD: | |
| std::reverse(testAllocations.begin(), testAllocations.end()); | |
| break; | |
| case FREE_ORDER::RANDOM: | |
| std::shuffle(testAllocations.begin(), testAllocations.end(), MyUniformRandomNumberGenerator(rand)); | |
| break; | |
| default: assert(0); | |
| } | |
| time_point freeTimeBeg = std::chrono::high_resolution_clock::now(); | |
| testAllocations.clear(); | |
| freeTotalDuration += std::chrono::high_resolution_clock::now() - freeTimeBeg; | |
| } | |
| // Delete baseAllocations | |
| baseAllocations.clear(); | |
| const float allocTotalSeconds = ToFloatSeconds(allocTotalDuration); | |
| const float freeTotalSeconds = ToFloatSeconds(freeTotalDuration); | |
| printf(" Algorithm=%s %s FreeOrder=%s: allocations %g s, free %g s\n", | |
| AlgorithmToStr(algorithm), | |
| empty ? "Empty" : "Not empty", | |
| FREE_ORDER_NAMES[(size_t)freeOrder], | |
| allocTotalSeconds, | |
| freeTotalSeconds); | |
| if (file) | |
| { | |
| std::string currTime; | |
| CurrentTimeToStr(currTime); | |
| fprintf(file, "%s,%s,%s,%u,%s,%g,%g\n", | |
| CODE_DESCRIPTION, currTime.c_str(), | |
| AlgorithmToStr(algorithm), | |
| empty ? 1 : 0, | |
| FREE_ORDER_NAMES[(uint32_t)freeOrder], | |
| allocTotalSeconds, | |
| freeTotalSeconds); | |
| } | |
| } | |
| static void BenchmarkAlgorithms(const TestContext& ctx, FILE* file) | |
| { | |
| wprintf(L"Benchmark algorithms\n"); | |
| if (file) | |
| { | |
| fprintf(file, | |
| "Code,Time," | |
| "Algorithm,Empty,Free order," | |
| "Allocation time (s),Deallocation time (s)\n"); | |
| } | |
| UINT32 freeOrderCount = 1; | |
| if (ConfigType >= CONFIG_TYPE::CONFIG_TYPE_LARGE) | |
| freeOrderCount = 3; | |
| else if (ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL) | |
| freeOrderCount = 2; | |
| const UINT32 emptyCount = ConfigType >= CONFIG_TYPE::CONFIG_TYPE_SMALL ? 2 : 1; | |
| for (UINT32 freeOrderIndex = 0; freeOrderIndex < freeOrderCount; ++freeOrderIndex) | |
| { | |
| FREE_ORDER freeOrder = FREE_ORDER::COUNT; | |
| switch (freeOrderIndex) | |
| { | |
| case 0: freeOrder = FREE_ORDER::BACKWARD; break; | |
| case 1: freeOrder = FREE_ORDER::FORWARD; break; | |
| case 2: freeOrder = FREE_ORDER::RANDOM; break; | |
| default: assert(0); | |
| } | |
| for (UINT32 emptyIndex = 0; emptyIndex < emptyCount; ++emptyIndex) | |
| { | |
| for (UINT32 algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex) | |
| { | |
| D3D12MA::POOL_FLAGS algorithm; | |
| switch (algorithmIndex) | |
| { | |
| case 0: | |
| algorithm = D3D12MA::POOL_FLAG_NONE; | |
| break; | |
| case 1: | |
| algorithm = D3D12MA::POOL_FLAG_ALGORITHM_LINEAR; | |
| break; | |
| default: | |
| assert(0); | |
| } | |
| BenchmarkAlgorithmsCase(ctx, | |
| file, | |
| algorithm, | |
| (emptyIndex == 0), // empty | |
| freeOrder); | |
| } | |
| } | |
| } | |
| } | |
| #ifdef __ID3D12Device4_INTERFACE_DEFINED__ | |
| static void TestDevice4(const TestContext& ctx) | |
| { | |
| wprintf(L"Test ID3D12Device4\n"); | |
| if(!IsProtectedResourceSessionSupported(ctx)) | |
| { | |
| wprintf(L"D3D12_FEATURE_PROTECTED_RESOURCE_SESSION_SUPPORT returned no support for protected resource session.\n"); | |
| return; | |
| } | |
| ComPtr<ID3D12Device4> dev4; | |
| HRESULT hr = ctx.device->QueryInterface(IID_PPV_ARGS(&dev4)); | |
| if(FAILED(hr)) | |
| { | |
| wprintf(L"QueryInterface for ID3D12Device4 FAILED.\n"); | |
| return; | |
| } | |
| D3D12_PROTECTED_RESOURCE_SESSION_DESC sessionDesc = {}; | |
| ComPtr<ID3D12ProtectedResourceSession> session; | |
| // This fails on the SOFTWARE adapter. | |
| hr = dev4->CreateProtectedResourceSession(&sessionDesc, IID_PPV_ARGS(&session)); | |
| if(FAILED(hr)) | |
| { | |
| wprintf(L"ID3D12Device4::CreateProtectedResourceSession FAILED.\n"); | |
| return; | |
| } | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; | |
| poolDesc.pProtectedSession = session.Get(); | |
| poolDesc.MinAllocationAlignment = 0; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| ComPtr<D3D12MA::Pool> pool; | |
| hr = ctx.allocator->CreatePool(&poolDesc, &pool); | |
| if(FAILED(hr)) | |
| { | |
| wprintf(L"Failed to create custom pool.\n"); | |
| return; | |
| } | |
| D3D12_RESOURCE_DESC resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, 1024); | |
| for(UINT testIndex = 0; testIndex < 2; ++testIndex) | |
| { | |
| // Create a buffer | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| if(testIndex == 0) | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| ComPtr<D3D12MA::Allocation> bufAlloc; | |
| ComPtr<ID3D12Resource> bufRes; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resourceDesc, | |
| D3D12_RESOURCE_STATE_COMMON, NULL, | |
| &bufAlloc, IID_PPV_ARGS(&bufRes))); | |
| CHECK_BOOL(bufAlloc && bufAlloc->GetResource() == bufRes.Get()); | |
| // Make sure it's (not) committed. | |
| CHECK_BOOL((bufAlloc->GetHeap() == NULL) == (testIndex == 0)); | |
| // Allocate memory/heap | |
| // Temporarily disabled on NVIDIA as it causes BSOD on RTX2080Ti driver 461.40. | |
| if(g_AdapterDesc.VendorId != VENDOR_ID_NVIDIA) | |
| { | |
| D3D12_RESOURCE_ALLOCATION_INFO heapAllocInfo = { | |
| D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT * 2, // SizeInBytes | |
| D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, // Alignment | |
| }; | |
| ComPtr<D3D12MA::Allocation> memAlloc; | |
| CHECK_HR(ctx.allocator->AllocateMemory(&allocDesc, &heapAllocInfo, &memAlloc)); | |
| CHECK_BOOL(memAlloc->GetHeap()); | |
| } | |
| } | |
| } | |
| #endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ | |
| #ifdef __ID3D12Device8_INTERFACE_DEFINED__ | |
| static void TestDevice8(const TestContext& ctx) | |
| { | |
| wprintf(L"Test ID3D12Device8\n"); | |
| ComPtr<ID3D12Device8> dev8; | |
| CHECK_HR(ctx.device->QueryInterface(IID_PPV_ARGS(&dev8))); | |
| D3D12_RESOURCE_DESC1 resourceDesc; | |
| FillResourceDescForBuffer(resourceDesc, 1024 * 1024); | |
| // Create a committed buffer | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| ComPtr<D3D12MA::Allocation> allocPtr0; | |
| ComPtr<ID3D12Resource> res0; | |
| CHECK_HR(ctx.allocator->CreateResource2(&allocDesc, &resourceDesc, | |
| D3D12_RESOURCE_STATE_COMMON, NULL, | |
| &allocPtr0, IID_PPV_ARGS(&res0))); | |
| CHECK_BOOL(allocPtr0->GetHeap() == NULL); | |
| // Create a placed buffer | |
| allocDesc.Flags &= ~D3D12MA::ALLOCATION_FLAG_COMMITTED; | |
| ComPtr<D3D12MA::Allocation> allocPtr1; | |
| ComPtr<ID3D12Resource> res1; | |
| CHECK_HR(ctx.allocator->CreateResource2(&allocDesc, &resourceDesc, | |
| D3D12_RESOURCE_STATE_COMMON, NULL, | |
| &allocPtr1, IID_PPV_ARGS(&res1))); | |
| CHECK_BOOL(allocPtr1->GetHeap()!= NULL); | |
| } | |
| #endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ | |
| static void TestVirtualBlocks(const TestContext& ctx) | |
| { | |
| wprintf(L"Test virtual blocks\n"); | |
| using namespace D3D12MA; | |
| const UINT64 blockSize = 16 * MEGABYTE; | |
| const UINT64 alignment = 256; | |
| // # Create block 16 MB | |
| ComPtr<D3D12MA::VirtualBlock> block; | |
| VIRTUAL_BLOCK_DESC blockDesc = {}; | |
| blockDesc.pAllocationCallbacks = ctx.allocationCallbacks; | |
| blockDesc.Size = blockSize; | |
| CHECK_HR(CreateVirtualBlock(&blockDesc, &block)); | |
| CHECK_BOOL(block); | |
| // # Allocate 8 MB | |
| VIRTUAL_ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.Alignment = alignment; | |
| allocDesc.pPrivateData = (void*)(uintptr_t)1; | |
| allocDesc.Size = 8 * MEGABYTE; | |
| VirtualAllocation alloc0; | |
| CHECK_HR(block->Allocate(&allocDesc, &alloc0, nullptr)); | |
| // # Validate the allocation | |
| VIRTUAL_ALLOCATION_INFO alloc0Info = {}; | |
| block->GetAllocationInfo(alloc0, &alloc0Info); | |
| CHECK_BOOL(alloc0Info.Offset < blockSize); | |
| CHECK_BOOL(alloc0Info.Size == allocDesc.Size); | |
| CHECK_BOOL(alloc0Info.pPrivateData == allocDesc.pPrivateData); | |
| // # Check SetUserData | |
| block->SetAllocationPrivateData(alloc0, (void*)(uintptr_t)2); | |
| block->GetAllocationInfo(alloc0, &alloc0Info); | |
| CHECK_BOOL(alloc0Info.pPrivateData == (void*)(uintptr_t)2); | |
| // # Allocate 4 MB | |
| allocDesc.Size = 4 * MEGABYTE; | |
| allocDesc.Alignment = alignment; | |
| VirtualAllocation alloc1; | |
| CHECK_HR(block->Allocate(&allocDesc, &alloc1, nullptr)); | |
| VIRTUAL_ALLOCATION_INFO alloc1Info = {}; | |
| block->GetAllocationInfo(alloc1, &alloc1Info); | |
| CHECK_BOOL(alloc1Info.Offset < blockSize); | |
| CHECK_BOOL(alloc1Info.Offset + 4 * MEGABYTE <= alloc0Info.Offset || alloc0Info.Offset + 8 * MEGABYTE <= alloc1Info.Offset); // Check if they don't overlap. | |
| // # Allocate another 8 MB - it should fail | |
| allocDesc.Size = 8 * MEGABYTE; | |
| allocDesc.Alignment = alignment; | |
| VirtualAllocation alloc2; | |
| CHECK_BOOL(FAILED(block->Allocate(&allocDesc, &alloc2, nullptr))); | |
| CHECK_BOOL(alloc2.AllocHandle == (AllocHandle)0); | |
| // # Free the 4 MB block. Now allocation of 8 MB should succeed. | |
| block->FreeAllocation(alloc1); | |
| UINT64 alloc2Offset; | |
| CHECK_HR(block->Allocate(&allocDesc, &alloc2, &alloc2Offset)); | |
| CHECK_BOOL(alloc2Offset < blockSize); | |
| CHECK_BOOL(alloc2Offset + 4 * MEGABYTE <= alloc0Info.Offset || alloc0Info.Offset + 8 * MEGABYTE <= alloc2Offset); // Check if they don't overlap. | |
| // # Calculate statistics | |
| DetailedStatistics statInfo = {}; | |
| block->CalculateStatistics(&statInfo); | |
| CHECK_BOOL(statInfo.Stats.AllocationCount == 2); | |
| CHECK_BOOL(statInfo.Stats.BlockCount == 1); | |
| CHECK_BOOL(statInfo.Stats.AllocationBytes == blockSize); | |
| CHECK_BOOL(statInfo.Stats.BlockBytes == blockSize); | |
| // # Generate JSON dump | |
| WCHAR* json = nullptr; | |
| block->BuildStatsString(&json); | |
| { | |
| std::wstring str(json); | |
| CHECK_BOOL(str.find(L"\"CustomData\": 1") != std::wstring::npos); | |
| CHECK_BOOL(str.find(L"\"CustomData\": 2") != std::wstring::npos); | |
| } | |
| block->FreeStatsString(json); | |
| // # Free alloc0, leave alloc2 unfreed. | |
| block->FreeAllocation(alloc0); | |
| // # Test alignment | |
| { | |
| constexpr size_t allocCount = 10; | |
| VirtualAllocation allocs[allocCount] = {}; | |
| for (size_t i = 0; i < allocCount; ++i) | |
| { | |
| const bool alignment0 = i == allocCount - 1; | |
| allocDesc.Size = i * 3 + 15; | |
| allocDesc.Alignment = alignment0 ? 0 : 8; | |
| UINT64 offset; | |
| CHECK_HR(block->Allocate(&allocDesc, &allocs[i], &offset)); | |
| if (!alignment0) | |
| { | |
| CHECK_BOOL(offset % allocDesc.Alignment == 0); | |
| } | |
| } | |
| for (size_t i = allocCount; i--; ) | |
| { | |
| block->FreeAllocation(allocs[i]); | |
| } | |
| } | |
| // # Final cleanup | |
| block->FreeAllocation(alloc2); | |
| } | |
| static void TestVirtualBlocksAlgorithms(const TestContext& ctx) | |
| { | |
| wprintf(L"Test virtual blocks algorithms\n"); | |
| RandomNumberGenerator rand{ 3454335 }; | |
| auto calcRandomAllocSize = [&rand]() -> UINT64 { return rand.Generate() % 20 + 5; }; | |
| for (size_t algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex) | |
| { | |
| // Create the block | |
| D3D12MA::VIRTUAL_BLOCK_DESC blockDesc = {}; | |
| blockDesc.pAllocationCallbacks = ctx.allocationCallbacks; | |
| blockDesc.Size = 10'000; | |
| switch (algorithmIndex) | |
| { | |
| case 0: blockDesc.Flags = D3D12MA::VIRTUAL_BLOCK_FLAG_NONE; break; | |
| case 1: blockDesc.Flags = D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR; break; | |
| } | |
| ComPtr<D3D12MA::VirtualBlock> block; | |
| CHECK_HR(D3D12MA::CreateVirtualBlock(&blockDesc, &block)); | |
| struct AllocData | |
| { | |
| D3D12MA::VirtualAllocation allocation; | |
| UINT64 allocOffset, requestedSize, allocationSize; | |
| }; | |
| std::vector<AllocData> allocations; | |
| // Make some allocations | |
| for (size_t i = 0; i < 20; ++i) | |
| { | |
| D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.Size = calcRandomAllocSize(); | |
| allocDesc.pPrivateData = (void*)(uintptr_t)(allocDesc.Size * 10); | |
| if (i < 10) {} | |
| else if (i < 20 && algorithmIndex == 1) allocDesc.Flags = D3D12MA::VIRTUAL_ALLOCATION_FLAG_UPPER_ADDRESS; | |
| AllocData alloc = {}; | |
| alloc.requestedSize = allocDesc.Size; | |
| CHECK_HR(block->Allocate(&allocDesc, &alloc.allocation, nullptr)); | |
| D3D12MA::VIRTUAL_ALLOCATION_INFO allocInfo; | |
| block->GetAllocationInfo(alloc.allocation, &allocInfo); | |
| CHECK_BOOL(allocInfo.Size >= allocDesc.Size); | |
| alloc.allocOffset = allocInfo.Offset; | |
| alloc.allocationSize = allocInfo.Size; | |
| allocations.push_back(alloc); | |
| } | |
| // Free some of the allocations | |
| for (size_t i = 0; i < 5; ++i) | |
| { | |
| const size_t index = rand.Generate() % allocations.size(); | |
| block->FreeAllocation(allocations[index].allocation); | |
| allocations.erase(allocations.begin() + index); | |
| } | |
| // Allocate some more | |
| for (size_t i = 0; i < 6; ++i) | |
| { | |
| D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.Size = calcRandomAllocSize(); | |
| allocDesc.pPrivateData = (void*)(uintptr_t)(allocDesc.Size * 10); | |
| AllocData alloc = {}; | |
| alloc.requestedSize = allocDesc.Size; | |
| CHECK_HR(block->Allocate(&allocDesc, &alloc.allocation, nullptr)); | |
| D3D12MA::VIRTUAL_ALLOCATION_INFO allocInfo; | |
| block->GetAllocationInfo(alloc.allocation, &allocInfo); | |
| CHECK_BOOL(allocInfo.Size >= allocDesc.Size); | |
| alloc.allocOffset = allocInfo.Offset; | |
| alloc.allocationSize = allocInfo.Size; | |
| allocations.push_back(alloc); | |
| } | |
| // Allocate some with extra alignment | |
| for (size_t i = 0; i < 3; ++i) | |
| { | |
| D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.Size = calcRandomAllocSize(); | |
| allocDesc.Alignment = 16; | |
| allocDesc.pPrivateData = (void*)(uintptr_t)(allocDesc.Size * 10); | |
| AllocData alloc = {}; | |
| alloc.requestedSize = allocDesc.Size; | |
| CHECK_HR(block->Allocate(&allocDesc, &alloc.allocation, nullptr)); | |
| D3D12MA::VIRTUAL_ALLOCATION_INFO allocInfo; | |
| block->GetAllocationInfo(alloc.allocation, &allocInfo); | |
| CHECK_BOOL(allocInfo.Offset % 16 == 0); | |
| CHECK_BOOL(allocInfo.Size >= allocDesc.Size); | |
| alloc.allocOffset = allocInfo.Offset; | |
| alloc.allocationSize = allocInfo.Size; | |
| allocations.push_back(alloc); | |
| } | |
| // Check if the allocations don't overlap | |
| std::sort(allocations.begin(), allocations.end(), [](const AllocData& lhs, const AllocData& rhs) { | |
| return lhs.allocOffset < rhs.allocOffset; }); | |
| for (size_t i = 0; i < allocations.size() - 1; ++i) | |
| { | |
| CHECK_BOOL(allocations[i + 1].allocOffset >= allocations[i].allocOffset + allocations[i].allocationSize); | |
| } | |
| // Check pPrivateData | |
| { | |
| const AllocData& alloc = allocations.back(); | |
| D3D12MA::VIRTUAL_ALLOCATION_INFO allocInfo; | |
| block->GetAllocationInfo(alloc.allocation, &allocInfo); | |
| CHECK_BOOL((uintptr_t)allocInfo.pPrivateData == alloc.requestedSize * 10); | |
| block->SetAllocationPrivateData(alloc.allocation, (void*)(uintptr_t)666); | |
| block->GetAllocationInfo(alloc.allocation, &allocInfo); | |
| CHECK_BOOL((uintptr_t)allocInfo.pPrivateData == 666); | |
| } | |
| // Calculate statistics | |
| { | |
| UINT64 actualAllocSizeMin = UINT64_MAX, actualAllocSizeMax = 0, actualAllocSizeSum = 0; | |
| std::for_each(allocations.begin(), allocations.end(), [&](const AllocData& a) { | |
| actualAllocSizeMin = std::min(actualAllocSizeMin, a.allocationSize); | |
| actualAllocSizeMax = std::max(actualAllocSizeMax, a.allocationSize); | |
| actualAllocSizeSum += a.allocationSize; | |
| }); | |
| D3D12MA::DetailedStatistics statInfo = {}; | |
| block->CalculateStatistics(&statInfo); | |
| CHECK_BOOL(statInfo.Stats.AllocationCount == allocations.size()); | |
| CHECK_BOOL(statInfo.Stats.BlockCount == 1); | |
| CHECK_BOOL(statInfo.Stats.BlockBytes == blockDesc.Size); | |
| CHECK_BOOL(statInfo.AllocationSizeMax == actualAllocSizeMax); | |
| CHECK_BOOL(statInfo.AllocationSizeMin == actualAllocSizeMin); | |
| CHECK_BOOL(statInfo.Stats.AllocationBytes >= actualAllocSizeSum); | |
| } | |
| // Build JSON dump string | |
| { | |
| WCHAR* json = nullptr; | |
| block->BuildStatsString(&json); | |
| int I = 0; // put a breakpoint here to debug | |
| block->FreeStatsString(json); | |
| } | |
| // Final cleanup | |
| block->Clear(); | |
| } | |
| } | |
| static void TestVirtualBlocksAlgorithmsBenchmark(const TestContext& ctx) | |
| { | |
| wprintf(L"Benchmark virtual blocks algorithms\n"); | |
| const size_t ALLOCATION_COUNT = 7200; | |
| const UINT32 MAX_ALLOC_SIZE = 2056; | |
| D3D12MA::VIRTUAL_BLOCK_DESC blockDesc = {}; | |
| blockDesc.pAllocationCallbacks = ctx.allocationCallbacks; | |
| blockDesc.Size = 0; | |
| RandomNumberGenerator rand{ 20092010 }; | |
| UINT32 allocSizes[ALLOCATION_COUNT]; | |
| for (size_t i = 0; i < ALLOCATION_COUNT; ++i) | |
| { | |
| allocSizes[i] = rand.Generate() % MAX_ALLOC_SIZE + 1; | |
| blockDesc.Size += allocSizes[i]; | |
| } | |
| blockDesc.Size = static_cast<UINT64>(blockDesc.Size * 1.5); // 50% size margin in case of alignment | |
| for (UINT8 alignmentIndex = 0; alignmentIndex < 4; ++alignmentIndex) | |
| { | |
| UINT64 alignment; | |
| switch (alignmentIndex) | |
| { | |
| case 0: alignment = 1; break; | |
| case 1: alignment = 16; break; | |
| case 2: alignment = 64; break; | |
| case 3: alignment = 256; break; | |
| default: assert(0); break; | |
| } | |
| printf(" Alignment=%llu\n", alignment); | |
| for (UINT8 algorithmIndex = 0; algorithmIndex < 2; ++algorithmIndex) | |
| { | |
| switch (algorithmIndex) | |
| { | |
| case 0: | |
| blockDesc.Flags = D3D12MA::VIRTUAL_BLOCK_FLAG_NONE; | |
| break; | |
| case 1: | |
| blockDesc.Flags = D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR; | |
| break; | |
| default: | |
| assert(0); | |
| } | |
| D3D12MA::VirtualAllocation allocs[ALLOCATION_COUNT]; | |
| ComPtr<D3D12MA::VirtualBlock> block; | |
| CHECK_HR(D3D12MA::CreateVirtualBlock(&blockDesc, &block)); | |
| duration allocDuration = duration::zero(); | |
| duration freeDuration = duration::zero(); | |
| // Alloc | |
| time_point timeBegin = std::chrono::high_resolution_clock::now(); | |
| for (size_t i = 0; i < ALLOCATION_COUNT; ++i) | |
| { | |
| D3D12MA::VIRTUAL_ALLOCATION_DESC allocCreateInfo = {}; | |
| allocCreateInfo.Size = allocSizes[i]; | |
| allocCreateInfo.Alignment = alignment; | |
| CHECK_HR(block->Allocate(&allocCreateInfo, allocs + i, nullptr)); | |
| } | |
| allocDuration += std::chrono::high_resolution_clock::now() - timeBegin; | |
| // Free | |
| timeBegin = std::chrono::high_resolution_clock::now(); | |
| for (size_t i = ALLOCATION_COUNT; i;) | |
| block->FreeAllocation(allocs[--i]); | |
| freeDuration += std::chrono::high_resolution_clock::now() - timeBegin; | |
| printf(" Algorithm=%s \tallocations %g s, \tfree %g s\n", | |
| VirtualAlgorithmToStr(blockDesc.Flags), | |
| ToFloatSeconds(allocDuration), | |
| ToFloatSeconds(freeDuration)); | |
| } | |
| printf("\n"); | |
| } | |
| } | |
| static void ProcessDefragmentationPass(const TestContext& ctx, D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO& stepInfo) | |
| { | |
| std::vector<D3D12_RESOURCE_BARRIER> startBarriers; | |
| std::vector<D3D12_RESOURCE_BARRIER> finalBarriers; | |
| bool defaultHeap = false; | |
| for (UINT32 i = 0; i < stepInfo.MoveCount; ++i) | |
| { | |
| if (stepInfo.pMoves[i].Operation == D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_COPY) | |
| { | |
| const bool isDefaultHeap = stepInfo.pMoves[i].pSrcAllocation->GetHeap()->GetDesc().Properties.Type == D3D12_HEAP_TYPE_DEFAULT; | |
| // Create new resource | |
| D3D12_RESOURCE_DESC desc = stepInfo.pMoves[i].pSrcAllocation->GetResource()->GetDesc(); | |
| ComPtr<ID3D12Resource> dstRes; | |
| CHECK_HR(ctx.device->CreatePlacedResource(stepInfo.pMoves[i].pDstTmpAllocation->GetHeap(), | |
| stepInfo.pMoves[i].pDstTmpAllocation->GetOffset(), &desc, | |
| isDefaultHeap ? D3D12_RESOURCE_STATE_COPY_DEST : D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, IID_PPV_ARGS(&dstRes))); | |
| stepInfo.pMoves[i].pDstTmpAllocation->SetResource(dstRes.Get()); | |
| // Perform barriers only if not in right state | |
| if (isDefaultHeap) | |
| { | |
| defaultHeap = true; | |
| // Move new resource into previous state | |
| D3D12_RESOURCE_BARRIER barrier = {}; | |
| barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; | |
| barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; | |
| barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; | |
| barrier.Transition.pResource = dstRes.Get(); | |
| barrier.Transition.StateAfter = (D3D12_RESOURCE_STATES)(uintptr_t)stepInfo.pMoves[i].pSrcAllocation->GetPrivateData(); | |
| barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; | |
| finalBarriers.emplace_back(barrier); | |
| // Move resource into right state | |
| barrier.Transition.pResource = stepInfo.pMoves[i].pSrcAllocation->GetResource(); | |
| barrier.Transition.StateBefore = barrier.Transition.StateAfter; | |
| barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; | |
| startBarriers.emplace_back(barrier); | |
| } | |
| } | |
| } | |
| if (defaultHeap) | |
| { | |
| ID3D12GraphicsCommandList* cl = BeginCommandList(); | |
| cl->ResourceBarrier(static_cast<UINT>(startBarriers.size()), startBarriers.data()); | |
| // Copy resources | |
| for (UINT32 i = 0; i < stepInfo.MoveCount; ++i) | |
| { | |
| if (stepInfo.pMoves[i].Operation == D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_COPY) | |
| { | |
| ID3D12Resource* dstRes = stepInfo.pMoves[i].pDstTmpAllocation->GetResource(); | |
| ID3D12Resource* srcRes = stepInfo.pMoves[i].pSrcAllocation->GetResource(); | |
| if (stepInfo.pMoves[i].pDstTmpAllocation->GetHeap()->GetDesc().Properties.Type == D3D12_HEAP_TYPE_DEFAULT) | |
| { | |
| cl->CopyResource(dstRes, srcRes); | |
| } | |
| else | |
| { | |
| D3D12_RANGE range = {}; | |
| void* dst; | |
| CHECK_HR(dstRes->Map(0, &range, &dst)); | |
| void* src; | |
| CHECK_HR(srcRes->Map(0, &range, &src)); | |
| memcpy(dst, src, stepInfo.pMoves[i].pSrcAllocation->GetSize()); | |
| dstRes->Unmap(0, nullptr); | |
| srcRes->Unmap(0, nullptr); | |
| } | |
| } | |
| } | |
| cl->ResourceBarrier(static_cast<UINT>(finalBarriers.size()), finalBarriers.data()); | |
| EndCommandList(cl); | |
| } | |
| else | |
| { | |
| // Copy only CPU-side | |
| for (UINT32 i = 0; i < stepInfo.MoveCount; ++i) | |
| { | |
| if (stepInfo.pMoves[i].Operation == D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_COPY) | |
| { | |
| D3D12_RANGE range = {}; | |
| void* dst; | |
| ID3D12Resource* dstRes = stepInfo.pMoves[i].pDstTmpAllocation->GetResource(); | |
| CHECK_HR(dstRes->Map(0, &range, &dst)); | |
| void* src; | |
| ID3D12Resource* srcRes = stepInfo.pMoves[i].pSrcAllocation->GetResource(); | |
| CHECK_HR(srcRes->Map(0, &range, &src)); | |
| memcpy(dst, src, stepInfo.pMoves[i].pSrcAllocation->GetSize()); | |
| dstRes->Unmap(0, nullptr); | |
| srcRes->Unmap(0, nullptr); | |
| } | |
| } | |
| } | |
| } | |
| static void Defragment(const TestContext& ctx, | |
| D3D12MA::DEFRAGMENTATION_DESC& defragDesc, | |
| D3D12MA::Pool* pool, | |
| D3D12MA::DEFRAGMENTATION_STATS* defragStats = nullptr) | |
| { | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx; | |
| if (pool != nullptr) | |
| { | |
| CHECK_HR(pool->BeginDefragmentation(&defragDesc, &defragCtx)); | |
| } | |
| else | |
| ctx.allocator->BeginDefragmentation(&defragDesc, &defragCtx); | |
| HRESULT hr = S_OK; | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| while ((hr = defragCtx->BeginPass(&pass)) == S_FALSE) | |
| { | |
| ProcessDefragmentationPass(ctx, pass); | |
| if ((hr = defragCtx->EndPass(&pass)) == S_OK) | |
| break; | |
| CHECK_BOOL(hr == S_FALSE); | |
| } | |
| CHECK_HR(hr); | |
| if (defragStats != nullptr) | |
| defragCtx->GetStats(defragStats); | |
| } | |
| static void TestDefragmentationSimple(const TestContext& ctx) | |
| { | |
| wprintf(L"Test defragmentation simple\n"); | |
| RandomNumberGenerator rand(667); | |
| const UINT ALLOC_SEED = 20220310; | |
| const UINT64 BUF_SIZE = 0x10000; | |
| const UINT64 BLOCK_SIZE = BUF_SIZE * 8; | |
| const UINT64 MIN_BUF_SIZE = 32; | |
| const UINT64 MAX_BUF_SIZE = BUF_SIZE * 4; | |
| auto RandomBufSize = [&]() -> UINT64 | |
| { | |
| return AlignUp<UINT64>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 64); | |
| }; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.BlockSize = BLOCK_SIZE; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| FillResourceDescForBuffer(resDesc, BUF_SIZE); | |
| D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; | |
| defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FAST; | |
| // Defragmentation of empty pool. | |
| { | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx = nullptr; | |
| CHECK_HR(pool->BeginDefragmentation(&defragDesc, &defragCtx)); | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| CHECK_BOOL(defragCtx->BeginPass(&pass) == S_OK); | |
| D3D12MA::DEFRAGMENTATION_STATS defragStats = {}; | |
| defragCtx->GetStats(&defragStats); | |
| CHECK_BOOL(defragStats.AllocationsMoved == 0 && defragStats.BytesFreed == 0 && | |
| defragStats.BytesMoved == 0 && defragStats.HeapsFreed == 0); | |
| } | |
| D3D12_RANGE mapRange = {}; | |
| void* mapPtr; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| // persistentlyMappedOption = 0 - not persistently mapped. | |
| // persistentlyMappedOption = 1 - persistently mapped. | |
| for (UINT8 persistentlyMappedOption = 0; persistentlyMappedOption < 2; ++persistentlyMappedOption) | |
| { | |
| wprintf(L" Persistently mapped option = %u\n", persistentlyMappedOption); | |
| const bool persistentlyMapped = persistentlyMappedOption != 0; | |
| // # Test 1 | |
| // Buffers of fixed size. | |
| // Fill 2 blocks. Remove odd buffers. Defragment everything. | |
| // Expected result: at least 1 block freed. | |
| { | |
| for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| if (persistentlyMapped) | |
| { | |
| CHECK_HR(alloc->GetResource()->Map(0, &mapRange, &mapPtr)); | |
| } | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| for (size_t i = 1; i < allocations.size(); ++i) | |
| allocations.erase(allocations.begin() + i); | |
| FillAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| // Set data for defragmentation retrieval | |
| for (auto& alloc : allocations) | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_GENERIC_READ); | |
| D3D12MA::DEFRAGMENTATION_STATS defragStats; | |
| Defragment(ctx, defragDesc, pool.Get(), & defragStats); | |
| CHECK_BOOL(defragStats.AllocationsMoved == 4 && defragStats.BytesMoved == 4 * BUF_SIZE); | |
| ValidateAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| allocations.clear(); | |
| } | |
| // # Test 2 | |
| // Buffers of fixed size. | |
| // Fill 2 blocks. Remove odd buffers. Defragment one buffer at time. | |
| // Expected result: Each of 4 interations makes some progress. | |
| { | |
| for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE * 2; ++i) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| if (persistentlyMapped) | |
| { | |
| CHECK_HR(alloc->GetResource()->Map(0, &mapRange, &mapPtr)); | |
| } | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| for (size_t i = 1; i < allocations.size(); ++i) | |
| allocations.erase(allocations.begin() + i); | |
| FillAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| // Set data for defragmentation retrieval | |
| for (auto& alloc : allocations) | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_GENERIC_READ); | |
| defragDesc.MaxAllocationsPerPass = 1; | |
| defragDesc.MaxBytesPerPass = BUF_SIZE; | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx; | |
| CHECK_HR(pool->BeginDefragmentation(&defragDesc, &defragCtx)); | |
| for (size_t i = 0; i < BLOCK_SIZE / BUF_SIZE / 2; ++i) | |
| { | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| CHECK_BOOL(defragCtx->BeginPass(&pass) == S_FALSE); | |
| ProcessDefragmentationPass(ctx, pass); | |
| CHECK_BOOL(defragCtx->EndPass(&pass) == S_FALSE); | |
| } | |
| D3D12MA::DEFRAGMENTATION_STATS defragStats = {}; | |
| defragCtx->GetStats(&defragStats); | |
| CHECK_BOOL(defragStats.AllocationsMoved == 4 && defragStats.BytesMoved == 4 * BUF_SIZE); | |
| ValidateAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| allocations.clear(); | |
| } | |
| // # Test 3 | |
| // Buffers of variable size. | |
| // Create a number of buffers. Remove some percent of them. | |
| // Defragment while having some percent of them unmovable. | |
| // Expected result: Just simple validation. | |
| { | |
| for (size_t i = 0; i < 100; ++i) | |
| { | |
| D3D12_RESOURCE_DESC localResDesc = resDesc; | |
| localResDesc.Width = RandomBufSize(); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &localResDesc, D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| if (persistentlyMapped) | |
| { | |
| CHECK_HR(alloc->GetResource()->Map(0, &mapRange, &mapPtr)); | |
| } | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| const UINT32 percentToDelete = 60; | |
| const size_t numberToDelete = allocations.size() * percentToDelete / 100; | |
| for (size_t i = 0; i < numberToDelete; ++i) | |
| { | |
| size_t indexToDelete = rand.Generate() % (UINT32)allocations.size(); | |
| allocations.erase(allocations.begin() + indexToDelete); | |
| } | |
| FillAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| // Non-movable allocations will be at the beginning of allocations array. | |
| const UINT32 percentNonMovable = 20; | |
| const size_t numberNonMovable = allocations.size() * percentNonMovable / 100; | |
| for (size_t i = 0; i < numberNonMovable; ++i) | |
| { | |
| size_t indexNonMovable = i + rand.Generate() % (UINT32)(allocations.size() - i); | |
| if (indexNonMovable != i) | |
| std::swap(allocations[i], allocations[indexNonMovable]); | |
| } | |
| // Set data for defragmentation retrieval | |
| for (auto& alloc : allocations) | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_GENERIC_READ); | |
| defragDesc.MaxAllocationsPerPass = 0; | |
| defragDesc.MaxBytesPerPass = 0; | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx; | |
| CHECK_HR(pool->BeginDefragmentation(&defragDesc, &defragCtx)); | |
| HRESULT hr = S_OK; | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| while ((hr = defragCtx->BeginPass(&pass)) == S_FALSE) | |
| { | |
| D3D12MA::DEFRAGMENTATION_MOVE* end = pass.pMoves + pass.MoveCount; | |
| for (UINT32 i = 0; i < numberNonMovable; ++i) | |
| { | |
| D3D12MA::DEFRAGMENTATION_MOVE* move = std::find_if(pass.pMoves, end, [&](D3D12MA::DEFRAGMENTATION_MOVE& move) { return move.pSrcAllocation == allocations[i].Get(); }); | |
| if (move != end) | |
| move->Operation = D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE; | |
| } | |
| ProcessDefragmentationPass(ctx, pass); | |
| if ((hr = defragCtx->EndPass(&pass)) == S_OK) | |
| break; | |
| CHECK_BOOL(hr == S_FALSE); | |
| } | |
| CHECK_BOOL(hr == S_OK); | |
| ValidateAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| allocations.clear(); | |
| } | |
| } | |
| } | |
| static void TestDefragmentationAlgorithms(const TestContext& ctx) | |
| { | |
| wprintf(L"Test defragmentation algorithms\n"); | |
| RandomNumberGenerator rand(669); | |
| const UINT ALLOC_SEED = 20091225; | |
| const UINT64 BUF_SIZE = 0x10000; | |
| const UINT64 BLOCK_SIZE = BUF_SIZE * 400; | |
| const UINT64 MIN_BUF_SIZE = 32; | |
| const UINT64 MAX_BUF_SIZE = BUF_SIZE * 4; | |
| auto RandomBufSize = [&]() -> UINT64 | |
| { | |
| return AlignUp<UINT64>(rand.Generate() % (MAX_BUF_SIZE - MIN_BUF_SIZE + 1) + MIN_BUF_SIZE, 64); | |
| }; | |
| D3D12MA::POOL_DESC poolDesc = {}; | |
| poolDesc.BlockSize = BLOCK_SIZE; | |
| poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_UPLOAD; | |
| poolDesc.HeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| ComPtr<D3D12MA::Pool> pool; | |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.CustomPool = pool.Get(); | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| FillResourceDescForBuffer(resDesc, BUF_SIZE); | |
| D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| for (UINT8 i = 0; i < 3; ++i) | |
| { | |
| switch (i) | |
| { | |
| case 0: | |
| defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FAST; | |
| break; | |
| case 1: | |
| defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED; | |
| break; | |
| case 2: | |
| defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FULL; | |
| break; | |
| } | |
| wprintf(L" Algorithm = %s\n", DefragmentationAlgorithmToStr(defragDesc.Flags)); | |
| // 0 - Without immovable allocations | |
| // 1 - With immovable allocations | |
| for (uint8_t j = 0; j < 2; ++j) | |
| { | |
| for (size_t i = 0; i < 800; ++i) | |
| { | |
| resDesc.Width = RandomBufSize(); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| const UINT32 percentToDelete = 55; | |
| const size_t numberToDelete = allocations.size() * percentToDelete / 100; | |
| for (size_t i = 0; i < numberToDelete; ++i) | |
| { | |
| size_t indexToDelete = rand.Generate() % (uint32_t)allocations.size(); | |
| allocations.erase(allocations.begin() + indexToDelete); | |
| } | |
| FillAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| // Non-movable allocations will be at the beginning of allocations array. | |
| const UINT32 percentNonMovable = 20; | |
| const size_t numberNonMovable = j == 0 ? 0 : (allocations.size() * percentNonMovable / 100); | |
| for (size_t i = 0; i < numberNonMovable; ++i) | |
| { | |
| size_t indexNonMovable = i + rand.Generate() % (UINT32)(allocations.size() - i); | |
| if (indexNonMovable != i) | |
| std::swap(allocations[i], allocations[indexNonMovable]); | |
| } | |
| // Set data for defragmentation retrieval | |
| for (auto& alloc : allocations) | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_GENERIC_READ); | |
| std::wstring output = DefragmentationAlgorithmToStr(defragDesc.Flags); | |
| if (j == 0) | |
| output += L"_NoMove"; | |
| else | |
| output += L"_Move"; | |
| SaveStatsStringToFile(ctx, (output + L"_Before.json").c_str()); | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx; | |
| CHECK_HR(pool->BeginDefragmentation(&defragDesc, &defragCtx)); | |
| HRESULT hr = S_OK; | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| while ((hr = defragCtx->BeginPass(&pass)) == S_FALSE) | |
| { | |
| D3D12MA::DEFRAGMENTATION_MOVE* end = pass.pMoves + pass.MoveCount; | |
| for (UINT32 i = 0; i < numberNonMovable; ++i) | |
| { | |
| D3D12MA::DEFRAGMENTATION_MOVE* move = std::find_if(pass.pMoves, end, [&](D3D12MA::DEFRAGMENTATION_MOVE& move) { return move.pSrcAllocation == allocations[i].Get(); }); | |
| if (move != end) | |
| move->Operation = D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE; | |
| } | |
| for (UINT32 i = 0; i < pass.MoveCount; ++i) | |
| { | |
| auto it = std::find_if(allocations.begin(), allocations.end(), [&](const ComPtr<D3D12MA::Allocation>& alloc) { return pass.pMoves[i].pSrcAllocation == alloc.Get(); }); | |
| assert(it != allocations.end()); | |
| } | |
| ProcessDefragmentationPass(ctx, pass); | |
| if ((hr = defragCtx->EndPass(&pass)) == S_OK) | |
| break; | |
| CHECK_BOOL(hr == S_FALSE); | |
| } | |
| CHECK_BOOL(hr == S_OK); | |
| SaveStatsStringToFile(ctx, (output + L"_After.json").c_str()); | |
| ValidateAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| allocations.clear(); | |
| } | |
| } | |
| } | |
| static void TestDefragmentationFull(const TestContext& ctx) | |
| { | |
| const UINT ALLOC_SEED = 20101220; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| FillResourceDescForBuffer(resDesc, 0x10000); | |
| // Create initial allocations. | |
| for (size_t i = 0; i < 400; ++i) | |
| { | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_GENERIC_READ, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| FillAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| // Delete random allocations | |
| const size_t allocationsToDeletePercent = 80; | |
| size_t allocationsToDelete = allocations.size() * allocationsToDeletePercent / 100; | |
| for (size_t i = 0; i < allocationsToDelete; ++i) | |
| { | |
| size_t index = (size_t)rand() % allocations.size(); | |
| allocations.erase(allocations.begin() + index); | |
| } | |
| SaveStatsStringToFile(ctx, L"FullBefore.json"); | |
| { | |
| // Set data for defragmentation retrieval | |
| for (auto& alloc : allocations) | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_GENERIC_READ); | |
| const UINT32 defragCount = 1; | |
| for (UINT32 defragIndex = 0; defragIndex < defragCount; ++defragIndex) | |
| { | |
| D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; | |
| defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FULL; | |
| wprintf(L"Test defragmentation full #%u\n", defragIndex); | |
| time_point begTime = std::chrono::high_resolution_clock::now(); | |
| D3D12MA::DEFRAGMENTATION_STATS stats; | |
| Defragment(ctx, defragDesc, nullptr, &stats); | |
| float defragmentDuration = ToFloatSeconds(std::chrono::high_resolution_clock::now() - begTime); | |
| wprintf(L"Moved allocations %u, bytes %llu\n", stats.AllocationsMoved, stats.BytesMoved); | |
| wprintf(L"Freed blocks %u, bytes %llu\n", stats.HeapsFreed, stats.BytesFreed); | |
| wprintf(L"Time: %.2f s\n", defragmentDuration); | |
| SaveStatsStringToFile(ctx, (L"FullAfter_" + std::to_wstring(defragIndex) + L".json").c_str()); | |
| } | |
| } | |
| ValidateAllocationsData(allocations.data(), allocations.size(), ALLOC_SEED); | |
| } | |
| static void TestDefragmentationGpu(const TestContext& ctx) | |
| { | |
| wprintf(L"Test defragmentation GPU\n"); | |
| const UINT ALLOC_SEED = 20180314; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| // Create that many allocations to surely fill 3 new blocks of 256 MB. | |
| const UINT64 bufSizeMin = 5ull * 1024 * 1024; | |
| const UINT64 bufSizeMax = 10ull * 1024 * 1024; | |
| const UINT64 totalSize = 3ull * 256 * 1024 * 1024; | |
| const size_t bufCount = (size_t)(totalSize / bufSizeMin); | |
| const size_t percentToLeave = 30; | |
| const size_t percentNonMovable = 3; | |
| RandomNumberGenerator rand = { 234522 }; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| FillResourceDescForBuffer(resDesc, 0x10000); | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS; | |
| // Create all intended buffers. | |
| for (size_t i = 0; i < bufCount; ++i) | |
| { | |
| resDesc.Width = AlignUp(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| // Destroy some percentage of them. | |
| { | |
| const size_t buffersToDestroy = RoundDiv<size_t>(bufCount * (100 - percentToLeave), 100); | |
| for (size_t i = 0; i < buffersToDestroy; ++i) | |
| { | |
| const size_t index = rand.Generate() % allocations.size(); | |
| allocations.erase(allocations.begin() + index); | |
| } | |
| } | |
| // Set data for defragmentation retrieval | |
| for (auto& alloc : allocations) | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); | |
| // Fill them with meaningful data. | |
| FillAllocationsDataGPU(ctx, allocations.data(), allocations.size(), ALLOC_SEED); | |
| SaveStatsStringToFile(ctx, L"GPU_defragmentation_A_before.json"); | |
| // Defragment using GPU only. | |
| { | |
| const size_t numberNonMovable = allocations.size() * percentNonMovable / 100; | |
| for (size_t i = 0; i < numberNonMovable; ++i) | |
| { | |
| size_t indexNonMovable = i + rand.Generate() % (UINT32)(allocations.size() - i); | |
| if (indexNonMovable != i) | |
| std::swap(allocations[i], allocations[indexNonMovable]); | |
| } | |
| D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; | |
| D3D12MA::DEFRAGMENTATION_STATS stats; | |
| Defragment(ctx, defragDesc, nullptr, &stats); | |
| CHECK_BOOL(stats.AllocationsMoved > 0 && stats.BytesMoved > 0); | |
| CHECK_BOOL(stats.HeapsFreed > 0 && stats.BytesFreed > 0); | |
| } | |
| SaveStatsStringToFile(ctx, L"GPU_defragmentation_B_after.json"); | |
| ValidateAllocationsDataGPU(ctx, allocations.data(), allocations.size(), ALLOC_SEED); | |
| } | |
| static void TestDefragmentationIncrementalBasic(const TestContext& ctx) | |
| { | |
| wprintf(L"Test defragmentation incremental basic\n"); | |
| const UINT ALLOC_SEED = 20210918; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| // Create that many allocations to surely fill 3 new blocks of 256 MB. | |
| const std::array<UINT32, 3> imageSizes = { 256, 512, 1024 }; | |
| const UINT64 bufSizeMin = 5ull * 1024 * 1024; | |
| const UINT64 bufSizeMax = 10ull * 1024 * 1024; | |
| const UINT64 totalSize = 3ull * 256 * 1024 * 1024; | |
| const size_t imageCount = totalSize / ((size_t)imageSizes[0] * imageSizes[0] * 4) / 2; | |
| const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2; | |
| const size_t percentToLeave = 30; | |
| RandomNumberGenerator rand = { 234522 }; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resDesc.Alignment = 0; | |
| resDesc.DepthOrArraySize = 1; | |
| resDesc.MipLevels = 1; | |
| resDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resDesc.SampleDesc.Count = 1; | |
| resDesc.SampleDesc.Quality = 0; | |
| resDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resDesc.Flags = D3D12_RESOURCE_FLAG_NONE; | |
| // Create all intended images. | |
| for (size_t i = 0; i < imageCount; ++i) | |
| { | |
| const UINT32 size = imageSizes[rand.Generate() % 3]; | |
| resDesc.Width = size; | |
| resDesc.Height = size; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| // And all buffers | |
| FillResourceDescForBuffer(resDesc, 0x10000); | |
| for (size_t i = 0; i < bufCount; ++i) | |
| { | |
| resDesc.Width = AlignUp(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| // Destroy some percentage of them. | |
| { | |
| const size_t allocationsToDestroy = RoundDiv<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100); | |
| for (size_t i = 0; i < allocationsToDestroy; ++i) | |
| { | |
| const size_t index = rand.Generate() % allocations.size(); | |
| allocations.erase(allocations.begin() + index); | |
| } | |
| } | |
| // Fill them with meaningful data. | |
| FillAllocationsDataGPU(ctx, allocations.data(), allocations.size(), ALLOC_SEED); | |
| SaveStatsStringToFile(ctx, L"GPU_defragmentation_incremental_basic_A_before.json"); | |
| // Defragment using GPU only. | |
| { | |
| D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx; | |
| ctx.allocator->BeginDefragmentation(&defragDesc, &defragCtx); | |
| HRESULT hr = S_OK; | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| while ((hr = defragCtx->BeginPass(&pass)) == S_FALSE) | |
| { | |
| // Ignore data outside of test | |
| for (UINT32 i = 0; i < pass.MoveCount; ++i) | |
| { | |
| auto it = std::find_if(allocations.begin(), allocations.end(), [&](const ComPtr<D3D12MA::Allocation>& alloc) { return pass.pMoves[i].pSrcAllocation == alloc.Get(); }); | |
| if (it == allocations.end()) | |
| pass.pMoves[i].Operation = D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE; | |
| } | |
| ProcessDefragmentationPass(ctx, pass); | |
| if ((hr = defragCtx->EndPass(&pass)) == S_OK) | |
| break; | |
| CHECK_BOOL(hr == S_FALSE); | |
| } | |
| CHECK_BOOL(hr == S_OK); | |
| D3D12MA::DEFRAGMENTATION_STATS stats = {}; | |
| defragCtx->GetStats(&stats); | |
| CHECK_BOOL(stats.AllocationsMoved > 0 && stats.BytesMoved > 0); | |
| CHECK_BOOL(stats.HeapsFreed > 0 && stats.BytesFreed > 0); | |
| } | |
| SaveStatsStringToFile(ctx, L"GPU_defragmentation_incremental_basic_B_after.json"); | |
| ValidateAllocationsDataGPU(ctx, allocations.data(), allocations.size(), ALLOC_SEED); | |
| } | |
| void TestDefragmentationIncrementalComplex(const TestContext& ctx) | |
| { | |
| wprintf(L"Test defragmentation incremental complex\n"); | |
| const UINT ALLOC_SEED = 20180112; | |
| std::vector<ComPtr<D3D12MA::Allocation>> allocations; | |
| // Create that many allocations to surely fill 3 new blocks of 256 MB. | |
| const std::array<UINT32, 3> imageSizes = { 256, 512, 1024 }; | |
| const UINT64 bufSizeMin = 5ull * 1024 * 1024; | |
| const UINT64 bufSizeMax = 10ull * 1024 * 1024; | |
| const UINT64 totalSize = 3ull * 256 * 1024 * 1024; | |
| const size_t imageCount = (size_t)(totalSize / (imageSizes[0] * imageSizes[0] * 4)) / 2; | |
| const size_t bufCount = (size_t)(totalSize / bufSizeMin) / 2; | |
| const size_t percentToLeave = 30; | |
| RandomNumberGenerator rand = { 234522 }; | |
| D3D12MA::ALLOCATION_DESC allocDesc = {}; | |
| allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| D3D12_RESOURCE_DESC resDesc = {}; | |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; | |
| resDesc.Alignment = 0; | |
| resDesc.DepthOrArraySize = 1; | |
| resDesc.MipLevels = 1; | |
| resDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
| resDesc.SampleDesc.Count = 1; | |
| resDesc.SampleDesc.Quality = 0; | |
| resDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; | |
| resDesc.Flags = D3D12_RESOURCE_FLAG_NONE; | |
| // Create all intended images. | |
| for (size_t i = 0; i < imageCount; ++i) | |
| { | |
| const UINT32 size = imageSizes[rand.Generate() % 3]; | |
| resDesc.Width = size; | |
| resDesc.Height = size; | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| // And all buffers | |
| FillResourceDescForBuffer(resDesc, 0x10000); | |
| for (size_t i = 0; i < bufCount; ++i) | |
| { | |
| resDesc.Width = AlignUp(rand.Generate() % (bufSizeMax - bufSizeMin) + bufSizeMin, 32ull); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COPY_DEST, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); | |
| allocations.emplace_back(std::move(alloc)); | |
| } | |
| // Destroy some percentage of them. | |
| { | |
| const size_t allocationsToDestroy = RoundDiv<size_t>((imageCount + bufCount) * (100 - percentToLeave), 100); | |
| for (size_t i = 0; i < allocationsToDestroy; ++i) | |
| { | |
| const size_t index = rand.Generate() % allocations.size(); | |
| allocations.erase(allocations.begin() + index); | |
| } | |
| } | |
| // Fill them with meaningful data. | |
| FillAllocationsDataGPU(ctx, allocations.data(), allocations.size(), ALLOC_SEED); | |
| SaveStatsStringToFile(ctx, L"GPU_defragmentation_incremental_complex_A_before.json"); | |
| const size_t maxAdditionalAllocations = 100; | |
| std::vector<ComPtr<D3D12MA::Allocation>> additionalAllocations; | |
| additionalAllocations.reserve(maxAdditionalAllocations); | |
| const auto makeAdditionalAllocation = [&]() | |
| { | |
| if (additionalAllocations.size() < maxAdditionalAllocations) | |
| { | |
| resDesc.Width = AlignUp(bufSizeMin + rand.Generate() % (bufSizeMax - bufSizeMin), 16ull); | |
| ComPtr<D3D12MA::Allocation> alloc; | |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, | |
| nullptr, &alloc, IID_NULL, nullptr)); | |
| alloc->SetPrivateData((void*)D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); | |
| additionalAllocations.emplace_back(std::move(alloc)); | |
| } | |
| }; | |
| // Defragment using GPU only. | |
| { | |
| D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; | |
| defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FULL; | |
| ComPtr<D3D12MA::DefragmentationContext> defragCtx; | |
| ctx.allocator->BeginDefragmentation(&defragDesc, &defragCtx); | |
| makeAdditionalAllocation(); | |
| HRESULT hr = S_OK; | |
| D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass = {}; | |
| while ((hr = defragCtx->BeginPass(&pass)) == S_FALSE) | |
| { | |
| makeAdditionalAllocation(); | |
| // Ignore data outside of test | |
| for (UINT32 i = 0; i < pass.MoveCount; ++i) | |
| { | |
| auto it = std::find_if(allocations.begin(), allocations.end(), [&](const ComPtr<D3D12MA::Allocation>& alloc) { return pass.pMoves[i].pSrcAllocation == alloc.Get(); }); | |
| if (it == allocations.end()) | |
| { | |
| auto it = std::find_if(additionalAllocations.begin(), additionalAllocations.end(), [&](const ComPtr<D3D12MA::Allocation>& alloc) { return pass.pMoves[i].pSrcAllocation == alloc.Get(); }); | |
| if (it == additionalAllocations.end()) | |
| pass.pMoves[i].Operation = D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE; | |
| } | |
| } | |
| ProcessDefragmentationPass(ctx, pass); | |
| makeAdditionalAllocation(); | |
| if ((hr = defragCtx->EndPass(&pass)) == S_OK) | |
| break; | |
| CHECK_BOOL(hr == S_FALSE); | |
| } | |
| CHECK_BOOL(hr == S_OK); | |
| D3D12MA::DEFRAGMENTATION_STATS stats = {}; | |
| defragCtx->GetStats(&stats); | |
| CHECK_BOOL(stats.AllocationsMoved > 0 && stats.BytesMoved > 0); | |
| CHECK_BOOL(stats.HeapsFreed > 0 && stats.BytesFreed > 0); | |
| } | |
| SaveStatsStringToFile(ctx, L"GPU_defragmentation_incremental_complex_B_after.json"); | |
| ValidateAllocationsDataGPU(ctx, allocations.data(), allocations.size(), ALLOC_SEED); | |
| } | |
| static void TestGroupVirtual(const TestContext& ctx) | |
| { | |
| TestVirtualBlocks(ctx); | |
| TestVirtualBlocksAlgorithms(ctx); | |
| TestVirtualBlocksAlgorithmsBenchmark(ctx); | |
| } | |
| static void TestGroupBasics(const TestContext& ctx) | |
| { | |
| #if D3D12MA_DEBUG_MARGIN | |
| TestDebugMargin(ctx); | |
| TestDebugMarginNotInVirtualAllocator(ctx); | |
| #else | |
| TestJson(ctx); | |
| TestCommittedResourcesAndJson(ctx); | |
| TestCustomHeapFlags(ctx); | |
| TestPlacedResources(ctx); | |
| TestOtherComInterface(ctx); | |
| TestCustomPools(ctx); | |
| TestCustomPool_MinAllocationAlignment(ctx); | |
| TestCustomPool_Committed(ctx); | |
| TestPoolsAndAllocationParameters(ctx); | |
| TestCustomHeaps(ctx); | |
| TestStandardCustomCommittedPlaced(ctx); | |
| TestAliasingMemory(ctx); | |
| TestAliasingImplicitCommitted(ctx); | |
| TestPoolMsaaTextureAsCommitted(ctx); | |
| TestMapping(ctx); | |
| TestStats(ctx); | |
| TestTransfer(ctx); | |
| TestZeroInitialized(ctx); | |
| TestMultithreading(ctx); | |
| TestLinearAllocator(ctx); | |
| TestLinearAllocatorMultiBlock(ctx); | |
| ManuallyTestLinearAllocator(ctx); | |
| #ifdef __ID3D12Device4_INTERFACE_DEFINED__ | |
| TestDevice4(ctx); | |
| #endif | |
| #ifdef __ID3D12Device8_INTERFACE_DEFINED__ | |
| TestDevice8(ctx); | |
| #endif | |
| FILE* file; | |
| fopen_s(&file, "Results.csv", "w"); | |
| assert(file != NULL); | |
| BenchmarkAlgorithms(ctx, file); | |
| fclose(file); | |
| #endif // #if D3D12_DEBUG_MARGIN | |
| } | |
| static void TestGroupDefragmentation(const TestContext& ctx) | |
| { | |
| TestDefragmentationSimple(ctx); | |
| TestDefragmentationAlgorithms(ctx); | |
| TestDefragmentationFull(ctx); | |
| TestDefragmentationGpu(ctx); | |
| TestDefragmentationIncrementalBasic(ctx); | |
| TestDefragmentationIncrementalComplex(ctx); | |
| } | |
| void Test(const TestContext& ctx) | |
| { | |
| wprintf(L"TESTS BEGIN\n"); | |
| if(false) | |
| { | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Temporarily insert custom tests here: | |
| return; | |
| } | |
| TestGroupVirtual(ctx); | |
| TestGroupBasics(ctx); | |
| TestGroupDefragmentation(ctx); | |
| wprintf(L"TESTS END\n"); | |
| } |