Add WGPUAdapterPropertiesMemoryHeaps

This struct may be chained on WGPUAdapterProperties to query
the size and type of memory heaps available on the adapter.

Bug: dawn:2249
Change-Id: Ia8205fde8d05c26a9e73f2093897a844617d7918
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/161240
Reviewed-by: Quyen Le <lehoangquyen@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index fc76c12..f4af0e0 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1920,6 +1920,7 @@
             {"value": 1024, "name": "multi planar format nv12a", "tags": ["dawn"]},
             {"value": 1025, "name": "framebuffer fetch", "tags": ["dawn"]},
             {"value": 1026, "name": "buffer map extended usages", "tags": ["dawn"]},
+            {"value": 1027, "name": "adapter properties memory heaps", "tags": ["dawn"]},
 
             {"value": 1100, "name": "shared texture memory vk dedicated allocation", "tags": ["dawn", "native"]},
             {"value": 1101, "name": "shared texture memory a hardware buffer", "tags": ["dawn", "native"]},
@@ -3287,6 +3288,7 @@
             {"value": 1016, "name": "pipeline layout pixel local storage", "tags": ["dawn"]},
             {"value": 1017, "name": "buffer host mapped pointer", "tags": ["dawn"]},
             {"value": 1018, "name": "dawn experimental subgroup limits", "tags": ["dawn"]},
+            {"value": 1019, "name": "adapter properties memory heaps", "tags": ["dawn"]},
 
             {"value": 1100, "name": "shared texture memory vk image descriptor", "tags": ["dawn", "native"]},
             {"value": 1101, "name": "shared texture memory vk dedicated allocation descriptor", "tags": ["dawn", "native"]},
@@ -3755,6 +3757,36 @@
             {"name": "power preference", "type": "power preference", "default": "undefined"}
         ]
     },
+    "heap property": {
+        "category": "bitmask",
+        "tags": ["dawn"],
+        "values": [
+            {"value": 0, "name": "undefined", "valid": false},
+            {"value": 1, "name": "device local"},
+            {"value": 2, "name": "host visible"},
+            {"value": 4, "name": "host coherent"},
+            {"value": 8, "name": "host uncached"},
+            {"value": 16, "name": "host cached"}
+        ]
+    },
+    "memory heap info": {
+        "category": "structure",
+        "tags": ["dawn"],
+        "members": [
+            {"name": "properties", "type": "heap property"},
+            {"name": "size", "type": "uint64_t"}
+        ]
+    },
+    "adapter properties memory heaps": {
+        "category": "structure",
+        "chained": "out",
+        "chain roots": ["adapter properties"],
+        "tags": ["dawn"],
+        "members": [
+            {"name": "heap count", "type": "size_t"},
+            {"name": "heap info", "type": "memory heap info", "annotation": "const*", "length": "heap count"}
+        ]
+    },
     "dawn buffer descriptor error info from wire client": {
         "category": "structure",
         "chained": "in",
diff --git a/docs/dawn/features/adapter_properties.md b/docs/dawn/features/adapter_properties.md
new file mode 100644
index 0000000..c4d348b
--- /dev/null
+++ b/docs/dawn/features/adapter_properties.md
@@ -0,0 +1,29 @@
+# Adapter Properties
+
+## Memory Heaps
+
+`wgpu::FeatureName::AdapterPropertiesMemoryHeaps` allows querying memory heap information from the adapter.
+
+`wgpu::AdapterPropertiesMemoryHeaps` may be chained on `wgpu::AdapterProperties` in a call to `wgpu::Adapter::GetProperties` in order to query information about the memory heaps on that adapter.
+The implementation will write out the number of memory heaps and information about each heap.
+
+If `wgpu::FeatureName::AdapterPropertiesMemoryHeaps` is not available, the struct will not be populated.
+
+Adds `wgpu::HeapProperty` which is a bitmask describing the type of memory a heap is. Valid bits:
+- DeviceLocal
+- HostVisible
+- HostCoherent
+- HostUncached
+- HostCached
+
+Note that both HostUncached and HostCached may be set if a heap can allocate pages with either cache property.
+
+Adds `wgpu::MemoryHeapInfo` which is a struct describing a memory heap.
+```
+struct MemoryHeapInfo {
+    HeapProperty properties;
+    uint64_t size;
+};
+```
+
+`wgpu::MemoryHeapInfo::size` is the size that should be allocated out of this heap. Allocating more than this may result in poor performance or may deterministically run out of memory.
diff --git a/generator/templates/api_cpp.h b/generator/templates/api_cpp.h
index 8c854b6..1f9ca0a 100644
--- a/generator/templates/api_cpp.h
+++ b/generator/templates/api_cpp.h
@@ -265,9 +265,11 @@
                 }
         {% else %}
             struct {{as_cppType(type.name)}} {
+                {% if type.has_free_members_function %}
+                    {{as_cppType(type.name)}}() = default;
+                {% endif %}
         {% endif %}
             {% if type.has_free_members_function %}
-                {{as_cppType(type.name)}}() = default;
                 ~{{as_cppType(type.name)}}();
                 {{as_cppType(type.name)}}(const {{as_cppType(type.name)}}&) = delete;
                 {{as_cppType(type.name)}}& operator=(const {{as_cppType(type.name)}}&) = delete;
diff --git a/generator/templates/dawn/native/api_structs.h b/generator/templates/dawn/native/api_structs.h
index 92c47de..f484bcd 100644
--- a/generator/templates/dawn/native/api_structs.h
+++ b/generator/templates/dawn/native/api_structs.h
@@ -69,9 +69,11 @@
                 }
         {% else %}
             struct {{as_cppType(type.name)}} {
+                {% if type.has_free_members_function %}
+                    {{as_cppType(type.name)}}() = default;
+                {% endif %}
         {% endif %}
             {% if type.has_free_members_function %}
-                {{as_cppType(type.name)}}() = default;
                 ~{{as_cppType(type.name)}}();
                 {{as_cppType(type.name)}}(const {{as_cppType(type.name)}}&) = delete;
                 {{as_cppType(type.name)}}& operator=(const {{as_cppType(type.name)}}&) = delete;
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index a120050..eb89ab8 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -118,8 +118,9 @@
 void AdapterBase::APIGetProperties(AdapterProperties* properties) const {
     DAWN_ASSERT(properties != nullptr);
 
-    MaybeError result = ValidateSingleSType(properties->nextInChain,
-                                            wgpu::SType::DawnAdapterPropertiesPowerPreference);
+    MaybeError result = ValidateSTypes(properties->nextInChain,
+                                       {{wgpu::SType::DawnAdapterPropertiesPowerPreference},
+                                        {wgpu::SType::AdapterPropertiesMemoryHeaps}});
     if (result.IsError()) {
         mPhysicalDevice->GetInstance()->ConsumedError(result.AcquireError());
         return;
@@ -128,6 +129,15 @@
     DawnAdapterPropertiesPowerPreference* powerPreferenceDesc = nullptr;
     FindInChain(properties->nextInChain, &powerPreferenceDesc);
 
+    AdapterPropertiesMemoryHeaps* memoryHeaps = nullptr;
+    FindInChain(properties->nextInChain, &memoryHeaps);
+
+    if (memoryHeaps != nullptr &&
+        !mSupportedFeatures.IsEnabled(wgpu::FeatureName::AdapterPropertiesMemoryHeaps)) {
+        mPhysicalDevice->GetInstance()->ConsumedError(
+            DAWN_VALIDATION_ERROR("Feature AdapterPropertiesMemoryHeaps is not available."));
+    }
+
     if (powerPreferenceDesc != nullptr) {
         powerPreferenceDesc->powerPreference = mPowerPreference;
     }
@@ -161,6 +171,10 @@
     properties->driverDescription = ptr;
     memcpy(ptr, mPhysicalDevice->GetDriverDescription().c_str(), driverDescriptionCLen);
     ptr += driverDescriptionCLen;
+
+    if (memoryHeaps != nullptr) {
+        mPhysicalDevice->PopulateMemoryHeapInfo(memoryHeaps);
+    }
 }
 
 void APIAdapterPropertiesFreeMembers(WGPUAdapterProperties properties) {
@@ -168,6 +182,11 @@
     delete[] properties.vendorName;
 }
 
+void APIAdapterPropertiesMemoryHeapsFreeMembers(
+    WGPUAdapterPropertiesMemoryHeaps memoryHeapProperties) {
+    delete[] memoryHeapProperties.heapInfo;
+}
+
 bool AdapterBase::APIHasFeature(wgpu::FeatureName feature) const {
     return mSupportedFeatures.IsEnabled(feature);
 }
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index fb1e83f..e9ead1b 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -301,6 +301,7 @@
     TextureBase* APICreateTexture(const TextureDescriptor* descriptor);
 
     wgpu::TextureUsage APIGetSupportedSurfaceUsage(Surface* surface);
+    size_t APIQueryMemoryHeapInfo(MemoryHeapInfo* info);
 
     InternalPipelineStore* GetInternalPipelineStore();
 
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 54d23d1..78e2650 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -289,6 +289,11 @@
       "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
       "buffer_map_extended_usages.md",
       FeatureInfo::FeatureState::Experimental}},
