Use chain utilities in more places to remove FindInChain.

Bug: dawn:1955
Change-Id: I105e14d9f37970508f269d45f65a773759b8ca32
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/165400
Commit-Queue: Loko Kung <lokokung@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/generator/templates/dawn/native/ChainUtils.h b/generator/templates/dawn/native/ChainUtils.h
index bd68411..a6bcdbf 100644
--- a/generator/templates/dawn/native/ChainUtils.h
+++ b/generator/templates/dawn/native/ChainUtils.h
@@ -93,25 +93,6 @@
 template <typename T>
 constexpr inline wgpu::SType STypeFor<const T*> = detail::STypeForImpl<T>;
 
-template <typename T>
-void FindInChain(const ChainedStruct* chain, const T** out) {
-    for (; chain; chain = chain->nextInChain) {
-        if (chain->sType == STypeFor<T>) {
-            *out = static_cast<const T*>(chain);
-            break;
-        }
-    }
-}
-template <typename T>
-void FindInChain(ChainedStructOut* chain, T** out) {
-    for (; chain; chain = chain->nextInChain) {
-        if (chain->sType == STypeFor<T>) {
-            *out = static_cast<T*>(chain);
-            break;
-        }
-    }
-}
-
 }  // namespace {{native_namespace}}
 
 // Include specializations before declaring types for ordering purposes.
diff --git a/src/dawn/native/Adapter.cpp b/src/dawn/native/Adapter.cpp
index 4cc6732..be930d2 100644
--- a/src/dawn/native/Adapter.cpp
+++ b/src/dawn/native/Adapter.cpp
@@ -189,13 +189,14 @@
     return result.AcquireSuccess().Detach();
 }
 
-ResultOrError<Ref<DeviceBase>> AdapterBase::CreateDevice(const DeviceDescriptor* descriptor) {
-    DAWN_ASSERT(descriptor != nullptr);
+ResultOrError<Ref<DeviceBase>> AdapterBase::CreateDevice(const DeviceDescriptor* rawDescriptor) {
+    DAWN_ASSERT(rawDescriptor != nullptr);
 
     // Create device toggles state from required toggles descriptor and inherited adapter toggles
     // state.
-    const DawnTogglesDescriptor* deviceTogglesDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &deviceTogglesDesc);
+    UnpackedPtr<DeviceDescriptor> descriptor;
+    DAWN_TRY_ASSIGN(descriptor, ValidateAndUnpack(rawDescriptor));
+    auto* deviceTogglesDesc = descriptor.Get<DawnTogglesDescriptor>();
 
     // Create device toggles state.
     TogglesState deviceToggles =
diff --git a/src/dawn/native/Adapter.h b/src/dawn/native/Adapter.h
index 5de34a3..6170358 100644
--- a/src/dawn/native/Adapter.h
+++ b/src/dawn/native/Adapter.h
@@ -60,7 +60,7 @@
                           WGPURequestDeviceCallback callback,
                           void* userdata);
     DeviceBase* APICreateDevice(const DeviceDescriptor* descriptor = nullptr);
-    ResultOrError<Ref<DeviceBase>> CreateDevice(const DeviceDescriptor* descriptor);
+    ResultOrError<Ref<DeviceBase>> CreateDevice(const DeviceDescriptor* rawDescriptor);
 
     void SetUseTieredLimits(bool useTieredLimits);
 
