Allow changing Vulkan allocator heap block size
The Vulkan ResourceMemoryAllocator defaults to a 8mb block size. That's
significantly different than VMA heap block size of 64kb used by VMA
with Ganesh/Vulkan. Graphite/Dawn/Vulkan wants to configure this
similarly so add a chainable struct wgpu::DawnDeviceAllocatorControl
that allows configuring the heap block size.
Bug: 407730048
Change-Id: Ib08f1010bf7be2014d8859631bd0848f79b49f98
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/235614
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@google.com>
diff --git a/docs/dawn/features/dawn_device_allocator_control.md b/docs/dawn/features/dawn_device_allocator_control.md
new file mode 100644
index 0000000..0e764f1
--- /dev/null
+++ b/docs/dawn/features/dawn_device_allocator_control.md
@@ -0,0 +1,7 @@
+# Dawn Device Allocator Control (experimental)
+
+This feature allows `wgpu::DawnDeviceAllocatorControl` to be chained on `wgpu::DeviceDescriptor`. If
+`wgpu::DawnDeviceAllocatorControl` is chained and `wgpu::FeatureName::DawnDeviceAllocatorControl` is
+not in `requiredFeatures` then creating the device will fail with a validation error.
+
+It is available in the Vulkan backend.
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index b630980..fed5999 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -264,6 +264,15 @@
{"name": "function userdata", "type": "void *", "default": "nullptr"}
]
},
+ "dawn device allocator control": {
+ "tags": ["dawn"],
+ "category": "structure",
+ "chained": "in",
+ "chain roots": ["device descriptor"],
+ "members": [
+ {"name": "allocator heap block size", "type": "size_t", "default": 0}
+ ]
+ },
"dawn WGSL blocklist": {
"tags": ["dawn", "native"],
"category": "structure",
@@ -2311,7 +2320,8 @@
{"value": 53, "name": "dawn texel copy buffer row alignment", "tags": ["dawn"]},
{"value": 54, "name": "flexible texture views", "tags": ["dawn"]},
{"value": 55, "name": "chromium experimental subgroup matrix", "tags": ["dawn"]},
- {"value": 56, "name": "shared fence EGL sync", "tags": ["dawn", "native"]}
+ {"value": 56, "name": "shared fence EGL sync", "tags": ["dawn", "native"]},
+ {"value": 57, "name": "dawn device allocator control", "tags": ["dawn"]}
]
},
"filter mode": {
@@ -3834,7 +3844,8 @@
{"value": 62, "name": "dawn injected invalid s type", "tags": ["dawn"]},
{"value": 63, "name": "dawn compilation message utf16", "tags": ["dawn", "emscripten"]},
{"value": 64, "name": "dawn fake buffer OOM for testing", "tags": ["dawn"]},
- {"value": 65, "name": "surface descriptor from windows WinUI swap chain panel", "tags": ["dawn"]}
+ {"value": 65, "name": "surface descriptor from windows WinUI swap chain panel", "tags": ["dawn"]},
+ {"value": 66, "name": "dawn device allocator control", "tags": ["dawn"]}
]
},
"texture": {
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index e4e14a3..6be0557 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -35,9 +35,11 @@
#include <utility>
#include <vector>
+#include "dawn/common/Math.h"
#include "dawn/common/StringViewUtils.h"
#include "dawn/native/ChainUtils.h"
#include "dawn/native/Device.h"
+#include "dawn/native/Error.h"
#include "dawn/native/Instance.h"
#include "dawn/native/PhysicalDevice.h"
#include "partition_alloc/pointers/raw_ptr.h"
@@ -345,6 +347,15 @@
DAWN_TRY_CONTEXT(ValidateLimits(GetLimits(), requiredLimits), "validating required limits");
}
+ if (auto* allocatorDesc = descriptor.Get<DawnDeviceAllocatorControl>()) {
+ DAWN_INVALID_IF(!requiredFeatureSet.count(wgpu::FeatureName::DawnDeviceAllocatorControl),
+ "%s is not enabled.", wgpu::FeatureName::DawnDeviceAllocatorControl);
+
+ DAWN_INVALID_IF(!IsPowerOfTwo(allocatorDesc->allocatorHeapBlockSize),
+ "allocator heap block size (%d) isn't a power of two.",
+ allocatorDesc->allocatorHeapBlockSize);
+ }
+
return mPhysicalDevice->CreateDevice(this, descriptor, deviceToggles, std::move(lostEvent));
}
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 403b9c8..469f201 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -404,7 +404,13 @@
FeatureInfo::FeatureState::Stable}},
{Feature::ChromiumExperimentalSubgroupMatrix,
{"Support the \"enable chromium_experimental_subgroup_matrix;\" directive in WGSL.",
- "https://github.com/gpuweb/gpuweb/issues/4195", FeatureInfo::FeatureState::Experimental}}};
+ "https://github.com/gpuweb/gpuweb/issues/4195", FeatureInfo::FeatureState::Experimental}},
+ {Feature::DawnDeviceAllocatorControl,
+ {"Supports configuring device allocator via DawnDeviceAllocatorControl",
+ "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
+ "dawn_device_allocator_control.md",
+ FeatureInfo::FeatureState::Experimental}}};
+
} // anonymous namespace
void FeaturesSet::EnableFeature(Feature feature) {
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 486f4e8..9719550 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -142,7 +142,11 @@
}
mRenderPassCache = std::make_unique<RenderPassCache>(this);
- mResourceMemoryAllocator = std::make_unique<MutexProtected<ResourceMemoryAllocator>>(this);
+
+ VkDeviceSize heapBlockSize =
+ ResourceMemoryAllocator::GetHeapBlockSize(descriptor.Get<DawnDeviceAllocatorControl>());
+ mResourceMemoryAllocator =
+ std::make_unique<MutexProtected<ResourceMemoryAllocator>>(this, heapBlockSize);
mExternalMemoryService = std::make_unique<external_memory::Service>(this);
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 9a987e2..2832a54 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -224,6 +224,7 @@
EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
EnableFeature(Feature::StaticSamplers);
EnableFeature(Feature::FlexibleTextureViews);
+ EnableFeature(Feature::DawnDeviceAllocatorControl);
// Initialize supported extensions
if (mDeviceInfo.features.textureCompressionBC == VK_TRUE) {
diff --git a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp
index 1c4cd52..31de727 100644
--- a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp
+++ b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp
@@ -44,14 +44,13 @@
namespace {
-// TODO(crbug.com/dawn/849): This is a hardcoded heurstic to choose when to
-// suballocate but it should ideally depend on the size of the memory heaps and other
-// factors.
-constexpr uint64_t kMaxSizeForSubAllocation = 4ull * 1024ull * 1024ull; // 4MiB
-
-// Have each bucket of the buddy system allocate at least some resource of the maximum
-// size
-constexpr uint64_t kBuddyHeapsSize = 2 * kMaxSizeForSubAllocation;
+VkDeviceSize GetMaxSuballocationSize(VkDeviceSize heapBlockSize) {
+ // Have each bucket of the buddy system allocate at least some resource of the maximum
+ // size
+ // TODO(crbug.com/dawn/849): This is a hardcoded heuristic to choose when to suballocate but it
+ // should ideally depend on the size of the memory heaps and other factors.
+ return heapBlockSize / 2;
+}
bool IsMemoryKindMappable(MemoryKind memoryKind) {
return memoryKind & (MemoryKind::ReadMappable | MemoryKind::WriteMappable);
@@ -103,21 +102,22 @@
public:
SingleTypeAllocator(Device* device,
size_t memoryTypeIndex,
- VkDeviceSize memoryHeapSize,
+ VkDeviceSize maxHeapSize,
+ VkDeviceSize heapBlockSize,
ResourceMemoryAllocator* memoryAllocator)
: mDevice(device),
mResourceMemoryAllocator(memoryAllocator),
mMemoryTypeIndex(memoryTypeIndex),
- mMemoryHeapSize(memoryHeapSize),
+ mMaxHeapSize(maxHeapSize),
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),
+ // be a multiple of heapBlockSize because heapBlockSize is a power of 2.
+ uint64_t(1) << Log2(mMaxHeapSize),
// Take the min in the very unlikely case the memory heap is tiny.
- std::min(uint64_t(1) << Log2(mMemoryHeapSize), kBuddyHeapsSize),
+ std::min(uint64_t(1) << Log2(mMaxHeapSize), heapBlockSize),
&mPooledMemoryAllocator) {
- DAWN_ASSERT(IsPowerOfTwo(kBuddyHeapsSize));
+ DAWN_ASSERT(IsPowerOfTwo(heapBlockSize));
}
~SingleTypeAllocator() override = default;
@@ -135,7 +135,7 @@
// Implementation of the MemoryAllocator interface to be a client of BuddyMemoryAllocator
ResultOrError<std::unique_ptr<ResourceHeapBase>> AllocateResourceHeap(uint64_t size) override {
- if (size > mMemoryHeapSize) {
+ if (size > mMaxHeapSize) {
return DAWN_OUT_OF_MEMORY_ERROR("Allocation size too large");
}
@@ -166,20 +166,30 @@
raw_ptr<Device> mDevice;
raw_ptr<ResourceMemoryAllocator> mResourceMemoryAllocator;
size_t mMemoryTypeIndex;
- VkDeviceSize mMemoryHeapSize;
+ VkDeviceSize mMaxHeapSize;
PooledResourceMemoryAllocator mPooledMemoryAllocator;
BuddyMemoryAllocator mBuddySystem;
};
-// Implementation of ResourceMemoryAllocator
+VkDeviceSize ResourceMemoryAllocator::GetHeapBlockSize(const DawnDeviceAllocatorControl* control) {
+ static constexpr VkDeviceSize kDefaultHeapBlockSize = 8ull * 1024ull * 1024ull; // 8MiB
+ VkDeviceSize heapBlockSize = kDefaultHeapBlockSize;
+ if (control && control->allocatorHeapBlockSize > 0) {
+ heapBlockSize = control->allocatorHeapBlockSize;
+ }
+ DAWN_ASSERT(IsPowerOfTwo(heapBlockSize));
+ return heapBlockSize;
+}
-ResourceMemoryAllocator::ResourceMemoryAllocator(Device* device) : mDevice(device) {
+// Implementation of ResourceMemoryAllocator
+ResourceMemoryAllocator::ResourceMemoryAllocator(Device* device, VkDeviceSize heapBlockSize)
+ : mDevice(device), mMaxSizeForSuballocation(GetMaxSuballocationSize(heapBlockSize)) {
const VulkanDeviceInfo& info = mDevice->GetDeviceInfo();
mAllocatorsPerType.reserve(info.memoryTypes.size());
for (size_t i = 0; i < info.memoryTypes.size(); i++) {
mAllocatorsPerType.emplace_back(std::make_unique<SingleTypeAllocator>(
- mDevice, i, info.memoryHeaps[info.memoryTypes[i].heapIndex].size, this));
+ mDevice, i, info.memoryHeaps[info.memoryTypes[i].heapIndex].size, heapBlockSize, this));
}
}
@@ -198,7 +208,7 @@
// Sub-allocate non-mappable resources because at the moment the mapped pointer
// is part of the resource and not the heap, which doesn't match the Vulkan model.
// TODO(crbug.com/dawn/849): allow sub-allocating mappable resources, maybe.
- if (!forceDisableSubAllocation && requirements.size < kMaxSizeForSubAllocation &&
+ if (!forceDisableSubAllocation && requirements.size < mMaxSizeForSuballocation &&
!IsMemoryKindMappable(kind) &&
!mDevice->IsToggleEnabled(Toggle::DisableResourceSuballocation)) {
// When sub-allocating, Vulkan requires that we respect bufferImageGranularity. Some
diff --git a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h
index ccd3f9f..d80087e 100644
--- a/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h
+++ b/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.h
@@ -61,7 +61,11 @@
class ResourceMemoryAllocator {
public:
- explicit ResourceMemoryAllocator(Device* device);
+ // Returns heap block size as specified by `control` or the default value if not.
+ static VkDeviceSize GetHeapBlockSize(const DawnDeviceAllocatorControl* control);
+
+ // `heapBlockSize` must be a power of two.
+ ResourceMemoryAllocator(Device* device, VkDeviceSize heapBlockSize);
~ResourceMemoryAllocator();
ResultOrError<ResourceMemoryAllocation> Allocate(const VkMemoryRequirements& requirements,
@@ -88,6 +92,7 @@
private:
raw_ptr<Device> mDevice;
+ const VkDeviceSize mMaxSizeForSuballocation;
class SingleTypeAllocator;
std::vector<std::unique_ptr<SingleTypeAllocator>> mAllocatorsPerType;
diff --git a/src/dawn/tests/unittests/native/DeviceCreationTests.cpp b/src/dawn/tests/unittests/native/DeviceCreationTests.cpp
index b103406..56bee86 100644
--- a/src/dawn/tests/unittests/native/DeviceCreationTests.cpp
+++ b/src/dawn/tests/unittests/native/DeviceCreationTests.cpp
@@ -114,6 +114,50 @@
EXPECT_NE(device, nullptr);
}
+// Test successful call to CreateDevice with allocator descriptor.
+TEST_F(DeviceCreationTest, CreateDeviceWithAllocatorSuccess) {
+ wgpu::DawnDeviceAllocatorControl allocationDesc = {};
+ allocationDesc.allocatorHeapBlockSize = 4 * 1024;
+
+ wgpu::DeviceDescriptor desc = {};
+ wgpu::FeatureName feature = wgpu::FeatureName::DawnDeviceAllocatorControl;
+ desc.requiredFeatures = &feature;
+ desc.requiredFeatureCount = 1;
+ desc.nextInChain = &allocationDesc;
+
+ wgpu::Device device = unsafeAdapter.CreateDevice(&desc);
+ EXPECT_NE(device, nullptr);
+}
+
+// Test failed call to CreateDevice with allocator descriptor. This is using an adapter that does
+// not have DawnDeviceAllocatorControl feature enabled.
+TEST_F(DeviceCreationTest, CreateDeviceWithAllocatorFailedMissingFeature) {
+ wgpu::DawnDeviceAllocatorControl allocationDesc = {};
+ allocationDesc.allocatorHeapBlockSize = 4 * 1024;
+
+ wgpu::DeviceDescriptor desc = {};
+ desc.nextInChain = &allocationDesc;
+
+ wgpu::Device device = adapter.CreateDevice(&desc);
+ EXPECT_EQ(device, nullptr);
+}
+
+// Test failed call to CreateDevice with allocator descriptor. The heap block size provided is not a
+// power of two.
+TEST_F(DeviceCreationTest, CreateDeviceWithAllocatorFailedHeapBlockSize) {
+ wgpu::DawnDeviceAllocatorControl allocationDesc = {};
+ allocationDesc.allocatorHeapBlockSize = 4 * 1024 + 1;
+
+ wgpu::DeviceDescriptor desc = {};
+ wgpu::FeatureName feature = wgpu::FeatureName::DawnDeviceAllocatorControl;
+ desc.requiredFeatures = &feature;
+ desc.requiredFeatureCount = 1;
+ desc.nextInChain = &allocationDesc;
+
+ wgpu::Device device = unsafeAdapter.CreateDevice(&desc);
+ EXPECT_EQ(device, nullptr);
+}
+
// Test successful call to CreateDevice with toggle descriptor.
TEST_F(DeviceCreationTest, CreateDeviceWithTogglesSuccess) {
wgpu::DeviceDescriptor desc = {};
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index efc6052..3786b3e 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -118,6 +118,7 @@
case WGPUFeatureName_FlexibleTextureViews:
case WGPUFeatureName_ChromiumExperimentalSubgroupMatrix:
case WGPUFeatureName_CoreFeaturesAndLimits:
+ case WGPUFeatureName_DawnDeviceAllocatorControl:
return true;
}