+    {Feature::AdapterPropertiesMemoryHeaps,
+     {"Support querying memory heap info from the adapter.",
+      "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
+      "adapter_properties.md",
+      FeatureInfo::FeatureState::Stable}},
 };
 
 }  // anonymous namespace
diff --git a/src/dawn/native/PhysicalDevice.h b/src/dawn/native/PhysicalDevice.h
index 8ec396b..ed917c3 100644
--- a/src/dawn/native/PhysicalDevice.h
+++ b/src/dawn/native/PhysicalDevice.h
@@ -106,6 +106,11 @@
     FeatureValidationResult ValidateFeatureSupportedWithToggles(wgpu::FeatureName feature,
                                                                 const TogglesState& toggles) const;
 
+    // Populate information about the memory heaps. Ownership of allocations written to
+    // `memoryHeapProperties` are owned by the caller.
+    virtual void PopulateMemoryHeapInfo(
+        AdapterPropertiesMemoryHeaps* memoryHeapProperties) const = 0;
+
   protected:
     uint32_t mVendorId = 0xFFFFFFFF;
     std::string mVendorName;
diff --git a/src/dawn/native/d3d11/DeviceInfoD3D11.cpp b/src/dawn/native/d3d11/DeviceInfoD3D11.cpp
index da7959c..a09a830 100644
--- a/src/dawn/native/d3d11/DeviceInfoD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceInfoD3D11.cpp
@@ -34,7 +34,8 @@
 
 namespace dawn::native::d3d11 {
 
-ResultOrError<DeviceInfo> GatherDeviceInfo(const ComPtr<ID3D11Device>& device) {
+ResultOrError<DeviceInfo> GatherDeviceInfo(IDXGIAdapter3* adapter,
+                                           const ComPtr<ID3D11Device>& device) {
     DeviceInfo info = {};
 
     D3D11_FEATURE_DATA_D3D11_OPTIONS2 options2;
@@ -62,6 +63,11 @@
     info.supportsSharedResourceCapabilityTier2 =
         featureOptions5.SharedResourceTier >= D3D11_SHARED_RESOURCE_TIER_2;
 
+    DXGI_ADAPTER_DESC adapterDesc;
+    DAWN_TRY(CheckHRESULT(adapter->GetDesc(&adapterDesc), "IDXGIAdapter3::GetDesc"));
+    info.dedicatedVideoMemory = adapterDesc.DedicatedVideoMemory;
+    info.sharedSystemMemory = adapterDesc.SharedSystemMemory;
+
     return std::move(info);
 }
 
diff --git a/src/dawn/native/d3d11/DeviceInfoD3D11.h b/src/dawn/native/d3d11/DeviceInfoD3D11.h
index f78a7c3..4129802 100644
--- a/src/dawn/native/d3d11/DeviceInfoD3D11.h
+++ b/src/dawn/native/d3d11/DeviceInfoD3D11.h
@@ -44,9 +44,12 @@
     uint32_t shaderModel;
     PerStage<std::wstring> shaderProfiles;
     bool supportsSharedResourceCapabilityTier2;
+    size_t dedicatedVideoMemory;
+    size_t sharedSystemMemory;
 };
 
-ResultOrError<DeviceInfo> GatherDeviceInfo(const ComPtr<ID3D11Device>& device);
+ResultOrError<DeviceInfo> GatherDeviceInfo(IDXGIAdapter3* adapter,
+                                           const ComPtr<ID3D11Device>& device);
 }  // namespace dawn::native::d3d11
 
 #endif  // SRC_DAWN_NATIVE_D3D11_DEVICEINFOD3D11_H_
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 36e9fc1..86fa5f8 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -167,7 +167,7 @@
     }
 
     mFeatureLevel = mD3D11Device->GetFeatureLevel();