diff --git a/src/dawn/native/AttachmentState.cpp b/src/dawn/native/AttachmentState.cpp
index 86bd40e..099c70d 100644
--- a/src/dawn/native/AttachmentState.cpp
+++ b/src/dawn/native/AttachmentState.cpp
@@ -60,12 +60,12 @@
 }
 
 AttachmentState::AttachmentState(DeviceBase* device,
-                                 const RenderPipelineDescriptor* descriptor,
+                                 const UnpackedPtr<RenderPipelineDescriptor>& descriptor,
                                  const PipelineLayoutBase* layout)
     : ObjectBase(device), mSampleCount(descriptor->multisample.count) {
-    const DawnMultisampleStateRenderToSingleSampled* msaaRenderToSingleSampledDesc = nullptr;
-    FindInChain(descriptor->multisample.nextInChain, &msaaRenderToSingleSampledDesc);
-    if (msaaRenderToSingleSampledDesc != nullptr) {
+    UnpackedPtr<MultisampleState> unpackedMultisampleState = Unpack(&descriptor->multisample);
+    if (auto* msaaRenderToSingleSampledDesc =
+            unpackedMultisampleState.Get<DawnMultisampleStateRenderToSingleSampled>()) {
         mIsMSAARenderToSingleSampledEnabled = msaaRenderToSingleSampledDesc->enabled;
     }
 
@@ -92,7 +92,8 @@
     SetContentHash(ComputeContentHash());
 }
 
-AttachmentState::AttachmentState(DeviceBase* device, const RenderPassDescriptor* descriptor)
+AttachmentState::AttachmentState(DeviceBase* device,
+                                 const UnpackedPtr<RenderPassDescriptor>& descriptor)
     : ObjectBase(device) {
     auto colorAttachments = ityp::SpanFromUntyped<ColorAttachmentIndex>(
         descriptor->colorAttachments, descriptor->colorAttachmentCount);
@@ -104,9 +105,9 @@
         mColorAttachmentsSet.set(i);
         mColorFormats[i] = attachment->GetFormat().format;
 
-        const DawnRenderPassColorAttachmentRenderToSingleSampled* msaaRenderToSingleSampledDesc =
-            nullptr;
-        FindInChain(colorAttachment.nextInChain, &msaaRenderToSingleSampledDesc);
+        UnpackedPtr<RenderPassColorAttachment> unpackedColorAttachment = Unpack(&colorAttachment);
+        auto* msaaRenderToSingleSampledDesc =
+            unpackedColorAttachment.Get<DawnRenderPassColorAttachmentRenderToSingleSampled>();
         uint32_t attachmentSampleCount;
         if (msaaRenderToSingleSampledDesc != nullptr &&
             msaaRenderToSingleSampledDesc->implicitSampleCount > 1) {
@@ -135,9 +136,7 @@
     }
 
     // Gather the PLS information.
-    const RenderPassPixelLocalStorage* pls = nullptr;
-    FindInChain(descriptor->nextInChain, &pls);
-    if (pls != nullptr) {
+    if (auto* pls = descriptor.Get<RenderPassPixelLocalStorage>()) {
         mHasPLS = true;
         mStorageAttachmentSlots = std::vector<wgpu::TextureFormat>(
             pls->totalPixelLocalStorageSize / kPLSSlotByteSize, wgpu::TextureFormat::Undefined);
diff --git a/src/dawn/native/AttachmentState.h b/src/dawn/native/AttachmentState.h
index aeab28b..088ad7e 100644
--- a/src/dawn/native/AttachmentState.h
+++ b/src/dawn/native/AttachmentState.h
@@ -53,9 +53,10 @@
     // Note: Descriptors must be validated before the AttachmentState is constructed.
     explicit AttachmentState(DeviceBase* device, const RenderBundleEncoderDescriptor* descriptor);
     explicit AttachmentState(DeviceBase* device,
-                             const RenderPipelineDescriptor* descriptor,
+                             const UnpackedPtr<RenderPipelineDescriptor>& descriptor,
                              const PipelineLayoutBase* layout);
-    explicit AttachmentState(DeviceBase* device, const RenderPassDescriptor* descriptor);
+    explicit AttachmentState(DeviceBase* device,
+                             const UnpackedPtr<RenderPassDescriptor>& descriptor);
 
     // Constructor used to avoid re-parsing descriptors when we already parsed them for cache keys.
     AttachmentState(const AttachmentState& blueprint);
diff --git a/src/dawn/native/BackendConnection.h b/src/dawn/native/BackendConnection.h
index 3be9147..3521ed7 100644
--- a/src/dawn/native/BackendConnection.h
+++ b/src/dawn/native/BackendConnection.h
@@ -52,7 +52,7 @@
     // Calling this multiple times in succession should return a vector with duplicate
     // references to the same PhysicalDevices (i.e. the backend should cache them).
     virtual std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) = 0;
+        const UnpackedPtr<RequestAdapterOptions>& options) = 0;
 
     // Clear all internal refs to physical devices.
     virtual void ClearPhysicalDevices() = 0;
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index 0a8a6fb..b61089d 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -407,34 +407,34 @@
     }
 
     for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
-        const BindGroupEntry& entry = descriptor->entries[i];
+        UnpackedPtr<BindGroupEntry> entry = Unpack(&descriptor->entries[i]);
 
-        BindingIndex bindingIndex = layout->GetBindingIndex(BindingNumber(entry.binding));
+        BindingIndex bindingIndex = layout->GetBindingIndex(BindingNumber(entry->binding));
         DAWN_ASSERT(bindingIndex < layout->GetBindingCount());
 
         // Only a single binding type should be set, so once we found it we can skip to the
         // next loop iteration.
 
-        if (entry.buffer != nullptr) {
+        if (entry->buffer != nullptr) {
             DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
-            mBindingData.bindings[bindingIndex] = entry.buffer;
-            mBindingData.bufferData[bindingIndex].offset = entry.offset;
-            uint64_t bufferSize = (entry.size == wgpu::kWholeSize)
-                                      ? entry.buffer->GetSize() - entry.offset
-                                      : entry.size;
+            mBindingData.bindings[bindingIndex] = entry->buffer;
+            mBindingData.bufferData[bindingIndex].offset = entry->offset;
+            uint64_t bufferSize = (entry->size == wgpu::kWholeSize)
+                                      ? entry->buffer->GetSize() - entry->offset
+                                      : entry->size;
             mBindingData.bufferData[bindingIndex].size = bufferSize;
             continue;
         }
 
-        if (entry.textureView != nullptr) {
+        if (entry->textureView != nullptr) {
             DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
-            mBindingData.bindings[bindingIndex] = entry.textureView;
+            mBindingData.bindings[bindingIndex] = entry->textureView;
             continue;
         }
 
-        if (entry.sampler != nullptr) {
+        if (entry->sampler != nullptr) {
             DAWN_ASSERT(mBindingData.bindings[bindingIndex] == nullptr);
-            mBindingData.bindings[bindingIndex] = entry.sampler;
+            mBindingData.bindings[bindingIndex] = entry->sampler;
             continue;
         }
 
@@ -442,15 +442,13 @@
         // external texture's contents. New binding locations previously determined in the bind
         // group layout are created in this bind group and filled with the external texture's
         // underlying resources.
-        const ExternalTextureBindingEntry* externalTextureBindingEntry = nullptr;
-        FindInChain(entry.nextInChain, &externalTextureBindingEntry);
-        if (externalTextureBindingEntry != nullptr) {
+        if (auto* externalTextureBindingEntry = entry.Get<ExternalTextureBindingEntry>()) {
             mBoundExternalTextures.push_back(externalTextureBindingEntry->externalTexture);
 
             ExternalTextureBindingExpansionMap expansions =
                 layout->GetExternalTextureBindingExpansionMap();
             ExternalTextureBindingExpansionMap::iterator it =
-                expansions.find(BindingNumber(entry.binding));
+                expansions.find(BindingNumber(entry->binding));
 
             DAWN_ASSERT(it != expansions.end());
 
diff --git a/src/dawn/native/BindGroupLayoutInternal.cpp b/src/dawn/native/BindGroupLayoutInternal.cpp
index 52842a4..4228f53 100644
--- a/src/dawn/native/BindGroupLayoutInternal.cpp
+++ b/src/dawn/native/BindGroupLayoutInternal.cpp
@@ -30,6 +30,7 @@
 #include <algorithm>
 #include <functional>
 #include <limits>
+#include <list>
 #include <set>
 #include <string>
 #include <vector>
@@ -109,15 +110,15 @@
 }
 
 MaybeError ValidateBindGroupLayoutEntry(DeviceBase* device,
-                                        const BindGroupLayoutEntry& entry,
+                                        const UnpackedPtr<BindGroupLayoutEntry>& entry,
                                         bool allowInternalBinding) {
-    DAWN_TRY(ValidateShaderStage(entry.visibility));
+    DAWN_TRY(ValidateShaderStage(entry->visibility));
 
     int bindingMemberCount = 0;
 
-    if (entry.buffer.type != wgpu::BufferBindingType::Undefined) {
+    if (entry->buffer.type != wgpu::BufferBindingType::Undefined) {
         bindingMemberCount++;
-        const BufferBindingLayout& buffer = entry.buffer;
+        const BufferBindingLayout& buffer = entry->buffer;
 
         // The kInternalStorageBufferBinding is used internally and not a value
         // in wgpu::BufferBindingType.
@@ -130,21 +131,21 @@
         if (buffer.type == wgpu::BufferBindingType::Storage ||
             buffer.type == kInternalStorageBufferBinding) {
             DAWN_INVALID_IF(
-                entry.visibility & wgpu::ShaderStage::Vertex,
+                entry->visibility & wgpu::ShaderStage::Vertex,
                 "Read-write storage buffer binding is used with a visibility (%s) that contains %s "
                 "(note that read-only storage buffer bindings are allowed).",
-                entry.visibility, wgpu::ShaderStage::Vertex);
+                entry->visibility, wgpu::ShaderStage::Vertex);
         }
     }
 
-    if (entry.sampler.type != wgpu::SamplerBindingType::Undefined) {
+    if (entry->sampler.type != wgpu::SamplerBindingType::Undefined) {
         bindingMemberCount++;
-        DAWN_TRY(ValidateSamplerBindingType(entry.sampler.type));
+        DAWN_TRY(ValidateSamplerBindingType(entry->sampler.type));
     }
 
-    if (entry.texture.sampleType != wgpu::TextureSampleType::Undefined) {
+    if (entry->texture.sampleType != wgpu::TextureSampleType::Undefined) {
         bindingMemberCount++;
-        const TextureBindingLayout& texture = entry.texture;
+        const TextureBindingLayout& texture = entry->texture;
         // The kInternalResolveAttachmentSampleType is used internally and not a value
         // in wgpu::TextureSampleType.
         switch (texture.sampleType) {
@@ -175,9 +176,9 @@
             "Sample type for multisampled texture binding was %s.", wgpu::TextureSampleType::Float);
     }
 
-    if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
+    if (entry->storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
         bindingMemberCount++;
-        const StorageTextureBindingLayout& storageTexture = entry.storageTexture;
+        const StorageTextureBindingLayout& storageTexture = entry->storageTexture;
         DAWN_TRY(ValidateStorageTextureAccess(storageTexture.access));
         DAWN_TRY(ValidateStorageTextureFormat(device, storageTexture.format));
 
@@ -194,19 +195,18 @@
                 break;
             case wgpu::StorageTextureAccess::ReadWrite:
             case wgpu::StorageTextureAccess::WriteOnly:
-                DAWN_INVALID_IF(entry.visibility & wgpu::ShaderStage::Vertex,
+                DAWN_INVALID_IF(entry->visibility & wgpu::ShaderStage::Vertex,
                                 "Storage texture binding with %s is used with a visibility (%s) "
                                 "that contains %s.",
-                                storageTexture.access, entry.visibility, wgpu::ShaderStage::Vertex);
+                                storageTexture.access, entry->visibility,
+                                wgpu::ShaderStage::Vertex);
                 break;
             default:
                 DAWN_UNREACHABLE();
         }
     }
 
-    const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
-    FindInChain(entry.nextInChain, &externalTextureBindingLayout);
-    if (externalTextureBindingLayout != nullptr) {
+    if (auto* externalTextureBindingLayout = entry.Get<ExternalTextureBindingLayout>()) {
         bindingMemberCount++;
     }
 
@@ -242,24 +242,35 @@
     return entry;
 }
 
-std::vector<BindGroupLayoutEntry> ExtractAndExpandBglEntries(
+// Helper struct to encapsulate additional backing BindGroupLayoutEntries created when expanding for
+// UnpackedPtr types. Users shouldn't need to use the list of additional entries directly, instead
+// they should use the list of unpacked pointers instead.
+struct UnpackedExpandedBglEntries {
+    // Backing memory for additional entries. Note that we use a list for pointer stability of the
+    // create elements that are used for the UnpackedPtrs.
+    std::list<BindGroupLayoutEntry> additionalEntries;
+    std::vector<UnpackedPtr<BindGroupLayoutEntry>> unpackedEntries;
+};
+
+UnpackedExpandedBglEntries ExtractAndExpandBglEntries(
     const BindGroupLayoutDescriptor* descriptor,
     BindingCounts* bindingCounts,
     ExternalTextureBindingExpansionMap* externalTextureBindingExpansions) {
-    std::vector<BindGroupLayoutEntry> expandedOutput;
+    UnpackedExpandedBglEntries result;
+    std::list<BindGroupLayoutEntry>& additionalEntries = result.additionalEntries;
+    std::vector<UnpackedPtr<BindGroupLayoutEntry>>& expandedOutput = result.unpackedEntries;
 
     // When new bgl entries are created, we use binding numbers larger than
     // kMaxBindingsPerBindGroup to ensure there are no collisions.
     uint32_t nextOpenBindingNumberForNewEntry = kMaxBindingsPerBindGroup;
     for (uint32_t i = 0; i < descriptor->entryCount; i++) {
-        const BindGroupLayoutEntry& entry = descriptor->entries[i];
-        const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
-        FindInChain(entry.nextInChain, &externalTextureBindingLayout);
+        UnpackedPtr<BindGroupLayoutEntry> entry = Unpack(&descriptor->entries[i]);
+
         // External textures are expanded from a texture_external into two sampled texture
         // bindings and one uniform buffer binding. The original binding number is used
         // for the first sampled texture.
-        if (externalTextureBindingLayout != nullptr) {
-            for (SingleShaderStage stage : IterateStages(entry.visibility)) {
+        if (auto* externalTextureBindingLayout = entry.Get<ExternalTextureBindingLayout>()) {
+            for (SingleShaderStage stage : IterateStages(entry->visibility)) {
                 // External textures are not fully implemented, which means that expanding
                 // the external texture at this time will not occupy the same number of
                 // binding slots as defined in the WebGPU specification. Here we prematurely
@@ -279,28 +290,27 @@
             dawn::native::ExternalTextureBindingExpansion bindingExpansion;
 
             BindGroupLayoutEntry plane0Entry =
-                CreateSampledTextureBindingForExternalTexture(entry.binding, entry.visibility);
+                CreateSampledTextureBindingForExternalTexture(entry->binding, entry->visibility);
             bindingExpansion.plane0 = BindingNumber(plane0Entry.binding);
-            expandedOutput.push_back(plane0Entry);
+            expandedOutput.push_back(Unpack(&additionalEntries.emplace_back(plane0Entry)));
 
             BindGroupLayoutEntry plane1Entry = CreateSampledTextureBindingForExternalTexture(
-                nextOpenBindingNumberForNewEntry++, entry.visibility);
+                nextOpenBindingNumberForNewEntry++, entry->visibility);
             bindingExpansion.plane1 = BindingNumber(plane1Entry.binding);
-            expandedOutput.push_back(plane1Entry);
+            expandedOutput.push_back(Unpack(&additionalEntries.emplace_back(plane1Entry)));
 
             BindGroupLayoutEntry paramsEntry = CreateUniformBindingForExternalTexture(
-                nextOpenBindingNumberForNewEntry++, entry.visibility);
+                nextOpenBindingNumberForNewEntry++, entry->visibility);
             bindingExpansion.params = BindingNumber(paramsEntry.binding);
-            expandedOutput.push_back(paramsEntry);
+            expandedOutput.push_back(Unpack(&additionalEntries.emplace_back(paramsEntry)));
 
             externalTextureBindingExpansions->insert(
-                {BindingNumber(entry.binding), bindingExpansion});
+                {BindingNumber(entry->binding), bindingExpansion});
         } else {
             expandedOutput.push_back(entry);
         }
     }
-
-    return expandedOutput;
+    return result;
 }
 }  // anonymous namespace
 
@@ -313,15 +323,16 @@
     BindingCounts bindingCounts = {};
 
     for (uint32_t i = 0; i < descriptor->entryCount; ++i) {
-        const BindGroupLayoutEntry& entry = descriptor->entries[i];
-        BindingNumber bindingNumber = BindingNumber(entry.binding);
+        UnpackedPtr<BindGroupLayoutEntry> entry;
+        DAWN_TRY_ASSIGN(entry, ValidateAndUnpack(&descriptor->entries[i]));
+        BindingNumber bindingNumber = BindingNumber(entry->binding);
 
         DAWN_INVALID_IF(bindingNumber >= kMaxBindingsPerBindGroupTyped,
                         "Binding number (%u) exceeds the maxBindingsPerBindGroup limit (%u).",
                         uint32_t(bindingNumber), kMaxBindingsPerBindGroup);
         DAWN_INVALID_IF(bindingsSet.count(bindingNumber) != 0,
                         "On entries[%u]: binding index (%u) was specified by a previous entry.", i,
-                        entry.binding);
+                        entry->binding);
 
         DAWN_TRY_CONTEXT(ValidateBindGroupLayoutEntry(device, entry, allowInternalBinding),
                          "validating entries[%u]", i);
@@ -365,46 +376,44 @@
     DAWN_UNREACHABLE();
 }
 
-bool IsBufferBinding(const BindGroupLayoutEntry& binding) {
-    return binding.buffer.type != wgpu::BufferBindingType::Undefined;
+bool IsBufferBinding(const UnpackedPtr<BindGroupLayoutEntry>& binding) {
+    return binding->buffer.type != wgpu::BufferBindingType::Undefined;
 }
 
-bool BindingHasDynamicOffset(const BindGroupLayoutEntry& binding) {
-    if (binding.buffer.type != wgpu::BufferBindingType::Undefined) {
-        return binding.buffer.hasDynamicOffset;
+bool BindingHasDynamicOffset(const UnpackedPtr<BindGroupLayoutEntry>& binding) {
+    if (binding->buffer.type != wgpu::BufferBindingType::Undefined) {
+        return binding->buffer.hasDynamicOffset;
     }
     return false;
 }
 
-BindingInfo CreateBindGroupLayoutInfo(const BindGroupLayoutEntry& binding) {
+BindingInfo CreateBindGroupLayoutInfo(const UnpackedPtr<BindGroupLayoutEntry>& binding) {
     BindingInfo bindingInfo;
-    bindingInfo.binding = BindingNumber(binding.binding);
-    bindingInfo.visibility = binding.visibility;
+    bindingInfo.binding = BindingNumber(binding->binding);
+    bindingInfo.visibility = binding->visibility;
 
-    if (binding.buffer.type != wgpu::BufferBindingType::Undefined) {
+    if (binding->buffer.type != wgpu::BufferBindingType::Undefined) {
         bindingInfo.bindingType = BindingInfoType::Buffer;
-        bindingInfo.buffer = binding.buffer;
-    } else if (binding.sampler.type != wgpu::SamplerBindingType::Undefined) {
+        bindingInfo.buffer = binding->buffer;
+    } else if (binding->sampler.type != wgpu::SamplerBindingType::Undefined) {
         bindingInfo.bindingType = BindingInfoType::Sampler;
-        bindingInfo.sampler = binding.sampler;
-    } else if (binding.texture.sampleType != wgpu::TextureSampleType::Undefined) {
+        bindingInfo.sampler = binding->sampler;
+    } else if (binding->texture.sampleType != wgpu::TextureSampleType::Undefined) {
         bindingInfo.bindingType = BindingInfoType::Texture;
-        bindingInfo.texture = binding.texture;
+        bindingInfo.texture = binding->texture;
 
-        if (binding.texture.viewDimension == wgpu::TextureViewDimension::Undefined) {
+        if (binding->texture.viewDimension == wgpu::TextureViewDimension::Undefined) {
             bindingInfo.texture.viewDimension = wgpu::TextureViewDimension::e2D;
         }
-    } else if (binding.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
+    } else if (binding->storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
         bindingInfo.bindingType = BindingInfoType::StorageTexture;
-        bindingInfo.storageTexture = binding.storageTexture;
+        bindingInfo.storageTexture = binding->storageTexture;
 
-        if (binding.storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) {
+        if (binding->storageTexture.viewDimension == wgpu::TextureViewDimension::Undefined) {
             bindingInfo.storageTexture.viewDimension = wgpu::TextureViewDimension::e2D;
         }
     } else {
-        const ExternalTextureBindingLayout* externalTextureBindingLayout = nullptr;
-        FindInChain(binding.nextInChain, &externalTextureBindingLayout);
-        if (externalTextureBindingLayout != nullptr) {
+        if (auto* externalTextureBindingLayout = binding.Get<ExternalTextureBindingLayout>()) {
             bindingInfo.bindingType = BindingInfoType::ExternalTexture;
         }
     }
@@ -412,7 +421,8 @@
     return bindingInfo;
 }
 
-bool SortBindingsCompare(const BindGroupLayoutEntry& a, const BindGroupLayoutEntry& b) {
+bool SortBindingsCompare(const UnpackedPtr<BindGroupLayoutEntry>& a,
+                         const UnpackedPtr<BindGroupLayoutEntry>& b) {
     if (&a == &b) {
         return false;
     }
@@ -436,11 +446,11 @@
         }
         if (aHasDynamicOffset) {
             DAWN_ASSERT(bHasDynamicOffset);
-            DAWN_ASSERT(a.binding != b.binding);
+            DAWN_ASSERT(a->binding != b->binding);
             // Above, we ensured that dynamic buffers are first. Now, ensure that
             // dynamic buffer bindings are in increasing order. This is because dynamic
             // buffer offsets are applied in increasing order of binding number.
-            return a.binding < b.binding;
+            return a->binding < b->binding;
         }
     }
 
@@ -453,8 +463,8 @@
         return aInfo.bindingType < bInfo.bindingType;
     }
 
-    if (a.visibility != b.visibility) {
-        return a.visibility < b.visibility;
+    if (a->visibility != b->visibility) {
+        return a->visibility < b->visibility;
     }
 
     switch (aInfo.bindingType) {
@@ -493,7 +503,7 @@
         case BindingInfoType::ExternalTexture:
             break;
     }
-    return a.binding < b.binding;
+    return a->binding < b->binding;
 }
 
 // This is a utility function to help DAWN_ASSERT that the BGL-binding comparator places buffers
@@ -523,14 +533,14 @@
     const BindGroupLayoutDescriptor* descriptor,
     ApiObjectBase::UntrackedByDeviceTag tag)
     : ApiObjectBase(device, descriptor->label), mUnexpandedBindingCount(descriptor->entryCount) {
-    std::vector<BindGroupLayoutEntry> sortedBindings = ExtractAndExpandBglEntries(
-        descriptor, &mBindingCounts, &mExternalTextureBindingExpansionMap);
+    auto unpackedBindings = ExtractAndExpandBglEntries(descriptor, &mBindingCounts,
+                                                       &mExternalTextureBindingExpansionMap);
+    auto& sortedBindings = unpackedBindings.unpackedEntries;
 
     std::sort(sortedBindings.begin(), sortedBindings.end(), SortBindingsCompare);
 
     for (uint32_t i = 0; i < sortedBindings.size(); ++i) {
-        const BindGroupLayoutEntry& binding = sortedBindings[static_cast<uint32_t>(i)];
-
+        const UnpackedPtr<BindGroupLayoutEntry>& binding = sortedBindings[static_cast<uint32_t>(i)];
         mBindingInfo.push_back(CreateBindGroupLayoutInfo(binding));
 
         if (IsBufferBinding(binding)) {
@@ -539,7 +549,7 @@
         }
         IncrementBindingCounts(&mBindingCounts, binding);
 
-        const auto& [_, inserted] = mBindingMap.emplace(BindingNumber(binding.binding), i);
+        const auto& [_, inserted] = mBindingMap.emplace(BindingNumber(binding->binding), i);
         DAWN_ASSERT(inserted);
     }
     DAWN_ASSERT(CheckBufferBindingsFirst({mBindingInfo.data(), GetBindingCount()}));
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index a9a0368..220b3fe 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -32,14 +32,15 @@
 
 namespace dawn::native {
 
-void IncrementBindingCounts(BindingCounts* bindingCounts, const BindGroupLayoutEntry& entry) {
+void IncrementBindingCounts(BindingCounts* bindingCounts,
+                            const UnpackedPtr<BindGroupLayoutEntry>& entry) {
     bindingCounts->totalCount += 1;
 
     uint32_t PerStageBindingCounts::*perStageBindingCountMember = nullptr;
 
-    if (entry.buffer.type != wgpu::BufferBindingType::Undefined) {
+    if (entry->buffer.type != wgpu::BufferBindingType::Undefined) {
         ++bindingCounts->bufferCount;
-        const BufferBindingLayout& buffer = entry.buffer;
+        const BufferBindingLayout& buffer = entry->buffer;
 
         if (buffer.minBindingSize == 0) {
             ++bindingCounts->unverifiedBufferCount;
@@ -67,22 +68,20 @@
                 DAWN_UNREACHABLE();
                 break;
         }
-    } else if (entry.sampler.type != wgpu::SamplerBindingType::Undefined) {
+    } else if (entry->sampler.type != wgpu::SamplerBindingType::Undefined) {
         perStageBindingCountMember = &PerStageBindingCounts::samplerCount;
-    } else if (entry.texture.sampleType != wgpu::TextureSampleType::Undefined) {
+    } else if (entry->texture.sampleType != wgpu::TextureSampleType::Undefined) {
         perStageBindingCountMember = &PerStageBindingCounts::sampledTextureCount;
-    } else if (entry.storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
+    } else if (entry->storageTexture.access != wgpu::StorageTextureAccess::Undefined) {
         perStageBindingCountMember = &PerStageBindingCounts::storageTextureCount;
     } else {
-        const ExternalTextureBindingLayout* externalTextureBindingLayout;
-        FindInChain(entry.nextInChain, &externalTextureBindingLayout);
-        if (externalTextureBindingLayout != nullptr) {
+        if (auto* externalTextureBindingLayout = entry.Get<ExternalTextureBindingLayout>()) {
             perStageBindingCountMember = &PerStageBindingCounts::externalTextureCount;
         }
     }
 
     DAWN_ASSERT(perStageBindingCountMember != nullptr);
-    for (SingleShaderStage stage : IterateStages(entry.visibility)) {
+    for (SingleShaderStage stage : IterateStages(entry->visibility)) {
         ++(bindingCounts->perStage[stage].*perStageBindingCountMember);
     }
 }
diff --git a/src/dawn/native/BindingInfo.h b/src/dawn/native/BindingInfo.h
index 9ffa23b..d21f22e 100644
--- a/src/dawn/native/BindingInfo.h
+++ b/src/dawn/native/BindingInfo.h
@@ -95,7 +95,8 @@
 
 struct CombinedLimits;
 
-void IncrementBindingCounts(BindingCounts* bindingCounts, const BindGroupLayoutEntry& entry);
+void IncrementBindingCounts(BindingCounts* bindingCounts,
+                            const UnpackedPtr<BindGroupLayoutEntry>& entry);
 void AccumulateBindingCounts(BindingCounts* bindingCounts, const BindingCounts& rhs);
 MaybeError ValidateBindingCounts(const CombinedLimits& limits, const BindingCounts& bindingCounts);
 
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 5f63832..490ceee 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -214,7 +214,9 @@
     }
 };
 
-MaybeError ValidateBufferDescriptor(DeviceBase* device, const BufferDescriptor* descriptor) {
+ResultOrError<UnpackedPtr<BufferDescriptor>> ValidateBufferDescriptor(
+    DeviceBase* device,
+    const BufferDescriptor* descriptor) {
     UnpackedPtr<BufferDescriptor> unpacked;
     DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
 
@@ -273,12 +275,12 @@
                     "Buffer size (%u) exceeds the max buffer size limit (%u).", descriptor->size,
                     device->GetLimits().v1.maxBufferSize);
 
-    return {};
+    return unpacked;
 }
 
 // Buffer
 
-BufferBase::BufferBase(DeviceBase* device, const BufferDescriptor* descriptor)
+BufferBase::BufferBase(DeviceBase* device, const UnpackedPtr<BufferDescriptor>& descriptor)
     : ApiObjectBase(device, descriptor->label),
       mSize(descriptor->size),
       mUsage(descriptor->usage),
@@ -318,8 +320,7 @@
         }
     }
 
-    const BufferHostMappedPointer* hostMappedDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &hostMappedDesc);
+    auto* hostMappedDesc = descriptor.Get<BufferHostMappedPointer>();
     if (hostMappedDesc != nullptr) {
         mState = BufferState::HostMappedPersistent;
     }
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h
index fb530c7..6f28716 100644
--- a/src/dawn/native/Buffer.h
+++ b/src/dawn/native/Buffer.h
@@ -47,7 +47,9 @@
 
 enum class MapType : uint32_t;
 
-MaybeError ValidateBufferDescriptor(DeviceBase* device, const BufferDescriptor* descriptor);
+ResultOrError<UnpackedPtr<BufferDescriptor>> ValidateBufferDescriptor(
+    DeviceBase* device,
+    const BufferDescriptor* descriptor);
 
 static constexpr wgpu::BufferUsage kReadOnlyBufferUsages =
     wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Index |
@@ -121,7 +123,7 @@
     uint64_t APIGetSize() const;
 
   protected:
-    BufferBase(DeviceBase* device, const BufferDescriptor* descriptor);
+    BufferBase(DeviceBase* device, const UnpackedPtr<BufferDescriptor>& descriptor);
     BufferBase(DeviceBase* device, const BufferDescriptor* descriptor, ObjectBase::ErrorTag tag);
 
     void DestroyImpl() override;
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index 6b039d1..a32aea4 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -915,8 +915,9 @@
             std::clamp(originalColor.a, minValue, maxValue)};
 }
 
-MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device,
-                                            const CommandEncoderDescriptor* descriptor) {
+ResultOrError<UnpackedPtr<CommandEncoderDescriptor>> ValidateCommandEncoderDescriptor(
+    const DeviceBase* device,
+    const CommandEncoderDescriptor* descriptor) {
     UnpackedPtr<CommandEncoderDescriptor> unpacked;
     DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
 
@@ -924,12 +925,13 @@
     DAWN_INVALID_IF(internalUsageDesc != nullptr &&
                         !device->APIHasFeature(wgpu::FeatureName::DawnInternalUsages),
                     "%s is not available.", wgpu::FeatureName::DawnInternalUsages);
-    return {};
+    return unpacked;
 }
 
 // static
-Ref<CommandEncoder> CommandEncoder::Create(DeviceBase* device,
-                                           const CommandEncoderDescriptor* descriptor) {
+Ref<CommandEncoder> CommandEncoder::Create(
+    DeviceBase* device,
+    const UnpackedPtr<CommandEncoderDescriptor>& descriptor) {
     return AcquireRef(new CommandEncoder(device, descriptor));
 }
 
@@ -938,13 +940,12 @@
     return new CommandEncoder(device, ObjectBase::kError, label);
 }
 
-CommandEncoder::CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor)
+CommandEncoder::CommandEncoder(DeviceBase* device,
+                               const UnpackedPtr<CommandEncoderDescriptor>& descriptor)
     : ApiObjectBase(device, descriptor->label), mEncodingContext(device, this) {
     GetObjectTrackingList()->Track(this);
 
-    const DawnEncoderInternalUsageDescriptor* internalUsageDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &internalUsageDesc);
-
+    auto* internalUsageDesc = descriptor.Get<DawnEncoderInternalUsageDescriptor>();
     if (internalUsageDesc != nullptr && internalUsageDesc->useInternalUsages) {
         mUsageValidationMode = UsageValidationMode::Internal;
     } else {
@@ -1097,7 +1098,7 @@
                 allocator->Allocate<BeginRenderPassCmd>(Command::BeginRenderPass);
             cmd->label = std::string(descriptor->label ? descriptor->label : "");
 
-            cmd->attachmentState = device->GetOrCreateAttachmentState(*descriptor);
+            cmd->attachmentState = device->GetOrCreateAttachmentState(descriptor);
             attachmentState = cmd->attachmentState;
 
             auto descColorAttachments = ityp::SpanFromUntyped<ColorAttachmentIndex>(
diff --git a/src/dawn/native/CommandEncoder.h b/src/dawn/native/CommandEncoder.h
index 4180c44..91086f1 100644
--- a/src/dawn/native/CommandEncoder.h
+++ b/src/dawn/native/CommandEncoder.h
@@ -46,13 +46,14 @@
 
 Color ClampClearColorValueToLegalRange(const Color& originalColor, const Format& format);
 
-MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device,
-                                            const CommandEncoderDescriptor* descriptor);
+ResultOrError<UnpackedPtr<CommandEncoderDescriptor>> ValidateCommandEncoderDescriptor(
+    const DeviceBase* device,
+    const CommandEncoderDescriptor* descriptor);
 
 class CommandEncoder final : public ApiObjectBase {
   public:
     static Ref<CommandEncoder> Create(DeviceBase* device,
-                                      const CommandEncoderDescriptor* descriptor);
+                                      const UnpackedPtr<CommandEncoderDescriptor>& descriptor);
     static CommandEncoder* MakeError(DeviceBase* device, const char* label);
 
     ObjectType GetType() const override;
@@ -133,7 +134,7 @@
     [[nodiscard]] InternalUsageScope MakeInternalUsageScope();
 
   private:
-    CommandEncoder(DeviceBase* device, const CommandEncoderDescriptor* descriptor);
+    CommandEncoder(DeviceBase* device, const UnpackedPtr<CommandEncoderDescriptor>& descriptor);
     CommandEncoder(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label);
 
     void DestroyImpl() override;
diff --git a/src/dawn/native/ComputePipeline.cpp b/src/dawn/native/ComputePipeline.cpp
index 0c9579f..d03a7ab 100644
--- a/src/dawn/native/ComputePipeline.cpp
+++ b/src/dawn/native/ComputePipeline.cpp
@@ -38,7 +38,7 @@
                                              const ComputePipelineDescriptor* descriptor) {
     UnpackedPtr<ComputePipelineDescriptor> unpacked;
     DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
-    const auto* fullSubgroupsOption = unpacked.Get<DawnComputePipelineFullSubgroups>();
+    auto* fullSubgroupsOption = unpacked.Get<DawnComputePipelineFullSubgroups>();
     DAWN_INVALID_IF(
         (fullSubgroupsOption && !device->HasFeature(Feature::ChromiumExperimentalSubgroups)),
         "DawnComputePipelineFullSubgroups is used without %s enabled.",
@@ -62,7 +62,7 @@
 // ComputePipelineBase
 
 ComputePipelineBase::ComputePipelineBase(DeviceBase* device,
-                                         const ComputePipelineDescriptor* descriptor)
+                                         const UnpackedPtr<ComputePipelineDescriptor>& descriptor)
     : PipelineBase(
           device,
           descriptor->layout,
@@ -73,9 +73,7 @@
     SetContentHash(ComputeContentHash());
     GetObjectTrackingList()->Track(this);
 
-    const DawnComputePipelineFullSubgroups* fullSubgroupsOption = nullptr;
-    FindInChain(descriptor->nextInChain, &fullSubgroupsOption);
-    if (fullSubgroupsOption) {
+    if (auto* fullSubgroupsOption = descriptor.Get<DawnComputePipelineFullSubgroups>()) {
         mRequiresFullSubgroups = fullSubgroupsOption->requiresFullSubgroups;
     }
 
diff --git a/src/dawn/native/ComputePipeline.h b/src/dawn/native/ComputePipeline.h
index 8558cae..f055fdf 100644
--- a/src/dawn/native/ComputePipeline.h
+++ b/src/dawn/native/ComputePipeline.h
@@ -44,7 +44,8 @@
 class ComputePipelineBase : public PipelineBase,
                             public ContentLessObjectCacheable<ComputePipelineBase> {
   public:
-    ComputePipelineBase(DeviceBase* device, const ComputePipelineDescriptor* descriptor);
+    ComputePipelineBase(DeviceBase* device,
+                        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     ~ComputePipelineBase() override;
 
     static ComputePipelineBase* MakeError(DeviceBase* device, const char* label);
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index f19c964..9ed8590 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -206,10 +206,10 @@
 // DeviceBase
 
 DeviceBase::DeviceBase(AdapterBase* adapter,
-                       const DeviceDescriptor* descriptor,
+                       const UnpackedPtr<DeviceDescriptor>& descriptor,
                        const TogglesState& deviceToggles)
     : mAdapter(adapter), mToggles(deviceToggles), mNextPipelineCompatibilityToken(1) {
-    DAWN_ASSERT(descriptor != nullptr);
+    DAWN_ASSERT(descriptor);
 
     mDeviceLostCallback = descriptor->deviceLostCallback;
     mDeviceLostUserdata = descriptor->deviceLostUserdata;
@@ -220,8 +220,7 @@
     ApplyFeatures(descriptor);
 
     DawnCacheDeviceDescriptor defaultCacheDesc = {};
-    const DawnCacheDeviceDescriptor* cacheDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &cacheDesc);
+    auto* cacheDesc = descriptor.Get<DawnCacheDeviceDescriptor>();
     if (cacheDesc == nullptr) {
         cacheDesc = &defaultCacheDesc;
     }
@@ -869,7 +868,7 @@
     desc.bindGroupLayoutCount = 0;
     desc.bindGroupLayouts = nullptr;
 
-    return GetOrCreatePipelineLayout(&desc);
+    return GetOrCreatePipelineLayout(Unpack(&desc));
 }
 
 BindGroupLayoutBase* DeviceBase::GetEmptyBindGroupLayout() {
@@ -962,7 +961,7 @@
 }
 
 ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::GetOrCreatePipelineLayout(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     PipelineLayoutBase blueprint(this, descriptor, ApiObjectBase::kUntrackedByDevice);
 
     const size_t blueprintHash = blueprint.ComputeContentHash();
@@ -993,7 +992,7 @@
 }
 
 ResultOrError<Ref<ShaderModuleBase>> DeviceBase::GetOrCreateShaderModule(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     DAWN_ASSERT(parseResult != nullptr);
@@ -1041,14 +1040,14 @@
 }
 
 Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
-    const RenderPipelineDescriptor* descriptor,
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor,
     const PipelineLayoutBase* layout) {
     AttachmentState blueprint(this, descriptor, layout);
     return GetOrCreateAttachmentState(&blueprint);
 }
 
 Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
-    const RenderPassDescriptor* descriptor) {
+    const UnpackedPtr<RenderPassDescriptor>& descriptor) {
     AttachmentState blueprint(this, descriptor);
     return GetOrCreateAttachmentState(&blueprint);
 }
@@ -1281,30 +1280,21 @@
 // For Dawn Wire
 
 BufferBase* DeviceBase::APICreateErrorBuffer(const BufferDescriptor* desc) {
-    BufferDescriptor fakeDescriptor = *desc;
-    fakeDescriptor.nextInChain = nullptr;
-
-    // The validation errors on BufferDescriptor should be prior to any OOM errors when
-    // MapppedAtCreation == false.
-    MaybeError maybeError = ValidateBufferDescriptor(this, &fakeDescriptor);
-
-    // Set the size of the error buffer to 0 as this function is called only when an OOM happens at
-    // the client side.
-    fakeDescriptor.size = 0;
-
-    if (maybeError.IsError()) {
-        std::unique_ptr<ErrorData> error = maybeError.AcquireError();
-        error->AppendContext("calling %s.CreateBuffer(%s).", this, desc);
-        HandleError(std::move(error), InternalErrorType::OutOfMemory);
-    } else {
-        const DawnBufferDescriptorErrorInfoFromWireClient* clientErrorInfo = nullptr;
-        FindInChain(desc->nextInChain, &clientErrorInfo);
+    UnpackedPtr<BufferDescriptor> unpacked;
+    if (!ConsumedError(ValidateBufferDescriptor(this, desc), &unpacked,
+                       InternalErrorType::OutOfMemory, "calling %s.CreateBuffer(%s).", this,
+                       desc)) {
+        auto* clientErrorInfo = unpacked.Get<DawnBufferDescriptorErrorInfoFromWireClient>();
         if (clientErrorInfo != nullptr && clientErrorInfo->outOfMemory) {
             HandleError(DAWN_OUT_OF_MEMORY_ERROR("Failed to allocate memory for buffer mapping"),
                         InternalErrorType::OutOfMemory);
         }
     }
 
+    // Set the size of the error buffer to 0 as this function is called only when an OOM happens at
+    // the client side.
+    BufferDescriptor fakeDescriptor = *desc;
+    fakeDescriptor.size = 0;
     return BufferBase::MakeError(this, &fakeDescriptor);
 }
 
@@ -1434,7 +1424,7 @@
     return DAWN_UNIMPLEMENTED_ERROR("Not implemented");
 }
 
-void DeviceBase::ApplyFeatures(const DeviceDescriptor* deviceDescriptor) {
+void DeviceBase::ApplyFeatures(const UnpackedPtr<DeviceDescriptor>& deviceDescriptor) {
     DAWN_ASSERT(deviceDescriptor);
     // Validate all required features with device toggles.
     DAWN_ASSERT(GetPhysicalDevice()->SupportsAllRequiredFeatures(
@@ -1629,10 +1619,13 @@
     return GetOrCreateBindGroupLayout(descriptor);
 }
 
-ResultOrError<Ref<BufferBase>> DeviceBase::CreateBuffer(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> DeviceBase::CreateBuffer(const BufferDescriptor* rawDescriptor) {
     DAWN_TRY(ValidateIsAlive());
+    UnpackedPtr<BufferDescriptor> descriptor;
     if (IsValidationEnabled()) {
-        DAWN_TRY(ValidateBufferDescriptor(this, descriptor));
+        DAWN_TRY_ASSIGN(descriptor, ValidateBufferDescriptor(this, rawDescriptor));
+    } else {
+        descriptor = Unpack(rawDescriptor);
     }
 
     Ref<BufferBase> buffer;
@@ -1675,10 +1668,13 @@
     }
 
     DAWN_TRY(ValidateIsAlive());
+    UnpackedPtr<CommandEncoderDescriptor> unpacked;
     if (IsValidationEnabled()) {
-        DAWN_TRY(ValidateCommandEncoderDescriptor(this, descriptor));
+        DAWN_TRY_ASSIGN(unpacked, ValidateCommandEncoderDescriptor(this, descriptor));
+    } else {
+        unpacked = Unpack(descriptor);
     }
-    return CommandEncoder::Create(this, descriptor);
+    return CommandEncoder::Create(this, unpacked);
 }
 
 // Overwritten on the backends to return pipeline caches if supported.
@@ -1698,7 +1694,7 @@
     DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetComputePipelineDescriptorWithDefaults(
                                    this, *descriptor, &appliedDescriptor));
 
-    return CreateUninitializedComputePipelineImpl(&appliedDescriptor);
+    return CreateUninitializedComputePipelineImpl(Unpack(&appliedDescriptor));
 }
 
 // This function is overwritten with the async version on the backends that supports
@@ -1744,10 +1740,13 @@
 ResultOrError<Ref<PipelineLayoutBase>> DeviceBase::CreatePipelineLayout(
     const PipelineLayoutDescriptor* descriptor) {
     DAWN_TRY(ValidateIsAlive());
+    UnpackedPtr<PipelineLayoutDescriptor> unpacked;
     if (IsValidationEnabled()) {
-        DAWN_TRY(ValidatePipelineLayoutDescriptor(this, descriptor));
+        DAWN_TRY_ASSIGN(unpacked, ValidatePipelineLayoutDescriptor(this, descriptor));
+    } else {
+        unpacked = Unpack(descriptor);
     }
-    return GetOrCreatePipelineLayout(descriptor);
+    return GetOrCreatePipelineLayout(unpacked);
 }
 
 ResultOrError<Ref<ExternalTextureBase>> DeviceBase::CreateExternalTextureImpl(
@@ -1819,7 +1818,7 @@
     DAWN_TRY_ASSIGN(layoutRef, ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
                                    this, *descriptor, &appliedDescriptor));
 
-    return CreateUninitializedRenderPipelineImpl(&appliedDescriptor);
+    return CreateUninitializedRenderPipelineImpl(Unpack(&appliedDescriptor));
 }
 
 ResultOrError<Ref<SamplerBase>> DeviceBase::CreateSampler(const SamplerDescriptor* descriptor) {
@@ -1842,13 +1841,18 @@
     // long as dawn_native don't use the compilationMessages of these internal shader modules.
     ShaderModuleParseResult parseResult;
 
+    UnpackedPtr<ShaderModuleDescriptor> unpacked;
     if (IsValidationEnabled()) {
+        DAWN_TRY_ASSIGN_CONTEXT(unpacked, ValidateAndUnpack(descriptor),
+                                "validating and unpacking %s", descriptor);
         DAWN_TRY_CONTEXT(
-            ValidateAndParseShaderModule(this, descriptor, &parseResult, compilationMessages),
+            ValidateAndParseShaderModule(this, unpacked, &parseResult, compilationMessages),
             "validating %s", descriptor);
+    } else {
+        unpacked = Unpack(descriptor);
     }
 
-    return GetOrCreateShaderModule(descriptor, &parseResult, compilationMessages);
+    return GetOrCreateShaderModule(unpacked, &parseResult, compilationMessages);
 }
 
 ResultOrError<Ref<SwapChainBase>> DeviceBase::CreateSwapChain(
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index deece61..ccdea13 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -77,7 +77,7 @@
 class DeviceBase : public RefCountedWithExternalCount {
   public:
     DeviceBase(AdapterBase* adapter,
-               const DeviceDescriptor* descriptor,
+               const UnpackedPtr<DeviceDescriptor>& descriptor,
                const TogglesState& deviceToggles);
     ~DeviceBase() override;
 
@@ -213,21 +213,23 @@
     ResultOrError<Ref<TextureViewBase>> GetOrCreatePlaceholderTextureViewForExternalTexture();
 
     ResultOrError<Ref<PipelineLayoutBase>> GetOrCreatePipelineLayout(
-        const PipelineLayoutDescriptor* descriptor);
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
 
     ResultOrError<Ref<SamplerBase>> GetOrCreateSampler(const SamplerDescriptor* descriptor);
 
     ResultOrError<Ref<ShaderModuleBase>> GetOrCreateShaderModule(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages);
 
     Ref<AttachmentState> GetOrCreateAttachmentState(AttachmentState* blueprint);
     Ref<AttachmentState> GetOrCreateAttachmentState(
         const RenderBundleEncoderDescriptor* descriptor);
-    Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor,
-                                                    const PipelineLayoutBase* layout);
-    Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor);
+    Ref<AttachmentState> GetOrCreateAttachmentState(
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor,
+        const PipelineLayoutBase* layout);
+    Ref<AttachmentState> GetOrCreateAttachmentState(
+        const UnpackedPtr<RenderPassDescriptor>& descriptor);
 
     Ref<PipelineCacheBase> GetOrCreatePipelineCache(const CacheKey& key);
 
@@ -238,7 +240,7 @@
     ResultOrError<Ref<BindGroupLayoutBase>> CreateBindGroupLayout(
         const BindGroupLayoutDescriptor* descriptor,
         bool allowInternalBinding = false);
-    ResultOrError<Ref<BufferBase>> CreateBuffer(const BufferDescriptor* descriptor);
+    ResultOrError<Ref<BufferBase>> CreateBuffer(const BufferDescriptor* rawDescriptor);
     ResultOrError<Ref<CommandEncoder>> CreateCommandEncoder(
         const CommandEncoderDescriptor* descriptor = nullptr);
     ResultOrError<Ref<ComputePipelineBase>> CreateComputePipeline(
@@ -246,7 +248,7 @@
     ResultOrError<Ref<ComputePipelineBase>> CreateUninitializedComputePipeline(
         const ComputePipelineDescriptor* descriptor);
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayout(
-        const PipelineLayoutDescriptor* descriptor);
+        const PipelineLayoutDescriptor* rawDescriptor);
     ResultOrError<Ref<QuerySetBase>> CreateQuerySet(const QuerySetDescriptor* descriptor);
     ResultOrError<Ref<RenderBundleEncoder>> CreateRenderBundleEncoder(
         const RenderBundleEncoderDescriptor* descriptor);
@@ -478,17 +480,18 @@
         const BindGroupDescriptor* descriptor) = 0;
     virtual ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) = 0;
-    virtual ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) = 0;
+    virtual ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) = 0;
     virtual ResultOrError<Ref<ExternalTextureBase>> CreateExternalTextureImpl(
         const ExternalTextureDescriptor* descriptor);
     virtual ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) = 0;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) = 0;
     virtual ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) = 0;
     virtual ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(
         const SamplerDescriptor* descriptor) = 0;
     virtual ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) = 0;
     // Note that previousSwapChain may be nullptr, or come from a different backend.
@@ -502,9 +505,9 @@
         TextureBase* texture,
         const TextureViewDescriptor* descriptor) = 0;
     virtual Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) = 0;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) = 0;
     virtual Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) = 0;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) = 0;
     virtual ResultOrError<Ref<SharedTextureMemoryBase>> ImportSharedTextureMemoryImpl(
         const SharedTextureMemoryDescriptor* descriptor);
     virtual ResultOrError<Ref<SharedFenceBase>> ImportSharedFenceImpl(
@@ -535,7 +538,7 @@
                                                    WGPUCreateRenderPipelineAsyncCallback callback,
                                                    void* userdata);
 
