blob: 0f22d35f439049f2f3cc82bb21b3613303cbe4bd [file] [log] [blame]
//
// Copyright (c) 2019 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 "Tests.h"
#include "Common.h"
#include <thread>
extern ID3D12GraphicsCommandList* BeginCommandList();
extern void EndCommandList(ID3D12GraphicsCommandList* cmdList);
struct AllocationDeleter
{
void operator()(D3D12MA::Allocation* obj) const
{
if(obj)
{
obj->Release();
}
}
};
typedef std::unique_ptr<D3D12MA::Allocation, AllocationDeleter> AllocationUniquePtr;
struct ResourceWithAllocation
{
CComPtr<ID3D12Resource> resource;
AllocationUniquePtr allocation;
UINT64 size = UINT64_MAX;
UINT dataSeed = 0;
};
static void FillResourceDescForBuffer(D3D12_RESOURCE_DESC& outResourceDesc, UINT64 size)
{
outResourceDesc = {};
outResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
outResourceDesc.Alignment = 0;
outResourceDesc.Width = size;
outResourceDesc.Height = 1;
outResourceDesc.DepthOrArraySize = 1;
outResourceDesc.MipLevels = 1;
outResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
outResourceDesc.SampleDesc.Count = 1;
outResourceDesc.SampleDesc.Quality = 0;
outResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
outResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
}
static void FillData(void* outPtr, const UINT64 sizeInBytes, UINT seed)
{
UINT* outValues = (UINT*)outPtr;
const UINT64 sizeInValues = sizeInBytes / sizeof(UINT);
UINT value = seed;
for(UINT i = 0; i < sizeInValues; ++i)
{
outValues[i] = value++;
}
}
static 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++)
{
//FAIL("ValidateData failed.");
return false;
}
}
return true;
}
static void TestCommittedResources(const TestContext& ctx)
{
wprintf(L"Test committed resources\n");
const UINT count = 4;
const UINT64 bufSize = 32ull * 1024;
const wchar_t* names[count] = {
L"Resource\nFoo\r\nBar",
L"Resource \"'&<>?#@!&-=_+[]{};:,./\\",
nullptr,
L"",
};
ResourceWithAllocation resources[count];
D3D12MA::ALLOCATION_DESC allocDesc = {};
allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
allocDesc.Flags = D3D12MA::ALLOCATION_FLAG_COMMITTED;
D3D12_RESOURCE_DESC resourceDesc;
FillResourceDescForBuffer(resourceDesc, bufSize);
for(UINT i = 0; i < count; ++i)
{
D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource(
&allocDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&alloc,
IID_PPV_ARGS(&resources[i].resource)) );
resources[i].allocation.reset(alloc);
// 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);
}
}
}
static void TestPlacedResources(const TestContext& ctx)
{
wprintf(L"Test placed resources\n");
const UINT count = 4;
const UINT64 bufSize = 32ull * 1024;
ResourceWithAllocation resources[count];
D3D12MA::ALLOCATION_DESC allocDesc = {};
allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
D3D12_RESOURCE_DESC resourceDesc;
FillResourceDescForBuffer(resourceDesc, bufSize);
D3D12MA::Allocation* alloc = nullptr;
for(UINT i = 0; i < count; ++i)
{
CHECK_HR( ctx.allocator->CreateResource(
&allocDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&alloc,
IID_PPV_ARGS(&resources[i].resource)) );
resources[i].allocation.reset(alloc);
// Make sure it doesn't have implicit heap.
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());
}
}
}
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,
&alloc,
IID_PPV_ARGS(&textureRes.resource)) );
textureRes.allocation.reset(alloc);
// 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,
&alloc,
IID_PPV_ARGS(&renderTargetRes.resource)) );
renderTargetRes.allocation.reset(alloc);
}
static void TestMapping(const TestContext& ctx)
{
wprintf(L"Test mapping\n");
const UINT count = 10;
const UINT64 bufSize = 32ull * 1024;
ResourceWithAllocation resources[count];
D3D12MA::ALLOCATION_DESC allocDesc = {};
allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC resourceDesc;
FillResourceDescForBuffer(resourceDesc, bufSize);
for(UINT i = 0; i < count; ++i)
{
D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource(
&allocDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&alloc,
IID_PPV_ARGS(&resources[i].resource)) );
resources[i].allocation.reset(alloc);
void* mappedPtr = NULL;
CHECK_HR( resources[i].resource->Map(0, NULL, &mappedPtr) );
FillData(mappedPtr, bufSize, i);
// Unmap every other buffer. Leave others mapped.
if((i % 2) != 0)
{
resources[i].resource->Unmap(0, NULL);
}
}
}
static void TestTransfer(const TestContext& ctx)
{
wprintf(L"Test mapping\n");
const UINT count = 10;
const UINT64 bufSize = 32ull * 1024;
ResourceWithAllocation resourcesUpload[count];
ResourceWithAllocation resourcesDefault[count];
ResourceWithAllocation resourcesReadback[count];
D3D12MA::ALLOCATION_DESC allocDescUpload = {};
allocDescUpload.HeapType = D3D12_HEAP_TYPE_UPLOAD;
D3D12MA::ALLOCATION_DESC allocDescDefault = {};
allocDescDefault.HeapType = D3D12_HEAP_TYPE_DEFAULT;
D3D12MA::ALLOCATION_DESC allocDescReadback = {};
allocDescReadback.HeapType = D3D12_HEAP_TYPE_READBACK;
D3D12_RESOURCE_DESC resourceDesc;
FillResourceDescForBuffer(resourceDesc, bufSize);
// Create 3 sets of resources.
for(UINT i = 0; i < count; ++i)
{
D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource(
&allocDescUpload,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&alloc,
IID_PPV_ARGS(&resourcesUpload[i].resource)) );
resourcesUpload[i].allocation.reset(alloc);
CHECK_HR( ctx.allocator->CreateResource(
&allocDescDefault,
&resourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
NULL,
&alloc,
IID_PPV_ARGS(&resourcesDefault[i].resource)) );
resourcesDefault[i].allocation.reset(alloc);
CHECK_HR( ctx.allocator->CreateResource(
&allocDescReadback,
&resourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
NULL,
&alloc,
IID_PPV_ARGS(&resourcesReadback[i].resource)) );
resourcesReadback[i].allocation.reset(alloc);
}
// Map and fill data in UPLOAD.
for(UINT i = 0; i < count; ++i)
{
void* mappedPtr = nullptr;
CHECK_HR( resourcesUpload[i].resource->Map(0, NULL, &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, 0, resourcesUpload[i].resource, 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;
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, 0, resourcesDefault[i].resource, 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)
{
const D3D12_RANGE writtenRange = {0, 0};
resourcesReadback[i].resource->Unmap(0, &writtenRange);
}
}
}
static void TestMultithreading(const TestContext& ctx)
{
wprintf(L"Test multithreading\n");
const UINT threadCount = 32;
const UINT bufSizeMin = 1024ull;
const UINT bufSizeMax = 1024ull * 1024;
D3D12MA::ALLOCATION_DESC allocDesc = {};
allocDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
// Launch threads.
std::thread threads[threadCount];
for(UINT threadIndex = 0; threadIndex < threadCount; ++threadIndex)
{
auto threadFunc = [&, threadIndex]()
{
RandomNumberGenerator rand(threadIndex);
std::vector<ResourceWithAllocation> resources;
resources.reserve(256);
// Create starting number of buffers.
const UINT bufToCreateCount = 64;
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);
D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource(
&allocDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&alloc,
IID_PPV_ARGS(&res.resource)) );
res.allocation.reset(alloc);
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);
// 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);
D3D12MA::Allocation* alloc = nullptr;
CHECK_HR( ctx.allocator->CreateResource(
&allocDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&alloc,
IID_PPV_ARGS(&res.resource)) );
res.allocation.reset(alloc);
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)
{
D3D12_RANGE writtenRange = {0, 0};
resources[resIndex].resource->Unmap(0, &writtenRange);
}
resources.pop_back();
}
};
threads[threadIndex] = std::thread(threadFunc);
}
// Wait for threads to finish.
for(UINT threadIndex = threadCount; threadIndex--; )
{
threads[threadIndex].join();
}
}
static void TestGroupBasics(const TestContext& ctx)
{
TestCommittedResources(ctx);
TestPlacedResources(ctx);
TestMapping(ctx);
TestTransfer(ctx);
TestMultithreading(ctx);
}
void Test(const TestContext& ctx)
{
wprintf(L"TESTS BEGIN\n");
if(false)
{
////////////////////////////////////////////////////////////////////////////////
// Temporarily insert custom tests here:
return;
}
TestGroupBasics(ctx);
wprintf(L"TESTS END\n");
}