-    DAWN_TRY_ASSIGN(mDeviceInfo, GatherDeviceInfo(mD3D11Device));
+    DAWN_TRY_ASSIGN(mDeviceInfo, GatherDeviceInfo(GetHardwareAdapter(), mD3D11Device));
 
     // Base::InitializeImpl() cannot distinguish between discrete and integrated GPUs, so we need to
     // overwrite it.
@@ -187,6 +187,7 @@
     EnableFeature(Feature::MSAARenderToSingleSampled);
     EnableFeature(Feature::DualSourceBlending);
     EnableFeature(Feature::Norm16TextureFormats);
+    EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
 
     // To import multi planar textures, we need to at least tier 2 support.
     if (mDeviceInfo.supportsSharedResourceCapabilityTier2) {
@@ -321,4 +322,33 @@
     return {};
 }
 
+void PhysicalDevice::PopulateMemoryHeapInfo(
+    AdapterPropertiesMemoryHeaps* memoryHeapProperties) const {
+    // https://microsoft.github.io/DirectX-Specs/d3d/D3D12GPUUploadHeaps.html describes
+    // the properties of D3D12 Default/Upload/Readback heaps. The assumption is that these are
+    // roughly how D3D11 allocates memory has well.
+    if (mDeviceInfo.isUMA) {
+        auto* heapInfo = new MemoryHeapInfo[1];
+        memoryHeapProperties->heapCount = 1;
+        memoryHeapProperties->heapInfo = heapInfo;
+
+        heapInfo[0].size = mDeviceInfo.dedicatedVideoMemory != 0 ? mDeviceInfo.dedicatedVideoMemory
+                                                                 : mDeviceInfo.sharedSystemMemory;
+        heapInfo[0].properties = wgpu::HeapProperty::DeviceLocal | wgpu::HeapProperty::HostVisible |
+                                 wgpu::HeapProperty::HostUncached | wgpu::HeapProperty::HostCached;
+    } else {
+        auto* heapInfo = new MemoryHeapInfo[2];
+        memoryHeapProperties->heapCount = 2;
+        memoryHeapProperties->heapInfo = heapInfo;
+
+        heapInfo[0].size = mDeviceInfo.dedicatedVideoMemory;
+        heapInfo[0].properties = wgpu::HeapProperty::DeviceLocal;
+
+        heapInfo[1].size = mDeviceInfo.sharedSystemMemory;
+        heapInfo[1].properties = wgpu::HeapProperty::HostVisible |
+                                 wgpu::HeapProperty::HostCoherent |
+                                 wgpu::HeapProperty::HostUncached | wgpu::HeapProperty::HostCached;
+    }
+}
+
 }  // namespace dawn::native::d3d11
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.h b/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
index ec400bf..08d1102 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
@@ -75,6 +75,8 @@
         wgpu::FeatureName feature,
         const TogglesState& toggles) const override;
 
+    void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
+
     const bool mIsSharedD3D11Device;
     ComPtr<ID3D11Device> mD3D11Device;
     D3D_FEATURE_LEVEL mFeatureLevel;
diff --git a/src/dawn/native/d3d12/D3D12Info.cpp b/src/dawn/native/d3d12/D3D12Info.cpp
index b8960c7..2768c9b 100644
--- a/src/dawn/native/d3d12/D3D12Info.cpp
+++ b/src/dawn/native/d3d12/D3D12Info.cpp
@@ -50,6 +50,7 @@
                           "ID3D12Device::CheckFeatureSupport"));
 
     info.isUMA = arch.UMA;
+    info.isCacheCoherentUMA = arch.CacheCoherentUMA;
 
     D3D12_FEATURE_DATA_D3D12_OPTIONS featureOptions = {};
     DAWN_TRY(CheckHRESULT(physicalDevice.GetDevice()->CheckFeatureSupport(
@@ -185,6 +186,12 @@
         }
     }
 
+    DXGI_ADAPTER_DESC adapterDesc;
+    DAWN_TRY(CheckHRESULT(physicalDevice.GetHardwareAdapter()->GetDesc(&adapterDesc),
+                          "IDXGIAdapter3::GetDesc"));
+    info.dedicatedVideoMemory = adapterDesc.DedicatedVideoMemory;
+    info.sharedSystemMemory = adapterDesc.SharedSystemMemory;
+
     return std::move(info);
 }
 
diff --git a/src/dawn/native/d3d12/D3D12Info.h b/src/dawn/native/d3d12/D3D12Info.h
index 9611071..3c1e486 100644
--- a/src/dawn/native/d3d12/D3D12Info.h
+++ b/src/dawn/native/d3d12/D3D12Info.h
@@ -38,6 +38,7 @@
 
 struct D3D12DeviceInfo {
     bool isUMA;
+    bool isCacheCoherentUMA;
     uint32_t resourceHeapTier;
     bool supportsRenderPass;
     bool supportsShaderF16;
@@ -60,6 +61,8 @@
     // unclear. Reference:
     // https://github.com/Microsoft/DirectXShaderCompiler/wiki/Wave-Intrinsics#:~:text=UINT%20WaveLaneCountMax
     uint32_t waveLaneCountMax;
+    size_t dedicatedVideoMemory;
+    size_t sharedSystemMemory;
 };
 
 ResultOrError<D3D12DeviceInfo> GatherDeviceInfo(const PhysicalDevice& physicalDevice);
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
index c9b5a30..2f229b5 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
@@ -141,6 +141,7 @@
     EnableFeature(Feature::Float32Filterable);
     EnableFeature(Feature::DualSourceBlending);
     EnableFeature(Feature::Norm16TextureFormats);
+    EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
 
     if (AreTimestampQueriesSupported()) {
         EnableFeature(Feature::TimestampQuery);
@@ -710,4 +711,40 @@
     return {};
 }
 