-    void ApplyFeatures(const DeviceDescriptor* deviceDescriptor);
+    void ApplyFeatures(const UnpackedPtr<DeviceDescriptor>& deviceDescriptor);
 
     void SetWGSLExtensionAllowList();
 
diff --git a/src/dawn/native/Instance.cpp b/src/dawn/native/Instance.cpp
index 437b951..660c73c 100644
--- a/src/dawn/native/Instance.cpp
+++ b/src/dawn/native/Instance.cpp
@@ -247,10 +247,6 @@
 void InstanceBase::APIRequestAdapter(const RequestAdapterOptions* options,
                                      WGPURequestAdapterCallback callback,
                                      void* userdata) {
-    static constexpr RequestAdapterOptions kDefaultOptions = {};
-    if (options == nullptr) {
-        options = &kDefaultOptions;
-    }
     auto adapters = EnumerateAdapters(options);
     if (adapters.empty()) {
         callback(WGPURequestAdapterStatus_Unavailable, nullptr, "No supported adapters.", userdata);
@@ -303,22 +299,22 @@
 
 std::vector<Ref<AdapterBase>> InstanceBase::EnumerateAdapters(
     const RequestAdapterOptions* options) {
+    static constexpr RequestAdapterOptions kDefaultOptions = {};
     if (options == nullptr) {
         // Default path that returns all WebGPU core adapters on the system with default toggles.
-        RequestAdapterOptions defaultOptions = {};
-        return EnumerateAdapters(&defaultOptions);
+        return EnumerateAdapters(&kDefaultOptions);
     }
 
-    const DawnTogglesDescriptor* togglesDesc = nullptr;
-    FindInChain(options->nextInChain, &togglesDesc);
+    UnpackedPtr<RequestAdapterOptions> unpacked = Unpack(options);
+    auto* togglesDesc = unpacked.Get<DawnTogglesDescriptor>();
 
     FeatureLevel featureLevel =
         options->compatibilityMode ? FeatureLevel::Compatibility : FeatureLevel::Core;
     std::vector<Ref<AdapterBase>> adapters;
-    for (const auto& physicalDevice : EnumeratePhysicalDevices(options)) {
+    for (const auto& physicalDevice : EnumeratePhysicalDevices(unpacked)) {
         DAWN_ASSERT(physicalDevice->SupportsFeatureLevel(featureLevel));
         adapters.push_back(
-            CreateAdapter(physicalDevice, featureLevel, togglesDesc, options->powerPreference));
+            CreateAdapter(physicalDevice, featureLevel, togglesDesc, unpacked->powerPreference));
     }
     return SortAdapters(std::move(adapters), options);
 }
@@ -399,7 +395,7 @@
 }
 
 std::vector<Ref<PhysicalDeviceBase>> InstanceBase::EnumeratePhysicalDevices(
-    const RequestAdapterOptions* options) {
+    const UnpackedPtr<RequestAdapterOptions>& options) {
     DAWN_ASSERT(options);
 
     BackendsBitset backendsToFind;
diff --git a/src/dawn/native/Instance.h b/src/dawn/native/Instance.h
index 7710053..e44b888 100644
--- a/src/dawn/native/Instance.h
+++ b/src/dawn/native/Instance.h
@@ -191,7 +191,7 @@
 
     // Enumerate physical devices according to options and return them.
     std::vector<Ref<PhysicalDeviceBase>> EnumeratePhysicalDevices(
-        const RequestAdapterOptions* options);
+        const UnpackedPtr<RequestAdapterOptions>& options);
 
     // Helper function that create adapter on given physical device handling required adapter
     // toggles descriptor.
diff --git a/src/dawn/native/PhysicalDevice.cpp b/src/dawn/native/PhysicalDevice.cpp
index a5b32d1..f0c62f9 100644
--- a/src/dawn/native/PhysicalDevice.cpp
+++ b/src/dawn/native/PhysicalDevice.cpp
@@ -68,9 +68,10 @@
     return {};
 }
 
