Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,11 +606,13 @@ namespace gpgmm { namespace d3d12 {
/*memoryAllocator*/ std::move(pooledOrNonPooledAllocator));
}
case ALLOCATOR_ALGORITHM_SLAB: {
// Any amount of fragmentation must be allowed for small buffers since the resource
// heap size cannot change.
return std::make_unique<SlabCacheAllocator>(
/*maxSlabSize*/ heapAlignment,
/*slabSize*/ heapAlignment,
/*slabAlignment*/ heapAlignment,
/*slabFragmentationLimit*/ 0,
/*slabFragmentationLimit*/ 1,
/*allowSlabPrefetch*/ false,
/*slabMemoryGrowth*/ 1,
/*memoryAllocator*/ std::move(pooledOrNonPooledAllocator));
Expand Down Expand Up @@ -819,7 +821,25 @@ namespace gpgmm { namespace d3d12 {
!mIsAlwaysCommitted && !neverSubAllocate) {
allocator = mSmallBufferAllocatorOfType[static_cast<size_t>(resourceHeapType)].get();

request.Alignment = (newResourceDesc.Alignment == 0) ? 1 : newResourceDesc.Alignment;
// GetResourceAllocationInfo() always rejects smaller alignments than 64KB.
if (resourceDescriptor.Alignment == 0) {
// Only constant buffers must be 256B aligned.
request.Alignment = (initialResourceState == D3D12_RESOURCE_STATE_GENERIC_READ)
? D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT
: 1;
} else {
request.Alignment = resourceDescriptor.Alignment;
}

if (resourceDescriptor.Alignment != 0 &&
resourceDescriptor.Alignment > request.Alignment) {
DebugEvent("ResourceAllocator.CreateResource",
ALLOCATOR_MESSAGE_ID_ALIGNMENT_MISMATCH)
<< "Requested resource alignment is much larger than required (" +
std::to_string(resourceDescriptor.Alignment) + " vs " +
std::to_string(request.Alignment) + " bytes) for resource : " +
JSONSerializer::Serialize(resourceDescriptor).ToString() + ".";
}

// Pre-fetching is not supported for resources since the pre-fetch thread must allocate
// through |this| via CreateCommittedResource which is already locked by
Expand Down
5 changes: 3 additions & 2 deletions src/tests/D3D12Test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ namespace gpgmm { namespace d3d12 {
D3D12_RESOURCE_DESC D3D12TestBase::CreateBasicTextureDesc(DXGI_FORMAT format,
uint64_t width,
uint64_t height,
uint32_t sampleCount) {
uint32_t sampleCount,
uint64_t alignment) {
D3D12_RESOURCE_DESC resourceDesc;
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resourceDesc.Alignment = 0;
resourceDesc.Alignment = alignment;
resourceDesc.Width = width;
resourceDesc.Height = height;
resourceDesc.DepthOrArraySize = 1;
Expand Down
3 changes: 2 additions & 1 deletion src/tests/D3D12Test.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ namespace gpgmm { namespace d3d12 {
static D3D12_RESOURCE_DESC CreateBasicTextureDesc(DXGI_FORMAT format,
uint64_t width,
uint64_t height,
uint32_t sampleCount = 1);
uint32_t sampleCount = 1,
uint64_t alignment = 0);

static std::vector<MEMORY_ALLOCATION_EXPECT> GenerateBufferAllocations();

Expand Down
202 changes: 144 additions & 58 deletions src/tests/end2end/D3D12ResourceAllocatorTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,89 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferNeverAllocate) {
ASSERT_NE(allocationB, nullptr);
}

TEST_F(D3D12ResourceAllocatorTests, CreateBufferSuballocatedWithin) {
TEST_F(D3D12ResourceAllocatorTests, CreateBufferWithin) {
ComPtr<ResourceAllocator> resourceAllocator;
ASSERT_SUCCEEDED(
ResourceAllocator::CreateAllocator(CreateBasicAllocatorDesc(), &resourceAllocator));
ASSERT_NE(resourceAllocator, nullptr);

ALLOCATION_DESC desc = {};
desc.Flags = ALLOCATION_FLAG_ALLOW_SUBALLOCATE_WITHIN_RESOURCE;

// Byte-aligned upload buffer within.
{
ALLOCATION_DESC smallBufferDesc = desc;
smallBufferDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;

ComPtr<ResourceAllocation> smallBuffer;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
smallBufferDesc, CreateBasicBufferDesc(4u, 1), D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr, &smallBuffer));
ASSERT_NE(smallBuffer, nullptr);
EXPECT_EQ(smallBuffer->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallBuffer->GetSize(), 4u);
EXPECT_EQ(smallBuffer->GetOffsetFromResource(), 0u);

EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockUsage, smallBuffer->GetSize());
}

// Custom-aligned upload buffer within.
{
ALLOCATION_DESC smallBufferDesc = desc;
smallBufferDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;

ComPtr<ResourceAllocation> smallBuffer;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
smallBufferDesc, CreateBasicBufferDesc(4u, 16), D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr, &smallBuffer));
ASSERT_NE(smallBuffer, nullptr);
EXPECT_EQ(smallBuffer->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallBuffer->GetSize(), 16u);
EXPECT_EQ(smallBuffer->GetOffsetFromResource(), 0u);

EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockUsage, smallBuffer->GetSize());
}

// Constant upload buffer within.
{
ALLOCATION_DESC smallBufferDesc = desc;
smallBufferDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;

ComPtr<ResourceAllocation> smallBuffer;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
smallBufferDesc, CreateBasicBufferDesc(4u, 0), D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr, &smallBuffer));
ASSERT_NE(smallBuffer, nullptr);
EXPECT_EQ(smallBuffer->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallBuffer->GetSize(), 256u);
EXPECT_EQ(smallBuffer->GetOffsetFromResource(), 0u);

EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockUsage, smallBuffer->GetSize());
}

