Pool sub-allocated resource heaps.
Allow resource heaps to be recycled when
no longer used.
BUG=dawn:496
Change-Id: I36518f8b0c0b26d2cceecc4e7b05e00a5fd5bd25
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/26126
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index a16e8de..0633915 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -228,6 +228,8 @@
"Pipeline.h",
"PipelineLayout.cpp",
"PipelineLayout.h",
+ "PooledResourceMemoryAllocator.cpp",
+ "PooledResourceMemoryAllocator.h",
"ProgrammablePassEncoder.cpp",
"ProgrammablePassEncoder.h",
"QuerySet.cpp",
diff --git a/src/dawn_native/CMakeLists.txt b/src/dawn_native/CMakeLists.txt
index 9befcd7..c7f0808 100644
--- a/src/dawn_native/CMakeLists.txt
+++ b/src/dawn_native/CMakeLists.txt
@@ -103,6 +103,8 @@
"Pipeline.h"
"PipelineLayout.cpp"
"PipelineLayout.h"
+ "PooledResourceMemoryAllocator.cpp"
+ "PooledResourceMemoryAllocator.h"
"ProgrammablePassEncoder.cpp"
"ProgrammablePassEncoder.h"
"QuerySet.cpp"
diff --git a/src/dawn_native/PooledResourceMemoryAllocator.cpp b/src/dawn_native/PooledResourceMemoryAllocator.cpp
new file mode 100644
index 0000000..5ba502e
--- /dev/null
+++ b/src/dawn_native/PooledResourceMemoryAllocator.cpp
@@ -0,0 +1,60 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn_native/PooledResourceMemoryAllocator.h"
+#include "dawn_native/Device.h"
+
+namespace dawn_native {
+
+ PooledResourceMemoryAllocator::PooledResourceMemoryAllocator(
+ ResourceHeapAllocator* heapAllocator)
+ : mHeapAllocator(heapAllocator) {
+ }
+
+ void PooledResourceMemoryAllocator::DestroyPool() {
+ for (auto& resourceHeap : mPool) {
+ ASSERT(resourceHeap != nullptr);
+ mHeapAllocator->DeallocateResourceHeap(std::move(resourceHeap));
+ }
+
+ mPool.clear();
+ }
+
+ ResultOrError<std::unique_ptr<ResourceHeapBase>>
+ PooledResourceMemoryAllocator::AllocateResourceHeap(uint64_t size) {
+ // Pooled memory is LIFO because memory can be evicted by LRU. However, this means
+ // pooling is disabled in-frame when the memory is still pending. For high in-frame
+ // memory users, FIFO might be preferable when memory consumption is a higher priority.
+ std::unique_ptr<ResourceHeapBase> memory;
+ if (!mPool.empty()) {
+ memory = std::move(mPool.front());
+ mPool.pop_front();
+ }
+
+ if (memory == nullptr) {
+ DAWN_TRY_ASSIGN(memory, mHeapAllocator->AllocateResourceHeap(size));
+ }
+
+ return std::move(memory);
+ }
+
+ void PooledResourceMemoryAllocator::DeallocateResourceHeap(
+ std::unique_ptr<ResourceHeapBase> allocation) {
+ mPool.push_front(std::move(allocation));
+ }
+
+ uint64_t PooledResourceMemoryAllocator::GetPoolSizeForTesting() const {
+ return mPool.size();
+ }
+} // namespace dawn_native
\ No newline at end of file
diff --git a/src/dawn_native/PooledResourceMemoryAllocator.h b/src/dawn_native/PooledResourceMemoryAllocator.h
new file mode 100644
index 0000000..5b6b816
--- /dev/null
+++ b/src/dawn_native/PooledResourceMemoryAllocator.h
@@ -0,0 +1,53 @@
+// Copyright 2020 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_POOLEDRESOURCEMEMORYALLOCATOR_H_
+#define DAWNNATIVE_POOLEDRESOURCEMEMORYALLOCATOR_H_
+
+#include "common/SerialQueue.h"
+#include "dawn_native/ResourceHeapAllocator.h"
+
+#include <deque>
+
+namespace dawn_native {
+
+ class DeviceBase;
+
+ // |PooledResourceMemoryAllocator| allocates a fixed-size resource memory from a resource memory
+ // pool. Internally, it manages a list of heaps using LIFO (newest heaps are recycled first).
+ // The heap is in one of two states: AVAILABLE or not. Upon de-allocate, the heap is returned
+ // the pool and made AVAILABLE.
+ class PooledResourceMemoryAllocator : public ResourceHeapAllocator {
+ public:
+ PooledResourceMemoryAllocator(ResourceHeapAllocator* heapAllocator);
+ ~PooledResourceMemoryAllocator() override = default;
+
+ ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap(
+ uint64_t size) override;
+ void DeallocateResourceHeap(std::unique_ptr<ResourceHeapBase> allocation) override;
+
+ void DestroyPool();
+
+ // For testing purposes.
+ uint64_t GetPoolSizeForTesting() const;
+
+ private:
+ ResourceHeapAllocator* mHeapAllocator = nullptr;
+
+ std::deque<std::unique_ptr<ResourceHeapBase>> mPool;
+ };
+
+} // namespace dawn_native
+
+#endif // DAWNNATIVE_POOLEDRESOURCEMEMORYALLOCATOR_H_
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index b040b1d..145a382 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -570,8 +570,11 @@
::CloseHandle(mFenceEvent);
}
+ // Release recycled resource heaps.
+ mResourceAllocatorManager->DestroyPool();
+
// We need to handle clearing up com object refs that were enqeued after TickImpl
- mUsedComObjectRefs.ClearUpTo(GetCompletedCommandSerial());
+ mUsedComObjectRefs.ClearUpTo(std::numeric_limits<Serial>::max());
ASSERT(mUsedComObjectRefs.Empty());
ASSERT(!mPendingCommands.IsOpen());
diff --git a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
index e8d9af3..3158cbe 100644
--- a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
+++ b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.cpp
@@ -172,8 +172,10 @@
mHeapAllocators[i] = std::make_unique<HeapAllocator>(
mDevice, GetD3D12HeapType(resourceHeapKind), GetD3D12HeapFlags(resourceHeapKind),
GetMemorySegment(device, GetD3D12HeapType(resourceHeapKind)));
+ mPooledHeapAllocators[i] =
+ std::make_unique<PooledResourceMemoryAllocator>(mHeapAllocators[i].get());
mSubAllocatedResourceAllocators[i] = std::make_unique<BuddyMemoryAllocator>(
- kMaxHeapSize, kMinHeapSize, mHeapAllocators[i].get());
+ kMaxHeapSize, kMinHeapSize, mPooledHeapAllocators[i].get());
}
}
@@ -396,4 +398,10 @@
/*offset*/ 0, std::move(committedResource), heap};
}
+ void ResourceAllocatorManager::DestroyPool() {
+ for (auto& alloc : mPooledHeapAllocators) {
+ alloc->DestroyPool();
+ }
+ }
+
}} // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.h b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.h
index 2e7cb39..0bf2a02 100644
--- a/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.h
+++ b/src/dawn_native/d3d12/ResourceAllocatorManagerD3D12.h
@@ -17,6 +17,7 @@
#include "common/SerialQueue.h"
#include "dawn_native/BuddyMemoryAllocator.h"
+#include "dawn_native/PooledResourceMemoryAllocator.h"
#include "dawn_native/d3d12/HeapAllocatorD3D12.h"
#include "dawn_native/d3d12/ResourceHeapAllocationD3D12.h"
@@ -67,6 +68,8 @@
void Tick(Serial lastCompletedSerial);
+ void DestroyPool();
+
private:
void FreeMemory(ResourceHeapAllocation& allocation);
@@ -92,6 +95,9 @@
mSubAllocatedResourceAllocators;
std::array<std::unique_ptr<HeapAllocator>, ResourceHeapKind::EnumCount> mHeapAllocators;
+ std::array<std::unique_ptr<PooledResourceMemoryAllocator>, ResourceHeapKind::EnumCount>
+ mPooledHeapAllocators;
+
SerialQueue<ResourceHeapAllocation> mAllocationsToDelete;
};
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index b79c79c..818af70 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -888,6 +888,9 @@
mResourceMemoryAllocator->Tick(GetCompletedCommandSerial());
mDeleter->Tick(GetCompletedCommandSerial());
+ // Allow recycled memory to be deleted.
+ mResourceMemoryAllocator->DestroyPool();
+
// The VkRenderPasses in the cache can be destroyed immediately since all commands referring
// to them are guaranteed to be finished executing.
mRenderPassCache = nullptr;
diff --git a/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp b/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp
index 22523a3..924a47b 100644
--- a/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp
+++ b/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.cpp
@@ -46,17 +46,22 @@
: mDevice(device),
mMemoryTypeIndex(memoryTypeIndex),
mMemoryHeapSize(memoryHeapSize),
+ mPooledMemoryAllocator(this),
mBuddySystem(
// Round down to a power of 2 that's <= mMemoryHeapSize. This will always
// be a multiple of kBuddyHeapsSize because kBuddyHeapsSize is a power of 2.
uint64_t(1) << Log2(mMemoryHeapSize),
// Take the min in the very unlikely case the memory heap is tiny.
std::min(uint64_t(1) << Log2(mMemoryHeapSize), kBuddyHeapsSize),
- this) {
+ &mPooledMemoryAllocator) {
ASSERT(IsPowerOfTwo(kBuddyHeapsSize));
}
~SingleTypeAllocator() override = default;
+ void DestroyPool() {
+ mPooledMemoryAllocator.DestroyPool();
+ }
+
ResultOrError<ResourceMemoryAllocation> AllocateMemory(
const VkMemoryRequirements& requirements) {
return mBuddySystem.Allocate(requirements.size, requirements.alignment);
@@ -100,6 +105,7 @@
Device* mDevice;
size_t mMemoryTypeIndex;
VkDeviceSize mMemoryHeapSize;
+ PooledResourceMemoryAllocator mPooledMemoryAllocator;
BuddyMemoryAllocator mBuddySystem;
};
@@ -258,4 +264,10 @@
return bestType;
}
+ void ResourceMemoryAllocator::DestroyPool() {
+ for (auto& alloc : mAllocatorsPerType) {
+ alloc->DestroyPool();
+ }
+ }
+
}} // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.h b/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.h
index 88f6d4e..04176a3 100644
--- a/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.h
+++ b/src/dawn_native/vulkan/ResourceMemoryAllocatorVk.h
@@ -18,6 +18,7 @@
#include "common/SerialQueue.h"
#include "common/vulkan_platform.h"
#include "dawn_native/Error.h"
+#include "dawn_native/PooledResourceMemoryAllocator.h"
#include "dawn_native/ResourceMemoryAllocation.h"
#include <memory>
@@ -36,6 +37,8 @@
bool mappable);
void Deallocate(ResourceMemoryAllocation* allocation);
+ void DestroyPool();
+
void Tick(Serial completedSerial);
int FindBestTypeIndex(VkMemoryRequirements requirements, bool mappable);
diff --git a/src/tests/unittests/BuddyMemoryAllocatorTests.cpp b/src/tests/unittests/BuddyMemoryAllocatorTests.cpp
index 55da401..74b092a 100644
--- a/src/tests/unittests/BuddyMemoryAllocatorTests.cpp
+++ b/src/tests/unittests/BuddyMemoryAllocatorTests.cpp
@@ -15,8 +15,12 @@
#include <gtest/gtest.h>
#include "dawn_native/BuddyMemoryAllocator.h"
+#include "dawn_native/PooledResourceMemoryAllocator.h"
#include "dawn_native/ResourceHeapAllocator.h"
+#include <set>
+#include <vector>
+
using namespace dawn_native;
class DummyResourceHeapAllocator : public ResourceHeapAllocator {
@@ -34,6 +38,12 @@
: mAllocator(maxBlockSize, memorySize, &mHeapAllocator) {
}
+ DummyBuddyResourceAllocator(uint64_t maxBlockSize,
+ uint64_t memorySize,
+ ResourceHeapAllocator* heapAllocator)
+ : mAllocator(maxBlockSize, memorySize, heapAllocator) {
+ }
+
ResourceMemoryAllocation Allocate(uint64_t allocationSize, uint64_t alignment = 1) {
ResultOrError<ResourceMemoryAllocation> result =
mAllocator.Allocate(allocationSize, alignment);
@@ -120,6 +130,7 @@
// Second allocation creates second heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
+ ASSERT_NE(allocation1.GetResourceHeap(), allocation2.GetResourceHeap());
// Deallocate both allocations
allocator.Deallocate(allocation1);
@@ -159,6 +170,7 @@
// Second allocation re-uses first heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
+ ASSERT_EQ(allocation1.GetResourceHeap(), allocation2.GetResourceHeap());
ResourceMemoryAllocation allocation3 = allocator.Allocate(heapSize / 2);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, heapSize);
@@ -166,6 +178,7 @@
// Third allocation creates second heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
+ ASSERT_NE(allocation1.GetResourceHeap(), allocation3.GetResourceHeap());
// Deallocate all allocations in reverse order.
allocator.Deallocate(allocation1);
@@ -210,6 +223,7 @@
// A1 and A2 share H0
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u);
+ ASSERT_EQ(allocation1.GetResourceHeap(), allocation2.GetResourceHeap());
ResourceMemoryAllocation allocation3 = allocator.Allocate(128);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, 128u);
@@ -218,6 +232,7 @@
// A3 creates and fully occupies a new heap.
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
+ ASSERT_NE(allocation2.GetResourceHeap(), allocation3.GetResourceHeap());
ResourceMemoryAllocation allocation4 = allocator.Allocate(64);
ASSERT_EQ(allocation4.GetInfo().mBlockOffset, 256u);
@@ -225,6 +240,7 @@
ASSERT_EQ(allocation4.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
+ ASSERT_NE(allocation3.GetResourceHeap(), allocation4.GetResourceHeap());
// R5 size forms 64 byte hole after R4.
ResourceMemoryAllocation allocation5 = allocator.Allocate(128);
@@ -233,6 +249,7 @@
ASSERT_EQ(allocation5.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 4u);
+ ASSERT_NE(allocation4.GetResourceHeap(), allocation5.GetResourceHeap());
// Deallocate allocations in staggered order.
allocator.Deallocate(allocation1);
@@ -282,6 +299,7 @@
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
+ ASSERT_NE(allocation1.GetResourceHeap(), allocation2.GetResourceHeap());
ResourceMemoryAllocation allocation3 = allocator.Allocate(64, 128);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, 256u);
@@ -289,6 +307,7 @@
ASSERT_EQ(allocation3.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
+ ASSERT_NE(allocation2.GetResourceHeap(), allocation3.GetResourceHeap());
ResourceMemoryAllocation allocation4 = allocator.Allocate(64, 64);
ASSERT_EQ(allocation4.GetInfo().mBlockOffset, 320u);
@@ -296,6 +315,7 @@
ASSERT_EQ(allocation4.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
+ ASSERT_EQ(allocation3.GetResourceHeap(), allocation4.GetResourceHeap());
}
// Verify resource sub-allocation of various sizes with same alignments.
@@ -330,6 +350,7 @@
ASSERT_EQ(allocation2.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 1u); // Reuses H0
+ ASSERT_EQ(allocation1.GetResourceHeap(), allocation2.GetResourceHeap());
ResourceMemoryAllocation allocation3 = allocator.Allocate(128, alignment);
ASSERT_EQ(allocation3.GetInfo().mBlockOffset, 128u);
@@ -337,6 +358,7 @@
ASSERT_EQ(allocation3.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 2u);
+ ASSERT_NE(allocation2.GetResourceHeap(), allocation3.GetResourceHeap());
ResourceMemoryAllocation allocation4 = allocator.Allocate(128, alignment);
ASSERT_EQ(allocation4.GetInfo().mBlockOffset, 256u);
@@ -344,6 +366,7 @@
ASSERT_EQ(allocation4.GetInfo().mMethod, AllocationMethod::kSubAllocated);
ASSERT_EQ(allocator.ComputeTotalNumOfHeapsForTesting(), 3u);
+ ASSERT_NE(allocation3.GetResourceHeap(), allocation4.GetResourceHeap());
}
// Verify allocating a very large resource does not overflow.
@@ -356,3 +379,82 @@
ResourceMemoryAllocation invalidAllocation = allocator.Allocate(largeBlock);
ASSERT_EQ(invalidAllocation.GetInfo().mMethod, AllocationMethod::kInvalid);
}
+
+// Verify resource heaps will be reused from a pool.
+TEST(BuddyMemoryAllocatorTests, ReuseFreedHeaps) {
+ constexpr uint64_t kHeapSize = 128;
+ constexpr uint64_t kMaxBlockSize = 4096;
+
+ DummyResourceHeapAllocator heapAllocator;
+ PooledResourceMemoryAllocator poolAllocator(&heapAllocator);
+ DummyBuddyResourceAllocator allocator(kMaxBlockSize, kHeapSize, &poolAllocator);
+
+ std::set<ResourceHeapBase*> heaps = {};
+ std::vector<ResourceMemoryAllocation> allocations = {};
+
+ constexpr uint32_t kNumOfAllocations = 100;
+
+ // Allocate |kNumOfAllocations|.
+ for (uint32_t i = 0; i < kNumOfAllocations; i++) {
+ ResourceMemoryAllocation allocation = allocator.Allocate(4);
+ ASSERT_EQ(allocation.GetInfo().mMethod, AllocationMethod::kSubAllocated);
+ heaps.insert(allocation.GetResourceHeap());
+ allocations.push_back(std::move(allocation));
+ }
+
+ ASSERT_EQ(poolAllocator.GetPoolSizeForTesting(), 0u);
+
+ // Return the allocations to the pool.
+ for (ResourceMemoryAllocation& allocation : allocations) {
+ allocator.Deallocate(allocation);
+ }
+
+ ASSERT_EQ(poolAllocator.GetPoolSizeForTesting(), heaps.size());
+
+ // Allocate again reusing the same heaps.
+ for (uint32_t i = 0; i < kNumOfAllocations; i++) {
+ ResourceMemoryAllocation allocation = allocator.Allocate(4);
+ ASSERT_EQ(allocation.GetInfo().mMethod, AllocationMethod::kSubAllocated);
+ ASSERT_FALSE(heaps.insert(allocation.GetResourceHeap()).second);
+ }
+
+ ASSERT_EQ(poolAllocator.GetPoolSizeForTesting(), 0u);
+}
+
+// Verify resource heaps that were reused from a pool can be destroyed.
+TEST(BuddyMemoryAllocatorTests, DestroyHeaps) {
+ constexpr uint64_t kHeapSize = 128;
+ constexpr uint64_t kMaxBlockSize = 4096;
+
+ DummyResourceHeapAllocator heapAllocator;
+ PooledResourceMemoryAllocator poolAllocator(&heapAllocator);
+ DummyBuddyResourceAllocator allocator(kMaxBlockSize, kHeapSize, &poolAllocator);
+
+ std::set<ResourceHeapBase*> heaps = {};
+ std::vector<ResourceMemoryAllocation> allocations = {};
+
+ // Count by heap (vs number of allocations) to ensure there are exactly |kNumOfHeaps| worth of
+ // buffers. Otherwise, the heap may be reused if not full.
+ constexpr uint32_t kNumOfHeaps = 10;
+
+ // Allocate |kNumOfHeaps| worth.
+ while (heaps.size() < kNumOfHeaps) {
+ ResourceMemoryAllocation allocation = allocator.Allocate(4);
+ ASSERT_EQ(allocation.GetInfo().mMethod, AllocationMethod::kSubAllocated);
+ heaps.insert(allocation.GetResourceHeap());
+ allocations.push_back(std::move(allocation));
+ }
+
+ ASSERT_EQ(poolAllocator.GetPoolSizeForTesting(), 0u);
+
+ // Return the allocations to the pool.
+ for (ResourceMemoryAllocation& allocation : allocations) {
+ allocator.Deallocate(allocation);
+ }
+
+ ASSERT_EQ(poolAllocator.GetPoolSizeForTesting(), kNumOfHeaps);
+
+ // Make sure we can destroy the remaining heaps.
+ poolAllocator.DestroyPool();
+ ASSERT_EQ(poolAllocator.GetPoolSizeForTesting(), 0u);
+}
\ No newline at end of file