-ResultOrError<Ref<DeviceBase>> PhysicalDeviceBase::CreateDevice(AdapterBase* adapter,
-                                                                const DeviceDescriptor* descriptor,
-                                                                const TogglesState& deviceToggles) {
+ResultOrError<Ref<DeviceBase>> PhysicalDeviceBase::CreateDevice(
+    AdapterBase* adapter,
+    const UnpackedPtr<DeviceDescriptor>& descriptor,
+    const TogglesState& deviceToggles) {
     return CreateDeviceImpl(adapter, descriptor, deviceToggles);
 }
 
diff --git a/src/dawn/native/PhysicalDevice.h b/src/dawn/native/PhysicalDevice.h
index ed917c3..2d56f42 100644
--- a/src/dawn/native/PhysicalDevice.h
+++ b/src/dawn/native/PhysicalDevice.h
@@ -39,6 +39,7 @@
 #include "dawn/common/ityp_span.h"
 #include "dawn/native/Error.h"
 #include "dawn/native/Features.h"
+#include "dawn/native/Forward.h"
 #include "dawn/native/Limits.h"
 #include "dawn/native/Toggles.h"
 #include "dawn/native/dawn_platform.h"
@@ -65,7 +66,7 @@
     MaybeError Initialize();
 
     ResultOrError<Ref<DeviceBase>> CreateDevice(AdapterBase* adapter,
-                                                const DeviceDescriptor* descriptor,
+                                                const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                 const TogglesState& deviceToggles);
 
     uint32_t GetVendorId() const;
@@ -133,9 +134,10 @@
     void GetDefaultLimitsForSupportedFeatureLevel(Limits* limits) const;
 
   private:
-    virtual ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                            const DeviceDescriptor* descriptor,
-                                                            const TogglesState& deviceToggles) = 0;
+    virtual ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(
+        AdapterBase* adapter,
+        const UnpackedPtr<DeviceDescriptor>& descriptor,
+        const TogglesState& deviceToggles) = 0;
 
     virtual MaybeError InitializeImpl() = 0;
 
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp
index b63b0ad..8457a12 100644
--- a/src/dawn/native/PipelineLayout.cpp
+++ b/src/dawn/native/PipelineLayout.cpp
@@ -47,13 +47,15 @@
 
 namespace dawn::native {
 
-MaybeError ValidatePipelineLayoutDescriptor(DeviceBase* device,
-                                            const PipelineLayoutDescriptor* descriptor,
-                                            PipelineCompatibilityToken pipelineCompatibilityToken) {
+ResultOrError<UnpackedPtr<PipelineLayoutDescriptor>> ValidatePipelineLayoutDescriptor(
+    DeviceBase* device,
+    const PipelineLayoutDescriptor* descriptor,
+    PipelineCompatibilityToken pipelineCompatibilityToken) {
+    UnpackedPtr<PipelineLayoutDescriptor> unpacked;
+    DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
+
     // Validation for any pixel local storage.
-    const PipelineLayoutPixelLocalStorage* pls = nullptr;
-    FindInChain(descriptor->nextInChain, &pls);
-    if (pls != nullptr) {
+    if (auto* pls = unpacked.Get<PipelineLayoutPixelLocalStorage>()) {
         StackVector<StorageAttachmentInfoForValidation, 4> attachments;
         for (size_t i = 0; i < pls->storageAttachmentCount; i++) {
             const PipelineLayoutStorageAttachment& attachment = pls->storageAttachments[i];
@@ -91,7 +93,7 @@
     }
 
     DAWN_TRY(ValidateBindingCounts(device->GetLimits(), bindingCounts));
-    return {};
+    return unpacked;
 }
 
 StageAndDescriptor::StageAndDescriptor(SingleShaderStage shaderStage,
@@ -108,7 +110,7 @@
 // PipelineLayoutBase
 
 PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device,
-                                       const PipelineLayoutDescriptor* descriptor,
+                                       const UnpackedPtr<PipelineLayoutDescriptor>& descriptor,
                                        ApiObjectBase::UntrackedByDeviceTag tag)
     : ApiObjectBase(device, descriptor->label) {
     DAWN_ASSERT(descriptor->bindGroupLayoutCount <= kMaxBindGroups);
@@ -121,9 +123,7 @@
     }
 
     // Gather the PLS information.
-    const PipelineLayoutPixelLocalStorage* pls = nullptr;
-    FindInChain(descriptor->nextInChain, &pls);
-    if (pls != nullptr) {
+    if (auto* pls = descriptor.Get<PipelineLayoutPixelLocalStorage>()) {
         mHasPLS = true;
         mStorageAttachmentSlots = std::vector<wgpu::TextureFormat>(
             pls->totalPixelLocalStorageSize / kPLSSlotByteSize, wgpu::TextureFormat::Undefined);
@@ -135,7 +135,7 @@
 }
 
 PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device,
-                                       const PipelineLayoutDescriptor* descriptor)
+                                       const UnpackedPtr<PipelineLayoutDescriptor>& descriptor)
     : PipelineLayoutBase(device, descriptor, kUntrackedByDevice) {
     GetObjectTrackingList()->Track(this);
 }
@@ -384,10 +384,12 @@
     desc.bindGroupLayouts = bgls.data();
     desc.bindGroupLayoutCount = static_cast<uint32_t>(pipelineBGLCount);
 
-    DAWN_TRY(ValidatePipelineLayoutDescriptor(device, &desc, pipelineCompatibilityToken));
+    UnpackedPtr<PipelineLayoutDescriptor> unpacked;
+    DAWN_TRY_ASSIGN(unpacked,
+                    ValidatePipelineLayoutDescriptor(device, &desc, pipelineCompatibilityToken));
 
     Ref<PipelineLayoutBase> result;
-    DAWN_TRY_ASSIGN(result, device->GetOrCreatePipelineLayout(&desc));
+    DAWN_TRY_ASSIGN(result, device->GetOrCreatePipelineLayout(unpacked));
     DAWN_ASSERT(!result->IsError());
 
     // Check in debug that the pipeline layout is compatible with the current pipeline.
diff --git a/src/dawn/native/PipelineLayout.h b/src/dawn/native/PipelineLayout.h
index 7d773f5..271fbd4 100644
--- a/src/dawn/native/PipelineLayout.h
+++ b/src/dawn/native/PipelineLayout.h
@@ -47,7 +47,7 @@
 
 namespace dawn::native {
 
-MaybeError ValidatePipelineLayoutDescriptor(
+ResultOrError<UnpackedPtr<PipelineLayoutDescriptor>> ValidatePipelineLayoutDescriptor(
     DeviceBase*,
     const PipelineLayoutDescriptor* descriptor,
     PipelineCompatibilityToken pipelineCompatibilityToken = PipelineCompatibilityToken(0));
@@ -71,9 +71,9 @@
                            public ContentLessObjectCacheable<PipelineLayoutBase> {
   public:
     PipelineLayoutBase(DeviceBase* device,
-                       const PipelineLayoutDescriptor* descriptor,
+                       const UnpackedPtr<PipelineLayoutDescriptor>& descriptor,
                        ApiObjectBase::UntrackedByDeviceTag tag);
-    PipelineLayoutBase(DeviceBase* device, const PipelineLayoutDescriptor* descriptor);
+    PipelineLayoutBase(DeviceBase* device, const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
     ~PipelineLayoutBase() override;
 
     static PipelineLayoutBase* MakeError(DeviceBase* device, const char* label);
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 1b6357f..87690ec 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -348,9 +348,10 @@
 }
 
 MaybeError ValidateMultisampleState(const DeviceBase* device, const MultisampleState* descriptor) {
-    const DawnMultisampleStateRenderToSingleSampled* msaaRenderToSingleSampledDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &msaaRenderToSingleSampledDesc);
-    if (msaaRenderToSingleSampledDesc != nullptr) {
+    UnpackedPtr<MultisampleState> unpacked;
+    DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
+    if (auto* msaaRenderToSingleSampledDesc =
+            unpacked.Get<DawnMultisampleStateRenderToSingleSampled>()) {
         DAWN_INVALID_IF(!device->HasFeature(Feature::MSAARenderToSingleSampled),
                         "The msaaRenderToSingleSampledDesc is not empty while the "
                         "msaa-render-to-single-sampled feature is not enabled.");
@@ -753,7 +754,8 @@
 
 MaybeError ValidateRenderPipelineDescriptor(DeviceBase* device,
                                             const RenderPipelineDescriptor* descriptor) {
-    DAWN_INVALID_IF(descriptor->nextInChain != nullptr, "nextInChain must be nullptr.");
+    UnpackedPtr<RenderPipelineDescriptor> unpacked;
+    DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
 
     if (descriptor->layout != nullptr) {
         DAWN_TRY(device->ValidateObject(descriptor->layout));
@@ -837,11 +839,11 @@
 // RenderPipelineBase
 
 RenderPipelineBase::RenderPipelineBase(DeviceBase* device,
-                                       const RenderPipelineDescriptor* descriptor)
+                                       const UnpackedPtr<RenderPipelineDescriptor>& descriptor)
     : PipelineBase(device,
                    descriptor->layout,
                    descriptor->label,
-                   GetRenderStagesAndSetPlaceholderShader(device, descriptor)),
+                   GetRenderStagesAndSetPlaceholderShader(device, *descriptor)),
       mAttachmentState(device->GetOrCreateAttachmentState(descriptor, GetLayout())) {
     mVertexBufferCount = descriptor->vertex.bufferCount;
 
@@ -896,9 +898,8 @@
     }
 
     mPrimitive = descriptor->primitive;
-    const PrimitiveDepthClipControl* depthClipControl = nullptr;
-    FindInChain(mPrimitive.nextInChain, &depthClipControl);
-    if (depthClipControl) {
+    UnpackedPtr<PrimitiveState> unpackedPrimitive = Unpack(&mPrimitive);
+    if (auto* depthClipControl = unpackedPrimitive.Get<PrimitiveDepthClipControl>()) {
         mUnclippedDepth = depthClipControl->unclippedDepth;
     }
 
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h
index 7142f54..ef08ff5 100644
--- a/src/dawn/native/RenderPipeline.h
+++ b/src/dawn/native/RenderPipeline.h
@@ -91,7 +91,7 @@
 class RenderPipelineBase : public PipelineBase,
                            public ContentLessObjectCacheable<RenderPipelineBase> {
   public:
-    RenderPipelineBase(DeviceBase* device, const RenderPipelineDescriptor* descriptor);
+    RenderPipelineBase(DeviceBase* device, const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     ~RenderPipelineBase() override;
 
     static RenderPipelineBase* MakeError(DeviceBase* device, const char* label);
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index be34649..0b962af 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -1006,24 +1006,23 @@
 };
 
 MaybeError ValidateAndParseShaderModule(DeviceBase* device,
-                                        const ShaderModuleDescriptor* descriptor,
+                                        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
                                         ShaderModuleParseResult* parseResult,
                                         OwnedCompilationMessages* outMessages) {
     DAWN_ASSERT(parseResult != nullptr);
 
-    UnpackedPtr<ShaderModuleDescriptor> unpacked;
-    DAWN_TRY_ASSIGN(unpacked, ValidateAndUnpack(descriptor));
     wgpu::SType moduleType;
     // A WGSL (or SPIR-V, if enabled) subdescriptor is required, and a Dawn-specific SPIR-V options
 // descriptor is allowed when using SPIR-V.
 #if TINT_BUILD_SPV_READER
     DAWN_TRY_ASSIGN(
         moduleType,
-        (unpacked.ValidateBranches<
+        (descriptor.ValidateBranches<
             Branch<ShaderModuleWGSLDescriptor>,
             Branch<ShaderModuleSPIRVDescriptor, DawnShaderModuleSPIRVOptionsDescriptor>>()));
 #else
-    DAWN_TRY_ASSIGN(moduleType, (unpacked.ValidateBranches<Branch<ShaderModuleWGSLDescriptor>>()));
+    DAWN_TRY_ASSIGN(moduleType,
+                    (descriptor.ValidateBranches<Branch<ShaderModuleWGSLDescriptor>>()));
 #endif
     DAWN_ASSERT(moduleType != wgpu::SType::Invalid);
 
@@ -1041,8 +1040,8 @@
         case wgpu::SType::ShaderModuleSPIRVDescriptor: {
             DAWN_INVALID_IF(device->IsToggleEnabled(Toggle::DisallowSpirv),
                             "SPIR-V is disallowed.");
-            const auto* spirvDesc = unpacked.Get<ShaderModuleSPIRVDescriptor>();
-            const auto* spirvOptions = unpacked.Get<DawnShaderModuleSPIRVOptionsDescriptor>();
+            const auto* spirvDesc = descriptor.Get<ShaderModuleSPIRVDescriptor>();
+            const auto* spirvOptions = descriptor.Get<DawnShaderModuleSPIRVOptionsDescriptor>();
 
             // TODO(dawn:2033): Avoid unnecessary copies of the SPIR-V code.
             std::vector<uint32_t> spirv(spirvDesc->code, spirvDesc->code + spirvDesc->codeSize);
@@ -1060,7 +1059,7 @@
         }
 #endif  // TINT_BUILD_SPV_READER
         case wgpu::SType::ShaderModuleWGSLDescriptor: {
-            wgslDesc = unpacked.Get<ShaderModuleWGSLDescriptor>();
+            wgslDesc = descriptor.Get<ShaderModuleWGSLDescriptor>();
             break;
         }
         default:
@@ -1229,26 +1228,22 @@
 // ShaderModuleBase
 
 ShaderModuleBase::ShaderModuleBase(DeviceBase* device,
-                                   const ShaderModuleDescriptor* descriptor,
+                                   const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
                                    ApiObjectBase::UntrackedByDeviceTag tag)
     : ApiObjectBase(device, descriptor->label), mType(Type::Undefined) {
-    DAWN_ASSERT(descriptor->nextInChain != nullptr);
-    const ShaderModuleSPIRVDescriptor* spirvDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &spirvDesc);
-    const ShaderModuleWGSLDescriptor* wgslDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &wgslDesc);
-    DAWN_ASSERT(spirvDesc || wgslDesc);
-
-    if (spirvDesc) {
+    if (auto* spirvDesc = descriptor.Get<ShaderModuleSPIRVDescriptor>()) {
         mType = Type::Spirv;
         mOriginalSpirv.assign(spirvDesc->code, spirvDesc->code + spirvDesc->codeSize);
-    } else if (wgslDesc) {
+    } else if (auto* wgslDesc = descriptor.Get<ShaderModuleWGSLDescriptor>()) {
         mType = Type::Wgsl;
         mWgsl = std::string(wgslDesc->code);
+    } else {
+        DAWN_ASSERT(false);
     }
 }
 
-ShaderModuleBase::ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor)
+ShaderModuleBase::ShaderModuleBase(DeviceBase* device,
+                                   const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor, kUntrackedByDevice) {
     GetObjectTrackingList()->Track(this);
 }
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index 4d1b329..0b8b00a 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -126,7 +126,7 @@
 };
 
 MaybeError ValidateAndParseShaderModule(DeviceBase* device,
-                                        const ShaderModuleDescriptor* descriptor,
+                                        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
                                         ShaderModuleParseResult* parseResult,
                                         OwnedCompilationMessages* outMessages);
 MaybeError ValidateCompatibilityWithPipelineLayout(DeviceBase* device,
@@ -293,9 +293,9 @@
                          public ContentLessObjectCacheable<ShaderModuleBase> {
   public:
     ShaderModuleBase(DeviceBase* device,
-                     const ShaderModuleDescriptor* descriptor,
+                     const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
                      ApiObjectBase::UntrackedByDeviceTag tag);
-    ShaderModuleBase(DeviceBase* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModuleBase(DeviceBase* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
     ~ShaderModuleBase() override;
 
     static Ref<ShaderModuleBase> MakeError(DeviceBase* device, const char* label);
diff --git a/src/dawn/native/d3d/BackendD3D.cpp b/src/dawn/native/d3d/BackendD3D.cpp
index 62bf7fa..70ff15b 100644
--- a/src/dawn/native/d3d/BackendD3D.cpp
+++ b/src/dawn/native/d3d/BackendD3D.cpp
@@ -289,7 +289,7 @@
 }
 
 std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
-    const RequestAdapterOptions* options) {
+    const UnpackedPtr<RequestAdapterOptions>& options) {
     if (options->forceFallbackAdapter) {
         return {};
     }
@@ -297,11 +297,8 @@
     FeatureLevel featureLevel =
         options->compatibilityMode ? FeatureLevel::Compatibility : FeatureLevel::Core;
 
-    const RequestAdapterOptionsLUID* luidOptions = nullptr;
-    FindInChain(options->nextInChain, &luidOptions);
-
     // Get or create just the physical device matching the dxgi adapter.
-    if (luidOptions != nullptr) {
+    if (auto* luidOptions = options.Get<RequestAdapterOptionsLUID>()) {
         Ref<PhysicalDeviceBase> physicalDevice;
         if (GetInstance()->ConsumedErrorAndWarnOnce(
                 GetOrCreatePhysicalDeviceFromLUID(luidOptions->adapterLUID), &physicalDevice) ||
diff --git a/src/dawn/native/d3d/BackendD3D.h b/src/dawn/native/d3d/BackendD3D.h
index 3edbf50..c24ffe7 100644
--- a/src/dawn/native/d3d/BackendD3D.h
+++ b/src/dawn/native/d3d/BackendD3D.h
@@ -92,7 +92,7 @@
     const PlatformFunctions* GetFunctions() const;
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) override;
+        const UnpackedPtr<RequestAdapterOptions>& options) override;
     void ClearPhysicalDevices() override;
     size_t GetPhysicalDeviceCountForTesting() const override;
 
diff --git a/src/dawn/native/d3d/DeviceD3D.cpp b/src/dawn/native/d3d/DeviceD3D.cpp
index 6a811f6..21e2d37 100644
--- a/src/dawn/native/d3d/DeviceD3D.cpp
+++ b/src/dawn/native/d3d/DeviceD3D.cpp
@@ -35,7 +35,7 @@
 namespace dawn::native::d3d {
 
 Device::Device(AdapterBase* adapter,
-               const DeviceDescriptor* descriptor,
+               const UnpackedPtr<DeviceDescriptor>& descriptor,
                const TogglesState& deviceToggles)
     : DeviceBase(adapter, descriptor, deviceToggles) {}
 
diff --git a/src/dawn/native/d3d/DeviceD3D.h b/src/dawn/native/d3d/DeviceD3D.h
index cfcff6e..98e7be1 100644
--- a/src/dawn/native/d3d/DeviceD3D.h
+++ b/src/dawn/native/d3d/DeviceD3D.h
@@ -45,7 +45,7 @@
 class Device : public DeviceBase {
   public:
     Device(AdapterBase* adapter,
-           const DeviceDescriptor* descriptor,
+           const UnpackedPtr<DeviceDescriptor>& descriptor,
            const TogglesState& deviceToggles);
     ~Device() override;
 
diff --git a/src/dawn/native/d3d11/BackendD3D11.cpp b/src/dawn/native/d3d11/BackendD3D11.cpp
index 41e3da5..8f3b370 100644
--- a/src/dawn/native/d3d11/BackendD3D11.cpp
+++ b/src/dawn/native/d3d11/BackendD3D11.cpp
@@ -41,15 +41,10 @@
 namespace dawn::native::d3d11 {
 namespace {
 
-MaybeError ValidateRequestOptions(const RequestAdapterOptions* options,
+MaybeError ValidateRequestOptions(const UnpackedPtr<RequestAdapterOptions>& options,
                                   ComPtr<IDXGIAdapter>* dxgiAdapter,
                                   ComPtr<ID3D11Device>* d3d11Device) {
-    const d3d::RequestAdapterOptionsLUID* luidOptions = nullptr;
-    FindInChain(options->nextInChain, &luidOptions);
-
-    const d3d11::RequestAdapterOptionsD3D11Device* d3d11DeviceOption = nullptr;
-    FindInChain(options->nextInChain, &d3d11DeviceOption);
-
+    auto* d3d11DeviceOption = options.Get<RequestAdapterOptionsD3D11Device>();
     if (!d3d11DeviceOption) {
         return {};
     }
@@ -73,9 +68,11 @@
     DXGI_ADAPTER_DESC adapterDesc;
     DAWN_TRY(CheckHRESULT(adapter->GetDesc(&adapterDesc), "D3D11: IDXGIAdapter::GetDesc()"));
 
-    DAWN_INVALID_IF(luidOptions && memcmp(&adapterDesc.AdapterLuid, &luidOptions->adapterLUID,
-                                          sizeof(LUID)) != 0,
-                    "RequestAdapterOptionsLUID and RequestAdapterOptionsD3D11Device don't match.");
+    if (auto* luidOptions = options.Get<d3d::RequestAdapterOptionsLUID>()) {
+        DAWN_INVALID_IF(
+            memcmp(&adapterDesc.AdapterLuid, &luidOptions->adapterLUID, sizeof(LUID)) != 0,
+            "RequestAdapterOptionsLUID and RequestAdapterOptionsD3D11Device don't match.");
+    }
 
     *dxgiAdapter = std::move(adapter);
     *d3d11Device = d3d11DeviceOption->device;
@@ -101,7 +98,7 @@
 }
 
 std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
-    const RequestAdapterOptions* options) {
+    const UnpackedPtr<RequestAdapterOptions>& options) {
     if (options->forceFallbackAdapter) {
         return {};
     }
diff --git a/src/dawn/native/d3d11/BackendD3D11.h b/src/dawn/native/d3d11/BackendD3D11.h
index 0dce2e6..45dcd9d 100644
--- a/src/dawn/native/d3d11/BackendD3D11.h
+++ b/src/dawn/native/d3d11/BackendD3D11.h
@@ -44,7 +44,7 @@
     const PlatformFunctions* GetFunctions() const;
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) override;
+        const UnpackedPtr<RequestAdapterOptions>& options) override;
 
   protected:
     ResultOrError<Ref<PhysicalDeviceBase>> CreatePhysicalDeviceFromIDXGIAdapter(
diff --git a/src/dawn/native/d3d11/BufferD3D11.cpp b/src/dawn/native/d3d11/BufferD3D11.cpp
index 65a6e85..1e79f14 100644
--- a/src/dawn/native/d3d11/BufferD3D11.cpp
+++ b/src/dawn/native/d3d11/BufferD3D11.cpp
@@ -34,6 +34,7 @@
 #include "dawn/common/Assert.h"
 #include "dawn/common/Constants.h"
 #include "dawn/common/Math.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/CommandBuffer.h"
 #include "dawn/native/DynamicUploader.h"
 #include "dawn/native/d3d/D3DError.h"
@@ -151,7 +152,7 @@
 
 // static
 ResultOrError<Ref<Buffer>> Buffer::Create(Device* device,
-                                          const BufferDescriptor* descriptor,
+                                          const UnpackedPtr<BufferDescriptor>& descriptor,
                                           const ScopedCommandRecordingContext* commandContext) {
     Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor));
     DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation, commandContext));
@@ -597,7 +598,7 @@
     descriptor.label = "DawnWriteStagingBuffer";
     Ref<BufferBase> stagingBuffer;
     DAWN_TRY_ASSIGN(stagingBuffer,
-                    Buffer::Create(ToBackend(GetDevice()), &descriptor, commandContext));
+                    Buffer::Create(ToBackend(GetDevice()), Unpack(&descriptor), commandContext));
 
     DAWN_TRY(ToBackend(stagingBuffer)->WriteInternal(commandContext, 0, data, size));
 
diff --git a/src/dawn/native/d3d11/BufferD3D11.h b/src/dawn/native/d3d11/BufferD3D11.h
index 36e577c..bc9602a 100644
--- a/src/dawn/native/d3d11/BufferD3D11.h
+++ b/src/dawn/native/d3d11/BufferD3D11.h
@@ -42,7 +42,7 @@
 class Buffer final : public BufferBase {
   public:
     static ResultOrError<Ref<Buffer>> Create(Device* device,
-                                             const BufferDescriptor* descriptor,
+                                             const UnpackedPtr<BufferDescriptor>& descriptor,
                                              const ScopedCommandRecordingContext* commandContext);
 
     MaybeError EnsureDataInitialized(const ScopedCommandRecordingContext* commandContext);
diff --git a/src/dawn/native/d3d11/CommandBufferD3D11.cpp b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
index e7e53b9..818c1c6 100644
--- a/src/dawn/native/d3d11/CommandBufferD3D11.cpp
+++ b/src/dawn/native/d3d11/CommandBufferD3D11.cpp
@@ -34,6 +34,7 @@
 #include <vector>
 
 #include "dawn/common/WindowsUtils.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/CommandEncoder.h"
 #include "dawn/native/CommandValidation.h"
 #include "dawn/native/Commands.h"
@@ -339,8 +340,8 @@
                                     ComputeRequiredBytesInCopy(blockInfo, copy->copySize,
                                                                src.bytesPerRow, src.rowsPerImage));
                     desc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
-                    DAWN_TRY_ASSIGN(stagingBuffer,
-                                    Buffer::Create(ToBackend(GetDevice()), &desc, commandContext));
+                    DAWN_TRY_ASSIGN(stagingBuffer, Buffer::Create(ToBackend(GetDevice()),
+                                                                  Unpack(&desc), commandContext));
 
                     DAWN_TRY(Buffer::Copy(commandContext, buffer, src.offset,
                                           stagingBuffer->GetSize(), ToBackend(stagingBuffer.Get()),
diff --git a/src/dawn/native/d3d11/ComputePipelineD3D11.cpp b/src/dawn/native/d3d11/ComputePipelineD3D11.cpp
index 36ddd90..9cbb947 100644
--- a/src/dawn/native/d3d11/ComputePipelineD3D11.cpp
+++ b/src/dawn/native/d3d11/ComputePipelineD3D11.cpp
@@ -41,7 +41,7 @@
 // static
 Ref<ComputePipeline> ComputePipeline::CreateUninitialized(
     Device* device,
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return AcquireRef(new ComputePipeline(device, descriptor));
 }
 
diff --git a/src/dawn/native/d3d11/ComputePipelineD3D11.h b/src/dawn/native/d3d11/ComputePipelineD3D11.h
index 079cf5e..81eb7eb 100644
--- a/src/dawn/native/d3d11/ComputePipelineD3D11.h
+++ b/src/dawn/native/d3d11/ComputePipelineD3D11.h
@@ -39,8 +39,9 @@
 
 class ComputePipeline final : public ComputePipelineBase {
   public:
-    static Ref<ComputePipeline> CreateUninitialized(Device* device,
-                                                    const ComputePipelineDescriptor* descriptor);
+    static Ref<ComputePipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<ComputePipelineBase> computePipeline,
                                 WGPUCreateComputePipelineAsyncCallback callback,
                                 void* userdata);
diff --git a/src/dawn/native/d3d11/DeviceD3D11.cpp b/src/dawn/native/d3d11/DeviceD3D11.cpp
index 397424c..ebdf352 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/DeviceD3D11.cpp
@@ -111,14 +111,14 @@
 
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
-                                          const DeviceDescriptor* descriptor,
+                                          const UnpackedPtr<DeviceDescriptor>& descriptor,
                                           const TogglesState& deviceToggles) {
     Ref<Device> device = AcquireRef(new Device(adapter, descriptor, deviceToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
 
-MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
+MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     DAWN_TRY_ASSIGN(mD3d11Device, ToBackend(GetPhysicalDevice())->CreateD3D11Device());
     DAWN_ASSERT(mD3d11Device != nullptr);
 
@@ -183,7 +183,8 @@
     return BindGroupLayout::Create(this, descriptor);
 }
 
-ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
+    const UnpackedPtr<BufferDescriptor>& descriptor) {
     return Buffer::Create(this, descriptor, /*commandContext=*/nullptr);
 }
 
@@ -194,12 +195,12 @@
 }
 
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return ComputePipeline::CreateUninitialized(this, descriptor);
 }
 
 ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return PipelineLayout::Create(this, descriptor);
 }
 
@@ -208,7 +209,7 @@
 }
 
 Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl(
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return RenderPipeline::CreateUninitialized(this, descriptor);
 }
 
@@ -217,7 +218,7 @@
 }
 
 ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
diff --git a/src/dawn/native/d3d11/DeviceD3D11.h b/src/dawn/native/d3d11/DeviceD3D11.h
index deebe47..7815c97 100644
--- a/src/dawn/native/d3d11/DeviceD3D11.h
+++ b/src/dawn/native/d3d11/DeviceD3D11.h
@@ -45,11 +45,11 @@
 class Device final : public d3d::Device {
   public:
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
-                                             const DeviceDescriptor* descriptor,
+                                             const UnpackedPtr<DeviceDescriptor>& descriptor,
                                              const TogglesState& deviceToggles);
     ~Device() override;
 
-    MaybeError Initialize(const DeviceDescriptor* descriptor);
+    MaybeError Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor);
 
     ID3D11Device* GetD3D11Device() const;
     ID3D11Device5* GetD3D11Device5() const;
@@ -109,14 +109,15 @@
         const BindGroupDescriptor* descriptor) override;
     ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) override;
-    ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
+    ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) override;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) override;
     ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) override;
     ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(const SamplerDescriptor* descriptor) override;
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) override;
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
@@ -129,9 +130,9 @@
         TextureBase* texture,
         const TextureViewDescriptor* descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) override;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) override;
     Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) override;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
     void InitializeComputePipelineAsyncImpl(Ref<ComputePipelineBase> computePipeline,
                                             WGPUCreateComputePipelineAsyncCallback callback,
                                             void* userdata) override;
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
index 8493cb8..86c7738 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.cpp
@@ -32,6 +32,7 @@
 #include <utility>
 
 #include "dawn/common/Constants.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/d3d/D3DError.h"
 #include "dawn/native/d3d11/BackendD3D11.h"
@@ -308,9 +309,10 @@
     deviceToggles->Default(Toggle::UseBlitForBufferToStencilTextureCopy, true);
 }
 
-ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(AdapterBase* adapter,
-                                                                const DeviceDescriptor* descriptor,
-                                                                const TogglesState& deviceToggles) {
+ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(
+    AdapterBase* adapter,
+    const UnpackedPtr<DeviceDescriptor>& descriptor,
+    const TogglesState& deviceToggles) {
     return Device::Create(adapter, descriptor, deviceToggles);
 }
 
diff --git a/src/dawn/native/d3d11/PhysicalDeviceD3D11.h b/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
index 08d1102..9b2efa8 100644
--- a/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
+++ b/src/dawn/native/d3d11/PhysicalDeviceD3D11.h
@@ -62,7 +62,7 @@
     void SetupBackendDeviceToggles(TogglesState* deviceToggles) const override;
 
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                    const DeviceDescriptor* descriptor,
+                                                    const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                     const TogglesState& deviceToggles) override;
 
     MaybeError ResetInternalDeviceForTestingImpl() override;
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
index e5c3a8a..84534c6 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.cpp
@@ -36,7 +36,7 @@
 // static
 ResultOrError<Ref<PipelineLayout>> PipelineLayout::Create(
     Device* device,
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     Ref<PipelineLayout> pipelineLayout = AcquireRef(new PipelineLayout(device, descriptor));
     DAWN_TRY(pipelineLayout->Initialize(device));
     return pipelineLayout;
diff --git a/src/dawn/native/d3d11/PipelineLayoutD3D11.h b/src/dawn/native/d3d11/PipelineLayoutD3D11.h
index 42f5048..57f7b6b 100644
--- a/src/dawn/native/d3d11/PipelineLayoutD3D11.h
+++ b/src/dawn/native/d3d11/PipelineLayoutD3D11.h
@@ -59,8 +59,9 @@
     static constexpr uint32_t kReservedConstantsBindGroupIndex = kMaxBindGroups;
     static constexpr uint32_t kFirstIndexOffsetBindingNumber = 0u;
 
-    static ResultOrError<Ref<PipelineLayout>> Create(Device* device,
-                                                     const PipelineLayoutDescriptor* descriptor);
+    static ResultOrError<Ref<PipelineLayout>> Create(
+        Device* device,
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
 
     using BindingIndexInfo = PerBindGroup<ityp::vector<BindingIndex, uint32_t>>;
     const BindingIndexInfo& GetBindingIndexInfo() const;
diff --git a/src/dawn/native/d3d11/RenderPipelineD3D11.cpp b/src/dawn/native/d3d11/RenderPipelineD3D11.cpp
index 48ab630..12062f4 100644
--- a/src/dawn/native/d3d11/RenderPipelineD3D11.cpp
+++ b/src/dawn/native/d3d11/RenderPipelineD3D11.cpp
@@ -216,11 +216,12 @@
 // static
 Ref<RenderPipeline> RenderPipeline::CreateUninitialized(
     Device* device,
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return AcquireRef(new RenderPipeline(device, descriptor));
 }
 
-RenderPipeline::RenderPipeline(Device* device, const RenderPipelineDescriptor* descriptor)
+RenderPipeline::RenderPipeline(Device* device,
+                               const UnpackedPtr<RenderPipelineDescriptor>& descriptor)
     : RenderPipelineBase(device, descriptor),
       mD3DPrimitiveTopology(D3DPrimitiveTopology(GetPrimitiveTopology())) {}
 
diff --git a/src/dawn/native/d3d11/RenderPipelineD3D11.h b/src/dawn/native/d3d11/RenderPipelineD3D11.h
index 1062346..9da3aaf 100644
--- a/src/dawn/native/d3d11/RenderPipelineD3D11.h
+++ b/src/dawn/native/d3d11/RenderPipelineD3D11.h
@@ -42,8 +42,9 @@
 
 class RenderPipeline final : public RenderPipelineBase {
   public:
-    static Ref<RenderPipeline> CreateUninitialized(Device* device,
-                                                   const RenderPipelineDescriptor* descriptor);
+    static Ref<RenderPipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
 
     static void InitializeAsync(Ref<RenderPipelineBase> renderPipeline,
                                 WGPUCreateRenderPipelineAsyncCallback callback,
@@ -61,7 +62,7 @@
     bool UsesInstanceIndex() const { return mUsesInstanceIndex; }
 
   private:
-    RenderPipeline(Device* device, const RenderPipelineDescriptor* descriptor);
+    RenderPipeline(Device* device, const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     ~RenderPipeline() override;
 
     MaybeError Initialize() override;
diff --git a/src/dawn/native/d3d11/ShaderModuleD3D11.cpp b/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
index ef9fe91..0431930 100644
--- a/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
+++ b/src/dawn/native/d3d11/ShaderModuleD3D11.cpp
@@ -55,7 +55,7 @@
 // static
 ResultOrError<Ref<ShaderModule>> ShaderModule::Create(
     Device* device,
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
@@ -63,7 +63,7 @@
     return module;
 }
 
-ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor)
+ShaderModule::ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor) {}
 
 MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult,
diff --git a/src/dawn/native/d3d11/ShaderModuleD3D11.h b/src/dawn/native/d3d11/ShaderModuleD3D11.h
index 6e8b431..f1688fe 100644
--- a/src/dawn/native/d3d11/ShaderModuleD3D11.h
+++ b/src/dawn/native/d3d11/ShaderModuleD3D11.h
@@ -48,10 +48,11 @@
 
 class ShaderModule final : public ShaderModuleBase {
   public:
-    static ResultOrError<Ref<ShaderModule>> Create(Device* device,
-                                                   const ShaderModuleDescriptor* descriptor,
-                                                   ShaderModuleParseResult* parseResult,
-                                                   OwnedCompilationMessages* compilationMessages);
+    static ResultOrError<Ref<ShaderModule>> Create(
+        Device* device,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
+        ShaderModuleParseResult* parseResult,
+        OwnedCompilationMessages* compilationMessages);
 
     ResultOrError<d3d::CompiledShader> Compile(
         const ProgrammableStage& programmableStage,
@@ -63,7 +64,7 @@
         const std::optional<tint::PixelLocalOptions>& pixelLocalOptions = {});
 
   private:
-    ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
     ~ShaderModule() override = default;
     MaybeError Initialize(ShaderModuleParseResult* parseResult,
                           OwnedCompilationMessages* compilationMessages);
diff --git a/src/dawn/native/d3d12/BufferD3D12.cpp b/src/dawn/native/d3d12/BufferD3D12.cpp
index af91d0d..6f2f0f2 100644
--- a/src/dawn/native/d3d12/BufferD3D12.cpp
+++ b/src/dawn/native/d3d12/BufferD3D12.cpp
@@ -116,12 +116,11 @@
 }  // namespace
 
 // static
-ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, const BufferDescriptor* descriptor) {
+ResultOrError<Ref<Buffer>> Buffer::Create(Device* device,
+                                          const UnpackedPtr<BufferDescriptor>& descriptor) {
     Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor));
 
-    const BufferHostMappedPointer* hostMappedDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &hostMappedDesc);
-    if (hostMappedDesc != nullptr) {
+    if (auto* hostMappedDesc = descriptor.Get<BufferHostMappedPointer>()) {
         DAWN_TRY(buffer->InitializeHostMapped(hostMappedDesc));
     } else {
         DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation));
@@ -129,7 +128,7 @@
     return buffer;
 }
 
