diff --git a/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp b/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp index 28a1ce259..eaece6bd5 100644 --- a/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp +++ b/src/gpgmm/d3d12/ResourceAllocatorD3D12.cpp @@ -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( /*maxSlabSize*/ heapAlignment, /*slabSize*/ heapAlignment, /*slabAlignment*/ heapAlignment, - /*slabFragmentationLimit*/ 0, + /*slabFragmentationLimit*/ 1, /*allowSlabPrefetch*/ false, /*slabMemoryGrowth*/ 1, /*memoryAllocator*/ std::move(pooledOrNonPooledAllocator)); @@ -819,7 +821,25 @@ namespace gpgmm { namespace d3d12 { !mIsAlwaysCommitted && !neverSubAllocate) { allocator = mSmallBufferAllocatorOfType[static_cast(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 diff --git a/src/tests/D3D12Test.cpp b/src/tests/D3D12Test.cpp index 7c0b5b49b..5fef409a5 100644 --- a/src/tests/D3D12Test.cpp +++ b/src/tests/D3D12Test.cpp @@ -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; diff --git a/src/tests/D3D12Test.h b/src/tests/D3D12Test.h index e6ed4156e..8920cb0b9 100644 --- a/src/tests/D3D12Test.h +++ b/src/tests/D3D12Test.h @@ -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 GenerateBufferAllocations(); diff --git a/src/tests/end2end/D3D12ResourceAllocatorTests.cpp b/src/tests/end2end/D3D12ResourceAllocatorTests.cpp index d44a33ff6..726461944 100644 --- a/src/tests/end2end/D3D12ResourceAllocatorTests.cpp +++ b/src/tests/end2end/D3D12ResourceAllocatorTests.cpp @@ -391,7 +391,89 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferNeverAllocate) { ASSERT_NE(allocationB, nullptr); } -TEST_F(D3D12ResourceAllocatorTests, CreateBufferSuballocatedWithin) { +TEST_F(D3D12ResourceAllocatorTests, CreateBufferWithin) { + ComPtr 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 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 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 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 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; ASSERT_SUCCEEDED( ResourceAllocator::CreateAllocator(CreateBasicAllocatorDesc(), &resourceAllocator)); @@ -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 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 tinyBufferAllocA; + ComPtr 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 tinyBufferAllocB; + ComPtr 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 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 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 dataAA(kSubAllocationSize, 0xAA); + // Fill small buffer A with value 0xAA. + std::vector 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 dataBB(kSubAllocationSize, 0xBB); + // Fill small buffer B with value 0xBB. + std::vector 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(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); } @@ -793,7 +879,7 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferGetInfo) { ComPtr 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); @@ -806,7 +892,7 @@ TEST_F(D3D12ResourceAllocatorTests, CreateBufferGetInfo) { ComPtr 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);