+void PhysicalDevice::PopulateMemoryHeapInfo(
+    AdapterPropertiesMemoryHeaps* memoryHeapProperties) const {
+    // https://microsoft.github.io/DirectX-Specs/d3d/D3D12GPUUploadHeaps.html describes
+    // the properties of D3D12 Default/Upload/Readback heaps.
+    if (mDeviceInfo.isUMA) {
+        auto* heapInfo = new MemoryHeapInfo[1];
+        memoryHeapProperties->heapCount = 1;
+        memoryHeapProperties->heapInfo = heapInfo;
+
+        heapInfo[0].size = mDeviceInfo.dedicatedVideoMemory != 0 ? mDeviceInfo.dedicatedVideoMemory
+                                                                 : mDeviceInfo.sharedSystemMemory;
+
+        if (mDeviceInfo.isCacheCoherentUMA) {
+            heapInfo[0].properties =
+                wgpu::HeapProperty::DeviceLocal | wgpu::HeapProperty::HostVisible |
+                wgpu::HeapProperty::HostCoherent | wgpu::HeapProperty::HostCached;
+        } else {
+            heapInfo[0].properties =
+                wgpu::HeapProperty::DeviceLocal | wgpu::HeapProperty::HostVisible |
+                wgpu::HeapProperty::HostUncached | wgpu::HeapProperty::HostCached;
+        }
+    } else {
+        auto* heapInfo = new MemoryHeapInfo[2];
+        memoryHeapProperties->heapCount = 2;
+        memoryHeapProperties->heapInfo = heapInfo;
+
+        heapInfo[0].size = mDeviceInfo.dedicatedVideoMemory;
+        heapInfo[0].properties = wgpu::HeapProperty::DeviceLocal;
+
+        heapInfo[1].size = mDeviceInfo.sharedSystemMemory;
+        heapInfo[1].properties = wgpu::HeapProperty::HostVisible |
+                                 wgpu::HeapProperty::HostCoherent |
+                                 wgpu::HeapProperty::HostUncached | wgpu::HeapProperty::HostCached;
+    }
+}
+
 }  // namespace dawn::native::d3d12
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.h b/src/dawn/native/d3d12/PhysicalDeviceD3D12.h
index c8a35f4..bdabed9 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.h
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.h
@@ -76,6 +76,8 @@
     MaybeError InitializeDebugLayerFilters();
     void CleanUpDebugLayerFilters();
 
+    void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
+
     ComPtr<ID3D12Device> mD3d12Device;
 
     D3D12DeviceInfo mDeviceInfo = {};
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index a4de8ca..3485981 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -546,6 +546,10 @@
             EnableFeature(Feature::Depth32FloatStencil8);
         }
 
+        if (@available(macOS 10.12, iOS 16.0, *)) {
+            EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
+        }
+
         // Uses newTextureWithDescriptor::iosurface::plane which is available
         // on ios 11.0+ and macOS 11.0+
         if (@available(macOS 10.11, iOS 11.0, *)) {
@@ -884,6 +888,37 @@
         return {};
     }
 
+    void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override {
+        if ([*mDevice hasUnifiedMemory]) {
+            auto* heapInfo = new MemoryHeapInfo[1];
+            memoryHeapProperties->heapCount = 1;
+            memoryHeapProperties->heapInfo = heapInfo;
+
+            heapInfo[0].properties =
+                wgpu::HeapProperty::DeviceLocal | wgpu::HeapProperty::HostVisible |
+                wgpu::HeapProperty::HostCoherent | wgpu::HeapProperty::HostCached;
+            heapInfo[0].size = [*mDevice recommendedMaxWorkingSetSize];
+        } else {
+            auto* heapInfo = new MemoryHeapInfo[2];
+            memoryHeapProperties->heapCount = 2;
+            memoryHeapProperties->heapInfo = heapInfo;
+
+            heapInfo[0].properties = wgpu::HeapProperty::DeviceLocal;
+            heapInfo[0].size = [*mDevice recommendedMaxWorkingSetSize];
+
+            mach_msg_type_number_t hostBasicInfoMsg = HOST_BASIC_INFO_COUNT;
+            host_basic_info_data_t hostInfo{};
+            DAWN_CHECK(host_info(mach_host_self(), HOST_BASIC_INFO,
+                                 reinterpret_cast<host_info_t>(&hostInfo),
+                                 &hostBasicInfoMsg) == KERN_SUCCESS);
+
+            heapInfo[1].properties = wgpu::HeapProperty::HostVisible |
+                                     wgpu::HeapProperty::HostCoherent |
+                                     wgpu::HeapProperty::HostCached;
+            heapInfo[1].size = hostInfo.max_mem;
+        }
+    }
+
     NSPRef<id<MTLDevice>> mDevice;
     const bool mMetalValidationEnabled;
 };
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index 9997aa1..5260cd2 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -90,6 +90,17 @@
     return Device::Create(adapter, descriptor, deviceToggles);
 }
 
+void PhysicalDevice::PopulateMemoryHeapInfo(
+    AdapterPropertiesMemoryHeaps* memoryHeapProperties) const {
+    auto* heapInfo = new MemoryHeapInfo[1];
+    memoryHeapProperties->heapCount = 1;
+    memoryHeapProperties->heapInfo = heapInfo;
+
+    heapInfo[0].size = 1024 * 1024 * 1024;
+    heapInfo[0].properties = wgpu::HeapProperty::DeviceLocal | wgpu::HeapProperty::HostVisible |
+                             wgpu::HeapProperty::HostCached;
+}
+
 FeatureValidationResult PhysicalDevice::ValidateFeatureSupportedWithTogglesImpl(
     wgpu::FeatureName feature,
     const TogglesState& toggles) const {
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index 4187b07..c776bdd 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -208,6 +208,8 @@
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
                                                     const DeviceDescriptor* descriptor,
                                                     const TogglesState& deviceToggles) override;
+
+    void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
 };
 
 // Helper class so |BindGroup| can allocate memory for its binding data,
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index fcf6b63..d78a66f 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -426,4 +426,10 @@
     const TogglesState& toggles) const {
     return {};
 }
+
+void PhysicalDevice::PopulateMemoryHeapInfo(
+    AdapterPropertiesMemoryHeaps* memoryHeapProperties) const {
+    DAWN_UNREACHABLE();
+}
+
 }  // namespace dawn::native::opengl
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.h b/src/dawn/native/opengl/PhysicalDeviceGL.h
index e0bec2b..33409b4 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.h
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.h
@@ -65,6 +65,8 @@
                                                     const DeviceDescriptor* descriptor,
                                                     const TogglesState& deviceToggles) override;
 