-Buffer::Buffer(Device* device, const BufferDescriptor* descriptor)
+Buffer::Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor)
     : BufferBase(device, descriptor) {}
 
 MaybeError Buffer::Initialize(bool mappedAtCreation) {
diff --git a/src/dawn/native/d3d12/BufferD3D12.h b/src/dawn/native/d3d12/BufferD3D12.h
index 2f78344..a25d3e5 100644
--- a/src/dawn/native/d3d12/BufferD3D12.h
+++ b/src/dawn/native/d3d12/BufferD3D12.h
@@ -43,7 +43,8 @@
 
 class Buffer final : public BufferBase {
   public:
-    static ResultOrError<Ref<Buffer>> Create(Device* device, const BufferDescriptor* descriptor);
+    static ResultOrError<Ref<Buffer>> Create(Device* device,
+                                             const UnpackedPtr<BufferDescriptor>& descriptor);
 
     ID3D12Resource* GetD3D12Resource() const;
     D3D12_GPU_VIRTUAL_ADDRESS GetVA() const;
@@ -68,7 +69,7 @@
     void SetLabelImpl() override;
 
   private:
-    Buffer(Device* device, const BufferDescriptor* descriptor);
+    Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor);
     ~Buffer() override;
 
     MaybeError Initialize(bool mappedAtCreation);
diff --git a/src/dawn/native/d3d12/ComputePipelineD3D12.cpp b/src/dawn/native/d3d12/ComputePipelineD3D12.cpp
index 4963e94..a5fac0c 100644
--- a/src/dawn/native/d3d12/ComputePipelineD3D12.cpp
+++ b/src/dawn/native/d3d12/ComputePipelineD3D12.cpp
@@ -44,7 +44,7 @@
 
 Ref<ComputePipeline> ComputePipeline::CreateUninitialized(
     Device* device,
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return AcquireRef(new ComputePipeline(device, descriptor));
 }
 
diff --git a/src/dawn/native/d3d12/ComputePipelineD3D12.h b/src/dawn/native/d3d12/ComputePipelineD3D12.h
index 166e93e..8d8c987 100644
--- a/src/dawn/native/d3d12/ComputePipelineD3D12.h
+++ b/src/dawn/native/d3d12/ComputePipelineD3D12.h
@@ -38,8 +38,9 @@
 
 class ComputePipeline final : public ComputePipelineBase {
   public:
-    static Ref<ComputePipeline> CreateUninitialized(Device* device,
-                                                    const ComputePipelineDescriptor* descriptor);
+    static Ref<ComputePipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<ComputePipelineBase> computePipeline,
                                 WGPUCreateComputePipelineAsyncCallback callback,
                                 void* userdata);
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index ba296c6..b65c47f 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -79,14 +79,14 @@
 
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
-                                          const DeviceDescriptor* descriptor,
+                                          const UnpackedPtr<DeviceDescriptor>& descriptor,
                                           const TogglesState& deviceToggles) {
     Ref<Device> device = AcquireRef(new Device(adapter, descriptor, deviceToggles));
     DAWN_TRY(device->Initialize(descriptor));
     return device;
 }
 
-MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
+MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     mD3d12Device = ToBackend(GetPhysicalDevice())->GetDevice();
 
     DAWN_ASSERT(mD3d12Device != nullptr);
@@ -187,7 +187,7 @@
 }
 
 Device::Device(AdapterBase* adapter,
-               const DeviceDescriptor* descriptor,
+               const UnpackedPtr<DeviceDescriptor>& descriptor,
                const TogglesState& deviceToggles)
     : Base(adapter, descriptor, deviceToggles) {}
 
@@ -240,7 +240,7 @@
     zeroBufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
     zeroBufferDescriptor.size = kZeroBufferSize;
     zeroBufferDescriptor.label = "ZeroBuffer_Internal";
-    DAWN_TRY_ASSIGN(mZeroBuffer, Buffer::Create(this, &zeroBufferDescriptor));
+    DAWN_TRY_ASSIGN(mZeroBuffer, Buffer::Create(this, Unpack(&zeroBufferDescriptor)));
 
     return {};
 }
@@ -316,7 +316,8 @@
     const BindGroupLayoutDescriptor* descriptor) {
     return BindGroupLayout::Create(this, descriptor);
 }
-ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
+    const UnpackedPtr<BufferDescriptor>& descriptor) {
     return Buffer::Create(this, descriptor);
 }
 ResultOrError<Ref<CommandBufferBase>> Device::CreateCommandBuffer(
@@ -325,25 +326,25 @@
     return CommandBuffer::Create(encoder, descriptor);
 }
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return ComputePipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return PipelineLayout::Create(this, descriptor);
 }
 ResultOrError<Ref<QuerySetBase>> Device::CreateQuerySetImpl(const QuerySetDescriptor* descriptor) {
     return QuerySet::Create(this, descriptor);
 }
 Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl(
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return RenderPipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<SamplerBase>> Device::CreateSamplerImpl(const SamplerDescriptor* descriptor) {
     return Sampler::Create(this, descriptor);
 }
 ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h
index edf0c8d..303c65a 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.h
+++ b/src/dawn/native/d3d12/DeviceD3D12.h
@@ -63,11 +63,11 @@
 class Device final : public d3d::Device {
   public:
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
-                                             const DeviceDescriptor* descriptor,
+                                             const UnpackedPtr<DeviceDescriptor>& descriptor,
                                              const TogglesState& deviceToggles);
     ~Device() override;
 
-    MaybeError Initialize(const DeviceDescriptor* descriptor);
+    MaybeError Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor);
 
     ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer(
         CommandEncoder* encoder,
@@ -179,21 +179,22 @@
     using Base = d3d::Device;
 
     Device(AdapterBase* adapter,
-           const DeviceDescriptor* descriptor,
+           const UnpackedPtr<DeviceDescriptor>& descriptor,
            const TogglesState& deviceToggles);
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
     ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) override;
-    ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
+    ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) override;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) override;
     ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) override;
     ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(const SamplerDescriptor* descriptor) override;
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) override;
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
@@ -206,9 +207,9 @@
         TextureBase* texture,
         const TextureViewDescriptor* descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) override;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) override;
     Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) override;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
     void InitializeComputePipelineAsyncImpl(Ref<ComputePipelineBase> computePipeline,
                                             WGPUCreateComputePipelineAsyncCallback callback,
                                             void* userdata) override;
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
index 77242b9..837bc2b 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.cpp
@@ -34,6 +34,7 @@
 #include "dawn/common/Constants.h"
 #include "dawn/common/Platform.h"
 #include "dawn/common/WindowsUtils.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/d3d/D3DError.h"
 #include "dawn/native/d3d12/BackendD3D12.h"
@@ -692,9 +693,10 @@
     }
 }
 
-ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(AdapterBase* adapter,
-                                                                const DeviceDescriptor* descriptor,
-                                                                const TogglesState& deviceToggles) {
+ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(
+    AdapterBase* adapter,
+    const UnpackedPtr<DeviceDescriptor>& descriptor,
+    const TogglesState& deviceToggles) {
     return Device::Create(adapter, descriptor, deviceToggles);
 }
 
diff --git a/src/dawn/native/d3d12/PhysicalDeviceD3D12.h b/src/dawn/native/d3d12/PhysicalDeviceD3D12.h
index bdabed9..9a978a5 100644
--- a/src/dawn/native/d3d12/PhysicalDeviceD3D12.h
+++ b/src/dawn/native/d3d12/PhysicalDeviceD3D12.h
@@ -58,7 +58,7 @@
     void SetupBackendDeviceToggles(TogglesState* deviceToggles) const override;
 
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                    const DeviceDescriptor* descriptor,
+                                                    const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                     const TogglesState& deviceToggles) override;
 
     MaybeError ResetInternalDeviceForTestingImpl() override;
diff --git a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
index 1e07733..28964bd 100644
--- a/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
+++ b/src/dawn/native/d3d12/PipelineLayoutD3D12.cpp
@@ -156,7 +156,7 @@
 
 ResultOrError<Ref<PipelineLayout>> PipelineLayout::Create(
     Device* device,
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     Ref<PipelineLayout> layout = AcquireRef(new PipelineLayout(device, descriptor));
     DAWN_TRY(layout->Initialize());
     return layout;
diff --git a/src/dawn/native/d3d12/PipelineLayoutD3D12.h b/src/dawn/native/d3d12/PipelineLayoutD3D12.h
index 91a7d47..0ab5cf9 100644
--- a/src/dawn/native/d3d12/PipelineLayoutD3D12.h
+++ b/src/dawn/native/d3d12/PipelineLayoutD3D12.h
@@ -43,8 +43,9 @@
 
 class PipelineLayout final : public PipelineLayoutBase {
   public:
-    static ResultOrError<Ref<PipelineLayout>> Create(Device* device,
-                                                     const PipelineLayoutDescriptor* descriptor);
+    static ResultOrError<Ref<PipelineLayout>> Create(
+        Device* device,
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
 
     uint32_t GetCbvUavSrvRootParameterIndex(BindGroupIndex group) const;
     uint32_t GetSamplerRootParameterIndex(BindGroupIndex group) const;
diff --git a/src/dawn/native/d3d12/RenderPipelineD3D12.cpp b/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
index 5a1f4a5..9115613 100644
--- a/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
+++ b/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
@@ -309,7 +309,7 @@
 
 Ref<RenderPipeline> RenderPipeline::CreateUninitialized(
     Device* device,
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return AcquireRef(new RenderPipeline(device, descriptor));
 }
 
diff --git a/src/dawn/native/d3d12/RenderPipelineD3D12.h b/src/dawn/native/d3d12/RenderPipelineD3D12.h
index d88cd9f..0df27d4 100644
--- a/src/dawn/native/d3d12/RenderPipelineD3D12.h
+++ b/src/dawn/native/d3d12/RenderPipelineD3D12.h
@@ -39,8 +39,9 @@
 
 class RenderPipeline final : public RenderPipelineBase {
   public:
-    static Ref<RenderPipeline> CreateUninitialized(Device* device,
-                                                   const RenderPipelineDescriptor* descriptor);
+    static Ref<RenderPipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<RenderPipelineBase> renderPipeline,
                                 WGPUCreateRenderPipelineAsyncCallback callback,
                                 void* userdata);
diff --git a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
index 947d697..184af9e 100644
--- a/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
+++ b/src/dawn/native/d3d12/ShaderModuleD3D12.cpp
@@ -106,7 +106,7 @@
 // static
 ResultOrError<Ref<ShaderModule>> ShaderModule::Create(
     Device* device,
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
@@ -114,7 +114,7 @@
     return module;
 }
 
-ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor)
+ShaderModule::ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor) {}
 
 MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult,
diff --git a/src/dawn/native/d3d12/ShaderModuleD3D12.h b/src/dawn/native/d3d12/ShaderModuleD3D12.h
index 4da8741..f98050a 100644
--- a/src/dawn/native/d3d12/ShaderModuleD3D12.h
+++ b/src/dawn/native/d3d12/ShaderModuleD3D12.h
@@ -48,10 +48,11 @@
 
 class ShaderModule final : public ShaderModuleBase {
   public:
-    static ResultOrError<Ref<ShaderModule>> Create(Device* device,
-                                                   const ShaderModuleDescriptor* descriptor,
-                                                   ShaderModuleParseResult* parseResult,
-                                                   OwnedCompilationMessages* compilationMessages);
+    static ResultOrError<Ref<ShaderModule>> Create(
+        Device* device,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
+        ShaderModuleParseResult* parseResult,
+        OwnedCompilationMessages* compilationMessages);
 
     ResultOrError<d3d::CompiledShader> Compile(
         const ProgrammableStage& programmableStage,
@@ -63,7 +64,7 @@
         std::optional<uint32_t> maxSubgroupSizeForFullSubgroups = std::nullopt);
 
   private:
-    ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
     ~ShaderModule() override = default;
     MaybeError Initialize(ShaderModuleParseResult* parseResult,
                           OwnedCompilationMessages* compilationMessages);
diff --git a/src/dawn/native/metal/BackendMTL.h b/src/dawn/native/metal/BackendMTL.h
index c1096fe..3ebe1e7 100644
--- a/src/dawn/native/metal/BackendMTL.h
+++ b/src/dawn/native/metal/BackendMTL.h
@@ -40,7 +40,7 @@
     ~Backend() override;
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) override;
+        const UnpackedPtr<RequestAdapterOptions>& options) override;
     void ClearPhysicalDevices() override;
     size_t GetPhysicalDeviceCountForTesting() const override;
 
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index 321d6e1..6abf054 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -33,6 +33,7 @@
 #include "dawn/common/NSRef.h"
 #include "dawn/common/Platform.h"
 #include "dawn/common/SystemUtils.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/MetalBackend.h"
 #include "dawn/native/metal/BufferMTL.h"
@@ -305,7 +306,7 @@
 
   private:
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                    const DeviceDescriptor* descriptor,
+                                                    const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                     const TogglesState& deviceToggles) override {
         return Device::Create(adapter, mDevice, descriptor, deviceToggles);
     }
@@ -955,7 +956,7 @@
 Backend::~Backend() = default;
 
 std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
-    const RequestAdapterOptions* options) {
+    const UnpackedPtr<RequestAdapterOptions>& options) {
     if (options->forceFallbackAdapter) {
         return {};
     }
diff --git a/src/dawn/native/metal/BufferMTL.h b/src/dawn/native/metal/BufferMTL.h
index e116127..39f77d9 100644
--- a/src/dawn/native/metal/BufferMTL.h
+++ b/src/dawn/native/metal/BufferMTL.h
@@ -41,9 +41,10 @@
 
 class Buffer final : public BufferBase {
   public:
-    static ResultOrError<Ref<Buffer>> Create(Device* device, const BufferDescriptor* descriptor);
+    static ResultOrError<Ref<Buffer>> Create(Device* device,
+                                             const UnpackedPtr<BufferDescriptor>& descriptor);
 
-    Buffer(DeviceBase* device, const BufferDescriptor* descriptor);
+    Buffer(DeviceBase* device, const UnpackedPtr<BufferDescriptor>& descriptor);
 
     id<MTLBuffer> GetMTLBuffer() const;
 
diff --git a/src/dawn/native/metal/BufferMTL.mm b/src/dawn/native/metal/BufferMTL.mm
index c02369e..5cc2263 100644
--- a/src/dawn/native/metal/BufferMTL.mm
+++ b/src/dawn/native/metal/BufferMTL.mm
@@ -44,13 +44,11 @@
 static constexpr uint32_t kMinUniformOrStorageBufferAlignment = 16u;
 
 // static
-ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, const BufferDescriptor* descriptor) {
+ResultOrError<Ref<Buffer>> Buffer::Create(Device* device,
+                                          const UnpackedPtr<BufferDescriptor>& descriptor) {
     Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor));
 
-    const BufferHostMappedPointer* hostMappedDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &hostMappedDesc);
-
-    if (hostMappedDesc != nullptr) {
+    if (auto* hostMappedDesc = descriptor.Get<BufferHostMappedPointer>()) {
         DAWN_TRY(buffer->InitializeHostMapped(hostMappedDesc));
     } else {
         DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation));
@@ -67,7 +65,8 @@
     return 256 * 1024 * 1024;
 }
 
-Buffer::Buffer(DeviceBase* dev, const BufferDescriptor* desc) : BufferBase(dev, desc) {}
+Buffer::Buffer(DeviceBase* dev, const UnpackedPtr<BufferDescriptor>& desc)
+    : BufferBase(dev, desc) {}
 
 MaybeError Buffer::Initialize(bool mappedAtCreation) {
     MTLResourceOptions storageMode;
diff --git a/src/dawn/native/metal/ComputePipelineMTL.h b/src/dawn/native/metal/ComputePipelineMTL.h
index 9c3fcae..f1dc6f5 100644
--- a/src/dawn/native/metal/ComputePipelineMTL.h
+++ b/src/dawn/native/metal/ComputePipelineMTL.h
@@ -42,13 +42,14 @@
 
 class ComputePipeline final : public ComputePipelineBase {
   public:
-    static Ref<ComputePipeline> CreateUninitialized(Device* device,
-                                                    const ComputePipelineDescriptor* descriptor);
+    static Ref<ComputePipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<ComputePipelineBase> computePipeline,
                                 WGPUCreateComputePipelineAsyncCallback callback,
                                 void* userdata);
 
-    ComputePipeline(DeviceBase* device, const ComputePipelineDescriptor* descriptor);
+    ComputePipeline(DeviceBase* device, const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     ~ComputePipeline() override;
 
     void Encode(id<MTLComputeCommandEncoder> encoder);
diff --git a/src/dawn/native/metal/ComputePipelineMTL.mm b/src/dawn/native/metal/ComputePipelineMTL.mm
index cb50086..17feac2 100644
--- a/src/dawn/native/metal/ComputePipelineMTL.mm
+++ b/src/dawn/native/metal/ComputePipelineMTL.mm
@@ -42,11 +42,12 @@
 // static
 Ref<ComputePipeline> ComputePipeline::CreateUninitialized(
     Device* device,
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return AcquireRef(new ComputePipeline(device, descriptor));
 }
 
-ComputePipeline::ComputePipeline(DeviceBase* dev, const ComputePipelineDescriptor* desc)
+ComputePipeline::ComputePipeline(DeviceBase* dev,
+                                 const UnpackedPtr<ComputePipelineDescriptor>& desc)
     : ComputePipelineBase(dev, desc) {}
 
 ComputePipeline::~ComputePipeline() = default;
diff --git a/src/dawn/native/metal/DeviceMTL.h b/src/dawn/native/metal/DeviceMTL.h
index 378ddd6..c493a0d 100644
--- a/src/dawn/native/metal/DeviceMTL.h
+++ b/src/dawn/native/metal/DeviceMTL.h
@@ -52,11 +52,11 @@
   public:
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
                                              NSPRef<id<MTLDevice>> mtlDevice,
-                                             const DeviceDescriptor* descriptor,
+                                             const UnpackedPtr<DeviceDescriptor>& descriptor,
                                              const TogglesState& deviceToggles);
     ~Device() override;
 
-    MaybeError Initialize(const DeviceDescriptor* descriptor);
+    MaybeError Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor);
 
     MaybeError TickImpl() override;
 
@@ -98,24 +98,25 @@
   private:
     Device(AdapterBase* adapter,
            NSPRef<id<MTLDevice>> mtlDevice,
-           const DeviceDescriptor* descriptor,
+           const UnpackedPtr<DeviceDescriptor>& descriptor,
            const TogglesState& deviceToggles);
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
     ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) override;
-    ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
+    ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) override;
     ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer(
         CommandEncoder* encoder,
         const CommandBufferDescriptor* descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) override;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) override;
     ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) override;
     ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(const SamplerDescriptor* descriptor) override;
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) override;
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
@@ -128,9 +129,9 @@
         TextureBase* texture,
         const TextureViewDescriptor* descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) override;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) override;
     Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) override;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
     void InitializeComputePipelineAsyncImpl(Ref<ComputePipelineBase> computePipeline,
                                             WGPUCreateComputePipelineAsyncCallback callback,
                                             void* userdata) override;
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index 6b3015d..44316ec 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -123,7 +123,7 @@
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
                                           NSPRef<id<MTLDevice>> mtlDevice,