// Create a read-back buffer within.
{
ALLOCATION_DESC smallBufferDesc = desc;
smallBufferDesc.HeapType = D3D12_HEAP_TYPE_READBACK;

ComPtr<ResourceAllocation> smallerBuffer;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
smallBufferDesc, CreateBasicBufferDesc(4u), D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
&smallerBuffer));
ASSERT_NE(smallerBuffer, nullptr);
EXPECT_EQ(smallerBuffer->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallerBuffer->GetSize(), 4u);
EXPECT_EQ(smallerBuffer->GetOffsetFromResource(), 0u);
}
}

TEST_F(D3D12ResourceAllocatorTests, CreateBufferWithinMany) {
ComPtr<ResourceAllocator> resourceAllocator;
ASSERT_SUCCEEDED(
ResourceAllocator::CreateAllocator(CreateBasicAllocatorDesc(), &resourceAllocator));
Expand All @@ -401,94 +483,98 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferSuballocatedWithin) {
desc.Flags = ALLOCATION_FLAG_ALLOW_SUBALLOCATE_WITHIN_RESOURCE;
desc.HeapType = D3D12_HEAP_TYPE_UPLOAD;

constexpr uint32_t kSubAllocationSize = 4u;
const D3D12_RESOURCE_DESC& smallBufferDesc = CreateBasicBufferDesc(4u, 1);

// Create two small buffers that will be byte-aligned.
ComPtr<ResourceAllocation> smallBufferA;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
desc, smallBufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, &smallBufferA));
ASSERT_NE(smallBufferA, nullptr);
EXPECT_EQ(smallBufferA->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallBufferA->GetSize(), smallBufferDesc.Width);

// Create two tiny buffers that will be byte-aligned.
ComPtr<ResourceAllocation> tinyBufferAllocA;
ComPtr<ResourceAllocation> smallBufferB;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
desc, CreateBasicBufferDesc(kSubAllocationSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
&tinyBufferAllocA));
ASSERT_NE(tinyBufferAllocA, nullptr);
EXPECT_EQ(tinyBufferAllocA->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(tinyBufferAllocA->GetSize(), kSubAllocationSize);
desc, smallBufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, &smallBufferB));
ASSERT_NE(smallBufferB, nullptr);
EXPECT_EQ(smallBufferB->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallBufferB->GetSize(), smallBufferDesc.Width);