+    void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
+
     OpenGLFunctions mFunctions;
     EGLDisplay mDisplay;
     EGLFunctions mEGLFunctions;
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 19a1867..bcd21f1 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -203,6 +203,8 @@
 }
 
 void PhysicalDevice::InitializeSupportedFeaturesImpl() {
+    EnableFeature(Feature::AdapterPropertiesMemoryHeaps);
+
     // Initialize supported extensions
     if (mDeviceInfo.features.textureCompressionBC == VK_TRUE) {
         EnableFeature(Feature::TextureCompressionBC);
@@ -751,4 +753,33 @@
     return mDefaultComputeSubgroupSize;
 }
 
+void PhysicalDevice::PopulateMemoryHeapInfo(
+    AdapterPropertiesMemoryHeaps* memoryHeapProperties) const {
+    size_t count = mDeviceInfo.memoryHeaps.size();
+    auto* heapInfo = new MemoryHeapInfo[count];
+    memoryHeapProperties->heapCount = count;
+    memoryHeapProperties->heapInfo = heapInfo;
+
+    for (size_t i = 0; i < count; ++i) {
+        heapInfo[i].size = mDeviceInfo.memoryHeaps[i].size;
+        heapInfo[i].properties = {};
+        if (mDeviceInfo.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
+            heapInfo[i].properties |= wgpu::HeapProperty::DeviceLocal;
+        }
+    }
+    for (const auto& memoryType : mDeviceInfo.memoryTypes) {
+        if (memoryType.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
+            heapInfo[memoryType.heapIndex].properties |= wgpu::HeapProperty::HostVisible;
+        }
+        if (memoryType.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
+            heapInfo[memoryType.heapIndex].properties |= wgpu::HeapProperty::HostCoherent;
+        }
+        if (memoryType.propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
+            heapInfo[memoryType.heapIndex].properties |= wgpu::HeapProperty::HostCached;
+        } else {
+            heapInfo[memoryType.heapIndex].properties |= wgpu::HeapProperty::HostUncached;
+        }
+    }
+}
+
 }  // namespace dawn::native::vulkan
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.h b/src/dawn/native/vulkan/PhysicalDeviceVk.h
index a0bc315..fdea0a0 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.h
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.h
@@ -78,6 +78,8 @@
 
     uint32_t FindDefaultComputeSubgroupSize() const;
 
+    void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
+
     VkPhysicalDevice mVkPhysicalDevice;
     Ref<VulkanInstance> mVulkanInstance;
     VulkanDeviceInfo mDeviceInfo = {};
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 28055ba..7e16c8e 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -566,6 +566,7 @@
     "end2end/IndexFormatTests.cpp",
     "end2end/MaxLimitTests.cpp",
     "end2end/MemoryAllocationStressTests.cpp",
+    "end2end/MemoryHeapPropertiesTests.cpp",
     "end2end/MultisampledRenderingTests.cpp",
     "end2end/MultisampledSamplingTests.cpp",
     "end2end/MultithreadTests.cpp",
diff --git a/src/dawn/tests/DawnTest.cpp b/src/dawn/tests/DawnTest.cpp
index 58cf777..0e111ce 100644
--- a/src/dawn/tests/DawnTest.cpp
+++ b/src/dawn/tests/DawnTest.cpp
@@ -717,7 +717,7 @@
     // Override procs to provide harness-specific behavior to always select the adapter required in
     // testing parameter, and to allow fixture-specific overriding of the test device with
     // CreateDeviceImpl.
