| //
|
| // Copyright (c) 2019-2026 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", };
|
|
|
| constexpr D3D12_RESOURCE_FLAGS D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT_COPY = (D3D12_RESOURCE_FLAGS)0x400;
|
|
|
| // Indexes match enum D3D12_HEAP_TYPE.
|
| static const WCHAR* const HEAP_TYPE_NAMES[] =
|
| {
|
| L"",
|
| L"DEFAULT",
|
| L"UPLOAD",
|
| L"READBACK",
|
| L"CUSTOM",
|
| L"GPU_UPLOAD",
|
| };
|
|
|
| bool operator==(const D3D12MA::Statistics& lhs, const D3D12MA::Statistics& rhs)
|
| {
|
| return lhs.BlockCount == rhs.BlockCount &&
|
| lhs.AllocationCount == rhs.AllocationCount &&
|
| lhs.BlockBytes == rhs.BlockBytes &&
|
| lhs.AllocationBytes == rhs.AllocationBytes;
|
| }
|
|
|
| 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 FillResourceDescForSmallMsaaRenderTarget(D3D12_RESOURCE_DESC& outResourceDesc) |
| { |
| outResourceDesc = {}; |
| outResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
| outResourceDesc.Alignment = 0;
|
| outResourceDesc.Width = 64;
|
| outResourceDesc.Height = 64;
|
| outResourceDesc.DepthOrArraySize = 1;
|
| outResourceDesc.MipLevels = 1;
|
| outResourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
| outResourceDesc.SampleDesc.Count = 2;
|
| outResourceDesc.SampleDesc.Quality = 0;
|
| outResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; |
| outResourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; |
| } |
| |
| static void ValidatePlacedTextureAlignmentAgainstRuntime( |
| const TestContext& ctx, |
| const D3D12MA::Allocation* alloc, |
| const D3D12_RESOURCE_DESC& resourceDesc) |
| { |
| CHECK_BOOL(alloc != nullptr); |
| CHECK_BOOL(alloc->GetHeap() != nullptr); |
| |
| D3D12_RESOURCE_DESC alignedDesc = resourceDesc; |
| alignedDesc.Alignment = D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; |
| const D3D12_RESOURCE_ALLOCATION_INFO expectedInfo = |
| ctx.device->GetResourceAllocationInfo(0, 1, &alignedDesc); |
| |
| CHECK_BOOL(expectedInfo.SizeInBytes != UINT64_MAX); |
| CHECK_BOOL(alloc->GetAlignment() == expectedInfo.Alignment); |
| CHECK_BOOL(alloc->GetOffset() % expectedInfo.Alignment == 0); |
| CHECK_BOOL(alloc->GetSize() == expectedInfo.SizeInBytes); |
| } |
|
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_UPLOAD,
|
| D3D12MA::ALLOCATION_FLAG_COMMITTED,
|
| NULL, // privateData
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS }; // extraHeapFlags
|
|
|
| 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)
|
| {
|
| // Fix for D3D12 ERROR: ID3D12Device::CreatePlacedResource: D3D12_RESOURCE_DESC::Alignment is invalid. The value is 8. When D3D12_RESOURCE_DESC::Flag bit for D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT is set, Alignment must be 0. [ STATE_CREATION ERROR #721: CREATERESOURCE_INVALIDALIGNMENT]
|
| if ((resDesc.Flags & D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT_COPY) != 0)
|
| resDesc.Alignment = 0;
|
|
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_READBACK,
|
| D3D12MA::ALLOCATION_FLAG_COMMITTED,
|
| NULL, // privateData
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS }; // extraHeapFlags
|
|
|
| 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)
|
| {
|
| // Fix for D3D12 ERROR: ID3D12Device::CreatePlacedResource: D3D12_RESOURCE_DESC::Alignment is invalid. The value is 8. When D3D12_RESOURCE_DESC::Flag bit for D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT is set, Alignment must be 0. [ STATE_CREATION ERROR #721: CREATERESOURCE_INVALIDALIGNMENT]
|
| if ((resDesc.Flags & D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT_COPY) != 0)
|
| resDesc.Alignment = 0;
|
|
|
| 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 bool QueryMsaa64KBAlignedTextureSupported(const TestContext& ctx)
|
| {
|
| D3D12_FEATURE_DATA_D3D12_OPTIONS4 options4 = {};
|
| CHECK_HR(ctx.device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS4, &options4, sizeof(options4)));
|
| return options4.MSAA64KBAlignedTextureSupported != FALSE;
|
| }
|
|
|
| static UINT64 ExpectedMsaaTexturePlacementAlignment(bool msaa64KBAlignedTextureSupported)
|
| {
|
| return msaa64KBAlignedTextureSupported ?
|
| D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT :
|
| D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT;
|
| }
|
|
|
| static void ValidatePlacedSmallMsaaTextureAllocation(const D3D12MA::Allocation* alloc, UINT64 expectedAlignment)
|
| {
|
| CHECK_BOOL(alloc != nullptr);
|
| CHECK_BOOL(alloc->GetResource() != nullptr);
|
| CHECK_BOOL(alloc->GetHeap() != nullptr);
|
| CHECK_BOOL(alloc->GetAlignment() == expectedAlignment);
|
| CHECK_BOOL(alloc->GetOffset() % expectedAlignment == 0);
|
| CHECK_BOOL(alloc->GetHeap()->GetDesc().Alignment == D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT);
|
| }
|
|
|
|
|
| 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 = {};
|
|
|
| CPOOL_DESC poolDesc = CPOOL_DESC{ D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS };
|
|
|
| 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)
|
| {
|
| CVIRTUAL_BLOCK_DESC blockDesc = CVIRTUAL_BLOCK_DESC{ 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)
|
| {
|
| CVIRTUAL_ALLOCATION_DESC allocDesc = CVIRTUAL_ALLOCATION_DESC{ 1 * MEGABYTE, 0 };
|
| 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_RESOURCE_STATE_COMMON;
|
| 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;
|
| break;
|
| case 1:
|
| allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
|
| break;
|
| case 2:
|
| allocDesc.HeapType = D3D12_HEAP_TYPE_READBACK;
|
| break;
|
| case 3:
|
| allocDesc.HeapType = D3D12_HEAP_TYPE_CUSTOM;
|
| 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;
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| 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_COMMON,
|
| 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 TestSmallBuffers(const TestContext& ctx)
|
| {
|
| wprintf(L"Test small buffers\n");
|
|
|
| const bool isTightAlignmentEnabled = ctx.allocator->IsTightAlignmentSupported() &&
|
| (ctx.allocatorFlags & D3D12MA::ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT) == 0;
|
| const bool allowSmallBuffersCommitted =
|
| (ctx.allocatorFlags & D3D12MA::ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED) == 0;
|
| const bool expectSmallBuffersCommitted = !isTightAlignmentEnabled &&
|
| allowSmallBuffersCommitted;
|
|
|
| const auto testPool = [&](D3D12MA::POOL_FLAGS poolFlags, bool expectDefaultCommitted)
|
| {
|
| D3D12MA::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| poolFlags };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ pool.Get() };
|
|
|
| D3D12_RESOURCE_DESC resDesc;
|
| FillResourceDescForBuffer(resDesc, 8 * KILOBYTE);
|
|
|
| D3D12_RESOURCE_DESC largeResDesc = resDesc;
|
| largeResDesc.Width = 128 * KILOBYTE;
|
|
|
| std::vector<ResourceWithAllocation> resources;
|
|
|
| // A large buffer placed inside the heap to allocate first block.
|
| {
|
| resources.emplace_back();
|
| ResourceWithAllocation& resWithAlloc = resources.back();
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &largeResDesc, D3D12_RESOURCE_STATE_COMMON,
|
| nullptr, &resWithAlloc.allocation, IID_PPV_ARGS(&resWithAlloc.resource)));
|
| CHECK_BOOL(resWithAlloc.allocation && resWithAlloc.allocation->GetResource());
|
| CHECK_BOOL(resWithAlloc.allocation->GetHeap()); // Expected to be placed.
|
| }
|
|
|
| // Test 1: COMMITTED.
|
| {
|
| resources.emplace_back();
|
| ResourceWithAllocation& resWithAlloc = resources.back();
|
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COMMON,
|
| nullptr, &resWithAlloc.allocation, IID_PPV_ARGS(&resWithAlloc.resource)));
|
| CHECK_BOOL(resWithAlloc.allocation && resWithAlloc.allocation->GetResource());
|
| CHECK_BOOL(!resWithAlloc.allocation->GetHeap()); // Expected to be committed.
|
| }
|
|
|
| // Test 2: Default.
|
| {
|
| resources.emplace_back();
|
| ResourceWithAllocation& resWithAlloc = resources.back();
|
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NONE;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COMMON,
|
| nullptr, &resWithAlloc.allocation, IID_PPV_ARGS(&resWithAlloc.resource)));
|
| CHECK_BOOL(resWithAlloc.allocation && resWithAlloc.allocation->GetResource());
|
| const bool isCommitted = resWithAlloc.allocation->GetHeap() == NULL;
|
| CHECK_BOOL(isCommitted == expectDefaultCommitted);
|
| }
|
|
|
| // Test 3: NEVER_ALLOCATE.
|
| {
|
| resources.emplace_back();
|
| ResourceWithAllocation& resWithAlloc = resources.back();
|
| allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_COMMON,
|
| nullptr, &resWithAlloc.allocation, IID_PPV_ARGS(&resWithAlloc.resource)));
|
| CHECK_BOOL(resWithAlloc.allocation && resWithAlloc.allocation->GetResource());
|
| CHECK_BOOL(resWithAlloc.allocation->GetHeap()); // Expected to be placed.
|
| }
|
| };
|
|
|
| testPool(D3D12MA::POOL_FLAG_NONE, expectSmallBuffersCommitted);
|
| testPool(D3D12MA::POOL_FLAG_DONT_USE_TIGHT_ALIGNMENT, allowSmallBuffersCommitted);
|
| }
|
|
|
| static void TestCustomHeapFlags(const TestContext& ctx)
|
| {
|
| wprintf(L"Test custom heap flags\n");
|
|
|
| // 1. Just memory heap with custom flags
|
| {
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12MA::ALLOCATION_FLAG_NONE,
|
| NULL, // privateData
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12MA::ALLOCATION_FLAG_NONE,
|
| NULL, // privateData,
|
| D3D12_HEAP_FLAG_SHARED | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER };
|
|
|
| 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 = 64ull * 1024;
|
| ResourceWithAllocation resources[count];
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_NONE,
|
| 11 * MEGABYTE, // blockSize
|
| 1, // minBlockCount
|
| 2, // maxBlockCount
|
| D3D12_RESIDENCY_PRIORITY_HIGH }; // Test some residency priority, by the way.
|
|
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{};
|
| 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_NONE,
|
| 2 * MEGABYTE + MEGABYTE / 2, // blockSize = 2.5 MB
|
| 0, // minBlockCount
|
| 1 }; // maxBlockCount
|
| 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_COMMON, 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_COMMON, 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_COMMON, 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_COMMON, 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_UPLOAD,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS };
|
| poolDesc.MinAllocationAlignment = MIN_ALIGNMENT;
|
|
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR( ctx.allocator->CreatePool(&poolDesc, &pool) );
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR( ctx.allocator->CreatePool(&poolDesc, &pool) );
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| pool.Get(),
|
| 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 void TestCustomPool_AlwaysCommitted(const TestContext& ctx)
|
| {
|
| wprintf(L"Test custom pool always committed\n");
|
|
|
| const UINT64 BUFFER_SIZE = 256;
|
|
|
| D3D12MA::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_ALWAYS_COMMITTED };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ pool.Get() };
|
|
|
| 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);
|
|
|
| D3D12MA::Statistics stats = {};
|
| pool->GetStatistics(&stats);
|
| CHECK_BOOL(stats.AllocationBytes >= BUFFER_SIZE);
|
| CHECK_BOOL(stats.AllocationCount == 1);
|
| CHECK_BOOL(stats.BlockBytes >= BUFFER_SIZE);
|
| CHECK_BOOL(stats.BlockCount == 1);
|
|
|
| D3D12MA::DetailedStatistics detailedStats = {};
|
| pool->CalculateStatistics(&detailedStats);
|
| CHECK_BOOL(detailedStats.Stats == stats);
|
| CHECK_BOOL(detailedStats.AllocationSizeMin == stats.AllocationBytes);
|
| CHECK_BOOL(detailedStats.AllocationSizeMax == stats.AllocationBytes);
|
| CHECK_BOOL(detailedStats.UnusedRangeCount == 0);
|
| CHECK_BOOL(detailedStats.UnusedRangeSizeMax == 0);
|
| }
|
|
|
| static void CheckBudgetBasics(const TestContext& ctx,
|
| const D3D12MA::Budget& localBudget, const D3D12MA::Budget& nonLocalBudget)
|
| {
|
| CHECK_BOOL(localBudget.BudgetBytes > 0);
|
| CHECK_BOOL(localBudget.BudgetBytes <= ctx.allocator->GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL));
|
| CHECK_BOOL(localBudget.Stats.AllocationBytes <= localBudget.Stats.BlockBytes);
|
|
|
| // Discrete graphics card with separate video memory.
|
| if (!ctx.allocator->IsUMA())
|
| {
|
| CHECK_BOOL(nonLocalBudget.BudgetBytes > 0);
|
| CHECK_BOOL(nonLocalBudget.BudgetBytes <= ctx.allocator->GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL));
|
| CHECK_BOOL(nonLocalBudget.Stats.AllocationBytes <= nonLocalBudget.Stats.BlockBytes);
|
| }
|
| }
|
|
|
| static D3D12MA::DetailedStatistics GetEmptyDetailedStatistics()
|
| {
|
| D3D12MA::DetailedStatistics out = {};
|
| out.AllocationSizeMin = UINT64_MAX;
|
| out.UnusedRangeSizeMin = UINT64_MAX;
|
| return out;
|
| }
|
|
|
| static void AddDetailedStatistics(D3D12MA::DetailedStatistics& inoutSum, const D3D12MA::DetailedStatistics& stats)
|
| {
|
| inoutSum.Stats.AllocationBytes += stats.Stats.AllocationBytes;
|
| inoutSum.Stats.AllocationCount += stats.Stats.AllocationCount;
|
| inoutSum.Stats.BlockBytes += stats.Stats.BlockBytes;
|
| inoutSum.Stats.BlockCount += stats.Stats.BlockCount;
|
| inoutSum.UnusedRangeCount += stats.UnusedRangeCount;
|
| inoutSum.AllocationSizeMax = std::max(inoutSum.AllocationSizeMax, stats.AllocationSizeMax);
|
| inoutSum.AllocationSizeMin = std::min(inoutSum.AllocationSizeMin, stats.AllocationSizeMin);
|
| inoutSum.UnusedRangeSizeMax = std::max(inoutSum.UnusedRangeSizeMax, stats.UnusedRangeSizeMax);
|
| inoutSum.UnusedRangeSizeMin = std::min(inoutSum.UnusedRangeSizeMin, stats.UnusedRangeSizeMin);
|
| }
|
|
|
| static inline bool StatisticsEqual(const D3D12MA::DetailedStatistics& lhs, const D3D12MA::DetailedStatistics& rhs)
|
| {
|
| return memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
|
| }
|
|
|
| static inline bool StatisticsEqual(const D3D12MA::Statistics& lhs, const D3D12MA::Statistics& 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 CheckTotalStatistics(const D3D12MA::TotalStatistics& stats)
|
| {
|
| D3D12MA::DetailedStatistics sum = GetEmptyDetailedStatistics();
|
| for (size_t i = 0; i < _countof(stats.HeapType); ++i)
|
| {
|
| AddDetailedStatistics(sum, stats.HeapType[i]);
|
| }
|
| CHECK_BOOL(StatisticsEqual(sum, stats.Total));
|
|
|
| sum = GetEmptyDetailedStatistics();
|
| for (size_t i = 0; i < _countof(stats.MemorySegmentGroup); ++i)
|
| {
|
| AddDetailedStatistics(sum, stats.MemorySegmentGroup[i]);
|
| }
|
| CHECK_BOOL(StatisticsEqual(sum, stats.Total));
|
| }
|
|
|
| static void TestCustomHeaps(const TestContext& ctx)
|
| {
|
| using namespace D3D12MA;
|
|
|
| wprintf(L"Test custom heap\n");
|
|
|
| // Use custom pool but the same as READBACK, which should be always available.
|
| D3D12_HEAP_PROPERTIES heapProps = {};
|
| heapProps.Type = D3D12_HEAP_TYPE_CUSTOM;
|
| heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_WRITE_BACK;
|
| heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_L0; // System memory
|
|
|
| const UINT64 BUFFER_SIZE = 1 * MEGABYTE;
|
| D3D12_RESOURCE_DESC resDesc;
|
| FillResourceDescForBuffer(resDesc, BUFFER_SIZE);
|
|
|
| Budget localBudgetBeg = {}, nonLocalBudgetBeg = {};
|
| ctx.allocator->GetBudget(&localBudgetBeg, &nonLocalBudgetBeg);
|
| CheckBudgetBasics(ctx, localBudgetBeg, nonLocalBudgetBeg);
|
|
|
| TotalStatistics globalStatsBeg = {};
|
| ctx.allocator->CalculateStatistics(&globalStatsBeg);
|
| CheckTotalStatistics(globalStatsBeg);
|
|
|
| // Test 0: Custom pool with fixed block size (it must end up as placed).
|
| // Test 1: Custom pool, requested committed.
|
|
|
| for (size_t testIndex = 0; testIndex < 2; ++testIndex)
|
| {
|
| const bool requestCommitted = testIndex == 1;
|
|
|
| POOL_DESC poolDesc = CPOOL_DESC{ heapProps, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS };
|
| if (testIndex == 0)
|
| {
|
| poolDesc.BlockSize = 10 * MEGABYTE;
|
| poolDesc.MinBlockCount = 1;
|
| poolDesc.MaxBlockCount = 1;
|
| }
|
| ComPtr<Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| ALLOCATION_DESC allocDesc = CALLOCATION_DESC{ pool.Get() };
|
| if (requestCommitted)
|
| {
|
| allocDesc.Flags = ALLOCATION_FLAG_COMMITTED;
|
| }
|
|
|
| ComPtr<Allocation> alloc;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &alloc, IID_NULL, NULL));
|
|
|
| const bool isCommitted = alloc->GetHeap() == NULL;
|
| CHECK_BOOL(isCommitted == requestCommitted);
|
|
|
| Budget localBudgetEnd = {}, nonLocalBudgetEnd = {};
|
| ctx.allocator->GetBudget(&localBudgetEnd, &nonLocalBudgetEnd);
|
| CheckBudgetBasics(ctx, localBudgetEnd, nonLocalBudgetEnd);
|
|
|
| D3D12MA::TotalStatistics globalStatsEnd = {};
|
| ctx.allocator->CalculateStatistics(&globalStatsEnd);
|
| CheckTotalStatistics(globalStatsEnd);
|
|
|
| // Make sure it is accounted only in CUSTOM heap not any of the standard heaps.
|
|
|
| const UINT thisMemSegmentGroupIndex = ctx.allocator->IsUMA() ? 0 : 1;
|
| const UINT otherMemSegmentGroupIndex = 1 - thisMemSegmentGroupIndex;
|
|
|
| CHECK_BOOL(globalStatsEnd.Total.Stats.AllocationCount == globalStatsBeg.Total.Stats.AllocationCount + 1);
|
| CHECK_BOOL(globalStatsEnd.Total.Stats.BlockCount == globalStatsBeg.Total.Stats.BlockCount + 1);
|
| CHECK_BOOL(globalStatsEnd.Total.Stats.AllocationBytes == globalStatsBeg.Total.Stats.AllocationBytes + BUFFER_SIZE);
|
|
|
| CHECK_BOOL(memcmp(&globalStatsEnd.HeapType[0], &globalStatsBeg.HeapType[0], sizeof(D3D12MA::DetailedStatistics)) == 0);
|
| CHECK_BOOL(memcmp(&globalStatsEnd.HeapType[1], &globalStatsBeg.HeapType[1], sizeof(D3D12MA::DetailedStatistics)) == 0);
|
| CHECK_BOOL(memcmp(&globalStatsEnd.HeapType[2], &globalStatsBeg.HeapType[2], sizeof(D3D12MA::DetailedStatistics)) == 0);
|
| CHECK_BOOL(memcmp(&globalStatsEnd.HeapType[4], &globalStatsBeg.HeapType[4], sizeof(D3D12MA::DetailedStatistics)) == 0);
|
|
|
| CHECK_BOOL(globalStatsEnd.HeapType[3].Stats.AllocationCount == globalStatsBeg.HeapType[3].Stats.AllocationCount + 1);
|
| CHECK_BOOL(globalStatsEnd.HeapType[3].Stats.BlockCount == globalStatsBeg.HeapType[3].Stats.BlockCount + 1);
|
| CHECK_BOOL(globalStatsEnd.HeapType[3].Stats.AllocationBytes == globalStatsBeg.HeapType[3].Stats.AllocationBytes + BUFFER_SIZE);
|
|
|
| CHECK_BOOL(globalStatsEnd.MemorySegmentGroup[thisMemSegmentGroupIndex].Stats.AllocationCount ==
|
| globalStatsBeg.MemorySegmentGroup[thisMemSegmentGroupIndex].Stats.AllocationCount + 1);
|
| CHECK_BOOL(globalStatsEnd.MemorySegmentGroup[thisMemSegmentGroupIndex].Stats.BlockCount ==
|
| globalStatsBeg.MemorySegmentGroup[thisMemSegmentGroupIndex].Stats.BlockCount + 1);
|
| CHECK_BOOL(globalStatsEnd.MemorySegmentGroup[thisMemSegmentGroupIndex].Stats.AllocationBytes ==
|
| globalStatsBeg.MemorySegmentGroup[thisMemSegmentGroupIndex].Stats.AllocationBytes + BUFFER_SIZE);
|
|
|
| CHECK_BOOL(memcmp(&globalStatsEnd.MemorySegmentGroup[otherMemSegmentGroupIndex],
|
| &globalStatsBeg.MemorySegmentGroup[otherMemSegmentGroupIndex], sizeof(D3D12MA::DetailedStatistics)) == 0);
|
|
|
| const Budget& thisBudgetBeg = ctx.allocator->IsUMA() ? localBudgetBeg : nonLocalBudgetBeg;
|
| const Budget& thisBudgetEnd = ctx.allocator->IsUMA() ? localBudgetEnd : nonLocalBudgetEnd;
|
|
|
| CHECK_BOOL(thisBudgetEnd.Stats.AllocationCount == thisBudgetBeg.Stats.AllocationCount + 1);
|
| CHECK_BOOL(thisBudgetEnd.Stats.BlockCount == thisBudgetBeg.Stats.BlockCount + 1);
|
| CHECK_BOOL(thisBudgetEnd.Stats.AllocationBytes == thisBudgetBeg.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;
|
| CHECK_HR(res->Map(0, &EMPTY_RANGE, (void**)&mappedPtr));
|
| *mappedPtr = 0xDEADC0DE;
|
| res->Unmap(0, nullptr);
|
| }
|
| }
|
| }
|
|
|
| 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| heapType,
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12MA::ALLOCATION_FLAG_NONE,
|
| NULL, // privateData
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_UPLOAD,
|
| 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
|
| D3D12MA::POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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);
|
| CHECK_BOOL(alloc->GetOffset() == 0);
|
| }
|
|
|
| static void TestMsaa64KBAlignedTextureSupported_DefaultPool(const TestContext& ctx)
|
| {
|
| wprintf(L"Test MSAA 64 KB alignment in default pool\n");
|
|
|
| CHECK_BOOL((ctx.allocatorFlags & D3D12MA::ALLOCATOR_FLAG_ALWAYS_COMMITTED) == 0);
|
| CHECK_BOOL((ctx.allocatorFlags & D3D12MA::ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED) == 0);
|
|
|
| const UINT64 expectedAlignment =
|
| ExpectedMsaaTexturePlacementAlignment(QueryMsaa64KBAlignedTextureSupported(ctx));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ D3D12_HEAP_TYPE_DEFAULT };
|
|
|
| D3D12_RESOURCE_DESC resDesc = {};
|
| FillResourceDescForSmallMsaaRenderTarget(resDesc);
|
|
|
| ComPtr<D3D12MA::Allocation> alloc;
|
| ComPtr<ID3D12Resource> resource;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
| nullptr, &alloc, IID_PPV_ARGS(&resource)));
|
|
|
| ValidatePlacedSmallMsaaTextureAllocation(alloc.Get(), expectedAlignment);
|
| }
|
|
|
| static void TestMsaa64KBAlignedTextureSupported_CustomPool(const TestContext& ctx) |
| { |
| wprintf(L"Test MSAA 64 KB alignment in custom pool\n");
|
|
|
| const UINT64 expectedAlignment =
|
| ExpectedMsaaTexturePlacementAlignment(QueryMsaa64KBAlignedTextureSupported(ctx));
|
|
|
| D3D12MA::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
|
| D3D12MA::POOL_FLAG_NONE };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ pool.Get() };
|
|
|
| D3D12_RESOURCE_DESC resDesc = {};
|
| FillResourceDescForSmallMsaaRenderTarget(resDesc);
|
|
|
| ComPtr<D3D12MA::Allocation> alloc;
|
| ComPtr<ID3D12Resource> resource;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc, D3D12_RESOURCE_STATE_RENDER_TARGET,
|
| nullptr, &alloc, IID_PPV_ARGS(&resource)));
|
| |
| ValidatePlacedSmallMsaaTextureAllocation(alloc.Get(), expectedAlignment); |
| } |
| |
| static void TestSmallTextureAlignmentAcrossDimensions(const TestContext& ctx) |
| { |
| wprintf(L"Test small texture alignment across dimensions\n"); |
| |
| D3D12MA::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{ |
| D3D12_HEAP_TYPE_DEFAULT, |
| D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES, |
| D3D12MA::POOL_FLAG_NONE }; |
| ComPtr<D3D12MA::Pool> pool; |
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool)); |
| |
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ pool.Get() }; |
| |
| auto createAndValidate = [&](const D3D12_RESOURCE_DESC& resourceDesc) |
| { |
| ComPtr<D3D12MA::Allocation> alloc; |
| ComPtr<ID3D12Resource> resource; |
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, |
| nullptr, &alloc, IID_PPV_ARGS(&resource))); |
| ValidatePlacedTextureAlignmentAgainstRuntime(ctx, alloc.Get(), resourceDesc); |
| }; |
| |
| D3D12_RESOURCE_DESC resDesc = {}; |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE1D; |
| resDesc.Alignment = 0; |
| resDesc.Width = 1024; |
| resDesc.Height = 1; |
| resDesc.DepthOrArraySize = 7; |
| resDesc.MipLevels = 4; |
| resDesc.Format = DXGI_FORMAT_R8_UNORM; |
| resDesc.SampleDesc.Count = 1; |
| resDesc.SampleDesc.Quality = 0; |
| resDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; |
| resDesc.Flags = D3D12_RESOURCE_FLAG_NONE; |
| createAndValidate(resDesc); |
| |
| resDesc = {}; |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; |
| resDesc.Alignment = 0; |
| resDesc.Width = 64; |
| resDesc.Height = 64; |
| resDesc.DepthOrArraySize = 8; |
| resDesc.MipLevels = 4; |
| 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; |
| createAndValidate(resDesc); |
| |
| resDesc = {}; |
| resDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE3D; |
| resDesc.Alignment = 0; |
| resDesc.Width = 8; |
| resDesc.Height = 8; |
| resDesc.DepthOrArraySize = 8; |
| resDesc.MipLevels = 4; |
| 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; |
| createAndValidate(resDesc); |
| } |
|
|
| static void TestMapping(const TestContext& ctx)
|
| {
|
| wprintf(L"Test mapping\n");
|
|
|
| const UINT count = 10;
|
| const UINT64 bufSize = 32ull * 1024;
|
| ResourceWithAllocation resources[count];
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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 void TestStats(const TestContext& ctx)
|
| {
|
| using namespace D3D12MA;
|
|
|
| wprintf(L"Test stats\n");
|
|
|
| constexpr UINT64 BUF_SIZE = 10 * MEGABYTE;
|
| constexpr UINT32 BUF_COUNT = 4;
|
| constexpr UINT64 PREALLOCATED_BLOCK_SIZE = BUF_SIZE * (BUF_COUNT + 1);
|
|
|
| /*
|
| Test 0: ALLOCATION_FLAG_COMMITTED.
|
| Test 1: normal allocations.
|
| Test 2: allocations in a custom pool.
|
| Test 3: allocations in a custom pool, COMMITTED.
|
| Test 4: allocations in a custom pool with preallocated memory.
|
| */
|
| for (uint32_t testIndex = 0; testIndex < 5; ++testIndex)
|
| {
|
| const bool usePool = testIndex >= 2;
|
| const bool useCommitted = testIndex == 0 || testIndex == 3;
|
| const bool usePreallocated = testIndex == 4;
|
|
|
| // Get stats "Beg".
|
| Budget localBudgetBeg = {};
|
| Budget nonLocalBudgetBeg = {};
|
| ctx.allocator->GetBudget(&localBudgetBeg, &nonLocalBudgetBeg);
|
| CheckBudgetBasics(ctx, localBudgetBeg, nonLocalBudgetBeg);
|
|
|
| TotalStatistics statsBeg = {};
|
| ctx.allocator->CalculateStatistics(&statsBeg);
|
| CheckTotalStatistics(statsBeg);
|
|
|
| // Create pool.
|
| ComPtr<Pool> pool;
|
| if (usePool)
|
| {
|
| POOL_DESC poolDesc = CPOOL_DESC(D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS);
|
| if (usePreallocated)
|
| {
|
| poolDesc.BlockSize = PREALLOCATED_BLOCK_SIZE;
|
| poolDesc.MinBlockCount = 1;
|
| poolDesc.MaxBlockCount = 1;
|
| }
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
| }
|
|
|
| // Get pool stats "Beg".
|
| Statistics poolStatsBeg = {};
|
| DetailedStatistics detailedPoolStatsBeg = {};
|
| if (usePool)
|
| {
|
| pool->GetStatistics(&poolStatsBeg);
|
| pool->CalculateStatistics(&detailedPoolStatsBeg);
|
| CheckStatistics(detailedPoolStatsBeg);
|
| }
|
|
|
| // Create buffers.
|
| D3D12_RESOURCE_DESC resDesc;
|
| FillResourceDescForBuffer(resDesc, BUF_SIZE);
|
|
|
| ALLOCATION_DESC allocDesc = {};
|
| if (usePool)
|
| allocDesc = CALLOCATION_DESC(pool.Get());
|
| else
|
| allocDesc = CALLOCATION_DESC(D3D12_HEAP_TYPE_DEFAULT);
|
| if (useCommitted)
|
| allocDesc.Flags |= ALLOCATION_FLAG_COMMITTED;
|
|
|
| ComPtr<Allocation> allocs[BUF_COUNT];
|
| for (UINT i = 0; i < BUF_COUNT; ++i)
|
| {
|
| CHECK_HR(ctx.allocator->CreateResource(
|
| &allocDesc, &resDesc, D3D12_RESOURCE_STATE_COMMON,
|
| NULL, &allocs[i], IID_NULL, NULL));
|
| }
|
|
|
| // Get stats "WithBufs".
|
| Budget localBudgetWithBufs = {};
|
| Budget nonLocalBudgetWithBufs = {};
|
| ctx.allocator->GetBudget(&localBudgetWithBufs, &nonLocalBudgetWithBufs);
|
| CheckBudgetBasics(ctx, localBudgetWithBufs, nonLocalBudgetWithBufs);
|
|
|
| TotalStatistics statsWithBufs = {};
|
| ctx.allocator->CalculateStatistics(&statsWithBufs);
|
| CheckTotalStatistics(statsWithBufs);
|
|
|
| Statistics poolStatsWithBufs = {};
|
| DetailedStatistics detailedPoolStatsWithBufs = {};
|
| if (usePool)
|
| {
|
| pool->GetStatistics(&poolStatsWithBufs);
|
| pool->CalculateStatistics(&detailedPoolStatsWithBufs);
|
| CheckStatistics(detailedPoolStatsWithBufs);
|
| }
|
|
|
| // Destroy buffers.
|
| for (size_t i = BUF_COUNT; i--; )
|
| {
|
| allocs[i].Reset();
|
| }
|
|
|
| // Get pool stats "End".
|
| Statistics poolStatsEnd = {};
|
| DetailedStatistics detailedPoolStatsEnd = {};
|
| if (usePool)
|
| {
|
| pool->GetStatistics(&poolStatsEnd);
|
| pool->CalculateStatistics(&detailedPoolStatsEnd);
|
| CheckStatistics(detailedPoolStatsEnd);
|
| }
|
|
|
| // Destroy the pool.
|
| pool.Reset();
|
|
|
| // Get stats "End".
|
| Budget localBudgetEnd = {};
|
| Budget nonLocalBudgetEnd = {};
|
| ctx.allocator->GetBudget(&localBudgetEnd, &nonLocalBudgetEnd);
|
| CheckBudgetBasics(ctx, localBudgetEnd, nonLocalBudgetEnd);
|
|
|
| TotalStatistics statsEnd = {};
|
| ctx.allocator->CalculateStatistics(&statsEnd);
|
| CheckTotalStatistics(statsEnd);
|
|
|
| // CHECK THE STATS: Local.
|
| {
|
| CHECK_BOOL(localBudgetBeg.Stats.AllocationBytes <= localBudgetEnd.Stats.AllocationBytes);
|
|
|
| // Budget::UsageBytes.
|
| CHECK_BOOL(localBudgetWithBufs.UsageBytes >= localBudgetBeg.UsageBytes);
|
| CHECK_BOOL(localBudgetEnd.UsageBytes <= localBudgetWithBufs.UsageBytes);
|
|
|
| // Budget - Statistics::AllocationBytes.
|
| CHECK_BOOL(localBudgetEnd.Stats.AllocationBytes == localBudgetBeg.Stats.AllocationBytes);
|
| CHECK_BOOL(localBudgetWithBufs.Stats.AllocationBytes == localBudgetBeg.Stats.AllocationBytes + BUF_SIZE * BUF_COUNT);
|
|
|
| // Budget - Statistics::BlockBytes.
|
| if (usePool)
|
| {
|
| CHECK_BOOL(localBudgetEnd.Stats.BlockBytes == localBudgetBeg.Stats.BlockBytes);
|
| CHECK_BOOL(localBudgetWithBufs.Stats.BlockBytes > localBudgetBeg.Stats.BlockBytes);
|
| }
|
| else
|
| {
|
| CHECK_BOOL(localBudgetWithBufs.Stats.BlockBytes >= localBudgetBeg.Stats.BlockBytes);
|
| }
|
|
|
| // Budget - Statistics::AllocationCount.
|
| CHECK_BOOL(localBudgetEnd.Stats.AllocationCount == localBudgetBeg.Stats.AllocationCount);
|
| CHECK_BOOL(localBudgetWithBufs.Stats.AllocationCount == localBudgetBeg.Stats.AllocationCount + BUF_COUNT);
|
|
|
| // Budget - Statistics::BlockCount.
|
| if (useCommitted)
|
| {
|
| CHECK_BOOL(localBudgetEnd.Stats.BlockCount == localBudgetBeg.Stats.BlockCount);
|
| CHECK_BOOL(localBudgetWithBufs.Stats.BlockCount == localBudgetBeg.Stats.BlockCount + BUF_COUNT);
|
| }
|
| else if (usePool)
|
| {
|
| CHECK_BOOL(localBudgetEnd.Stats.BlockCount == localBudgetBeg.Stats.BlockCount);
|
| if (usePreallocated)
|
| {
|
| CHECK_BOOL(localBudgetWithBufs.Stats.BlockCount == localBudgetBeg.Stats.BlockCount + 1);
|
| }
|
| else
|
| {
|
| CHECK_BOOL(localBudgetWithBufs.Stats.BlockCount > localBudgetBeg.Stats.BlockCount);
|
| }
|
| }
|
|
|
| // Compare CalculateStatistics per memory segment group with GetBudget.
|
| CHECK_BOOL(StatisticsEqual(statsBeg.MemorySegmentGroup[0].Stats, localBudgetBeg.Stats));
|
| CHECK_BOOL(StatisticsEqual(statsWithBufs.MemorySegmentGroup[0].Stats, localBudgetWithBufs.Stats));
|
| CHECK_BOOL(StatisticsEqual(statsEnd.MemorySegmentGroup[0].Stats, localBudgetEnd.Stats));
|
| }
|
|
|
| // CHECK THE STATS: Non-local.
|
| {
|
| CHECK_BOOL(nonLocalBudgetEnd.Stats.AllocationBytes == nonLocalBudgetBeg.Stats.AllocationBytes &&
|
| nonLocalBudgetEnd.Stats.AllocationBytes == nonLocalBudgetWithBufs.Stats.AllocationBytes);
|
| CHECK_BOOL(nonLocalBudgetEnd.Stats.BlockBytes == nonLocalBudgetBeg.Stats.BlockBytes &&
|
| nonLocalBudgetEnd.Stats.BlockBytes == nonLocalBudgetWithBufs.Stats.BlockBytes);
|
| CHECK_BOOL(nonLocalBudgetEnd.Stats.AllocationCount == nonLocalBudgetBeg.Stats.AllocationCount &&
|
| nonLocalBudgetEnd.Stats.AllocationCount == nonLocalBudgetWithBufs.Stats.AllocationCount);
|
| CHECK_BOOL(nonLocalBudgetEnd.Stats.BlockCount == nonLocalBudgetBeg.Stats.BlockCount &&
|
| nonLocalBudgetEnd.Stats.BlockCount == nonLocalBudgetWithBufs.Stats.BlockCount);
|
|
|
| // Compare CalculateStatistics per memory segment group with GetBudget.
|
| CHECK_BOOL(StatisticsEqual(statsBeg.MemorySegmentGroup[1].Stats, nonLocalBudgetBeg.Stats));
|
| CHECK_BOOL(StatisticsEqual(statsWithBufs.MemorySegmentGroup[1].Stats, nonLocalBudgetWithBufs.Stats));
|
| CHECK_BOOL(StatisticsEqual(statsEnd.MemorySegmentGroup[1].Stats, nonLocalBudgetEnd.Stats));
|
| }
|
|
|
| if (usePool)
|
| {
|
| // Compare simple stats with calculated stats to make sure they are identical.
|
| CHECK_BOOL(StatisticsEqual(poolStatsBeg, detailedPoolStatsBeg.Stats));
|
| CHECK_BOOL(StatisticsEqual(poolStatsWithBufs, detailedPoolStatsWithBufs.Stats));
|
| CHECK_BOOL(StatisticsEqual(poolStatsEnd, detailedPoolStatsEnd.Stats));
|
|
|
| // Validate stats of an empty pool.
|
| CHECK_BOOL(detailedPoolStatsBeg.AllocationSizeMax == 0);
|
| CHECK_BOOL(detailedPoolStatsEnd.AllocationSizeMax == 0);
|
| CHECK_BOOL(detailedPoolStatsBeg.AllocationSizeMin == UINT64_MAX);
|
| CHECK_BOOL(detailedPoolStatsEnd.AllocationSizeMin == UINT64_MAX);
|
| CHECK_BOOL(poolStatsBeg.AllocationCount == 0);
|
| CHECK_BOOL(poolStatsBeg.AllocationBytes == 0);
|
| CHECK_BOOL(poolStatsEnd.AllocationCount == 0);
|
| CHECK_BOOL(poolStatsEnd.AllocationBytes == 0);
|
| if (usePreallocated)
|
| {
|
| CHECK_BOOL(poolStatsBeg.BlockCount == 1);
|
| CHECK_BOOL(poolStatsEnd.BlockCount == 1);
|
| CHECK_BOOL(poolStatsBeg.BlockBytes == PREALLOCATED_BLOCK_SIZE);
|
| CHECK_BOOL(poolStatsEnd.BlockBytes == PREALLOCATED_BLOCK_SIZE);
|
| }
|
| else
|
| {
|
| CHECK_BOOL(poolStatsBeg.BlockCount == 0);
|
| CHECK_BOOL(poolStatsBeg.BlockBytes == 0);
|
| // Not checking poolStatsEnd.blockCount, blockBytes, because an empty block may stay allocated.
|
| }
|
|
|
| // Validate stats of a pool with buffers.
|
| CHECK_BOOL(detailedPoolStatsWithBufs.AllocationSizeMin == BUF_SIZE);
|
| CHECK_BOOL(detailedPoolStatsWithBufs.AllocationSizeMax == BUF_SIZE);
|
| CHECK_BOOL(poolStatsWithBufs.AllocationCount == BUF_COUNT);
|
| CHECK_BOOL(poolStatsWithBufs.AllocationBytes == BUF_COUNT * BUF_SIZE);
|
| if (usePreallocated)
|
| {
|
| CHECK_BOOL(poolStatsWithBufs.BlockCount == 1);
|
| CHECK_BOOL(poolStatsWithBufs.BlockBytes == PREALLOCATED_BLOCK_SIZE);
|
| }
|
| else
|
| {
|
| CHECK_BOOL(poolStatsWithBufs.BlockCount > 0);
|
| CHECK_BOOL(poolStatsWithBufs.BlockBytes >= poolStatsWithBufs.AllocationBytes);
|
| }
|
| }
|
|
|
| // No allocation from D3D12_HEAP_TYPE_CUSTOM or GPU_UPLOAD in this test.
|
| CHECK_BOOL(statsEnd.HeapType[3].Stats.BlockCount == 0);
|
| CHECK_BOOL(statsEnd.HeapType[4].Stats.BlockCount == 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::CALLOCATION_DESC allocDescUpload = D3D12MA::CALLOCATION_DESC{ D3D12_HEAP_TYPE_UPLOAD };
|
| D3D12MA::CALLOCATION_DESC allocDescDefault = D3D12MA::CALLOCATION_DESC{ D3D12_HEAP_TYPE_DEFAULT };
|
| D3D12MA::CALLOCATION_DESC allocDescReadback = D3D12MA::CALLOCATION_DESC{ 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_COMMON,
|
| NULL,
|
| &resourcesUpload[i].allocation,
|
| IID_PPV_ARGS(&resourcesUpload[i].resource)) );
|
|
|
| CHECK_HR( ctx.allocator->CreateResource(
|
| &allocDescDefault,
|
| &resourceDesc,
|
| D3D12_RESOURCE_STATE_COMMON,
|
| NULL,
|
| &resourcesDefault[i].allocation,
|
| IID_PPV_ARGS(&resourcesDefault[i].resource)) );
|
|
|
| CHECK_HR( ctx.allocator->CreateResource(
|
| &allocDescReadback,
|
| &resourceDesc,
|
| D3D12_RESOURCE_STATE_COMMON,
|
| 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 TestMultithreading(const TestContext& ctx)
|
| {
|
| wprintf(L"Test multithreading\n");
|
|
|
| const UINT threadCount = 32;
|
| const UINT bufSizeMin = 1024ull;
|
| const UINT bufSizeMax = 1024ull * 1024;
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_ALGORITHM_LINEAR,
|
| 64 * KILOBYTE * 300, // blockSize; alignment of buffers is always 64 KB.
|
| 1, // minBlockCount
|
| 1 }; // maxBlockCount
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12_RESOURCE_DESC buffDesc = {};
|
| FillResourceDescForBuffer(buffDesc, 0);
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_ALGORITHM_LINEAR,
|
| 6 * 64 * KILOBYTE, // blockSize
|
| 1, // minBlockCount
|
| 1 }; // maxBlockCount
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12_RESOURCE_DESC buffDesc = {};
|
| FillResourceDescForBuffer(buffDesc, 0);
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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
|
| 393216 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| algorithm, // flags
|
| bufSize * maxBufCapacity, // blockSize
|
| 1, // minBlockCount
|
| 1 }; // maxBlockCount
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12_RESOURCE_ALLOCATION_INFO allocInfo = {};
|
| allocInfo.SizeInBytes = bufSize;
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS };
|
| poolDesc.pProtectedSession = session.Get();
|
|
|
| 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, 64 * KILOBYTE);
|
|
|
| for(UINT testIndex = 0; testIndex < 2; ++testIndex)
|
| {
|
| // Create a buffer
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| 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 heap and placed buffer in it
|
|
|
| allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_CAN_ALIAS;
|
|
|
| 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);
|
|
|
| // Create a placed buffer
|
|
|
| allocDesc.Flags &= ~D3D12MA::ALLOCATION_FLAG_COMMITTED;
|
|
|
| ComPtr<D3D12MA::Allocation> allocPtr2;
|
| ComPtr<ID3D12Resource> res2;
|
| CHECK_HR(ctx.allocator->CreateResource2(&allocDesc, &resourceDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL,
|
| &allocPtr2, IID_PPV_ARGS(&res2)));
|
| CHECK_BOOL(allocPtr2->GetHeap()!= NULL);
|
|
|
| // Create an aliasing buffer
|
| ComPtr<ID3D12Resource> res3;
|
| CHECK_HR(ctx.allocator->CreateAliasingResource1(allocPtr2.Get(), 0, &resourceDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL,
|
| IID_PPV_ARGS(&res3)));
|
| }
|
| #endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__
|
|
|
| #ifdef __ID3D12Device10_INTERFACE_DEFINED__
|
| static void TestDevice10(const TestContext& ctx)
|
| {
|
| wprintf(L"Test ID3D12Device10\n");
|
|
|
| ComPtr<ID3D12Device10> dev10;
|
| if(FAILED(ctx.device->QueryInterface(IID_PPV_ARGS(&dev10))))
|
| {
|
| wprintf(L"QueryInterface for ID3D12Device10 failed!\n");
|
| return;
|
| }
|
|
|
| D3D12_RESOURCE_DESC1 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_UNKNOWN;
|
|
|
| // Create a committed texture
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12MA::ALLOCATION_FLAG_COMMITTED };
|
|
|
| ComPtr<D3D12MA::Allocation> allocPtr0;
|
| ComPtr<ID3D12Resource> res0;
|
| CHECK_HR(ctx.allocator->CreateResource3(&allocDesc, &resourceDesc,
|
| D3D12_BARRIER_LAYOUT_UNDEFINED, NULL, 0, NULL,
|
| &allocPtr0, IID_PPV_ARGS(&res0)));
|
| CHECK_BOOL(allocPtr0->GetHeap() == NULL);
|
|
|
| // Create a heap and placed texture in it
|
|
|
| allocDesc.Flags |= D3D12MA::ALLOCATION_FLAG_CAN_ALIAS;
|
|
|
| ComPtr<D3D12MA::Allocation> allocPtr1;
|
| ComPtr<ID3D12Resource> res1;
|
| CHECK_HR(ctx.allocator->CreateResource3(&allocDesc, &resourceDesc,
|
| D3D12_BARRIER_LAYOUT_UNDEFINED, NULL, 0, NULL,
|
| &allocPtr1, IID_PPV_ARGS(&res1)));
|
| CHECK_BOOL(allocPtr1->GetHeap() != NULL);
|
|
|
| // Create a placed texture
|
|
|
| allocDesc.Flags &= ~D3D12MA::ALLOCATION_FLAG_COMMITTED;
|
|
|
| ComPtr<D3D12MA::Allocation> allocPtr2;
|
| ComPtr<ID3D12Resource> res2;
|
| CHECK_HR(ctx.allocator->CreateResource3(&allocDesc, &resourceDesc,
|
| D3D12_BARRIER_LAYOUT_UNDEFINED, NULL, 0, NULL,
|
| &allocPtr2, IID_PPV_ARGS(&res2)));
|
| CHECK_BOOL(allocPtr2->GetHeap() != NULL);
|
|
|
| // Create an aliasing texture
|
| ComPtr<ID3D12Resource> res3;
|
| CHECK_HR(ctx.allocator->CreateAliasingResource2(allocPtr2.Get(), 0, &resourceDesc,
|
| D3D12_BARRIER_LAYOUT_UNDEFINED, NULL, 0, NULL,
|
| IID_PPV_ARGS(&res3)));
|
| }
|
| #endif // #ifdef __ID3D12Device10_INTERFACE_DEFINED__
|
|
|
| #ifdef __ID3D12Device12_INTERFACE_DEFINED__
|
| static void TestDevice12(const TestContext& ctx)
|
| {
|
| wprintf(L"Test ID3D12Device12\n");
|
|
|
| ComPtr<ID3D12Device12> dev12;
|
| if (FAILED(ctx.device->QueryInterface(IID_PPV_ARGS(&dev12))))
|
| {
|
| wprintf(L"QueryInterface for ID3D12Device12 failed!\n");
|
| return;
|
| }
|
|
|
| // Texture based on Issue #62.
|
| D3D12_RESOURCE_DESC1 resourceDesc = {};
|
| resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
| resourceDesc.Width = 1920;
|
| resourceDesc.Height = 1080;
|
| resourceDesc.DepthOrArraySize = 1;
|
| resourceDesc.MipLevels = 1;
|
| resourceDesc.Format = DXGI_FORMAT_BC3_UNORM;
|
| resourceDesc.SampleDesc.Count = 1;
|
| resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
| resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
|
|
| const DXGI_FORMAT castableFormats[] = { DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_BC3_UNORM_SRGB };
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ D3D12_HEAP_TYPE_DEFAULT };
|
|
|
| ComPtr<D3D12MA::Allocation> alloc0;
|
| ComPtr<ID3D12Resource> res0;
|
| HRESULT hr = ctx.allocator->CreateResource3(&allocDesc, &resourceDesc,
|
| D3D12_BARRIER_LAYOUT_UNDEFINED, NULL,
|
| _countof(castableFormats), castableFormats,
|
| &alloc0, IID_PPV_ARGS(&res0));
|
|
|
| if (hr == E_INVALIDARG)
|
| {
|
| wprintf(L"Allocator::CreateResource3 failed with E_INVALIDARG!\n");
|
| return;
|
| }
|
|
|
| CHECK_HR(hr);
|
| CHECK_BOOL(alloc0 && res0);
|
| }
|
| #endif // #ifdef __ID3D12Device12_INTERFACE_DEFINED__
|
|
|
| static void TestGPUUploadHeap(const TestContext& ctx)
|
| {
|
| #if D3D12_SDK_VERSION >= 610
|
| using namespace D3D12MA;
|
|
|
| wprintf(L"Test GPU Upload Heap\n");
|
|
|
| const bool supported = ctx.allocator->IsGPUUploadHeapSupported();
|
|
|
| Budget begLocalBudget = {};
|
| ctx.allocator->GetBudget(&begLocalBudget, NULL);
|
| TotalStatistics begStats = {};
|
| ctx.allocator->CalculateStatistics(&begStats);
|
|
|
| // Create a buffer, likely placed.
|
| CALLOCATION_DESC allocDesc = CALLOCATION_DESC{ D3D12_HEAP_TYPE_GPU_UPLOAD };
|
| D3D12_RESOURCE_DESC resDesc;
|
| FillResourceDescForBuffer(resDesc, 64 * KILOBYTE);
|
|
|
| ComPtr<Allocation> alloc;
|
| HRESULT hr = ctx.allocator->CreateResource(&allocDesc, &resDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &alloc, IID_NULL, NULL);
|
| if (!supported)
|
| {
|
| // Skip further tests. Just wanted to test that the respource creation fails with the right error code.
|
| CHECK_BOOL(hr == E_NOTIMPL);
|
| return;
|
| }
|
| CHECK_HR(hr);
|
| CHECK_BOOL(alloc && alloc->GetResource());
|
| CHECK_BOOL(alloc->GetResource()->GetGPUVirtualAddress() != 0);
|
|
|
| {
|
| D3D12_HEAP_PROPERTIES heapProps = {};
|
| D3D12_HEAP_FLAGS heapFlags = {};
|
| CHECK_HR(alloc->GetResource()->GetHeapProperties(&heapProps, &heapFlags));
|
| CHECK_BOOL(heapProps.Type == D3D12_HEAP_TYPE_GPU_UPLOAD);
|
| }
|
|
|
| // Create a committed one.
|
| CALLOCATION_DESC committedAllocDesc = allocDesc;
|
| committedAllocDesc.Flags |= ALLOCATION_FLAG_COMMITTED;
|
| ComPtr<Allocation> committedAlloc;
|
| CHECK_HR(ctx.allocator->CreateResource(&committedAllocDesc, &resDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &committedAlloc, IID_NULL, NULL));
|
| CHECK_BOOL(committedAlloc && committedAlloc->GetResource());
|
| CHECK_BOOL(committedAlloc->GetHeap() == NULL); // Committed, heap is implicit and inaccessible.
|
|
|
| // Create a custom pool and a buffer inside of it.
|
| CPOOL_DESC poolDesc = CPOOL_DESC{ D3D12_HEAP_TYPE_GPU_UPLOAD, D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS };
|
| ComPtr<Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| CALLOCATION_DESC poolAllocDesc = CALLOCATION_DESC{ pool.Get() };
|
| ComPtr<Allocation> poolAlloc;
|
| CHECK_HR(ctx.allocator->CreateResource(&poolAllocDesc, &resDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &poolAlloc, IID_NULL, NULL));
|
| CHECK_BOOL(poolAlloc && poolAlloc->GetResource());
|
|
|
| // Map the original buffer, write, then read
|
| {
|
| const auto res = alloc->GetResource();
|
|
|
| UINT* mappedData = NULL;
|
| CHECK_HR(res->Map(0, &EMPTY_RANGE, (void**)&mappedData)); // {0, 0} - not reading anything.
|
| for(UINT i = 0; i < resDesc.Width / sizeof(UINT); ++i)
|
| {
|
| mappedData[i] = i * 3;
|
| }
|
| res->Unmap(0, NULL); // NULL - written everything.
|
|
|
| CHECK_HR(res->Map(0, NULL, (void**)&mappedData)); // NULL - reading everything.
|
| CHECK_BOOL(mappedData[100] == 300);
|
| res->Unmap(0, &EMPTY_RANGE); // {0, 0} - not written anything.
|
|
|
| }
|
|
|
| // Create two big buffers.
|
| D3D12_RESOURCE_DESC bigResDesc = resDesc;
|
| bigResDesc.Width = 128 * MEGABYTE;
|
|
|
| ComPtr<Allocation> bigAllocs[2];
|
| for(UINT i = 0; i < 2; ++i)
|
| {
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &bigResDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &bigAllocs[i], IID_NULL, NULL));
|
| CHECK_BOOL(bigAllocs[i] && bigAllocs[i]->GetResource());
|
| }
|
|
|
| // Create a texture.
|
| constexpr UINT texSize = 256;
|
| D3D12_RESOURCE_DESC texDesc = {};
|
| texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
| texDesc.Alignment = 0;
|
| texDesc.Width = texSize;
|
| texDesc.Height = texSize;
|
| texDesc.DepthOrArraySize = 1;
|
| texDesc.MipLevels = 1;
|
| texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
| texDesc.SampleDesc.Count = 1;
|
| texDesc.SampleDesc.Quality = 0;
|
| texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
| texDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
| ComPtr<Allocation> texAlloc;
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &texDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &texAlloc, IID_NULL, NULL));
|
| CHECK_BOOL(texAlloc && texAlloc->GetResource());
|
|
|
| {
|
| std::vector<UINT> texPixels(texSize * texSize);
|
| // Contents of texPixels[i] doesn't matter.
|
| const auto texRes = texAlloc->GetResource();
|
| // Need to pass ppData == NULL for Map() to be used with a texture having D3D12_TEXTURE_LAYOUT_UNKNOWN.
|
| CHECK_HR(texRes->Map(0, &EMPTY_RANGE, NULL)); // {0, 0} - not reading anything.
|
| CHECK_HR(texRes->WriteToSubresource(
|
| 0, // DstSubresource
|
| NULL, // pDstBox
|
| texPixels.data(), // pSrcData
|
| texSize * sizeof(DWORD), // SrcRowPitch
|
| texSize * texSize * sizeof(DWORD))); // SrcDepthPitch
|
| texRes->Unmap(0, NULL); // NULL - written everything.
|
| }
|
|
|
| // Check budget and stats
|
| constexpr UINT totalAllocCount = 6;
|
| Budget endLocalBudget = {};
|
| ctx.allocator->GetBudget(&endLocalBudget, NULL);
|
| TotalStatistics endStats = {};
|
| ctx.allocator->CalculateStatistics(&endStats);
|
| CHECK_BOOL(endLocalBudget.UsageBytes >= begLocalBudget.UsageBytes
|
| + 2 * bigResDesc.Width
|
| && "This can fail if GPU_UPLOAD falls back to system RAM e.g. when under PIX?");
|
| auto validateStats = [totalAllocCount, &bigResDesc](const Statistics& begStats, const Statistics& endStats)
|
| {
|
| CHECK_BOOL(endStats.BlockCount >= begStats.BlockCount);
|
| CHECK_BOOL(endStats.BlockBytes >= begStats.BlockBytes);
|
| CHECK_BOOL(endStats.AllocationCount == begStats.AllocationCount + totalAllocCount);
|
| CHECK_BOOL(endStats.AllocationBytes > begStats.AllocationBytes + 2 * bigResDesc.Width);
|
| };
|
| validateStats(begLocalBudget.Stats, endLocalBudget.Stats);
|
| validateStats(begStats.Total.Stats, endStats.Total.Stats);
|
| validateStats(begStats.MemorySegmentGroup[0].Stats, endStats.MemorySegmentGroup[0].Stats); // DXGI_MEMORY_SEGMENT_GROUP_LOCAL
|
| validateStats(begStats.HeapType[4].Stats, endStats.HeapType[4].Stats); // D3D12_HEAP_TYPE_GPU_UPLOAD
|
| #endif
|
| }
|
|
|
| static void TestTightAlignment(const TestContext& ctx)
|
| {
|
| using namespace D3D12MA;
|
|
|
| wprintf(L"Test resource tight alignment\n");
|
|
|
| const bool isTightAlignmentEnabled = ctx.allocator->IsTightAlignmentSupported() &&
|
| (ctx.allocatorFlags & ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT) == 0;
|
|
|
| // Use a custom pool to make sure our small buffers are not created as committed.
|
| POOL_DESC poolDesc = {};
|
| poolDesc.BlockSize = MEGABYTE;
|
| poolDesc.MinBlockCount = poolDesc.MaxBlockCount = 1;
|
|
|
| D3D12_RESOURCE_DESC resDesc;
|
| FillResourceDescForBuffer(resDesc, 4);
|
|
|
| const D3D12_HEAP_TYPE heapTypes[] = { D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD };
|
| for (auto heapType : heapTypes)
|
| {
|
| poolDesc.HeapProperties.Type = heapType;
|
| for (uint32_t poolFlagsIndex = 0; poolFlagsIndex < 2; ++poolFlagsIndex)
|
| {
|
| poolDesc.Flags = poolFlagsIndex == 0 ? POOL_FLAG_NONE : POOL_FLAG_DONT_USE_TIGHT_ALIGNMENT;
|
| const bool isPoolTightAlignmentEnabled = isTightAlignmentEnabled && poolDesc.Flags == POOL_FLAG_NONE;
|
| for (uint32_t minAlignmentTestIndex = 0; minAlignmentTestIndex < 2; ++minAlignmentTestIndex)
|
| {
|
| // MinAllocationAlignment == 0 - default alignment.
|
| // MinAllocationAlignment == 1 - minimum possible alignment.
|
| poolDesc.MinAllocationAlignment = minAlignmentTestIndex;
|
|
|
| ComPtr<Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| ALLOCATION_DESC allocDesc = {};
|
| allocDesc.CustomPool = pool.Get();
|
|
|
| ComPtr<Allocation> allocs[2] = {};
|
|
|
| for (size_t i = 0; i < _countof(allocs); ++i)
|
| {
|
| CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc,
|
| D3D12_RESOURCE_STATE_COMMON, NULL, &allocs[i], IID_NULL, NULL));
|
| CHECK_BOOL(allocs[i] && allocs[i]->GetResource());
|
| }
|
|
|
| const UINT64 secondAllocOffset = allocs[1]->GetOffset();
|
|
|
| // Print the offset of the 2nd buffer.
|
| wprintf(L" In D3D12_HEAP_TYPE_%s, with Flags=0x%X, MinAllocationAlignment=%llu, a %llu B buffer was aligned to %llu B.\n",
|
| HEAP_TYPE_NAMES[(size_t)heapType],
|
| poolDesc.Flags,
|
| poolDesc.MinAllocationAlignment,
|
| resDesc.Width,
|
| secondAllocOffset);
|
|
|
| UINT64 expectedMinAlignment = 1;
|
| if (isPoolTightAlignmentEnabled)
|
| {
|
| if (minAlignmentTestIndex == 0)
|
| expectedMinAlignment = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT;
|
| }
|
| else
|
| expectedMinAlignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
|
| CHECK_BOOL(secondAllocOffset % expectedMinAlignment == 0);
|
| }
|
| }
|
| }
|
| }
|
|
|
| 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;
|
| CVIRTUAL_BLOCK_DESC blockDesc = CVIRTUAL_BLOCK_DESC{
|
| blockSize,
|
| VIRTUAL_BLOCK_FLAG_NONE,
|
| ctx.allocationCallbacks };
|
| CHECK_HR(CreateVirtualBlock(&blockDesc, &block));
|
| CHECK_BOOL(block);
|
|
|
| // # Allocate 8 MB
|
|
|
| CVIRTUAL_ALLOCATION_DESC allocDesc = CVIRTUAL_ALLOCATION_DESC{
|
| 8 * MEGABYTE, // size
|
| alignment,
|
| D3D12MA::VIRTUAL_ALLOCATION_FLAG_NONE,
|
| (void*)(uintptr_t)1 }; // privateData
|
| 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::CVIRTUAL_BLOCK_DESC blockDesc = D3D12MA::CVIRTUAL_BLOCK_DESC{
|
| 10'000,
|
| D3D12MA::VIRTUAL_BLOCK_FLAG_NONE,
|
| ctx.allocationCallbacks };
|
| 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)
|
| {
|
| const UINT64 size = calcRandomAllocSize();
|
| D3D12MA::CVIRTUAL_ALLOCATION_DESC allocDesc = D3D12MA::CVIRTUAL_ALLOCATION_DESC{
|
| size,
|
| 0, // alignment
|
| D3D12MA::VIRTUAL_ALLOCATION_FLAG_NONE,
|
| (void*)(uintptr_t)(size * 10) }; // privateData
|
| 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)
|
| {
|
| const UINT64 size = calcRandomAllocSize();
|
| D3D12MA::CVIRTUAL_ALLOCATION_DESC allocDesc = D3D12MA::CVIRTUAL_ALLOCATION_DESC{
|
| size,
|
| 0, // alignment
|
| D3D12MA::VIRTUAL_ALLOCATION_FLAG_NONE,
|
| (void*)(uintptr_t)(size * 10) }; // privateData
|
|
|
| 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)
|
| {
|
| const UINT64 size = calcRandomAllocSize();
|
| D3D12MA::CVIRTUAL_ALLOCATION_DESC allocDesc = D3D12MA::CVIRTUAL_ALLOCATION_DESC{
|
| size,
|
| 16, // alignment
|
| D3D12MA::VIRTUAL_ALLOCATION_FLAG_NONE,
|
| (void*)(uintptr_t)(size * 10) }; // privateData
|
|
|
| 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::CVIRTUAL_BLOCK_DESC blockDesc = D3D12MA::CVIRTUAL_BLOCK_DESC{
|
| 0,
|
| D3D12MA::VIRTUAL_BLOCK_FLAG_NONE,
|
| ctx.allocationCallbacks };
|
|
|
| RandomNumberGenerator rand{ 20092010 };
|
|
|
| std::vector<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);
|
| }
|
|
|
| std::vector <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::CVIRTUAL_ALLOCATION_DESC allocCreateInfo = D3D12MA::CVIRTUAL_ALLOCATION_DESC{
|
| allocSizes[i],
|
| 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();
|
|
|
| // Fix for D3D12 ERROR: ID3D12Device::CreatePlacedResource: D3D12_RESOURCE_DESC::Alignment is invalid. The value is 8. When D3D12_RESOURCE_DESC::Flag bit for D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT is set, Alignment must be 0. [ STATE_CREATION ERROR #721: CREATERESOURCE_INVALIDALIGNMENT]
|
| if ((desc.Flags & D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT_COPY) != 0)
|
| desc.Alignment = 0;
|
|
|
| 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_UPLOAD,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_NONE,
|
| BLOCK_SIZE };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CPOOL_DESC poolDesc = D3D12MA::CPOOL_DESC{
|
| D3D12_HEAP_TYPE_UPLOAD,
|
| D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
|
| D3D12MA::POOL_FLAG_NONE,
|
| BLOCK_SIZE };
|
| ComPtr<D3D12MA::Pool> pool;
|
| CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));
|
|
|
| D3D12MA::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_UPLOAD,
|
| D3D12MA::ALLOCATION_FLAG_NONE,
|
| NULL, // privateData
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{
|
| D3D12_HEAP_TYPE_DEFAULT,
|
| D3D12MA::ALLOCATION_FLAG_NONE,
|
| NULL, // privateData
|
| 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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::CALLOCATION_DESC allocDesc = D3D12MA::CALLOCATION_DESC{ 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);
|
| TestSmallBuffers(ctx);
|
| TestCustomHeapFlags(ctx);
|
| TestPlacedResources(ctx);
|
| TestOtherComInterface(ctx);
|
| TestCustomPools(ctx);
|
| TestCustomPool_MinAllocationAlignment(ctx);
|
| TestCustomPool_Committed(ctx);
|
| TestCustomPool_AlwaysCommitted(ctx);
|
| TestPoolsAndAllocationParameters(ctx);
|
| TestCustomHeaps(ctx);
|
| TestStandardCustomCommittedPlaced(ctx);
|
| TestAliasingMemory(ctx); |
| TestAliasingImplicitCommitted(ctx); |
| TestMsaa64KBAlignedTextureSupported_DefaultPool(ctx); |
| TestMsaa64KBAlignedTextureSupported_CustomPool(ctx); |
| TestSmallTextureAlignmentAcrossDimensions(ctx); |
| TestPoolMsaaTextureAsCommitted(ctx); |
| TestMapping(ctx); |
| TestStats(ctx);
|
| TestTransfer(ctx);
|
| TestMultithreading(ctx);
|
| TestLinearAllocator(ctx);
|
| TestLinearAllocatorMultiBlock(ctx);
|
| ManuallyTestLinearAllocator(ctx);
|
| #ifdef __ID3D12Device4_INTERFACE_DEFINED__
|
| TestDevice4(ctx);
|
| #endif
|
| #ifdef __ID3D12Device8_INTERFACE_DEFINED__
|
| TestDevice8(ctx);
|
| #endif
|
| #ifdef __ID3D12Device10_INTERFACE_DEFINED__
|
| TestDevice10(ctx);
|
| #endif
|
| #ifdef __ID3D12Device12_INTERFACE_DEFINED__
|
| TestDevice12(ctx);
|
| #endif
|
|
|
| TestGPUUploadHeap(ctx);
|
| TestTightAlignment(ctx);
|
|
|
| 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");
|
| }
|