-                                          const DeviceDescriptor* descriptor,
+                                          const UnpackedPtr<DeviceDescriptor>& descriptor,
                                           const TogglesState& deviceToggles) {
     @autoreleasepool {
         Ref<Device> device =
@@ -135,7 +135,7 @@
 
 Device::Device(AdapterBase* adapter,
                NSPRef<id<MTLDevice>> mtlDevice,
-               const DeviceDescriptor* descriptor,
+               const UnpackedPtr<DeviceDescriptor>& descriptor,
                const TogglesState& deviceToggles)
     : DeviceBase(adapter, descriptor, deviceToggles), mMtlDevice(std::move(mtlDevice)) {
     // On macOS < 11.0, we only can check whether counter sampling is supported, and the counter
@@ -157,7 +157,7 @@
     Destroy();
 }
 
-MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
+MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     Ref<Queue> queue;
     DAWN_TRY_ASSIGN(queue, Queue::Create(this, &descriptor->defaultQueue));
 
@@ -191,7 +191,8 @@
     const BindGroupLayoutDescriptor* descriptor) {
     return BindGroupLayout::Create(this, descriptor);
 }
-ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
+    const UnpackedPtr<BufferDescriptor>& descriptor) {
     return Buffer::Create(this, descriptor);
 }
 ResultOrError<Ref<CommandBufferBase>> Device::CreateCommandBuffer(
@@ -200,25 +201,25 @@
     return CommandBuffer::Create(encoder, descriptor);
 }
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return ComputePipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return PipelineLayout::Create(this, descriptor);
 }
 ResultOrError<Ref<QuerySetBase>> Device::CreateQuerySetImpl(const QuerySetDescriptor* descriptor) {
     return QuerySet::Create(this, descriptor);
 }
 Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl(
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return RenderPipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<SamplerBase>> Device::CreateSamplerImpl(const SamplerDescriptor* descriptor) {
     return Sampler::Create(this, descriptor);
 }
 ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
diff --git a/src/dawn/native/metal/PipelineLayoutMTL.h b/src/dawn/native/metal/PipelineLayoutMTL.h
index 3662efe..6816f60 100644
--- a/src/dawn/native/metal/PipelineLayoutMTL.h
+++ b/src/dawn/native/metal/PipelineLayoutMTL.h
@@ -51,7 +51,8 @@
 
 class PipelineLayout final : public PipelineLayoutBase {
   public:
-    static Ref<PipelineLayout> Create(Device* device, const PipelineLayoutDescriptor* descriptor);
+    static Ref<PipelineLayout> Create(Device* device,
+                                      const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
 
     using BindingIndexInfo =
         PerBindGroup<ityp::stack_vec<BindingIndex, uint32_t, kMaxOptimalBindingsPerGroup>>;
@@ -61,7 +62,7 @@
     uint32_t GetBufferBindingCount(SingleShaderStage stage) const;
 
   private:
-    PipelineLayout(Device* device, const PipelineLayoutDescriptor* descriptor);
+    PipelineLayout(Device* device, const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
     ~PipelineLayout() override;
     PerStage<BindingIndexInfo> mIndexInfo;
     PerStage<uint32_t> mBufferBindingCount;
diff --git a/src/dawn/native/metal/PipelineLayoutMTL.mm b/src/dawn/native/metal/PipelineLayoutMTL.mm
index e3f057f..ab78922 100644
--- a/src/dawn/native/metal/PipelineLayoutMTL.mm
+++ b/src/dawn/native/metal/PipelineLayoutMTL.mm
@@ -34,12 +34,14 @@
 namespace dawn::native::metal {
 
 // static
-Ref<PipelineLayout> PipelineLayout::Create(Device* device,
-                                           const PipelineLayoutDescriptor* descriptor) {
+Ref<PipelineLayout> PipelineLayout::Create(
+    Device* device,
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return AcquireRef(new PipelineLayout(device, descriptor));
 }
 
-PipelineLayout::PipelineLayout(Device* device, const PipelineLayoutDescriptor* descriptor)
+PipelineLayout::PipelineLayout(Device* device,
+                               const UnpackedPtr<PipelineLayoutDescriptor>& descriptor)
     : PipelineLayoutBase(device, descriptor) {
     // Each stage has its own numbering namespace in CompilerMSL.
     for (auto stage : IterateStages(kAllStages)) {
diff --git a/src/dawn/native/metal/RenderPipelineMTL.h b/src/dawn/native/metal/RenderPipelineMTL.h
index 51d2a3c..163da93 100644
--- a/src/dawn/native/metal/RenderPipelineMTL.h
+++ b/src/dawn/native/metal/RenderPipelineMTL.h
@@ -40,13 +40,14 @@
 
 class RenderPipeline final : public RenderPipelineBase {
   public:
-    static Ref<RenderPipelineBase> CreateUninitialized(Device* device,
-                                                       const RenderPipelineDescriptor* descriptor);
+    static Ref<RenderPipelineBase> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<RenderPipelineBase> renderPipeline,
                                 WGPUCreateRenderPipelineAsyncCallback callback,
                                 void* userdata);
 
-    RenderPipeline(DeviceBase* device, const RenderPipelineDescriptor* descriptor);
+    RenderPipeline(DeviceBase* device, const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     ~RenderPipeline() override;
 
     MTLPrimitiveType GetMTLPrimitiveTopology() const;
diff --git a/src/dawn/native/metal/RenderPipelineMTL.mm b/src/dawn/native/metal/RenderPipelineMTL.mm
index 3ea2a23..94d14e71 100644
--- a/src/dawn/native/metal/RenderPipelineMTL.mm
+++ b/src/dawn/native/metal/RenderPipelineMTL.mm
@@ -334,11 +334,11 @@
 // static
 Ref<RenderPipelineBase> RenderPipeline::CreateUninitialized(
     Device* device,
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return AcquireRef(new RenderPipeline(device, descriptor));
 }
 
-RenderPipeline::RenderPipeline(DeviceBase* dev, const RenderPipelineDescriptor* desc)
+RenderPipeline::RenderPipeline(DeviceBase* dev, const UnpackedPtr<RenderPipelineDescriptor>& desc)
     : RenderPipelineBase(dev, desc) {}
 
 RenderPipeline::~RenderPipeline() = default;
diff --git a/src/dawn/native/metal/ShaderModuleMTL.h b/src/dawn/native/metal/ShaderModuleMTL.h
index 5d4527a..c04327f 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.h
+++ b/src/dawn/native/metal/ShaderModuleMTL.h
@@ -50,10 +50,11 @@
 
 class ShaderModule final : public ShaderModuleBase {
   public:
-    static ResultOrError<Ref<ShaderModule>> Create(Device* device,
-                                                   const ShaderModuleDescriptor* descriptor,
-                                                   ShaderModuleParseResult* parseResult,
-                                                   OwnedCompilationMessages* compilationMessages);
+    static ResultOrError<Ref<ShaderModule>> Create(
+        Device* device,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
+        ShaderModuleParseResult* parseResult,
+        OwnedCompilationMessages* compilationMessages);
 
     struct MetalFunctionData {
         NSPRef<id<MTLFunction>> function;
@@ -72,7 +73,7 @@
         std::optional<uint32_t> maxSubgroupSizeForFullSubgroups = std::nullopt);
 
   private:
-    ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
     ~ShaderModule() override;
     MaybeError Initialize(ShaderModuleParseResult* parseResult,
                           OwnedCompilationMessages* compilationMessages);
diff --git a/src/dawn/native/metal/ShaderModuleMTL.mm b/src/dawn/native/metal/ShaderModuleMTL.mm
index f983c20..056ed49 100644
--- a/src/dawn/native/metal/ShaderModuleMTL.mm
+++ b/src/dawn/native/metal/ShaderModuleMTL.mm
@@ -88,7 +88,7 @@
 // static
 ResultOrError<Ref<ShaderModule>> ShaderModule::Create(
     Device* device,
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
@@ -96,7 +96,7 @@
     return module;
 }
 
-ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor)
+ShaderModule::ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor) {}
 
 ShaderModule::~ShaderModule() = default;
diff --git a/src/dawn/native/null/DeviceNull.cpp b/src/dawn/native/null/DeviceNull.cpp
index 3d6094c..fc3cbd8 100644
--- a/src/dawn/native/null/DeviceNull.cpp
+++ b/src/dawn/native/null/DeviceNull.cpp
@@ -88,9 +88,10 @@
 
 void PhysicalDevice::SetupBackendDeviceToggles(TogglesState* deviceToggles) const {}
 
-ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(AdapterBase* adapter,
-                                                                const DeviceDescriptor* descriptor,
-                                                                const TogglesState& deviceToggles) {
+ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(
+    AdapterBase* adapter,
+    const UnpackedPtr<DeviceDescriptor>& descriptor,
+    const TogglesState& deviceToggles) {
     return Device::Create(adapter, descriptor, deviceToggles);
 }
 
@@ -117,7 +118,7 @@
         : BackendConnection(instance, wgpu::BackendType::Null) {}
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) override {
+        const UnpackedPtr<RequestAdapterOptions>& options) override {
         if (options->forceFallbackAdapter) {
             return {};
         }
@@ -158,7 +159,7 @@
 
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
-                                          const DeviceDescriptor* descriptor,
+                                          const UnpackedPtr<DeviceDescriptor>& descriptor,
                                           const TogglesState& deviceToggles) {
     Ref<Device> device = AcquireRef(new Device(adapter, descriptor, deviceToggles));
     DAWN_TRY(device->Initialize(descriptor));
@@ -169,7 +170,7 @@
     Destroy();
 }
 
-MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
+MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     return DeviceBase::Initialize(AcquireRef(new Queue(this, &descriptor->defaultQueue)));
 }
 
@@ -181,7 +182,8 @@
     const BindGroupLayoutDescriptor* descriptor) {
     return AcquireRef(new BindGroupLayout(this, descriptor));
 }
-ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
+    const UnpackedPtr<BufferDescriptor>& descriptor) {
     DAWN_TRY(IncrementMemoryUsage(descriptor->size));
     return AcquireRef(new Buffer(this, descriptor));
 }
@@ -191,25 +193,25 @@
     return AcquireRef(new CommandBuffer(encoder, descriptor));
 }
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return AcquireRef(new ComputePipeline(this, descriptor));
 }
 ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return AcquireRef(new PipelineLayout(this, descriptor));
 }
 ResultOrError<Ref<QuerySetBase>> Device::CreateQuerySetImpl(const QuerySetDescriptor* descriptor) {
     return AcquireRef(new QuerySet(this, descriptor));
 }
 Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl(
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return AcquireRef(new RenderPipeline(this, descriptor));
 }
 ResultOrError<Ref<SamplerBase>> Device::CreateSamplerImpl(const SamplerDescriptor* descriptor) {
     return AcquireRef(new Sampler(this, descriptor));
 }
 ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     Ref<ShaderModule> module = AcquireRef(new ShaderModule(this, descriptor));
@@ -343,7 +345,7 @@
 
 // Buffer
 
-Buffer::Buffer(Device* device, const BufferDescriptor* descriptor)
+Buffer::Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor)
     : BufferBase(device, descriptor) {
     mBackingData = std::unique_ptr<uint8_t[]>(new uint8_t[GetSize()]);
     mAllocatedSize = GetSize();
diff --git a/src/dawn/native/null/DeviceNull.h b/src/dawn/native/null/DeviceNull.h
index 43f154b..153a6b2 100644
--- a/src/dawn/native/null/DeviceNull.h
+++ b/src/dawn/native/null/DeviceNull.h
@@ -102,11 +102,11 @@
 class Device final : public DeviceBase {
   public:
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
-                                             const DeviceDescriptor* descriptor,
+                                             const UnpackedPtr<DeviceDescriptor>& descriptor,
                                              const TogglesState& deviceToggles);
     ~Device() override;
 
-    MaybeError Initialize(const DeviceDescriptor* descriptor);
+    MaybeError Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor);
 
     ResultOrError<Ref<CommandBufferBase>> CreateCommandBuffer(
         CommandEncoder* encoder,
@@ -145,18 +145,19 @@
         const BindGroupDescriptor* descriptor) override;
     ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) override;
-    ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
+    ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) override;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) override;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) override;
     ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) override;
     Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) override;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
     ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(const SamplerDescriptor* descriptor) override;
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) override;
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
@@ -207,7 +208,7 @@
     void SetupBackendAdapterToggles(TogglesState* adapterToggles) const override;
     void SetupBackendDeviceToggles(TogglesState* deviceToggles) const override;
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                    const DeviceDescriptor* descriptor,
+                                                    const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                     const TogglesState& deviceToggles) override;
 
     void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
@@ -243,7 +244,7 @@
 
 class Buffer final : public BufferBase {
   public:
-    Buffer(Device* device, const BufferDescriptor* descriptor);
+    Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor);
 
     void CopyFromStaging(BufferBase* staging,
                          uint64_t sourceOffset,
diff --git a/src/dawn/native/opengl/BackendGL.cpp b/src/dawn/native/opengl/BackendGL.cpp
index 8902183..338754f 100644
--- a/src/dawn/native/opengl/BackendGL.cpp
+++ b/src/dawn/native/opengl/BackendGL.cpp
@@ -40,7 +40,7 @@
     : BackendConnection(instance, backendType) {}
 
 std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
-    const RequestAdapterOptions* options) {
+    const UnpackedPtr<RequestAdapterOptions>& options) {
     if (options->forceFallbackAdapter) {
         return {};
     }
@@ -52,9 +52,7 @@
     void* (*getProc)(const char* name) = nullptr;
     EGLDisplay display = EGL_NO_DISPLAY;
 
-    const RequestAdapterOptionsGetGLProc* glGetProcOptions = nullptr;
-    FindInChain(options->nextInChain, &glGetProcOptions);
-    if (glGetProcOptions) {
+    if (auto* glGetProcOptions = options.Get<RequestAdapterOptionsGetGLProc>()) {
         getProc = glGetProcOptions->getProc;
         display = glGetProcOptions->display;
     }
diff --git a/src/dawn/native/opengl/BackendGL.h b/src/dawn/native/opengl/BackendGL.h
index 17beb22..221a165 100644
--- a/src/dawn/native/opengl/BackendGL.h
+++ b/src/dawn/native/opengl/BackendGL.h
@@ -43,7 +43,7 @@
     Backend(InstanceBase* instance, wgpu::BackendType backendType);
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) override;
+        const UnpackedPtr<RequestAdapterOptions>& options) override;
     void ClearPhysicalDevices() override;
     size_t GetPhysicalDeviceCountForTesting() const override;
 
diff --git a/src/dawn/native/opengl/BufferGL.cpp b/src/dawn/native/opengl/BufferGL.cpp
index d2c2b56..d19f011 100644
--- a/src/dawn/native/opengl/BufferGL.cpp
+++ b/src/dawn/native/opengl/BufferGL.cpp
@@ -31,6 +31,7 @@
 #include <utility>
 #include <vector>
 
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/CommandBuffer.h"
 #include "dawn/native/opengl/DeviceGL.h"
 
@@ -42,7 +43,7 @@
 ResultOrError<Ref<Buffer>> Buffer::CreateInternalBuffer(Device* device,
                                                         const BufferDescriptor* descriptor,
                                                         bool shouldLazyClear) {
-    Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor, shouldLazyClear));
+    Ref<Buffer> buffer = AcquireRef(new Buffer(device, Unpack(descriptor), shouldLazyClear));
     if (descriptor->mappedAtCreation) {
         DAWN_TRY(buffer->MapAtCreationInternal());
     }
@@ -50,7 +51,7 @@
     return std::move(buffer);
 }
 
-Buffer::Buffer(Device* device, const BufferDescriptor* descriptor)
+Buffer::Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor)
     : BufferBase(device, descriptor) {
     const OpenGLFunctions& gl = device->GetGL();
     // Allocate at least 4 bytes so clamped accesses are always in bounds.
@@ -74,7 +75,9 @@
     TrackUsage();
 }
 
-Buffer::Buffer(Device* device, const BufferDescriptor* descriptor, bool shouldLazyClear)
+Buffer::Buffer(Device* device,
+               const UnpackedPtr<BufferDescriptor>& descriptor,
+               bool shouldLazyClear)
     : Buffer(device, descriptor) {
     if (!shouldLazyClear) {
         SetIsDataInitialized();
diff --git a/src/dawn/native/opengl/BufferGL.h b/src/dawn/native/opengl/BufferGL.h
index edb103f..5bdab7a 100644
--- a/src/dawn/native/opengl/BufferGL.h
+++ b/src/dawn/native/opengl/BufferGL.h
@@ -42,7 +42,7 @@
                                                            const BufferDescriptor* descriptor,
                                                            bool shouldLazyClear);
 
-    Buffer(Device* device, const BufferDescriptor* descriptor);
+    Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor);
 
     GLuint GetHandle() const;
 
@@ -53,7 +53,7 @@
     void TrackUsage() { MarkUsedInPendingCommands(); }
 
   private:
-    Buffer(Device* device, const BufferDescriptor* descriptor, bool shouldLazyClear);
+    Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor, bool shouldLazyClear);
     ~Buffer() override;
     MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) override;
     void UnmapImpl() override;
diff --git a/src/dawn/native/opengl/ComputePipelineGL.cpp b/src/dawn/native/opengl/ComputePipelineGL.cpp
index 8820907..b5dc43b 100644
--- a/src/dawn/native/opengl/ComputePipelineGL.cpp
+++ b/src/dawn/native/opengl/ComputePipelineGL.cpp
@@ -34,7 +34,7 @@
 // static
 Ref<ComputePipeline> ComputePipeline::CreateUninitialized(
     Device* device,
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return AcquireRef(new ComputePipeline(device, descriptor));
 }
 
diff --git a/src/dawn/native/opengl/ComputePipelineGL.h b/src/dawn/native/opengl/ComputePipelineGL.h
index 2ebfac7..b8885ba 100644
--- a/src/dawn/native/opengl/ComputePipelineGL.h
+++ b/src/dawn/native/opengl/ComputePipelineGL.h
@@ -40,8 +40,9 @@
 
 class ComputePipeline final : public ComputePipelineBase, public PipelineGL {
   public:
-    static Ref<ComputePipeline> CreateUninitialized(Device* device,
-                                                    const ComputePipelineDescriptor* descriptor);
+    static Ref<ComputePipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
 
     void ApplyNow();
 
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index c2029eb..267f48c 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -118,7 +118,7 @@
 
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
-                                          const DeviceDescriptor* descriptor,
+                                          const UnpackedPtr<DeviceDescriptor>& descriptor,
                                           const OpenGLFunctions& functions,
                                           std::unique_ptr<Context> context,
                                           const TogglesState& deviceToggles) {
@@ -129,7 +129,7 @@
 }
 
 Device::Device(AdapterBase* adapter,
-               const DeviceDescriptor* descriptor,
+               const UnpackedPtr<DeviceDescriptor>& descriptor,
                const OpenGLFunctions& functions,
                std::unique_ptr<Context> context,
                const TogglesState& deviceToggles)
@@ -141,7 +141,7 @@
     Destroy();
 }
 
-MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
+MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     // Directly set the context current and use mGL instead of calling GetGL as GetGL will notify
     // the (yet inexistent) queue that GL was used.
     mContext->MakeCurrent();
@@ -222,7 +222,8 @@
     const BindGroupLayoutDescriptor* descriptor) {
     return AcquireRef(new BindGroupLayout(this, descriptor));
 }
-ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
+    const UnpackedPtr<BufferDescriptor>& descriptor) {
     return AcquireRef(new Buffer(this, descriptor));
 }
 ResultOrError<Ref<CommandBufferBase>> Device::CreateCommandBuffer(
@@ -231,25 +232,25 @@
     return AcquireRef(new CommandBuffer(encoder, descriptor));
 }
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return ComputePipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return AcquireRef(new PipelineLayout(this, descriptor));
 }
 ResultOrError<Ref<QuerySetBase>> Device::CreateQuerySetImpl(const QuerySetDescriptor* descriptor) {
     return AcquireRef(new QuerySet(this, descriptor));
 }
 Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl(
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return RenderPipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<SamplerBase>> Device::CreateSamplerImpl(const SamplerDescriptor* descriptor) {
     return AcquireRef(new Sampler(this, descriptor));
 }
 ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
diff --git a/src/dawn/native/opengl/DeviceGL.h b/src/dawn/native/opengl/DeviceGL.h
index 5c9c266..2237dc8 100644
--- a/src/dawn/native/opengl/DeviceGL.h
+++ b/src/dawn/native/opengl/DeviceGL.h
@@ -52,13 +52,13 @@
   public:
     class Context;
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
-                                             const DeviceDescriptor* descriptor,
+                                             const UnpackedPtr<DeviceDescriptor>& descriptor,
                                              const OpenGLFunctions& functions,
                                              std::unique_ptr<Context> context,
                                              const TogglesState& deviceToggles);
     ~Device() override;
 
-    MaybeError Initialize(const DeviceDescriptor* descriptor);
+    MaybeError Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor);
 
     // Returns all the OpenGL entry points and ensures that the associated
     // Context is current.
@@ -102,7 +102,7 @@
 
   private:
     Device(AdapterBase* adapter,
-           const DeviceDescriptor* descriptor,
+           const UnpackedPtr<DeviceDescriptor>& descriptor,
            const OpenGLFunctions& functions,
            std::unique_ptr<Context> context,
            const TogglesState& deviceToggless);
@@ -111,14 +111,15 @@
         const BindGroupDescriptor* descriptor) override;
     ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) override;
-    ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
+    ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) override;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) override;
     ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) override;
     ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(const SamplerDescriptor* descriptor) override;
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) override;
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
@@ -131,9 +132,9 @@
         TextureBase* texture,
         const TextureViewDescriptor* descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) override;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) override;
     Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) override;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
 
     ResultOrError<wgpu::TextureUsage> GetSupportedSurfaceUsageImpl(
         const Surface* surface) const override;
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index d78a66f..7f85eb5 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -34,6 +34,7 @@
 #include <utility>
 
 #include "dawn/common/GPUInfo.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/opengl/ContextEGL.h"
 #include "dawn/native/opengl/DeviceGL.h"
@@ -399,9 +400,10 @@
     deviceToggles->Default(Toggle::UseBlitForBufferToStencilTextureCopy, true);
 }
 
-ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(AdapterBase* adapter,
-                                                                const DeviceDescriptor* descriptor,
-                                                                const TogglesState& deviceToggles) {
+ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(
+    AdapterBase* adapter,
+    const UnpackedPtr<DeviceDescriptor>& descriptor,
+    const TogglesState& deviceToggles) {
     EGLenum api =
         GetBackendType() == wgpu::BackendType::OpenGL ? EGL_OPENGL_API : EGL_OPENGL_ES_API;
     std::unique_ptr<Device::Context> context;
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.h b/src/dawn/native/opengl/PhysicalDeviceGL.h
index 33409b4..4af8540 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.h
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.h
@@ -62,7 +62,7 @@
     void SetupBackendAdapterToggles(TogglesState* adapterToggles) const override;
     void SetupBackendDeviceToggles(TogglesState* deviceToggles) const override;
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                    const DeviceDescriptor* descriptor,
+                                                    const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                     const TogglesState& deviceToggles) override;
 
     void PopulateMemoryHeapInfo(AdapterPropertiesMemoryHeaps* memoryHeapProperties) const override;
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.cpp b/src/dawn/native/opengl/PipelineLayoutGL.cpp
index 2392ce5..57bcf00 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.cpp
+++ b/src/dawn/native/opengl/PipelineLayoutGL.cpp
@@ -33,7 +33,8 @@
 
 namespace dawn::native::opengl {
 
-PipelineLayout::PipelineLayout(Device* device, const PipelineLayoutDescriptor* descriptor)
+PipelineLayout::PipelineLayout(Device* device,
+                               const UnpackedPtr<PipelineLayoutDescriptor>& descriptor)
     : PipelineLayoutBase(device, descriptor) {
     GLuint uboIndex = 0;
     GLuint samplerIndex = 0;
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.h b/src/dawn/native/opengl/PipelineLayoutGL.h
index 15993a3..eb4863b 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.h
+++ b/src/dawn/native/opengl/PipelineLayoutGL.h
@@ -41,7 +41,7 @@
 
 class PipelineLayout final : public PipelineLayoutBase {
   public:
-    PipelineLayout(Device* device, const PipelineLayoutDescriptor* descriptor);
+    PipelineLayout(Device* device, const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
 
     // GL backend does not support separate bind group index
     // BindingIndexInfo is a map from BindingPoint(group, binding) to a flattened GLuint binding
diff --git a/src/dawn/native/opengl/RenderPipelineGL.cpp b/src/dawn/native/opengl/RenderPipelineGL.cpp
index d5cb755..a545026 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.cpp
+++ b/src/dawn/native/opengl/RenderPipelineGL.cpp
@@ -238,11 +238,12 @@
 // static
 Ref<RenderPipeline> RenderPipeline::CreateUninitialized(
     Device* device,
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return AcquireRef(new RenderPipeline(device, descriptor));
 }
 
-RenderPipeline::RenderPipeline(Device* device, const RenderPipelineDescriptor* descriptor)
+RenderPipeline::RenderPipeline(Device* device,
+                               const UnpackedPtr<RenderPipelineDescriptor>& descriptor)
     : RenderPipelineBase(device, descriptor),
       mVertexArrayObject(0),
       mGlPrimitiveTopology(GLPrimitiveTopology(GetPrimitiveTopology())) {}
diff --git a/src/dawn/native/opengl/RenderPipelineGL.h b/src/dawn/native/opengl/RenderPipelineGL.h
index 10396bd..bccc310 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.h
+++ b/src/dawn/native/opengl/RenderPipelineGL.h
@@ -42,8 +42,9 @@
 
 class RenderPipeline final : public RenderPipelineBase, public PipelineGL {
   public:
-    static Ref<RenderPipeline> CreateUninitialized(Device* device,
-                                                   const RenderPipelineDescriptor* descriptor);
+    static Ref<RenderPipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
 
     GLenum GetGLPrimitiveTopology() const;
     VertexAttributeMask GetAttributesUsingVertexBuffer(VertexBufferSlot slot) const;
@@ -53,7 +54,7 @@
     MaybeError Initialize() override;
 
   private:
-    RenderPipeline(Device* device, const RenderPipelineDescriptor* descriptor);
+    RenderPipeline(Device* device, const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     ~RenderPipeline() override;
     void DestroyImpl() override;
 
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index 5e2aea4..6477239 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -147,7 +147,7 @@
 // static
 ResultOrError<Ref<ShaderModule>> ShaderModule::Create(
     Device* device,
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
@@ -155,7 +155,7 @@
     return module;
 }
 
-ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor)
+ShaderModule::ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor) {}
 
 MaybeError ShaderModule::Initialize(ShaderModuleParseResult* parseResult,
diff --git a/src/dawn/native/opengl/ShaderModuleGL.h b/src/dawn/native/opengl/ShaderModuleGL.h
index b66c91a..86e238e 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.h
+++ b/src/dawn/native/opengl/ShaderModuleGL.h
@@ -83,10 +83,11 @@
 
 class ShaderModule final : public ShaderModuleBase {
   public:
-    static ResultOrError<Ref<ShaderModule>> Create(Device* device,
-                                                   const ShaderModuleDescriptor* descriptor,
-                                                   ShaderModuleParseResult* parseResult,
-                                                   OwnedCompilationMessages* compilationMessages);
+    static ResultOrError<Ref<ShaderModule>> Create(
+        Device* device,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
+        ShaderModuleParseResult* parseResult,
+        OwnedCompilationMessages* compilationMessages);
 
     ResultOrError<GLuint> CompileShader(
         const OpenGLFunctions& gl,
@@ -100,7 +101,7 @@
         const;
 
   private:
-    ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
     ~ShaderModule() override = default;
     MaybeError Initialize(ShaderModuleParseResult* parseResult,
                           OwnedCompilationMessages* compilationMessages);
diff --git a/src/dawn/native/vulkan/BackendVk.cpp b/src/dawn/native/vulkan/BackendVk.cpp
index a420b01..9a3fb8a 100644
--- a/src/dawn/native/vulkan/BackendVk.cpp
+++ b/src/dawn/native/vulkan/BackendVk.cpp
@@ -34,6 +34,7 @@
 #include "dawn/common/BitSetIterator.h"
 #include "dawn/common/Log.h"
 #include "dawn/common/SystemUtils.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/VulkanBackend.h"
 #include "dawn/native/vulkan/DeviceVk.h"
@@ -548,7 +549,7 @@
 Backend::~Backend() = default;
 
 std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
-    const RequestAdapterOptions* options) {
+    const UnpackedPtr<RequestAdapterOptions>& options) {
     std::vector<Ref<PhysicalDeviceBase>> physicalDevices;
     InstanceBase* instance = GetInstance();
     for (ICD icd : kICDs) {
diff --git a/src/dawn/native/vulkan/BackendVk.h b/src/dawn/native/vulkan/BackendVk.h
index 969e838..ad4d3ee 100644
--- a/src/dawn/native/vulkan/BackendVk.h
+++ b/src/dawn/native/vulkan/BackendVk.h
@@ -108,7 +108,7 @@
     MaybeError Initialize();
 
     std::vector<Ref<PhysicalDeviceBase>> DiscoverPhysicalDevices(
-        const RequestAdapterOptions* options) override;
+        const UnpackedPtr<RequestAdapterOptions>& options) override;
     void ClearPhysicalDevices() override;
     size_t GetPhysicalDeviceCountForTesting() const override;
 
diff --git a/src/dawn/native/vulkan/BufferVk.cpp b/src/dawn/native/vulkan/BufferVk.cpp
index 3498c3c..084b31d 100644
--- a/src/dawn/native/vulkan/BufferVk.cpp
+++ b/src/dawn/native/vulkan/BufferVk.cpp
@@ -157,13 +157,11 @@
 }  // namespace
 
 // static
-ResultOrError<Ref<Buffer>> Buffer::Create(Device* device, const BufferDescriptor* descriptor) {
+ResultOrError<Ref<Buffer>> Buffer::Create(Device* device,
+                                          const UnpackedPtr<BufferDescriptor>& descriptor) {
     Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor));
 
-    const BufferHostMappedPointer* hostMappedDesc = nullptr;
-    FindInChain(descriptor->nextInChain, &hostMappedDesc);
-
-    if (hostMappedDesc != nullptr) {
+    if (auto* hostMappedDesc = descriptor.Get<BufferHostMappedPointer>()) {
         DAWN_TRY(buffer->InitializeHostMapped(hostMappedDesc));
     } else {
         DAWN_TRY(buffer->Initialize(descriptor->mappedAtCreation));
diff --git a/src/dawn/native/vulkan/BufferVk.h b/src/dawn/native/vulkan/BufferVk.h
index e185aa6..7acebaa 100644
--- a/src/dawn/native/vulkan/BufferVk.h
+++ b/src/dawn/native/vulkan/BufferVk.h
@@ -44,7 +44,8 @@
 
 class Buffer final : public BufferBase {
   public:
-    static ResultOrError<Ref<Buffer>> Create(Device* device, const BufferDescriptor* descriptor);
+    static ResultOrError<Ref<Buffer>> Create(Device* device,
+                                             const UnpackedPtr<BufferDescriptor>& descriptor);
 
     VkBuffer GetHandle() const;
 
diff --git a/src/dawn/native/vulkan/ComputePipelineVk.cpp b/src/dawn/native/vulkan/ComputePipelineVk.cpp
index b2bf71a..a21050d 100644
--- a/src/dawn/native/vulkan/ComputePipelineVk.cpp
+++ b/src/dawn/native/vulkan/ComputePipelineVk.cpp
@@ -46,7 +46,7 @@
 // static
 Ref<ComputePipeline> ComputePipeline::CreateUninitialized(
     Device* device,
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return AcquireRef(new ComputePipeline(device, descriptor));
 }
 
diff --git a/src/dawn/native/vulkan/ComputePipelineVk.h b/src/dawn/native/vulkan/ComputePipelineVk.h
index a15c1b1..791aaf2 100644
--- a/src/dawn/native/vulkan/ComputePipelineVk.h
+++ b/src/dawn/native/vulkan/ComputePipelineVk.h
@@ -39,8 +39,9 @@
 
 class ComputePipeline final : public ComputePipelineBase {
   public:
-    static Ref<ComputePipeline> CreateUninitialized(Device* device,
-                                                    const ComputePipelineDescriptor* descriptor);
+    static Ref<ComputePipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<ComputePipelineBase> computePipeline,
                                 WGPUCreateComputePipelineAsyncCallback callback,
                                 void* userdata);
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index 09f95a0..5a19e07 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -65,7 +65,7 @@
 
 // static
 ResultOrError<Ref<Device>> Device::Create(AdapterBase* adapter,
-                                          const DeviceDescriptor* descriptor,
+                                          const UnpackedPtr<DeviceDescriptor>& descriptor,
                                           const TogglesState& deviceToggles) {
     Ref<Device> device = AcquireRef(new Device(adapter, descriptor, deviceToggles));
     DAWN_TRY(device->Initialize(descriptor));
@@ -73,11 +73,11 @@
 }
 
 Device::Device(AdapterBase* adapter,
-               const DeviceDescriptor* descriptor,
+               const UnpackedPtr<DeviceDescriptor>& descriptor,
                const TogglesState& deviceToggles)
     : DeviceBase(adapter, descriptor, deviceToggles), mDebugPrefix(GetNextDeviceDebugPrefix()) {}
 
-MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
+MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     // Copy the adapter's device info to the device so that we can change the "knobs"
     mDeviceInfo = ToBackend(GetPhysicalDevice())->GetDeviceInfo();
 
@@ -159,7 +159,8 @@
     const BindGroupLayoutDescriptor* descriptor) {
     return BindGroupLayout::Create(this, descriptor);
 }
-ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(const BufferDescriptor* descriptor) {
+ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
+    const UnpackedPtr<BufferDescriptor>& descriptor) {
     return Buffer::Create(this, descriptor);
 }
 ResultOrError<Ref<CommandBufferBase>> Device::CreateCommandBuffer(
@@ -168,25 +169,25 @@
     return CommandBuffer::Create(encoder, descriptor);
 }
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
-    const ComputePipelineDescriptor* descriptor) {
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     return ComputePipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<PipelineLayoutBase>> Device::CreatePipelineLayoutImpl(
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     return PipelineLayout::Create(this, descriptor);
 }
 ResultOrError<Ref<QuerySetBase>> Device::CreateQuerySetImpl(const QuerySetDescriptor* descriptor) {
     return QuerySet::Create(this, descriptor);
 }
 Ref<RenderPipelineBase> Device::CreateUninitializedRenderPipelineImpl(
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return RenderPipeline::CreateUninitialized(this, descriptor);
 }
 ResultOrError<Ref<SamplerBase>> Device::CreateSamplerImpl(const SamplerDescriptor* descriptor) {
     return Sampler::Create(this, descriptor);
 }
 ResultOrError<Ref<ShaderModuleBase>> Device::CreateShaderModuleImpl(
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     return ShaderModule::Create(this, descriptor, parseResult, compilationMessages);
@@ -642,13 +643,11 @@
                                        const std::vector<ExternalSemaphoreHandle>& waitHandles,
                                        VkDeviceMemory* outAllocation,
                                        std::vector<VkSemaphore>* outWaitSemaphores) {
-    const TextureDescriptor* textureDescriptor = FromAPI(descriptor->cTextureDescriptor);
-
-    const DawnTextureInternalUsageDescriptor* internalUsageDesc = nullptr;
-    FindInChain(textureDescriptor->nextInChain, &internalUsageDesc);
+    UnpackedPtr<TextureDescriptor> textureDescriptor;
+    DAWN_TRY_ASSIGN(textureDescriptor, ValidateAndUnpack(FromAPI(descriptor->cTextureDescriptor)));
 
     wgpu::TextureUsage usage = textureDescriptor->usage;
-    if (internalUsageDesc != nullptr) {
+    if (auto* internalUsageDesc = textureDescriptor.Get<DawnTextureInternalUsageDescriptor>()) {
         usage |= internalUsageDesc->internalUsage;
     }
 
diff --git a/src/dawn/native/vulkan/DeviceVk.h b/src/dawn/native/vulkan/DeviceVk.h
index 3171297..e330ff8 100644
--- a/src/dawn/native/vulkan/DeviceVk.h
+++ b/src/dawn/native/vulkan/DeviceVk.h
@@ -58,11 +58,11 @@
 class Device final : public DeviceBase {
   public:
     static ResultOrError<Ref<Device>> Create(AdapterBase* adapter,
-                                             const DeviceDescriptor* descriptor,
+                                             const UnpackedPtr<DeviceDescriptor>& descriptor,
                                              const TogglesState& deviceToggles);
     ~Device() override;
 
-    MaybeError Initialize(const DeviceDescriptor* descriptor);
+    MaybeError Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor);
 
     // Contains all the Vulkan entry points, vkDoFoo is called via device->fn.DoFoo.
     const VulkanFunctions fn;
@@ -130,21 +130,22 @@
 
   private:
     Device(AdapterBase* adapter,
-           const DeviceDescriptor* descriptor,
+           const UnpackedPtr<DeviceDescriptor>& descriptor,
            const TogglesState& deviceToggles);
 
     ResultOrError<Ref<BindGroupBase>> CreateBindGroupImpl(
         const BindGroupDescriptor* descriptor) override;
     ResultOrError<Ref<BindGroupLayoutInternalBase>> CreateBindGroupLayoutImpl(
         const BindGroupLayoutDescriptor* descriptor) override;
-    ResultOrError<Ref<BufferBase>> CreateBufferImpl(const BufferDescriptor* descriptor) override;
+    ResultOrError<Ref<BufferBase>> CreateBufferImpl(
+        const UnpackedPtr<BufferDescriptor>& descriptor) override;
     ResultOrError<Ref<PipelineLayoutBase>> CreatePipelineLayoutImpl(
-        const PipelineLayoutDescriptor* descriptor) override;
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) override;
     ResultOrError<Ref<QuerySetBase>> CreateQuerySetImpl(
         const QuerySetDescriptor* descriptor) override;
     ResultOrError<Ref<SamplerBase>> CreateSamplerImpl(const SamplerDescriptor* descriptor) override;
     ResultOrError<Ref<ShaderModuleBase>> CreateShaderModuleImpl(
-        const ShaderModuleDescriptor* descriptor,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
         ShaderModuleParseResult* parseResult,
         OwnedCompilationMessages* compilationMessages) override;
     ResultOrError<Ref<SwapChainBase>> CreateSwapChainImpl(
@@ -157,9 +158,9 @@
         TextureBase* texture,
         const TextureViewDescriptor* descriptor) override;
     Ref<ComputePipelineBase> CreateUninitializedComputePipelineImpl(
-        const ComputePipelineDescriptor* descriptor) override;
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor) override;
     Ref<RenderPipelineBase> CreateUninitializedRenderPipelineImpl(
-        const RenderPipelineDescriptor* descriptor) override;
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor) override;
     Ref<PipelineCacheBase> GetOrCreatePipelineCacheImpl(const CacheKey& key) override;
     void InitializeComputePipelineAsyncImpl(Ref<ComputePipelineBase> computePipeline,
                                             WGPUCreateComputePipelineAsyncCallback callback,
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 0d7b0e5..3669575 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -31,6 +31,7 @@
 #include <string>
 
 #include "dawn/common/GPUInfo.h"
+#include "dawn/native/ChainUtils.h"
 #include "dawn/native/Instance.h"
 #include "dawn/native/Limits.h"
 #include "dawn/native/vulkan/BackendVk.h"
@@ -719,9 +720,10 @@
     }
 }
 
-ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(AdapterBase* adapter,
-                                                                const DeviceDescriptor* descriptor,
-                                                                const TogglesState& deviceToggles) {
+ResultOrError<Ref<DeviceBase>> PhysicalDevice::CreateDeviceImpl(
+    AdapterBase* adapter,
+    const UnpackedPtr<DeviceDescriptor>& descriptor,
+    const TogglesState& deviceToggles) {
     return Device::Create(adapter, descriptor, deviceToggles);
 }
 
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.h b/src/dawn/native/vulkan/PhysicalDeviceVk.h
index 0eca655..2c8038b 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.h
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.h
@@ -73,7 +73,7 @@
     void SetupBackendAdapterToggles(TogglesState* adapterToggles) const override;
     void SetupBackendDeviceToggles(TogglesState* deviceToggles) const override;
     ResultOrError<Ref<DeviceBase>> CreateDeviceImpl(AdapterBase* adapter,
-                                                    const DeviceDescriptor* descriptor,
+                                                    const UnpackedPtr<DeviceDescriptor>& descriptor,
                                                     const TogglesState& deviceToggles) override;
 
     uint32_t FindDefaultComputeSubgroupSize() const;
diff --git a/src/dawn/native/vulkan/PipelineLayoutVk.cpp b/src/dawn/native/vulkan/PipelineLayoutVk.cpp
index a31ba6d..cced6c4 100644
--- a/src/dawn/native/vulkan/PipelineLayoutVk.cpp
+++ b/src/dawn/native/vulkan/PipelineLayoutVk.cpp
@@ -39,7 +39,7 @@
 // static
 ResultOrError<Ref<PipelineLayout>> PipelineLayout::Create(
     Device* device,
-    const PipelineLayoutDescriptor* descriptor) {
+    const UnpackedPtr<PipelineLayoutDescriptor>& descriptor) {
     Ref<PipelineLayout> layout = AcquireRef(new PipelineLayout(device, descriptor));
     DAWN_TRY(layout->Initialize());
     return layout;
diff --git a/src/dawn/native/vulkan/PipelineLayoutVk.h b/src/dawn/native/vulkan/PipelineLayoutVk.h
index 769a949..06bd9f0 100644
--- a/src/dawn/native/vulkan/PipelineLayoutVk.h
+++ b/src/dawn/native/vulkan/PipelineLayoutVk.h
@@ -50,8 +50,9 @@
 
 class PipelineLayout final : public PipelineLayoutBase {
   public:
-    static ResultOrError<Ref<PipelineLayout>> Create(Device* device,
-                                                     const PipelineLayoutDescriptor* descriptor);
+    static ResultOrError<Ref<PipelineLayout>> Create(
+        Device* device,
+        const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
 
     VkPipelineLayout GetHandle() const;
 
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.cpp b/src/dawn/native/vulkan/RenderPipelineVk.cpp
index 50d2610..084a250 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.cpp
+++ b/src/dawn/native/vulkan/RenderPipelineVk.cpp
@@ -352,7 +352,7 @@
 // static
 Ref<RenderPipeline> RenderPipeline::CreateUninitialized(
     Device* device,
-    const RenderPipelineDescriptor* descriptor) {
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     return AcquireRef(new RenderPipeline(device, descriptor));
 }
 
diff --git a/src/dawn/native/vulkan/RenderPipelineVk.h b/src/dawn/native/vulkan/RenderPipelineVk.h
index 9c311ec..b25ce12 100644
--- a/src/dawn/native/vulkan/RenderPipelineVk.h
+++ b/src/dawn/native/vulkan/RenderPipelineVk.h
@@ -39,8 +39,9 @@
 
 class RenderPipeline final : public RenderPipelineBase {
   public:
-    static Ref<RenderPipeline> CreateUninitialized(Device* device,
-                                                   const RenderPipelineDescriptor* descriptor);
+    static Ref<RenderPipeline> CreateUninitialized(
+        Device* device,
+        const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
     static void InitializeAsync(Ref<RenderPipelineBase> renderPipeline,
                                 WGPUCreateRenderPipelineAsyncCallback callback,
                                 void* userdata);
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp
index c717d4b..ff7863d 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.cpp
+++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp
@@ -156,7 +156,7 @@
 // static
 ResultOrError<Ref<ShaderModule>> ShaderModule::Create(
     Device* device,
-    const ShaderModuleDescriptor* descriptor,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
     ShaderModuleParseResult* parseResult,
     OwnedCompilationMessages* compilationMessages) {
     Ref<ShaderModule> module = AcquireRef(new ShaderModule(device, descriptor));
@@ -164,7 +164,7 @@
     return module;
 }
 
-ShaderModule::ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor)
+ShaderModule::ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor),
       mTransformedShaderModuleCache(
           std::make_unique<ConcurrentTransformedShaderModuleCache>(device)) {}
diff --git a/src/dawn/native/vulkan/ShaderModuleVk.h b/src/dawn/native/vulkan/ShaderModuleVk.h
index 8076841..1060fa6 100644
--- a/src/dawn/native/vulkan/ShaderModuleVk.h
+++ b/src/dawn/native/vulkan/ShaderModuleVk.h
@@ -71,10 +71,11 @@
         const char* remappedEntryPoint;
     };
 
-    static ResultOrError<Ref<ShaderModule>> Create(Device* device,
-                                                   const ShaderModuleDescriptor* descriptor,
-                                                   ShaderModuleParseResult* parseResult,
-                                                   OwnedCompilationMessages* compilationMessages);
+    static ResultOrError<Ref<ShaderModule>> Create(
+        Device* device,
+        const UnpackedPtr<ShaderModuleDescriptor>& descriptor,
+        ShaderModuleParseResult* parseResult,
+        OwnedCompilationMessages* compilationMessages);
 
     ResultOrError<ModuleAndSpirv> GetHandleAndSpirv(
         SingleShaderStage stage,
@@ -84,7 +85,7 @@
         std::optional<uint32_t> maxSubgroupSizeForFullSubgroups);
 
   private:
-    ShaderModule(Device* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModule(Device* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
     ~ShaderModule() override;
     MaybeError Initialize(ShaderModuleParseResult* parseResult,
                           OwnedCompilationMessages* compilationMessages);
diff --git a/src/dawn/tests/unittests/ChainUtilsTests.cpp b/src/dawn/tests/unittests/ChainUtilsTests.cpp
index 4962c1a..9a0ed6e 100644
--- a/src/dawn/tests/unittests/ChainUtilsTests.cpp
+++ b/src/dawn/tests/unittests/ChainUtilsTests.cpp
@@ -36,70 +36,6 @@
 
 using ::testing::HasSubstr;
 
-// Checks that we cannot find any structs in an empty chain
-TEST(ChainUtilsTests, FindEmptyChain) {
-    {
-        const PrimitiveDepthClipControl* info = nullptr;
-        const ChainedStruct* chained = nullptr;
-        FindInChain(chained, &info);
-
-        ASSERT_EQ(nullptr, info);
-    }
-
-    {
-        DawnAdapterPropertiesPowerPreference* info = nullptr;
-        ChainedStructOut* chained = nullptr;
-        FindInChain(chained, &info);
-
-        ASSERT_EQ(nullptr, info);
-    }
-}
-
-// Checks that searching a chain for a present struct returns that struct
-TEST(ChainUtilsTests, FindPresentInChain) {
-    {
-        PrimitiveDepthClipControl chain1;
-        ShaderModuleSPIRVDescriptor chain2;
-        chain1.nextInChain = &chain2;
-        const PrimitiveDepthClipControl* info1 = nullptr;
-        const ShaderModuleSPIRVDescriptor* info2 = nullptr;
-        FindInChain(&chain1, &info1);
-        FindInChain(&chain1, &info2);
-
-        ASSERT_NE(nullptr, info1);
-        ASSERT_NE(nullptr, info2);
-    }
-
-    {
-        DawnAdapterPropertiesPowerPreference chain;
-        DawnAdapterPropertiesPowerPreference* output = nullptr;
-        FindInChain(&chain, &output);
-
-        ASSERT_NE(nullptr, output);
-    }
-}
-
-// Checks that searching a chain for a struct that doesn't exist returns a nullptr
-TEST(ChainUtilsTests, FindMissingInChain) {
-    {
-        PrimitiveDepthClipControl chain1;
-        ShaderModuleSPIRVDescriptor chain2;
-        chain1.nextInChain = &chain2;
-        const SurfaceDescriptorFromMetalLayer* info = nullptr;
-        FindInChain(&chain1, &info);
-
-        ASSERT_EQ(nullptr, info);
-    }
-
-    {
-        AdapterProperties adapterProperties;
-        DawnAdapterPropertiesPowerPreference* output = nullptr;
-        FindInChain(adapterProperties.nextInChain, &output);
-
-        ASSERT_EQ(nullptr, output);
-    }
-}
-
 // Empty chain on roots that have and don't have valid extensions should not fail validation and all
 // values should be nullptr.
 TEST(ChainUtilsTests, ValidateAndUnpackEmpty) {
diff --git a/src/dawn/tests/unittests/native/AllowedErrorTests.cpp b/src/dawn/tests/unittests/native/AllowedErrorTests.cpp
index 08bdcacb..c6f3ae7 100644
--- a/src/dawn/tests/unittests/native/AllowedErrorTests.cpp
+++ b/src/dawn/tests/unittests/native/AllowedErrorTests.cpp
@@ -125,7 +125,7 @@
     desc.size.height = 1;
     desc.usage = wgpu::TextureUsage::CopyDst;
     desc.format = wgpu::TextureFormat::RGBA8Unorm;
-    TextureMock* textureMock = new NiceMock<TextureMock>(mDeviceMock, Unpack(&desc));
+    TextureMock* textureMock = new NiceMock<TextureMock>(mDeviceMock, &desc);
     wgpu::Texture texture = wgpu::Texture::Acquire(ToAPI(textureMock));
 
     EXPECT_CALL(*(mDeviceMock->GetQueueMock()), WriteTextureImpl)
diff --git a/src/dawn/tests/unittests/native/DestroyObjectTests.cpp b/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
index 003122c..48f3753 100644
--- a/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
+++ b/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
@@ -283,7 +283,8 @@
 
 TEST_F(DestroyObjectTests, CommandBufferNativeExplicit) {
     CommandEncoderDescriptor commandEncoderDesc = {};
-    Ref<CommandEncoder> commandEncoder = CommandEncoder::Create(mDeviceMock, &commandEncoderDesc);
+    Ref<CommandEncoder> commandEncoder =
+        CommandEncoder::Create(mDeviceMock, Unpack(&commandEncoderDesc));
 
     CommandBufferDescriptor commandBufferDesc = {};
 
@@ -365,8 +366,7 @@
     textureDesc.size.width = 1;
     textureDesc.size.height = 1;
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
-    Ref<TextureMock> textureMock =
-        AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&textureDesc)));
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
 
     TextureViewDescriptor textureViewDesc = {};
     textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -396,8 +396,7 @@
     textureDesc.size.width = 1;
     textureDesc.size.height = 1;
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
-    Ref<TextureMock> textureMock =
-        AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&textureDesc)));
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
 
     TextureViewDescriptor textureViewDesc = {};
     textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -431,8 +430,7 @@
     textureDesc.size.width = 1;
     textureDesc.size.height = 1;
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
-    Ref<TextureMock> textureMock =
-        AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&textureDesc)));
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
 
     TextureViewDescriptor textureViewDesc = {};
     textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -654,7 +652,7 @@
     desc.size.height = 1;
     desc.format = wgpu::TextureFormat::RGBA8Unorm;
 
-    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, Unpack(&desc)));
+    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc));
     EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
 
     EXPECT_TRUE(textureMock->IsAlive());
@@ -669,7 +667,7 @@
     desc.size.height = 1;
     desc.format = wgpu::TextureFormat::RGBA8Unorm;
 
-    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, Unpack(&desc)));
+    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc));
     EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
 
     EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(ByMove(std::move(textureMock))));