-    procs.instanceRequestAdapter = [](WGPUInstance instance, const WGPURequestAdapterOptions*,
+    procs.instanceRequestAdapter = [](WGPUInstance cInstance, const WGPURequestAdapterOptions*,
                                       WGPURequestAdapterCallback callback, void* userdata) {
         DAWN_ASSERT(gCurrentTest);
 
@@ -738,9 +738,9 @@
         // Find the adapter that exactly matches our adapter properties.
         const auto& adapters = gTestEnv->GetInstance()->EnumerateAdapters(&adapterOptions);
         const auto& it =
-            std::find_if(adapters.begin(), adapters.end(), [&](const native::Adapter& adapter) {
+            std::find_if(adapters.begin(), adapters.end(), [&](const native::Adapter& candidate) {
                 wgpu::AdapterProperties properties;
-                adapter.GetProperties(&properties);
+                candidate.GetProperties(&properties);
 
                 const auto& param = gCurrentTest->mParam;
                 return (param.adapterProperties.selected &&
@@ -758,7 +758,7 @@
         callback(WGPURequestAdapterStatus_Success, cAdapter, nullptr, userdata);
     };
 
-    procs.adapterRequestDevice = [](WGPUAdapter adapter, const WGPUDeviceDescriptor* descriptor,
+    procs.adapterRequestDevice = [](WGPUAdapter cAdapter, const WGPUDeviceDescriptor* descriptor,
                                     WGPURequestDeviceCallback callback, void* userdata) {
         DAWN_ASSERT(gCurrentTest);
 
@@ -784,8 +784,8 @@
     mReadbackSlots.clear();
     queue = nullptr;
     device = nullptr;
-    mAdapter = nullptr;
-    mInstance = nullptr;
+    adapter = nullptr;
+    instance = nullptr;
 
     // D3D11 and D3D12's GPU-based validation will accumulate objects over time if the backend
     // device is not destroyed and recreated, so we reset it here.
@@ -1001,7 +1001,7 @@
 }
 
 const wgpu::Instance& DawnTestBase::GetInstance() const {
-    return mInstance;
+    return instance;
 }
 
 native::Adapter DawnTestBase::GetAdapter() const {
@@ -1022,7 +1022,7 @@
 
 wgpu::SupportedLimits DawnTestBase::GetAdapterLimits() {
     wgpu::SupportedLimits supportedLimits = {};
-    mAdapter.GetLimits(&supportedLimits);
+    adapter.GetLimits(&supportedLimits);
     return supportedLimits;
 }
 
@@ -1104,7 +1104,7 @@
     deviceDesc.deviceLostCallback = mDeviceLostCallback.Callback();
     deviceDesc.deviceLostUserdata = mDeviceLostCallback.MakeUserdata(deviceUserdata);
 
-    mAdapter.RequestDevice(
+    adapter.RequestDevice(
         &deviceDesc,
         [](WGPURequestDeviceStatus, WGPUDevice cDevice, const char*, void* userdata) {
             *static_cast<wgpu::Device*>(userdata) = wgpu::Device::Acquire(cDevice);
@@ -1150,7 +1150,7 @@
     mTestPlatform = CreateTestPlatform();
     native::FromAPI(gTestEnv->GetInstance()->Get())->SetPlatformForTesting(mTestPlatform.get());
 
-    mInstance = mWireHelper->RegisterInstance(gTestEnv->GetInstance()->Get());
+    instance = mWireHelper->RegisterInstance(gTestEnv->GetInstance()->Get());
 
     std::string traceName =
         std::string(::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name()) +
@@ -1158,14 +1158,14 @@
     mWireHelper->BeginWireTrace(traceName.c_str());
 
     // RequestAdapter is overriden to ignore RequestAdapterOptions, and select based on test params.
-    mInstance.RequestAdapter(
+    instance.RequestAdapter(
         nullptr,
         [](WGPURequestAdapterStatus, WGPUAdapter cAdapter, const char*, void* userdata) {
             *static_cast<wgpu::Adapter*>(userdata) = wgpu::Adapter::Acquire(cAdapter);
         },
-        &mAdapter);
+        &adapter);
     FlushWire();
-    DAWN_ASSERT(mAdapter);
+    DAWN_ASSERT(adapter);
 
     device = CreateDevice();
     backendDevice = mLastCreatedBackendDevice;
@@ -1575,7 +1575,7 @@
 
 void DawnTestBase::WaitABit(wgpu::Instance targetInstance) {
     if (targetInstance == nullptr) {
-        targetInstance = mInstance;
+        targetInstance = instance;
     }
     if (targetInstance != nullptr) {
         targetInstance.ProcessEvents();
diff --git a/src/dawn/tests/DawnTest.h b/src/dawn/tests/DawnTest.h
index 7a3d476..513e6c2 100644
--- a/src/dawn/tests/DawnTest.h
+++ b/src/dawn/tests/DawnTest.h
@@ -319,6 +319,8 @@
     void ResolveDeferredExpectationsNow();
 
   protected:
+    wgpu::Instance instance;
+    wgpu::Adapter adapter;
     wgpu::Device device;
     wgpu::Queue queue;
 
@@ -606,8 +608,6 @@
   private:
     AdapterTestParam mParam;
     std::unique_ptr<utils::WireHelper> mWireHelper;
-    wgpu::Instance mInstance;
-    wgpu::Adapter mAdapter;
 
     // Helps generate unique userdata values passed to deviceLostUserdata.
     std::atomic<uintptr_t> mNextUniqueUserdata = 0;
diff --git a/src/dawn/tests/end2end/MemoryHeapPropertiesTests.cpp b/src/dawn/tests/end2end/MemoryHeapPropertiesTests.cpp
new file mode 100644
index 0000000..532207e
--- /dev/null
+++ b/src/dawn/tests/end2end/MemoryHeapPropertiesTests.cpp
@@ -0,0 +1,77 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <vector>
+
+#include "dawn/tests/DawnTest.h"
+
+namespace dawn {
+namespace {
+
+class MemoryHeapPropertiesTest : public DawnTest {};
+
+// TODO(dawn:2257) test that is is invalid to request AdapterPropertiesMemoryHeaps if the
+// feature is not available.
+
+// Test that it is possible to query the memory, and it is populated with valid enums.
+TEST_P(MemoryHeapPropertiesTest, GetMemoryHeapProperties) {
+    DAWN_TEST_UNSUPPORTED_IF(!adapter.HasFeature(wgpu::FeatureName::AdapterPropertiesMemoryHeaps));
+
+    wgpu::AdapterProperties properties;
+    wgpu::AdapterPropertiesMemoryHeaps memoryHeapProperties;
+    properties.nextInChain = &memoryHeapProperties;
+
+    adapter.GetProperties(&properties);
+
+    EXPECT_GT(memoryHeapProperties.heapCount, 0u);
+    for (size_t i = 0; i < memoryHeapProperties.heapCount; ++i) {
+        // Check the heap is non-zero in size.
+        EXPECT_GT(memoryHeapProperties.heapInfo[i].size, 0ull);
+
+        constexpr wgpu::HeapProperty kValidProps =
+            wgpu::HeapProperty::DeviceLocal | wgpu::HeapProperty::HostVisible |
+            wgpu::HeapProperty::HostCoherent | wgpu::HeapProperty::HostUncached |
+            wgpu::HeapProperty::HostCached;
+
+        // Check the heap properties only contain the set of valid enums.
+        EXPECT_EQ(memoryHeapProperties.heapInfo[i].properties & ~kValidProps, 0u);
+
+        // Check the heap properies have at least one bit.
+        EXPECT_NE(uint32_t(memoryHeapProperties.heapInfo[i].properties), 0u);
+    }
+}
+
+DAWN_INSTANTIATE_TEST(MemoryHeapPropertiesTest,
+                      D3D11Backend(),
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
+
+}  // anonymous namespace
+}  // namespace dawn
diff --git a/src/dawn/tests/unittests/wire/WireAdapterTests.cpp b/src/dawn/tests/unittests/wire/WireAdapterTests.cpp
index b67a5ca..d3c8af7 100644
--- a/src/dawn/tests/unittests/wire/WireAdapterTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireAdapterTests.cpp
@@ -72,6 +72,10 @@
         apiAdapter = api.GetNewAdapter();
         EXPECT_CALL(api, OnInstanceRequestAdapter(apiInstance, NotNull(), NotNull(), NotNull()))
             .WillOnce(InvokeWithoutArgs([&] {
+                EXPECT_CALL(api, AdapterHasFeature(apiAdapter,
+                                                   WGPUFeatureName_AdapterPropertiesMemoryHeaps))
+                    .WillOnce(Return(false));
+
                 EXPECT_CALL(api, AdapterGetProperties(apiAdapter, NotNull()))
                     .WillOnce(WithArg<1>(Invoke([&](WGPUAdapterProperties* properties) {
                         *properties = {};
diff --git a/src/dawn/tests/unittests/wire/WireInstanceTests.cpp b/src/dawn/tests/unittests/wire/WireInstanceTests.cpp
index 59a2594..2caf2e0 100644
--- a/src/dawn/tests/unittests/wire/WireInstanceTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireInstanceTests.cpp
@@ -40,6 +40,7 @@
 namespace dawn::wire {
 namespace {
 
+using testing::_;
 using testing::Invoke;
 using testing::InvokeWithoutArgs;
 using testing::MockCallback;
@@ -142,6 +143,8 @@
     WGPUAdapter apiAdapter = api.GetNewAdapter();
     EXPECT_CALL(api, OnInstanceRequestAdapter(apiInstance, NotNull(), NotNull(), NotNull()))
         .WillOnce(InvokeWithoutArgs([&] {
+            EXPECT_CALL(api, AdapterHasFeature(apiAdapter, _)).WillRepeatedly(Return(false));
+
             EXPECT_CALL(api, AdapterGetProperties(apiAdapter, NotNull()))
                 .WillOnce(SetArgPointee<1>(fakeProperties));
 
@@ -204,6 +207,100 @@
     });
 }
 
+// Test that RequestAdapter forwards the memory heap properties to the client.
+TEST_P(WireInstanceTests, RequestAdapterPassesMemoryHeapProperties) {
+    WGPURequestAdapterOptions options = {};
+    InstanceRequestAdapter(instance, &options, nullptr);
+
+    WGPUMemoryHeapInfo fakeHeapInfo[3] = {
+        {WGPUHeapProperty_DeviceLocal, 64},
+        {WGPUHeapProperty_DeviceLocal | WGPUHeapProperty_HostVisible, 136},
+        {WGPUHeapProperty_HostCached | WGPUHeapProperty_HostVisible, 460},
+    };
+
+    WGPUAdapterPropertiesMemoryHeaps fakeMemoryHeapProperties = {};
+    fakeMemoryHeapProperties.chain.sType = WGPUSType_AdapterPropertiesMemoryHeaps;
+    fakeMemoryHeapProperties.heapCount = 3;
+    fakeMemoryHeapProperties.heapInfo = fakeHeapInfo;
+
+    std::initializer_list<WGPUFeatureName> fakeFeatures = {
+        WGPUFeatureName_AdapterPropertiesMemoryHeaps,
+    };
+
+    // Expect the server to receive the message. Then, mock a fake reply.
+    WGPUAdapter apiAdapter = api.GetNewAdapter();
+    EXPECT_CALL(api, OnInstanceRequestAdapter(apiInstance, NotNull(), NotNull(), NotNull()))
+        .WillOnce(InvokeWithoutArgs([&] {
+            EXPECT_CALL(api,
+                        AdapterHasFeature(apiAdapter, WGPUFeatureName_AdapterPropertiesMemoryHeaps))
+                .WillOnce(Return(true));
+
+            EXPECT_CALL(api, AdapterGetProperties(apiAdapter, NotNull()))
+                .WillOnce(WithArg<1>(Invoke([&](WGPUAdapterProperties* properties) {
+                    properties->vendorName = "fake-vendor";
+                    properties->architecture = "fake-architecture";
+                    properties->name = "fake adapter";
+                    properties->driverDescription = "hello world";
+
+                    EXPECT_EQ(properties->nextInChain->sType,
+                              WGPUSType_AdapterPropertiesMemoryHeaps);
+                    *reinterpret_cast<WGPUAdapterPropertiesMemoryHeaps*>(properties->nextInChain) =
+                        fakeMemoryHeapProperties;
+                })));
+
+            EXPECT_CALL(api, AdapterGetLimits(apiAdapter, NotNull()))
+                .WillOnce(WithArg<1>(Invoke([&](WGPUSupportedLimits* limits) {
+                    *limits = {};
+                    return true;
+                })));
+
+            EXPECT_CALL(api, AdapterEnumerateFeatures(apiAdapter, nullptr))
+                .WillOnce(Return(fakeFeatures.size()));
+
+            EXPECT_CALL(api, AdapterEnumerateFeatures(apiAdapter, NotNull()))
+                .WillOnce(WithArg<1>(Invoke([&](WGPUFeatureName* features) {
+                    for (WGPUFeatureName feature : fakeFeatures) {
+                        *(features++) = feature;
+                    }
+                    return fakeFeatures.size();
+                })));
+            api.CallInstanceRequestAdapterCallback(apiInstance, WGPURequestAdapterStatus_Success,
+                                                   apiAdapter, nullptr);
+        }));
+
+    FlushClient();
+    FlushFutures();
+
+    // Expect the callback in the client and the adapter information to match.
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPURequestAdapterStatus_Success, NotNull(), nullptr, nullptr))
+            .WillOnce(WithArg<1>(Invoke([&](WGPUAdapter adapter) {
+                // Request properties without a chained struct.
+                // It should be nullptr.
+                WGPUAdapterProperties properties = {};
+                wgpuAdapterGetProperties(adapter, &properties);
+                EXPECT_EQ(properties.nextInChain, nullptr);
+
+                // Request the memory heap properties.
+                WGPUAdapterPropertiesMemoryHeaps memoryHeapProperties = {};
+                memoryHeapProperties.chain.sType = WGPUSType_AdapterPropertiesMemoryHeaps;
+                properties.nextInChain = &memoryHeapProperties.chain;
+                wgpuAdapterGetProperties(adapter, &properties);
+
+                // Expect everything matches the fake properties returned by the server.
+                EXPECT_EQ(memoryHeapProperties.heapCount, fakeMemoryHeapProperties.heapCount);
+                for (size_t i = 0; i < fakeMemoryHeapProperties.heapCount; ++i) {
+                    EXPECT_EQ(memoryHeapProperties.heapInfo[i].properties,
+                              fakeMemoryHeapProperties.heapInfo[i].properties);
+                    EXPECT_EQ(memoryHeapProperties.heapInfo[i].size,
+                              fakeMemoryHeapProperties.heapInfo[i].size);
+                }
+            })));
+
+        FlushCallbacks();
+    });
+}
+
 // Test that features returned by the implementation that aren't supported in the wire are not
 // exposed.
 TEST_P(WireInstanceTests, RequestAdapterWireLacksFeatureSupport) {
@@ -220,6 +317,8 @@
     WGPUAdapter apiAdapter = api.GetNewAdapter();
     EXPECT_CALL(api, OnInstanceRequestAdapter(apiInstance, NotNull(), NotNull(), NotNull()))
         .WillOnce(InvokeWithoutArgs([&] {
+            EXPECT_CALL(api, AdapterHasFeature(apiAdapter, _)).WillRepeatedly(Return(false));
+
             EXPECT_CALL(api, AdapterGetProperties(apiAdapter, NotNull()))
                 .WillOnce(WithArg<1>(Invoke([&](WGPUAdapterProperties* properties) {
                     *properties = {};
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index 623ebe2..bdc5d7e 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -93,6 +93,7 @@
         case WGPUFeatureName_PixelLocalStorageNonCoherent:
         case WGPUFeatureName_Norm16TextureFormats:
         case WGPUFeatureName_FramebufferFetch:
+        case WGPUFeatureName_AdapterPropertiesMemoryHeaps:
             return true;
     }
 
diff --git a/src/dawn/wire/client/Adapter.cpp b/src/dawn/wire/client/Adapter.cpp
index 44e1ffc..c98cc93 100644
--- a/src/dawn/wire/client/Adapter.cpp
+++ b/src/dawn/wire/client/Adapter.cpp
@@ -69,9 +69,51 @@
 void Adapter::SetProperties(const WGPUAdapterProperties* properties) {
     mProperties = *properties;
     mProperties.nextInChain = nullptr;
+
+    // Loop through the chained struct.
+    WGPUChainedStructOut* chain = properties->nextInChain;
+    while (chain != nullptr) {
+        switch (chain->sType) {
+            case WGPUSType_AdapterPropertiesMemoryHeaps: {
+                // Make a copy of the heap info in `mMemoryHeapInfo`.
+                const auto* memoryHeapProperties =
+                    reinterpret_cast<const WGPUAdapterPropertiesMemoryHeaps*>(chain);
+                mMemoryHeapInfo = {
+                    memoryHeapProperties->heapInfo,
+                    memoryHeapProperties->heapInfo + memoryHeapProperties->heapCount};
+                break;
+            }
+            default:
+                DAWN_UNREACHABLE();
+                break;
+        }
+        chain = chain->next;
+    }
 }
 
 void Adapter::GetProperties(WGPUAdapterProperties* properties) const {
+    // Loop through the chained struct.
+    WGPUChainedStructOut* chain = properties->nextInChain;
+    while (chain != nullptr) {
+        switch (chain->sType) {
+            case WGPUSType_AdapterPropertiesMemoryHeaps: {
+                // Copy `mMemoryHeapInfo` into a new allocation.
+                auto* memoryHeapProperties =
+                    reinterpret_cast<WGPUAdapterPropertiesMemoryHeaps*>(chain);
+                size_t heapCount = mMemoryHeapInfo.size();
+                auto* heapInfo = new WGPUMemoryHeapInfo[heapCount];
+                memcpy(heapInfo, mMemoryHeapInfo.data(), sizeof(WGPUMemoryHeapInfo) * heapCount);
+                // Write out the pointer and count to the heap properties out-struct.
+                memoryHeapProperties->heapCount = heapCount;
+                memoryHeapProperties->heapInfo = heapInfo;
+                break;
+            }
+            default:
+                break;
+        }
+        chain = chain->next;
+    }
+
     *properties = mProperties;
 
     // Get lengths, with null terminators.
@@ -105,6 +147,11 @@
     delete[] properties.vendorName;
 }
 
+void ClientAdapterPropertiesMemoryHeapsFreeMembers(
+    WGPUAdapterPropertiesMemoryHeaps memoryHeapProperties) {
+    delete[] memoryHeapProperties.heapInfo;
+}
+
 void Adapter::RequestDevice(const WGPUDeviceDescriptor* descriptor,
                             WGPURequestDeviceCallback callback,
                             void* userdata) {
diff --git a/src/dawn/wire/client/Adapter.h b/src/dawn/wire/client/Adapter.h
index 1b9e6d1..b7cf683 100644
--- a/src/dawn/wire/client/Adapter.h
+++ b/src/dawn/wire/client/Adapter.h
@@ -28,8 +28,9 @@
 #ifndef SRC_DAWN_WIRE_CLIENT_ADAPTER_H_
 #define SRC_DAWN_WIRE_CLIENT_ADAPTER_H_
 
-#include "dawn/webgpu.h"
+#include <vector>
 
+#include "dawn/webgpu.h"
 #include "dawn/wire/WireClient.h"
 #include "dawn/wire/WireCmd_autogen.h"
 #include "dawn/wire/client/LimitsAndFeatures.h"
@@ -70,6 +71,7 @@
   private:
     LimitsAndFeatures mLimitsAndFeatures;
     WGPUAdapterProperties mProperties;
+    std::vector<WGPUMemoryHeapInfo> mMemoryHeapInfo;
 
     struct RequestDeviceData {
         WGPURequestDeviceCallback callback = nullptr;
@@ -80,6 +82,7 @@
 };
 
 void ClientAdapterPropertiesFreeMembers(WGPUAdapterProperties);
+void ClientAdapterPropertiesMemoryHeapsFreeMembers(WGPUAdapterPropertiesMemoryHeaps);
 
 }  // namespace dawn::wire::client
 
diff --git a/src/dawn/wire/server/ServerInstance.cpp b/src/dawn/wire/server/ServerInstance.cpp
index 446e089..3f3f8c4 100644
--- a/src/dawn/wire/server/ServerInstance.cpp
+++ b/src/dawn/wire/server/ServerInstance.cpp
@@ -87,6 +87,14 @@
 
     // Query and report the adapter properties.
     WGPUAdapterProperties properties = {};
+
+    // Query AdapterPropertiesMemoryHeaps if the feature is supported.
+    WGPUAdapterPropertiesMemoryHeaps memoryHeapProperties = {};
+    memoryHeapProperties.chain.sType = WGPUSType_AdapterPropertiesMemoryHeaps;
+    if (mProcs.adapterHasFeature(adapter, WGPUFeatureName_AdapterPropertiesMemoryHeaps)) {
+        properties.nextInChain = &memoryHeapProperties.chain;
+    }
+
     mProcs.adapterGetProperties(adapter, &properties);
     cmd.properties = &properties;
 
@@ -102,6 +110,7 @@
 
     SerializeCommand(cmd);
     mProcs.adapterPropertiesFreeMembers(properties);
+    mProcs.adapterPropertiesMemoryHeapsFreeMembers(memoryHeapProperties);
 }
 
 }  // namespace dawn::wire::server