ComPtr<ResourceAllocation> tinyBufferAllocB;
ComPtr<ResourceAllocation> smallBufferC;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
desc, CreateBasicBufferDesc(kSubAllocationSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
&tinyBufferAllocB));
ASSERT_NE(tinyBufferAllocB, nullptr);
EXPECT_EQ(tinyBufferAllocB->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(tinyBufferAllocB->GetSize(), kSubAllocationSize);
desc, smallBufferDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, &smallBufferC));
ASSERT_NE(smallBufferC, nullptr);
EXPECT_EQ(smallBufferC->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(smallBufferC->GetSize(), smallBufferDesc.Width);

EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 2u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 3u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 1u);

// Both buffers should be allocated in sequence, back-to-back.
EXPECT_EQ(tinyBufferAllocA->GetOffsetFromResource() + kSubAllocationSize,
tinyBufferAllocB->GetOffsetFromResource());
// Should be allocated in sequence, back-to-back.
EXPECT_EQ(smallBufferA->GetOffsetFromResource() + smallBufferDesc.Width,
smallBufferB->GetOffsetFromResource());

EXPECT_EQ(tinyBufferAllocA->GetResource(), tinyBufferAllocB->GetResource());
EXPECT_EQ(smallBufferB->GetOffsetFromResource() + smallBufferDesc.Width,
smallBufferC->GetOffsetFromResource());

EXPECT_EQ(tinyBufferAllocA->GetGPUVirtualAddress() + kSubAllocationSize,
tinyBufferAllocB->GetGPUVirtualAddress());
EXPECT_EQ(smallBufferA->GetGPUVirtualAddress() + smallBufferDesc.Width,
smallBufferB->GetGPUVirtualAddress());

// Mapping a resource allocation allocated within itself must use the entire resource.
ASSERT_FAILED(tinyBufferAllocA->Map(1));
ASSERT_FAILED(tinyBufferAllocB->Map(1));
EXPECT_EQ(smallBufferB->GetGPUVirtualAddress() + smallBufferDesc.Width,
smallBufferC->GetGPUVirtualAddress());

// Create another using a new heap type, it must be given it's own resource.
desc.HeapType = D3D12_HEAP_TYPE_READBACK;
// Should share the same resource.
EXPECT_EQ(smallBufferA->GetResource(), smallBufferB->GetResource());
EXPECT_EQ(smallBufferB->GetResource(), smallBufferC->GetResource());

ComPtr<ResourceAllocation> tinyBufferAllocC;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
desc, CreateBasicBufferDesc(kSubAllocationSize), D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
&tinyBufferAllocC));
ASSERT_NE(tinyBufferAllocC, nullptr);
EXPECT_EQ(tinyBufferAllocC->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
EXPECT_EQ(tinyBufferAllocC->GetSize(), kSubAllocationSize);
EXPECT_EQ(tinyBufferAllocC->GetOffsetFromResource(), 0u);
EXPECT_NE(tinyBufferAllocC->GetResource(), tinyBufferAllocA->GetResource());
// Mapping within must use the entire resource.
ASSERT_FAILED(smallBufferA->Map(/*subresource*/ 1));
ASSERT_FAILED(smallBufferB->Map(/*subresource*/ 1));
ASSERT_FAILED(smallBufferC->Map(/*subresource*/ 1));

EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 3u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 2u);
// Fill small buffer C with value 0xCC.
std::vector<uint8_t> dataCC(smallBufferC->GetSize(), 0xCC);
void* mappedBufferC = nullptr;
ASSERT_SUCCEEDED(smallBufferC->Map(0, nullptr, &mappedBufferC));
memcpy(mappedBufferC, dataCC.data(), dataCC.size());