@@ -689,7 +687,7 @@
     desc.size.height = 1;
     desc.format = wgpu::TextureFormat::RGBA8Unorm;
 
-    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, Unpack(&desc)));
+    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc));
     EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
     {
         ScopedRawPtrExpectation scoped(textureMock.Get());
@@ -708,8 +706,7 @@
     textureDesc.size.width = 1;
     textureDesc.size.height = 1;
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
-    Ref<TextureMock> textureMock =
-        AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&textureDesc)));
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
 
     TextureViewDescriptor desc = {};
     desc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -741,8 +738,7 @@
     textureDesc.size.width = 1;
     textureDesc.size.height = 1;
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
-    Ref<TextureMock> textureMock =
-        AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&textureDesc)));
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
 
     TextureViewDescriptor viewDesc = {};
     viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -769,8 +765,7 @@
     textureDesc.size.width = 1;
     textureDesc.size.height = 1;
     textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
-    Ref<TextureMock> textureMock =
-        AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&textureDesc)));
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
 
     TextureViewDescriptor viewDesc = {};
     viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
@@ -963,7 +958,7 @@
         desc.format = wgpu::TextureFormat::RGBA8Unorm;
 
         ScopedRawPtrExpectation scoped(mDeviceMock);
-        textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, Unpack(&desc)));
+        textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &desc));
         EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(textureMock));
         texture = device.CreateTexture(ToCppAPI(&desc));
     }
diff --git a/src/dawn/tests/unittests/native/mocks/BufferMock.cpp b/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
index d179136..c1f646f 100644
--- a/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
@@ -27,11 +27,13 @@
 
 #include "dawn/tests/unittests/native/mocks/BufferMock.h"
 
+#include "dawn/native/ChainUtils.h"
+
 namespace dawn::native {
 
 using ::testing::Return;
 
-BufferMock::BufferMock(DeviceMock* device, const BufferDescriptor* descriptor)
+BufferMock::BufferMock(DeviceMock* device, const UnpackedPtr<BufferDescriptor>& descriptor)
     : BufferBase(device, descriptor) {
     mBackingData = std::unique_ptr<uint8_t[]>(new uint8_t[GetSize()]);
     mAllocatedSize = GetSize();
@@ -43,6 +45,9 @@
     });
 }
 
+BufferMock::BufferMock(DeviceMock* device, const BufferDescriptor* descriptor)
+    : BufferMock(device, Unpack(descriptor)) {}
+
 BufferMock::~BufferMock() = default;
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/BufferMock.h b/src/dawn/tests/unittests/native/mocks/BufferMock.h
index e53e9ea..60bcf69 100644
--- a/src/dawn/tests/unittests/native/mocks/BufferMock.h
+++ b/src/dawn/tests/unittests/native/mocks/BufferMock.h
@@ -39,6 +39,7 @@
 
 class BufferMock : public BufferBase {
   public:
+    BufferMock(DeviceMock* device, const UnpackedPtr<BufferDescriptor>& descriptor);
     BufferMock(DeviceMock* device, const BufferDescriptor* descriptor);
     ~BufferMock() override;
 
diff --git a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp
index c5fea89..7c037802 100644
--- a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp
@@ -27,12 +27,14 @@
 
 #include "dawn/tests/unittests/native/mocks/ComputePipelineMock.h"
 
+#include "dawn/native/ChainUtils.h"
+
 namespace dawn::native {
 
 using ::testing::NiceMock;
 
 ComputePipelineMock::ComputePipelineMock(DeviceBase* device,
-                                         const ComputePipelineDescriptor* descriptor)
+                                         const UnpackedPtr<ComputePipelineDescriptor>& descriptor)
     : ComputePipelineBase(device, descriptor) {
     ON_CALL(*this, Initialize).WillByDefault([]() -> MaybeError { return {}; });
     ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->ComputePipelineBase::DestroyImpl(); });
@@ -41,13 +43,20 @@
 ComputePipelineMock::~ComputePipelineMock() = default;
 
 // static
-Ref<ComputePipelineMock> ComputePipelineMock::Create(DeviceMock* device,
-                                                     const ComputePipelineDescriptor* descriptor) {
+Ref<ComputePipelineMock> ComputePipelineMock::Create(
+    DeviceMock* device,
+    const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
     ComputePipelineDescriptor appliedDescriptor;
     Ref<PipelineLayoutBase> layoutRef = ValidateLayoutAndGetComputePipelineDescriptorWithDefaults(
-                                            device, *descriptor, &appliedDescriptor)
+                                            device, **descriptor, &appliedDescriptor)
                                             .AcquireSuccess();
-    return AcquireRef(new NiceMock<ComputePipelineMock>(device, &appliedDescriptor));
+    return AcquireRef(new NiceMock<ComputePipelineMock>(device, Unpack(&appliedDescriptor)));
+}
+
+// static
+Ref<ComputePipelineMock> ComputePipelineMock::Create(DeviceMock* device,
+                                                     const ComputePipelineDescriptor* descriptor) {
+    return Create(device, Unpack(descriptor));
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h
index 704b1bd..7df23f3 100644
--- a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h
+++ b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h
@@ -38,6 +38,9 @@
 class ComputePipelineMock : public ComputePipelineBase {
   public:
     // Creates a compute pipeline given the descriptor.
+    static Ref<ComputePipelineMock> Create(
+        DeviceMock* device,
+        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
     static Ref<ComputePipelineMock> Create(DeviceMock* device,
                                            const ComputePipelineDescriptor* descriptor);
 
@@ -47,7 +50,8 @@
     MOCK_METHOD(void, DestroyImpl, (), (override));
 
   protected:
-    ComputePipelineMock(DeviceBase* device, const ComputePipelineDescriptor* descriptor);
+    ComputePipelineMock(DeviceBase* device,
+                        const UnpackedPtr<ComputePipelineDescriptor>& descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp b/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
index 76ca9d4..07d4c4f 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
@@ -62,10 +62,10 @@
             return AcquireRef(new NiceMock<BindGroupLayoutMock>(this, descriptor));
         }));
     ON_CALL(*this, CreateBufferImpl)
-        .WillByDefault(WithArgs<0>(
-            [this](const BufferDescriptor* descriptor) -> ResultOrError<Ref<BufferBase>> {
-                return AcquireRef(new NiceMock<BufferMock>(this, descriptor));
-            }));
+        .WillByDefault(WithArgs<0>([this](const UnpackedPtr<BufferDescriptor>& descriptor)
+                                       -> ResultOrError<Ref<BufferBase>> {
+            return AcquireRef(new NiceMock<BufferMock>(this, descriptor));
+        }));
     ON_CALL(*this, CreateCommandBuffer)
         .WillByDefault(WithArgs<0, 1>(
             [this](CommandEncoder* encoder, const CommandBufferDescriptor* descriptor)
@@ -78,7 +78,7 @@
             return ExternalTextureMock::Create(this, descriptor);
         }));
     ON_CALL(*this, CreatePipelineLayoutImpl)
-        .WillByDefault(WithArgs<0>([this](const PipelineLayoutDescriptor* descriptor)
+        .WillByDefault(WithArgs<0>([this](const UnpackedPtr<PipelineLayoutDescriptor>& descriptor)
                                        -> ResultOrError<Ref<PipelineLayoutBase>> {
             return AcquireRef(new NiceMock<PipelineLayoutMock>(this, descriptor));
         }));
@@ -93,7 +93,7 @@
                 return AcquireRef(new NiceMock<SamplerMock>(this, descriptor));
             }));
     ON_CALL(*this, CreateShaderModuleImpl)
-        .WillByDefault(WithArgs<0>([this](const ShaderModuleDescriptor* descriptor)
+        .WillByDefault(WithArgs<0>([this](const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
                                        -> ResultOrError<Ref<ShaderModuleBase>> {
             return ShaderModuleMock::Create(this, descriptor);
         }));
@@ -109,15 +109,15 @@
                 return AcquireRef(new NiceMock<TextureViewMock>(texture, descriptor));
             }));
     ON_CALL(*this, CreateUninitializedComputePipelineImpl)
-        .WillByDefault(WithArgs<0>(
-            [this](const ComputePipelineDescriptor* descriptor) -> Ref<ComputePipelineBase> {
-                return ComputePipelineMock::Create(this, descriptor);
-            }));
+        .WillByDefault(WithArgs<0>([this](const UnpackedPtr<ComputePipelineDescriptor>& descriptor)
+                                       -> Ref<ComputePipelineBase> {
+            return ComputePipelineMock::Create(this, descriptor);
+        }));
     ON_CALL(*this, CreateUninitializedRenderPipelineImpl)
-        .WillByDefault(WithArgs<0>(
-            [this](const RenderPipelineDescriptor* descriptor) -> Ref<RenderPipelineBase> {
-                return RenderPipelineMock::Create(this, descriptor);
-            }));
+        .WillByDefault(WithArgs<0>([this](const UnpackedPtr<RenderPipelineDescriptor>& descriptor)
+                                       -> Ref<RenderPipelineBase> {
+            return RenderPipelineMock::Create(this, descriptor);
+        }));
 
     // By default, the mock's TickImpl will succeed.
     ON_CALL(*this, TickImpl).WillByDefault([]() -> MaybeError { return {}; });
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.h b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
index 604898d..d49e9a8 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.h
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
@@ -87,11 +87,11 @@
                 (override));
     MOCK_METHOD(ResultOrError<Ref<BufferBase>>,
                 CreateBufferImpl,
-                (const BufferDescriptor*),
+                (const UnpackedPtr<BufferDescriptor>&),
                 (override));
     MOCK_METHOD(Ref<ComputePipelineBase>,
                 CreateUninitializedComputePipelineImpl,
-                (const ComputePipelineDescriptor*),
+                (const UnpackedPtr<ComputePipelineDescriptor>&),
                 (override));
     MOCK_METHOD(ResultOrError<Ref<ExternalTextureBase>>,
                 CreateExternalTextureImpl,
@@ -99,7 +99,7 @@
                 (override));
     MOCK_METHOD(ResultOrError<Ref<PipelineLayoutBase>>,
                 CreatePipelineLayoutImpl,
-                (const PipelineLayoutDescriptor*),
+                (const UnpackedPtr<PipelineLayoutDescriptor>&),
                 (override));
     MOCK_METHOD(ResultOrError<Ref<QuerySetBase>>,
                 CreateQuerySetImpl,
@@ -107,7 +107,7 @@
                 (override));
     MOCK_METHOD(Ref<RenderPipelineBase>,
                 CreateUninitializedRenderPipelineImpl,
-                (const RenderPipelineDescriptor*),
+                (const UnpackedPtr<RenderPipelineDescriptor>&),
                 (override));
     MOCK_METHOD(ResultOrError<Ref<SamplerBase>>,
                 CreateSamplerImpl,
@@ -115,7 +115,7 @@
                 (override));
     MOCK_METHOD(ResultOrError<Ref<ShaderModuleBase>>,
                 CreateShaderModuleImpl,
-                (const ShaderModuleDescriptor*,
+                (const UnpackedPtr<ShaderModuleDescriptor>&,
                  ShaderModuleParseResult*,
                  OwnedCompilationMessages*),
                 (override));
diff --git a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp
index fa58ffd..4d6ed00 100644
--- a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp
@@ -27,16 +27,22 @@
 
 #include "dawn/tests/unittests/native/mocks/PipelineLayoutMock.h"
 
+#include "dawn/native/ChainUtils.h"
+
 namespace dawn::native {
 
 PipelineLayoutMock::PipelineLayoutMock(DeviceMock* device,
-                                       const PipelineLayoutDescriptor* descriptor)
+                                       const UnpackedPtr<PipelineLayoutDescriptor>& descriptor)
     : PipelineLayoutBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->PipelineLayoutBase::DestroyImpl(); });
 
     SetContentHash(ComputeContentHash());
 }
 
+PipelineLayoutMock::PipelineLayoutMock(DeviceMock* device,
+                                       const PipelineLayoutDescriptor* descriptor)
+    : PipelineLayoutMock(device, Unpack(descriptor)) {}
+
 PipelineLayoutMock::~PipelineLayoutMock() = default;
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h
index 0db90fe..0437ef9 100644
--- a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h
+++ b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h
@@ -37,6 +37,7 @@
 
 class PipelineLayoutMock : public PipelineLayoutBase {
   public:
+    PipelineLayoutMock(DeviceMock* device, const UnpackedPtr<PipelineLayoutDescriptor>& descriptor);
     PipelineLayoutMock(DeviceMock* device, const PipelineLayoutDescriptor* descriptor);
     ~PipelineLayoutMock() override;
 
diff --git a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp
index dec86a8..6682497 100644
--- a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp
@@ -27,12 +27,14 @@
 
 #include "dawn/tests/unittests/native/mocks/RenderPipelineMock.h"
 
+#include "dawn/native/ChainUtils.h"
+
 namespace dawn::native {
 
 using ::testing::NiceMock;
 
 RenderPipelineMock::RenderPipelineMock(DeviceMock* device,
-                                       const RenderPipelineDescriptor* descriptor)
+                                       const UnpackedPtr<RenderPipelineDescriptor>& descriptor)
     : RenderPipelineBase(device, descriptor) {
     ON_CALL(*this, Initialize).WillByDefault([]() -> MaybeError { return {}; });
     ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->RenderPipelineBase::DestroyImpl(); });
@@ -41,13 +43,20 @@
 RenderPipelineMock::~RenderPipelineMock() = default;
 
 // static
-Ref<RenderPipelineMock> RenderPipelineMock::Create(DeviceMock* device,
-                                                   const RenderPipelineDescriptor* descriptor) {
+Ref<RenderPipelineMock> RenderPipelineMock::Create(
+    DeviceMock* device,
+    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
     RenderPipelineDescriptor appliedDescriptor;
     Ref<PipelineLayoutBase> layoutRef = ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
-                                            device, *descriptor, &appliedDescriptor)
+                                            device, **descriptor, &appliedDescriptor)
                                             .AcquireSuccess();
-    return AcquireRef(new NiceMock<RenderPipelineMock>(device, &appliedDescriptor));
+    return AcquireRef(new NiceMock<RenderPipelineMock>(device, Unpack(&appliedDescriptor)));
+}
+
+// static
+Ref<RenderPipelineMock> RenderPipelineMock::Create(DeviceMock* device,
+                                                   const RenderPipelineDescriptor* descriptor) {
+    return Create(device, Unpack(descriptor));
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h
index 50a1d23..37469f7 100644
--- a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h
+++ b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h
@@ -39,6 +39,8 @@
   public:
     // Creates a compute pipeline given the descriptor.
     static Ref<RenderPipelineMock> Create(DeviceMock* device,
+                                          const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
+    static Ref<RenderPipelineMock> Create(DeviceMock* device,
                                           const RenderPipelineDescriptor* descriptor);
 
     ~RenderPipelineMock() override;
@@ -47,7 +49,7 @@
     MOCK_METHOD(void, DestroyImpl, (), (override));
 
   protected:
-    RenderPipelineMock(DeviceMock* device, const RenderPipelineDescriptor* descriptor);
+    RenderPipelineMock(DeviceMock* device, const UnpackedPtr<RenderPipelineDescriptor>& descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
index 6510b31..1f13df0 100644
--- a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
@@ -33,7 +33,8 @@
 
 using ::testing::NiceMock;
 
-ShaderModuleMock::ShaderModuleMock(DeviceMock* device, const ShaderModuleDescriptor* descriptor)
+ShaderModuleMock::ShaderModuleMock(DeviceMock* device,
+                                   const UnpackedPtr<ShaderModuleDescriptor>& descriptor)
     : ShaderModuleBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->ShaderModuleBase::DestroyImpl(); });
 
@@ -43,17 +44,25 @@
 ShaderModuleMock::~ShaderModuleMock() = default;
 
 // static
-Ref<ShaderModuleMock> ShaderModuleMock::Create(DeviceMock* device,
-                                               const ShaderModuleDescriptor* descriptor) {
-    Ref<ShaderModuleMock> shaderModule =
-        AcquireRef(new NiceMock<ShaderModuleMock>(device, descriptor));
+Ref<ShaderModuleMock> ShaderModuleMock::Create(
+    DeviceMock* device,
+    const UnpackedPtr<ShaderModuleDescriptor>& descriptor) {
     ShaderModuleParseResult parseResult;
     ValidateAndParseShaderModule(device, descriptor, &parseResult, nullptr).AcquireSuccess();
+
+    Ref<ShaderModuleMock> shaderModule =
+        AcquireRef(new NiceMock<ShaderModuleMock>(device, descriptor));
     shaderModule->InitializeBase(&parseResult, nullptr).AcquireSuccess();
     return shaderModule;
 }
 
 // static
+Ref<ShaderModuleMock> ShaderModuleMock::Create(DeviceMock* device,
+                                               const ShaderModuleDescriptor* descriptor) {
+    return Create(device, Unpack(descriptor));
+}
+
+// static
 Ref<ShaderModuleMock> ShaderModuleMock::Create(DeviceMock* device, const char* source) {
     ShaderModuleWGSLDescriptor wgslDesc = {};
     wgslDesc.code = source;
diff --git a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h
index 9c569d1..678276e 100644
--- a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h
+++ b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h
@@ -39,6 +39,8 @@
   public:
     // Creates a shader module mock based on a descriptor or wgsl source.
     static Ref<ShaderModuleMock> Create(DeviceMock* device,
+                                        const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
+    static Ref<ShaderModuleMock> Create(DeviceMock* device,
                                         const ShaderModuleDescriptor* descriptor);
     static Ref<ShaderModuleMock> Create(DeviceMock* device, const char* source);
 
@@ -47,7 +49,7 @@
     MOCK_METHOD(void, DestroyImpl, (), (override));
 
   protected:
-    ShaderModuleMock(DeviceMock* device, const ShaderModuleDescriptor* descriptor);
+    ShaderModuleMock(DeviceMock* device, const UnpackedPtr<ShaderModuleDescriptor>& descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/TextureMock.cpp b/src/dawn/tests/unittests/native/mocks/TextureMock.cpp
index 8b01460..2fc70a1 100644
--- a/src/dawn/tests/unittests/native/mocks/TextureMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/TextureMock.cpp
@@ -27,6 +27,8 @@
 
 #include "dawn/tests/unittests/native/mocks/TextureMock.h"
 
+#include "dawn/native/ChainUtils.h"
+
 namespace dawn::native {
 
 TextureMock::TextureMock(DeviceMock* device, const UnpackedPtr<TextureDescriptor>& descriptor)
@@ -34,6 +36,9 @@
     ON_CALL(*this, DestroyImpl).WillByDefault([this] { this->TextureBase::DestroyImpl(); });
 }
 
+TextureMock::TextureMock(DeviceMock* device, const TextureDescriptor* descriptor)
+    : TextureMock(device, Unpack(descriptor)) {}
+
 TextureMock::~TextureMock() = default;
 
 TextureViewMock::TextureViewMock(TextureBase* texture, const TextureViewDescriptor* descriptor)
diff --git a/src/dawn/tests/unittests/native/mocks/TextureMock.h b/src/dawn/tests/unittests/native/mocks/TextureMock.h
index b88953a..e85c129 100644
--- a/src/dawn/tests/unittests/native/mocks/TextureMock.h
+++ b/src/dawn/tests/unittests/native/mocks/TextureMock.h
@@ -38,6 +38,7 @@
 class TextureMock : public TextureBase {
   public:
     TextureMock(DeviceMock* device, const UnpackedPtr<TextureDescriptor>& descriptor);
+    TextureMock(DeviceMock* device, const TextureDescriptor* descriptor);
     ~TextureMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));