// Write kSubAllocationSize worth of bytes with value 0xAA in mapped subAllocation A.
std::vector<uint8_t> dataAA(kSubAllocationSize, 0xAA);
// Fill small buffer A with value 0xAA.
std::vector<uint8_t> dataAA(smallBufferA->GetSize(), 0xAA);
void* mappedBufferA = nullptr;
ASSERT_SUCCEEDED(tinyBufferAllocA->Map(0, nullptr, &mappedBufferA));
ASSERT_SUCCEEDED(smallBufferA->Map(0, nullptr, &mappedBufferA));
memcpy(mappedBufferA, dataAA.data(), dataAA.size());

// Write kSubAllocationSize worth of bytes with value 0xBB in mapped subAllocation B.
std::vector<uint8_t> dataBB(kSubAllocationSize, 0xBB);
// Fill small buffer B with value 0xBB.
std::vector<uint8_t> dataBB(smallBufferB->GetSize(), 0xBB);
void* mappedBufferB = nullptr;
ASSERT_SUCCEEDED(tinyBufferAllocB->Map(0, nullptr, &mappedBufferB));
ASSERT_SUCCEEDED(smallBufferB->Map(0, nullptr, &mappedBufferB));
memcpy(mappedBufferB, dataBB.data(), dataBB.size());

EXPECT_NE(mappedBufferA, mappedBufferB);
EXPECT_NE(mappedBufferB, mappedBufferC);

// Map the entire buffer and check both allocated ranges.
// Map the entire resource and check values, in-order.
void* mappedBuffer = nullptr;
ASSERT_SUCCEEDED(tinyBufferAllocB->GetResource()->Map(0, nullptr, &mappedBuffer));
ASSERT_SUCCEEDED(smallBufferB->GetResource()->Map(0, nullptr, &mappedBuffer));

const uint8_t* mappedByte = static_cast<uint8_t*>(mappedBuffer);
for (uint32_t i = 0; i < kSubAllocationSize; i++, mappedByte++) {
for (uint32_t i = 0; i < smallBufferA->GetSize(); i++, mappedByte++) {
EXPECT_EQ(*mappedByte, 0xAA);
}

for (uint32_t i = 0; i < kSubAllocationSize; i++, mappedByte++) {
for (uint32_t i = 0; i < smallBufferB->GetSize(); i++, mappedByte++) {
EXPECT_EQ(*mappedByte, 0xBB);
}

// Deallocate in reverse order (for good measure).
tinyBufferAllocA = nullptr;
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 2u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 2u);
for (uint32_t i = 0; i < smallBufferC->GetSize(); i++, mappedByte++) {
EXPECT_EQ(*mappedByte, 0xCC);
}

tinyBufferAllocB = nullptr;
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 1u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 1u);
// Deallocate in reverse order (for good measure).
smallBufferA = nullptr;
smallBufferB = nullptr;
smallBufferC = nullptr;

tinyBufferAllocC = nullptr;
EXPECT_EQ(resourceAllocator->GetInfo().UsedBlockCount, 0u);
EXPECT_EQ(resourceAllocator->GetInfo().UsedMemoryCount, 0u);
}
Expand Down Expand Up @@ -793,7 +879,7 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferGetInfo) {

ComPtr<ResourceAllocation> firstAllocation;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
allocationWithinDesc, CreateBasicBufferDesc(kBufferSize),
allocationWithinDesc, CreateBasicBufferDesc(kBufferSize, 1),
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, &firstAllocation));
ASSERT_NE(firstAllocation, nullptr);
EXPECT_EQ(firstAllocation->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
Expand All @@ -806,7 +892,7 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferGetInfo) {

ComPtr<ResourceAllocation> secondAllocation;
ASSERT_SUCCEEDED(resourceAllocator->CreateResource(
allocationWithinDesc, CreateBasicBufferDesc(kBufferSize),
allocationWithinDesc, CreateBasicBufferDesc(kBufferSize, 1),
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, &secondAllocation));
ASSERT_NE(secondAllocation, nullptr);
EXPECT_EQ(secondAllocation->GetMethod(), gpgmm::AllocationMethod::kSubAllocatedWithin);
Expand Down