Bubble up errors from EnsureSubresourceContentInitialized.

Bug: dawn:1336
Change-Id: I1fd189bd6e3689df6f10351e8ba19fee569bda23
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/122023
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/CommandValidation.cpp b/src/dawn/native/CommandValidation.cpp
index d328f82..5f002de 100644
--- a/src/dawn/native/CommandValidation.cpp
+++ b/src/dawn/native/CommandValidation.cpp
@@ -55,18 +55,18 @@
     // combination of readonly usages.
     for (size_t i = 0; i < scope.textureUsages.size(); ++i) {
         const TextureSubresourceUsage& textureUsage = scope.textureUsages[i];
-        MaybeError error = {};
-        textureUsage.Iterate([&](const SubresourceRange&, const wgpu::TextureUsage& usage) {
-            bool readOnly = IsSubset(usage, kReadOnlyTextureUsages);
-            bool singleUse = wgpu::HasZeroOrOneBits(usage);
-            if (!readOnly && !singleUse && !error.IsError()) {
-                error = DAWN_VALIDATION_ERROR(
-                    "%s usage (%s) includes writable usage and another usage in the same "
-                    "synchronization scope.",
-                    scope.textures[i], usage);
-            }
-        });
-        DAWN_TRY(std::move(error));
+        DAWN_TRY(textureUsage.Iterate(
+            [&](const SubresourceRange&, const wgpu::TextureUsage& usage) -> MaybeError {
+                bool readOnly = IsSubset(usage, kReadOnlyTextureUsages);
+                bool singleUse = wgpu::HasZeroOrOneBits(usage);
+                if (!readOnly && !singleUse) {
+                    return DAWN_VALIDATION_ERROR(
+                        "%s usage (%s) includes writable usage and another usage in the same "
+                        "synchronization scope.",
+                        scope.textures[i], usage);
+                }
+                return {};
+            }));
     }
     return {};
 }
diff --git a/src/dawn/native/SubresourceStorage.h b/src/dawn/native/SubresourceStorage.h
index cc3d10d..34e5dcf 100644
--- a/src/dawn/native/SubresourceStorage.h
+++ b/src/dawn/native/SubresourceStorage.h
@@ -18,11 +18,13 @@
 #include <array>
 #include <limits>
 #include <memory>
+#include <type_traits>
 #include <vector>
 
 #include "dawn/common/Assert.h"
 #include "dawn/common/TypeTraits.h"
 #include "dawn/native/EnumMaskIterator.h"
+#include "dawn/native/Error.h"
 #include "dawn/native/Subresource.h"
 
 namespace dawn::native {
@@ -120,17 +122,27 @@
     // same for multiple subresources.
     const T& Get(Aspect aspect, uint32_t arrayLayer, uint32_t mipLevel) const;
 
-    // Given an iterateFunc that's a function or function-like objet that can be called with
-    // arguments of type (const SubresourceRange& range, const T& data) and returns void,
-    // calls it with aggregate ranges if possible, such that each subresource is part of
+    // Given an iterateFunc that's a function or function-like object that can be called with
+    // arguments of type (const SubresourceRange& range, const T& data) and returns either void or
+    // MaybeError, calls it with aggregate ranges if possible, such that each subresource is part of
     // exactly one of the ranges iterateFunc is called with (and obviously data is the value
-    // stored for that subresource). For example:
+    // stored for that subresource). Note that for MaybeError version, Iterate will return on the
+    // first error. Example usages:
     //
+    //   // Returning void version:
     //   subresources.Iterate([&](const SubresourceRange& range, const T& data) {
     //       // Do something with range and data.
     //   });
-    template <typename F>
-    void Iterate(F&& iterateFunc) const;
+    //
+    //   // Return MaybeError version:
+    //   DAWN_TRY(subresources.Iterate(
+    //       [&](const SubresourceRange& range, const T& data) -> MaybeError {
+    //           // Do something with range and data.
+    //           // Return a MaybeError.
+    //       })
+    //   );
+    template <typename F, typename R = std::invoke_result_t<F, const SubresourceRange&, const T&>>
+    R Iterate(F&& iterateFunc) const;
 
     // Given an updateFunc that's a function or function-like objet that can be called with
     // arguments of type (const SubresourceRange& range, T* data) and returns void,
@@ -239,6 +251,11 @@
 template <typename T>
 template <typename F>
 void SubresourceStorage<T>::Update(const SubresourceRange& range, F&& updateFunc) {
+    ASSERT(range.baseArrayLayer < mArrayLayerCount &&
+           range.baseArrayLayer + range.layerCount <= mArrayLayerCount);
+    ASSERT(range.baseMipLevel < mMipLevelCount &&
+           range.baseMipLevel + range.levelCount <= mMipLevelCount);
+
     bool fullLayers = range.baseMipLevel == 0 && range.levelCount == mMipLevelCount;
     bool fullAspects =
         range.baseArrayLayer == 0 && range.layerCount == mArrayLayerCount && fullLayers;
@@ -351,8 +368,12 @@
 }
 
 template <typename T>
-template <typename F>
-void SubresourceStorage<T>::Iterate(F&& iterateFunc) const {
+template <typename F, typename R>
+R SubresourceStorage<T>::Iterate(F&& iterateFunc) const {
+    static_assert(std::is_same_v<R, MaybeError> || std::is_same_v<R, void>,
+                  "R must be either void or MaybeError");
+    constexpr bool mayError = std::is_same_v<R, MaybeError>;
+
     for (Aspect aspect : IterateEnumMask(mAspects)) {
         uint32_t aspectIndex = GetAspectIndex(aspect);
 
@@ -360,7 +381,11 @@
         if (mAspectCompressed[aspectIndex]) {
             SubresourceRange range =
                 SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount);
-            iterateFunc(range, DataInline(aspectIndex));
+            if constexpr (mayError) {
+                DAWN_TRY(iterateFunc(range, DataInline(aspectIndex)));
+            } else {
+                iterateFunc(range, DataInline(aspectIndex));
+            }
             continue;
         }
 
@@ -368,17 +393,28 @@
             // Fast path, call iterateFunc on the whole array layer at once.
             if (LayerCompressed(aspectIndex, layer)) {
                 SubresourceRange range = GetFullLayerRange(aspect, layer);
-                iterateFunc(range, Data(aspectIndex, layer));
+                if constexpr (mayError) {
+                    DAWN_TRY(iterateFunc(range, Data(aspectIndex, layer)));
+                } else {
+                    iterateFunc(range, Data(aspectIndex, layer));
+                }
                 continue;
             }
 
             // Slow path, call iterateFunc for each mip level.
             for (uint32_t level = 0; level < mMipLevelCount; level++) {
                 SubresourceRange range = SubresourceRange::MakeSingle(aspect, layer, level);
-                iterateFunc(range, Data(aspectIndex, layer, level));
+                if constexpr (mayError) {
+                    DAWN_TRY(iterateFunc(range, Data(aspectIndex, layer, level)));
+                } else {
+                    iterateFunc(range, Data(aspectIndex, layer, level));
+                }
             }
         }
     }
+    if constexpr (mayError) {
+        return {};
+    }
 }
 
 template <typename T>
diff --git a/src/dawn/native/d3d12/CommandBufferD3D12.cpp b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
index 1ddead1..53aa856 100644
--- a/src/dawn/native/d3d12/CommandBufferD3D12.cpp
+++ b/src/dawn/native/d3d12/CommandBufferD3D12.cpp
@@ -322,11 +322,12 @@
                                               0);
 }
 
-// Records the necessary barriers for a synchronization scope using the resource usage
-// data pre-computed in the frontend. Also performs lazy initialization if required.
-// Returns whether any UAV are used in the synchronization scope.
-bool TransitionAndClearForSyncScope(CommandRecordingContext* commandContext,
-                                    const SyncScopeResourceUsage& usages) {
+// Records the necessary barriers for a synchronization scope using the resource usage data
+// pre-computed in the frontend. Also performs lazy initialization if required. Returns whether any
+// UAV are used in the synchronization scope if `passHasUAV` is passed and no errors are hit.
+MaybeError TransitionAndClearForSyncScope(CommandRecordingContext* commandContext,
+                                          const SyncScopeResourceUsage& usages,
+                                          bool* passHasUAV = nullptr) {
     std::vector<D3D12_RESOURCE_BARRIER> barriers;
 
     ID3D12GraphicsCommandList* commandList = commandContext->GetCommandList();
@@ -336,9 +337,8 @@
     for (size_t i = 0; i < usages.buffers.size(); ++i) {
         Buffer* buffer = ToBackend(usages.buffers[i]);
 
-        // TODO(crbug.com/dawn/852): clear storage buffers with
-        // ClearUnorderedAccessView*().
-        buffer->GetDevice()->ConsumedError(buffer->EnsureDataInitialized(commandContext));
+        // TODO(crbug.com/dawn/852): clear storage buffers with ClearUnorderedAccessView*().
+        DAWN_TRY(buffer->EnsureDataInitialized(commandContext));
 
         D3D12_RESOURCE_BARRIER barrier;
         if (buffer->TrackUsageAndGetResourceBarrier(commandContext, &barrier,
@@ -356,13 +356,14 @@
         // Clear subresources that are not render attachments. Render attachments will be
         // cleared in RecordBeginRenderPass by setting the loadop to clear when the texture
         // subresource has not been initialized before the render pass.
-        usages.textureUsages[i].Iterate(
-            [&](const SubresourceRange& range, wgpu::TextureUsage usage) {
+        DAWN_TRY(usages.textureUsages[i].Iterate(
+            [&](const SubresourceRange& range, wgpu::TextureUsage usage) -> MaybeError {
                 if (usage & ~wgpu::TextureUsage::RenderAttachment) {
-                    texture->EnsureSubresourceContentInitialized(commandContext, range);
+                    DAWN_TRY(texture->EnsureSubresourceContentInitialized(commandContext, range));
                 }
                 textureUsages |= usage;
-            });
+                return {};
+            }));
 
         ToBackend(usages.textures[i])
             ->TrackUsageAndGetResourceBarrierForPass(commandContext, &barriers,
@@ -373,8 +374,11 @@
         commandList->ResourceBarrier(barriers.size(), barriers.data());
     }
 
-    return (bufferUsages & wgpu::BufferUsage::Storage ||
-            textureUsages & wgpu::TextureUsage::StorageBinding);
+    if (passHasUAV) {
+        *passHasUAV = bufferUsages & wgpu::BufferUsage::Storage ||
+                      textureUsages & wgpu::TextureUsage::StorageBinding;
+    }
+    return {};
 }
 
 }  // anonymous namespace
@@ -753,8 +757,10 @@
                 BeginRenderPassCmd* beginRenderPassCmd =
                     mCommands.NextCommand<BeginRenderPassCmd>();
 
-                const bool passHasUAV = TransitionAndClearForSyncScope(
-                    commandContext, GetResourceUsages().renderPasses[nextRenderPassNumber]);
+                bool passHasUAV;
+                DAWN_TRY(TransitionAndClearForSyncScope(
+                    commandContext, GetResourceUsages().renderPasses[nextRenderPassNumber],
+                    &passHasUAV));
                 bindingTracker.SetInComputePass(false);
 
                 LazyClearRenderPassAttachments(beginRenderPassCmd);
@@ -808,7 +814,8 @@
                                                   copy->destination.mipLevel)) {
                     texture->SetIsSubresourceContentInitialized(true, subresources);
                 } else {
-                    texture->EnsureSubresourceContentInitialized(commandContext, subresources);
+                    DAWN_TRY(
+                        texture->EnsureSubresourceContentInitialized(commandContext, subresources));
                 }
 
                 buffer->TrackUsageAndTransitionNow(commandContext, wgpu::BufferUsage::CopySrc);
@@ -842,7 +849,8 @@
                 SubresourceRange subresources =
                     GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
 
-                texture->EnsureSubresourceContentInitialized(commandContext, subresources);
+                DAWN_TRY(
+                    texture->EnsureSubresourceContentInitialized(commandContext, subresources));
 
                 texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopySrc,
                                                     subresources);
@@ -875,12 +883,13 @@
                 SubresourceRange dstRange =
                     GetSubresourcesAffectedByCopy(copy->destination, copy->copySize);
 
-                source->EnsureSubresourceContentInitialized(commandContext, srcRange);
+                DAWN_TRY(source->EnsureSubresourceContentInitialized(commandContext, srcRange));
                 if (IsCompleteSubresourceCopiedTo(destination, copy->copySize,
                                                   copy->destination.mipLevel)) {
                     destination->SetIsSubresourceContentInitialized(true, dstRange);
                 } else {
-                    destination->EnsureSubresourceContentInitialized(commandContext, dstRange);
+                    DAWN_TRY(
+                        destination->EnsureSubresourceContentInitialized(commandContext, dstRange));
                 }
 
                 if (copy->source.texture.Get() == copy->destination.texture.Get() &&
@@ -1145,8 +1154,8 @@
                     break;
                 }
 
-                TransitionAndClearForSyncScope(commandContext,
-                                               resourceUsages.dispatchUsages[currentDispatch]);
+                DAWN_TRY(TransitionAndClearForSyncScope(
+                    commandContext, resourceUsages.dispatchUsages[currentDispatch]));
                 DAWN_TRY(bindingTracker->Apply(commandContext));
 
                 RecordNumWorkgroupsForDispatch(commandList, lastPipeline, dispatch);
@@ -1158,8 +1167,8 @@
             case Command::DispatchIndirect: {
                 DispatchIndirectCmd* dispatch = mCommands.NextCommand<DispatchIndirectCmd>();
 
-                TransitionAndClearForSyncScope(commandContext,
-                                               resourceUsages.dispatchUsages[currentDispatch]);
+                DAWN_TRY(TransitionAndClearForSyncScope(
+                    commandContext, resourceUsages.dispatchUsages[currentDispatch]));
                 DAWN_TRY(bindingTracker->Apply(commandContext));
 
                 ComPtr<ID3D12CommandSignature> signature =
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index 62a0d9e..43fa4cf 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -530,7 +530,7 @@
     if (IsCompleteSubresourceCopiedTo(texture, copySizePixels, dst.mipLevel)) {
         texture->SetIsSubresourceContentInitialized(true, range);
     } else {
-        texture->EnsureSubresourceContentInitialized(commandContext, range);
+        DAWN_TRY(texture->EnsureSubresourceContentInitialized(commandContext, range));
     }
 
     texture->TrackUsageAndTransitionNow(commandContext, wgpu::TextureUsage::CopyDst, range);
diff --git a/src/dawn/native/d3d12/TextureD3D12.cpp b/src/dawn/native/d3d12/TextureD3D12.cpp
index 7b49391..2d08a14 100644
--- a/src/dawn/native/d3d12/TextureD3D12.cpp
+++ b/src/dawn/native/d3d12/TextureD3D12.cpp
@@ -1212,17 +1212,17 @@
     SetLabelHelper("Dawn_InternalTexture");
 }
 
-void Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
-                                                  const SubresourceRange& range) {
+MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
+                                                        const SubresourceRange& range) {
     if (!ToBackend(GetDevice())->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
-        return;
+        return {};
     }
     if (!IsSubresourceContentInitialized(range)) {
         // If subresource has not been initialized, clear it to black as it could contain
         // dirty bits from recycled memory
-        GetDevice()->ConsumedError(
-            ClearTexture(commandContext, range, TextureBase::ClearValue::Zero));
+        DAWN_TRY(ClearTexture(commandContext, range, TextureBase::ClearValue::Zero));
     }
+    return {};
 }
 
 bool Texture::StateAndDecay::operator==(const Texture::StateAndDecay& other) const {
diff --git a/src/dawn/native/d3d12/TextureD3D12.h b/src/dawn/native/d3d12/TextureD3D12.h
index 866fcdf..f12121e 100644
--- a/src/dawn/native/d3d12/TextureD3D12.h
+++ b/src/dawn/native/d3d12/TextureD3D12.h
@@ -77,8 +77,8 @@
                                                    bool depthReadOnly,
                                                    bool stencilReadOnly) const;
 
-    void EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
-                                             const SubresourceRange& range);
+    MaybeError EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
+                                                   const SubresourceRange& range);
 
     MaybeError SynchronizeImportedTextureBeforeUse();
     MaybeError SynchronizeImportedTextureAfterUse();
diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm
index a0a83ee..2812563 100644
--- a/src/dawn/native/metal/CommandBufferMTL.mm
+++ b/src/dawn/native/metal/CommandBufferMTL.mm
@@ -735,23 +735,25 @@
     size_t nextRenderPassNumber = 0;
 
     auto LazyClearSyncScope = [](const SyncScopeResourceUsage& scope,
-                                 CommandRecordingContext* commandContext) {
+                                 CommandRecordingContext* commandContext) -> MaybeError {
         for (size_t i = 0; i < scope.textures.size(); ++i) {
             Texture* texture = ToBackend(scope.textures[i]);
 
             // Clear subresources that are not render attachments. Render attachments will be
             // cleared in RecordBeginRenderPass by setting the loadop to clear when the texture
             // subresource has not been initialized before the render pass.
-            scope.textureUsages[i].Iterate(
-                [&](const SubresourceRange& range, wgpu::TextureUsage usage) {
-                    if (usage & ~wgpu::TextureUsage::RenderAttachment) {
-                        texture->EnsureSubresourceContentInitialized(commandContext, range);
-                    }
-                });
+            DAWN_TRY(scope.textureUsages[i].Iterate([&](const SubresourceRange& range,
+                                                        wgpu::TextureUsage usage) -> MaybeError {
+                if (usage & ~wgpu::TextureUsage::RenderAttachment) {
+                    DAWN_TRY(texture->EnsureSubresourceContentInitialized(commandContext, range));
+                }
+                return {};
+            }));
         }
         for (BufferBase* bufferBase : scope.buffers) {
             ToBackend(bufferBase)->EnsureDataInitialized(commandContext);
         }
+        return {};
     };
 
     Command type;
@@ -766,7 +768,7 @@
                 }
                 for (const SyncScopeResourceUsage& scope :
                      GetResourceUsages().computePasses[nextComputePassNumber].dispatchUsages) {
-                    LazyClearSyncScope(scope, commandContext);
+                    DAWN_TRY(LazyClearSyncScope(scope, commandContext));
                 }
                 commandContext->EndBlit();
 
@@ -793,8 +795,8 @@
                         }
                     }
                 }
-                LazyClearSyncScope(GetResourceUsages().renderPasses[nextRenderPassNumber],
-                                   commandContext);
+                DAWN_TRY(LazyClearSyncScope(GetResourceUsages().renderPasses[nextRenderPassNumber],
+                                            commandContext));
                 commandContext->EndBlit();
 
                 LazyClearRenderPassAttachments(cmd);
@@ -858,7 +860,8 @@
                 Texture* texture = ToBackend(dst.texture.Get());
 
                 buffer->EnsureDataInitialized(commandContext);
-                EnsureDestinationTextureInitialized(commandContext, texture, dst, copySize);
+                DAWN_TRY(
+                    EnsureDestinationTextureInitialized(commandContext, texture, dst, copySize));
 
                 buffer->TrackUsage();
                 texture->SynchronizeTextureBeforeUse(commandContext);
@@ -884,8 +887,8 @@
                 buffer->EnsureDataInitializedAsDestination(commandContext, copy);
 
                 texture->SynchronizeTextureBeforeUse(commandContext);
-                texture->EnsureSubresourceContentInitialized(
-                    commandContext, GetSubresourcesAffectedByCopy(src, copySize));
+                DAWN_TRY(texture->EnsureSubresourceContentInitialized(
+                    commandContext, GetSubresourcesAffectedByCopy(src, copySize)));
                 buffer->TrackUsage();
 
                 TextureBufferCopySplit splitCopies = ComputeTextureBufferCopySplit(
@@ -975,10 +978,10 @@
 
                 srcTexture->SynchronizeTextureBeforeUse(commandContext);
                 dstTexture->SynchronizeTextureBeforeUse(commandContext);
-                srcTexture->EnsureSubresourceContentInitialized(
-                    commandContext, GetSubresourcesAffectedByCopy(copy->source, copy->copySize));
-                EnsureDestinationTextureInitialized(commandContext, dstTexture, copy->destination,
-                                                    copy->copySize);
+                DAWN_TRY(srcTexture->EnsureSubresourceContentInitialized(
+                    commandContext, GetSubresourcesAffectedByCopy(copy->source, copy->copySize)));
+                DAWN_TRY(EnsureDestinationTextureInitialized(commandContext, dstTexture,
+                                                             copy->destination, copy->copySize));
 
                 const MTLSize sizeOneSlice =
                     MTLSizeMake(copy->copySize.width, copy->copySize.height, 1);
diff --git a/src/dawn/native/metal/DeviceMTL.mm b/src/dawn/native/metal/DeviceMTL.mm
index f2cf974..840e3c8 100644
--- a/src/dawn/native/metal/DeviceMTL.mm
+++ b/src/dawn/native/metal/DeviceMTL.mm
@@ -395,8 +395,8 @@
                                                 const Extent3D& copySizePixels) {
     Texture* texture = ToBackend(dst.texture.Get());
     texture->SynchronizeTextureBeforeUse(GetPendingCommandContext());
-    EnsureDestinationTextureInitialized(GetPendingCommandContext(DeviceBase::SubmitMode::Passive),
-                                        texture, dst, copySizePixels);
+    DAWN_TRY(EnsureDestinationTextureInitialized(
+        GetPendingCommandContext(DeviceBase::SubmitMode::Passive), texture, dst, copySizePixels));
 
     RecordCopyBufferToTexture(GetPendingCommandContext(DeviceBase::SubmitMode::Passive),
                               ToBackend(source)->GetMTLBuffer(), source->GetSize(),
diff --git a/src/dawn/native/metal/TextureMTL.h b/src/dawn/native/metal/TextureMTL.h
index 4141c2e..79ad11b 100644
--- a/src/dawn/native/metal/TextureMTL.h
+++ b/src/dawn/native/metal/TextureMTL.h
@@ -58,8 +58,8 @@
     bool ShouldKeepInitialized() const;
 
     MTLBlitOption ComputeMTLBlitOption(Aspect aspect) const;
-    void EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
-                                             const SubresourceRange& range);
+    MaybeError EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
+                                                   const SubresourceRange& range);
 
     void SynchronizeTextureBeforeUse(CommandRecordingContext* commandContext);
     void IOSurfaceEndAccess(ExternalImageIOSurfaceEndAccessDescriptor* descriptor);
diff --git a/src/dawn/native/metal/TextureMTL.mm b/src/dawn/native/metal/TextureMTL.mm
index a8d457b..af1074f 100644
--- a/src/dawn/native/metal/TextureMTL.mm
+++ b/src/dawn/native/metal/TextureMTL.mm
@@ -1076,19 +1076,19 @@
     return MTLBlitOptionNone;
 }
 
-void Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
-                                                  const SubresourceRange& range) {
+MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
+                                                        const SubresourceRange& range) {
     if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
-        return;
+        return {};
     }
     if (!IsSubresourceContentInitialized(range)) {
         // If subresource has not been initialized, clear it to black as it could
         // contain dirty bits from recycled memory
-        GetDevice()->ConsumedError(
-            ClearTexture(commandContext, range, TextureBase::ClearValue::Zero));
+        DAWN_TRY(ClearTexture(commandContext, range, TextureBase::ClearValue::Zero));
         SetIsSubresourceContentInitialized(true, range);
         GetDevice()->IncrementLazyClearCountForTesting();
     }
+    return {};
 }
 
 // static
@@ -1165,9 +1165,8 @@
                 // TODO(enga): Add a workaround to back combined depth/stencil textures
                 // with Sampled usage using two separate textures.
                 // Or, consider always using the workaround for D32S8.
-                device->ConsumedError(
-                    DAWN_DEVICE_LOST_ERROR("Cannot create stencil-only texture view of "
-                                           "combined depth/stencil format."));
+                return DAWN_INTERNAL_ERROR("Cannot create stencil-only texture view of combined "
+                                           "depth/stencil format.");
             }
         } else if (GetTexture()->GetFormat().HasDepth() && GetTexture()->GetFormat().HasStencil()) {
             // Depth-only views for depth/stencil textures in Metal simply use the original
diff --git a/src/dawn/native/metal/UtilsMetal.h b/src/dawn/native/metal/UtilsMetal.h
index bb218e9..caf8d7b 100644
--- a/src/dawn/native/metal/UtilsMetal.h
+++ b/src/dawn/native/metal/UtilsMetal.h
@@ -75,10 +75,10 @@
                                                      uint32_t rowsPerImage,
                                                      Aspect aspect);
 
-void EnsureDestinationTextureInitialized(CommandRecordingContext* commandContext,
-                                         Texture* texture,
-                                         const TextureCopy& dst,
-                                         const Extent3D& size);
+MaybeError EnsureDestinationTextureInitialized(CommandRecordingContext* commandContext,
+                                               Texture* texture,
+                                               const TextureCopy& dst,
+                                               const Extent3D& size);
 
 // Allow use MTLStoreActionStoreAndMultismapleResolve because the logic in the backend is
 // first to compute what the "best" Metal render pass descriptor is, then fix it up if we
diff --git a/src/dawn/native/metal/UtilsMetal.mm b/src/dawn/native/metal/UtilsMetal.mm
index a80672b..0eae076 100644
--- a/src/dawn/native/metal/UtilsMetal.mm
+++ b/src/dawn/native/metal/UtilsMetal.mm
@@ -344,17 +344,18 @@
     return copy;
 }
 
-void EnsureDestinationTextureInitialized(CommandRecordingContext* commandContext,
-                                         Texture* texture,
-                                         const TextureCopy& dst,
-                                         const Extent3D& size) {
+MaybeError EnsureDestinationTextureInitialized(CommandRecordingContext* commandContext,
+                                               Texture* texture,
+                                               const TextureCopy& dst,
+                                               const Extent3D& size) {
     ASSERT(texture == dst.texture.Get());
     SubresourceRange range = GetSubresourcesAffectedByCopy(dst, size);
     if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), size, dst.mipLevel)) {
         texture->SetIsSubresourceContentInitialized(true, range);
     } else {
-        texture->EnsureSubresourceContentInitialized(commandContext, range);
+        DAWN_TRY(texture->EnsureSubresourceContentInitialized(commandContext, range));
     }
+    return {};
 }
 
 MaybeError EncodeMetalRenderPass(Device* device,
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 99d219b..9eadba6 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -451,24 +451,26 @@
 MaybeError CommandBuffer::Execute() {
     const OpenGLFunctions& gl = ToBackend(GetDevice())->GetGL();
 
-    auto LazyClearSyncScope = [](const SyncScopeResourceUsage& scope) {
+    auto LazyClearSyncScope = [](const SyncScopeResourceUsage& scope) -> MaybeError {
         for (size_t i = 0; i < scope.textures.size(); i++) {
             Texture* texture = ToBackend(scope.textures[i]);
 
             // Clear subresources that are not render attachments. Render attachments will be
             // cleared in RecordBeginRenderPass by setting the loadop to clear when the texture
             // subresource has not been initialized before the render pass.
-            scope.textureUsages[i].Iterate(
-                [&](const SubresourceRange& range, wgpu::TextureUsage usage) {
+            DAWN_TRY(scope.textureUsages[i].Iterate(
+                [&](const SubresourceRange& range, wgpu::TextureUsage usage) -> MaybeError {
                     if (usage & ~wgpu::TextureUsage::RenderAttachment) {
-                        texture->EnsureSubresourceContentInitialized(range);
+                        DAWN_TRY(texture->EnsureSubresourceContentInitialized(range));
                     }
-                });
+                    return {};
+                }));
         }
 
         for (BufferBase* bufferBase : scope.buffers) {
             ToBackend(bufferBase)->EnsureDataInitialized();
         }
+        return {};
     };
 
     size_t nextComputePassNumber = 0;
@@ -481,7 +483,7 @@
                 mCommands.NextCommand<BeginComputePassCmd>();
                 for (const SyncScopeResourceUsage& scope :
                      GetResourceUsages().computePasses[nextComputePassNumber].dispatchUsages) {
-                    LazyClearSyncScope(scope);
+                    DAWN_TRY(LazyClearSyncScope(scope));
                 }
                 DAWN_TRY(ExecuteComputePass());
 
@@ -491,7 +493,8 @@
 
             case Command::BeginRenderPass: {
                 auto* cmd = mCommands.NextCommand<BeginRenderPassCmd>();
-                LazyClearSyncScope(GetResourceUsages().renderPasses[nextRenderPassNumber]);
+                DAWN_TRY(
+                    LazyClearSyncScope(GetResourceUsages().renderPasses[nextRenderPassNumber]));
                 LazyClearRenderPassAttachments(cmd);
                 DAWN_TRY(ExecuteRenderPass(cmd));
 
@@ -546,7 +549,7 @@
                                                   dst.mipLevel)) {
                     dst.texture->SetIsSubresourceContentInitialized(true, range);
                 } else {
-                    ToBackend(dst.texture)->EnsureSubresourceContentInitialized(range);
+                    DAWN_TRY(ToBackend(dst.texture)->EnsureSubresourceContentInitialized(range));
                 }
 
                 gl.BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->GetHandle());
@@ -593,7 +596,7 @@
                 buffer->EnsureDataInitializedAsDestination(copy);
 
                 SubresourceRange subresources = GetSubresourcesAffectedByCopy(src, copy->copySize);
-                texture->EnsureSubresourceContentInitialized(subresources);
+                DAWN_TRY(texture->EnsureSubresourceContentInitialized(subresources));
                 // The only way to move data from a texture to a buffer in GL is via
                 // glReadPixels with a pack buffer. Create a temporary FBO for the copy.
                 gl.BindTexture(target, texture->GetHandle());
@@ -694,11 +697,11 @@
                 SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize);
                 SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize);
 
-                srcTexture->EnsureSubresourceContentInitialized(srcRange);
+                DAWN_TRY(srcTexture->EnsureSubresourceContentInitialized(srcRange));
                 if (IsCompleteSubresourceCopiedTo(dstTexture, copySize, dst.mipLevel)) {
                     dstTexture->SetIsSubresourceContentInitialized(true, dstRange);
                 } else {
-                    dstTexture->EnsureSubresourceContentInitialized(dstRange);
+                    DAWN_TRY(dstTexture->EnsureSubresourceContentInitialized(dstRange));
                 }
                 CopyImageSubData(gl, src.aspect, srcTexture->GetHandle(), srcTexture->GetGLTarget(),
                                  src.mipLevel, src.origin, dstTexture->GetHandle(),
diff --git a/src/dawn/native/opengl/DeviceGL.cpp b/src/dawn/native/opengl/DeviceGL.cpp
index 40e948b..2e4f8a9 100644
--- a/src/dawn/native/opengl/DeviceGL.cpp
+++ b/src/dawn/native/opengl/DeviceGL.cpp
@@ -250,7 +250,7 @@
     return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
 }
 ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(const TextureDescriptor* descriptor) {
-    return AcquireRef(new Texture(this, descriptor));
+    return Texture::Create(this, descriptor);
 }
 ResultOrError<Ref<TextureViewBase>> Device::CreateTextureViewImpl(
     TextureBase* texture,
@@ -319,10 +319,10 @@
     if (textureDescriptor->size.width != static_cast<uint32_t>(width) ||
         textureDescriptor->size.height != static_cast<uint32_t>(height) ||
         textureDescriptor->size.depthOrArrayLayers != 1) {
-        ConsumedError(DAWN_VALIDATION_ERROR(
+        gl.DeleteTextures(1, &tex);
+        HandleError(DAWN_VALIDATION_ERROR(
             "EGLImage size (width: %u, height: %u, depth: 1) doesn't match descriptor size %s.",
             width, height, &textureDescriptor->size));
-        gl.DeleteTextures(1, &tex);
         return nullptr;
     }
 
diff --git a/src/dawn/native/opengl/QueueGL.cpp b/src/dawn/native/opengl/QueueGL.cpp
index f056c16..5042a8b 100644
--- a/src/dawn/native/opengl/QueueGL.cpp
+++ b/src/dawn/native/opengl/QueueGL.cpp
@@ -69,7 +69,7 @@
     if (IsCompleteSubresourceCopiedTo(destination.texture, writeSizePixel, destination.mipLevel)) {
         destination.texture->SetIsSubresourceContentInitialized(true, range);
     } else {
-        ToBackend(destination.texture)->EnsureSubresourceContentInitialized(range);
+        DAWN_TRY(ToBackend(destination.texture)->EnsureSubresourceContentInitialized(range));
     }
     DoTexSubImage(ToBackend(GetDevice())->GetGL(), textureCopy, data, dataLayout, writeSizePixel);
     ToBackend(destination.texture)->Touch();
diff --git a/src/dawn/native/opengl/TextureGL.cpp b/src/dawn/native/opengl/TextureGL.cpp
index 906e398..4774a5a 100644
--- a/src/dawn/native/opengl/TextureGL.cpp
+++ b/src/dawn/native/opengl/TextureGL.cpp
@@ -15,6 +15,7 @@
 #include "dawn/native/opengl/TextureGL.h"
 
 #include <limits>
+#include <utility>
 
 #include "dawn/common/Assert.h"
 #include "dawn/common/Constants.h"
@@ -170,6 +171,16 @@
 
 // Texture
 
+// static
+ResultOrError<Ref<Texture>> Texture::Create(Device* device, const TextureDescriptor* descriptor) {
+    Ref<Texture> texture = AcquireRef(new Texture(device, descriptor));
+    if (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
+        DAWN_TRY(
+            texture->ClearTexture(texture->GetAllSubresources(), TextureBase::ClearValue::NonZero));
+    }
+    return std::move(texture);
+}
+
 Texture::Texture(Device* device, const TextureDescriptor* descriptor)
     : Texture(device, descriptor, 0, TextureState::OwnedInternal) {
     const OpenGLFunctions& gl = device->GetGL();
@@ -186,11 +197,6 @@
     // The texture is not complete if it uses mipmapping and not all levels up to
     // MAX_LEVEL have been defined.
     gl.TexParameteri(mTarget, GL_TEXTURE_MAX_LEVEL, levels - 1);
-
-    if (GetDevice()->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
-        GetDevice()->ConsumedError(
-            ClearTexture(GetAllSubresources(), TextureBase::ClearValue::NonZero));
-    }
 }
 
 void Texture::Touch() {
@@ -539,13 +545,14 @@
     return {};
 }
 
-void Texture::EnsureSubresourceContentInitialized(const SubresourceRange& range) {
+MaybeError Texture::EnsureSubresourceContentInitialized(const SubresourceRange& range) {
     if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
-        return;
+        return {};
     }
     if (!IsSubresourceContentInitialized(range)) {
-        GetDevice()->ConsumedError(ClearTexture(range, TextureBase::ClearValue::Zero));
+        DAWN_TRY(ClearTexture(range, TextureBase::ClearValue::Zero));
     }
+    return {};
 }
 
 // TextureView
diff --git a/src/dawn/native/opengl/TextureGL.h b/src/dawn/native/opengl/TextureGL.h
index c9bf63f..d9bacc8 100644
--- a/src/dawn/native/opengl/TextureGL.h
+++ b/src/dawn/native/opengl/TextureGL.h
@@ -26,7 +26,7 @@
 
 class Texture final : public TextureBase {
   public:
-    Texture(Device* device, const TextureDescriptor* descriptor);
+    static ResultOrError<Ref<Texture>> Create(Device* device, const TextureDescriptor* descriptor);
     Texture(Device* device, const TextureDescriptor* descriptor, GLuint handle, TextureState state);
 
     GLuint GetHandle() const;
@@ -35,9 +35,10 @@
     uint32_t GetGenID() const;
     void Touch();
 
-    void EnsureSubresourceContentInitialized(const SubresourceRange& range);
+    MaybeError EnsureSubresourceContentInitialized(const SubresourceRange& range);
 
   private:
+    Texture(Device* device, const TextureDescriptor* descriptor);
     ~Texture() override;
 
     void DestroyImpl() override;
diff --git a/src/dawn/native/vulkan/CommandBufferVk.cpp b/src/dawn/native/vulkan/CommandBufferVk.cpp
index 54e197d..6ddfba0 100644
--- a/src/dawn/native/vulkan/CommandBufferVk.cpp
+++ b/src/dawn/native/vulkan/CommandBufferVk.cpp
@@ -154,9 +154,9 @@
 
 // Records the necessary barriers for a synchronization scope using the resource usage
 // data pre-computed in the frontend. Also performs lazy initialization if required.
-void TransitionAndClearForSyncScope(Device* device,
-                                    CommandRecordingContext* recordingContext,
-                                    const SyncScopeResourceUsage& scope) {
+MaybeError TransitionAndClearForSyncScope(Device* device,
+                                          CommandRecordingContext* recordingContext,
+                                          const SyncScopeResourceUsage& scope) {
     std::vector<VkBufferMemoryBarrier> bufferBarriers;
     std::vector<VkImageMemoryBarrier> imageBarriers;
     VkPipelineStageFlags srcStages = 0;
@@ -179,12 +179,13 @@
         // Clear subresources that are not render attachments. Render attachments will be
         // cleared in RecordBeginRenderPass by setting the loadop to clear when the texture
         // subresource has not been initialized before the render pass.
-        scope.textureUsages[i].Iterate(
-            [&](const SubresourceRange& range, wgpu::TextureUsage usage) {
+        DAWN_TRY(scope.textureUsages[i].Iterate(
+            [&](const SubresourceRange& range, wgpu::TextureUsage usage) -> MaybeError {
                 if (usage & ~wgpu::TextureUsage::RenderAttachment) {
-                    texture->EnsureSubresourceContentInitialized(recordingContext, range);
+                    DAWN_TRY(texture->EnsureSubresourceContentInitialized(recordingContext, range));
                 }
-            });
+                return {};
+            }));
         texture->TransitionUsageForPass(recordingContext, scope.textureUsages[i], &imageBarriers,
                                         &srcStages, &dstStages);
     }
@@ -194,6 +195,7 @@
                                       nullptr, bufferBarriers.size(), bufferBarriers.data(),
                                       imageBarriers.size(), imageBarriers.data());
     }
+    return {};
 }
 
 MaybeError RecordBeginRenderPass(CommandRecordingContext* recordingContext,
@@ -512,8 +514,8 @@
     // And resets the used query sets which are rewritten on the render pass.
     auto PrepareResourcesForRenderPass = [](Device* device,
                                             CommandRecordingContext* recordingContext,
-                                            const RenderPassResourceUsage& usages) {
-        TransitionAndClearForSyncScope(device, recordingContext, usages);
+                                            const RenderPassResourceUsage& usages) -> MaybeError {
+        DAWN_TRY(TransitionAndClearForSyncScope(device, recordingContext, usages));
 
         // Reset all query set used on current render pass together before beginning render pass
         // because the reset command must be called outside render pass
@@ -521,6 +523,7 @@
             ResetUsedQuerySetsOnRenderPass(device, recordingContext->commandBuffer,
                                            usages.querySets[i], usages.queryAvailabilities[i]);
         }
+        return {};
     };
 
     size_t nextComputePassNumber = 0;
@@ -580,8 +583,8 @@
                     // Since texture has been overwritten, it has been "initialized"
                     dst.texture->SetIsSubresourceContentInitialized(true, range);
                 } else {
-                    ToBackend(dst.texture)
-                        ->EnsureSubresourceContentInitialized(recordingContext, range);
+                    DAWN_TRY(ToBackend(dst.texture)
+                                 ->EnsureSubresourceContentInitialized(recordingContext, range));
                 }
                 ToBackend(src.buffer)
                     ->TransitionUsageNow(recordingContext, wgpu::BufferUsage::CopySrc);
@@ -614,8 +617,8 @@
                 SubresourceRange range =
                     GetSubresourcesAffectedByCopy(copy->source, copy->copySize);
 
-                ToBackend(src.texture)
-                    ->EnsureSubresourceContentInitialized(recordingContext, range);
+                DAWN_TRY(ToBackend(src.texture)
+                             ->EnsureSubresourceContentInitialized(recordingContext, range));
 
                 ToBackend(src.texture)
                     ->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc, range);
@@ -642,15 +645,15 @@
                 SubresourceRange srcRange = GetSubresourcesAffectedByCopy(src, copy->copySize);
                 SubresourceRange dstRange = GetSubresourcesAffectedByCopy(dst, copy->copySize);
 
-                ToBackend(src.texture)
-                    ->EnsureSubresourceContentInitialized(recordingContext, srcRange);
+                DAWN_TRY(ToBackend(src.texture)
+                             ->EnsureSubresourceContentInitialized(recordingContext, srcRange));
                 if (IsCompleteSubresourceCopiedTo(dst.texture.Get(), copy->copySize,
                                                   dst.mipLevel)) {
                     // Since destination texture has been overwritten, it has been "initialized"
                     dst.texture->SetIsSubresourceContentInitialized(true, dstRange);
                 } else {
-                    ToBackend(dst.texture)
-                        ->EnsureSubresourceContentInitialized(recordingContext, dstRange);
+                    DAWN_TRY(ToBackend(dst.texture)
+                                 ->EnsureSubresourceContentInitialized(recordingContext, dstRange));
                 }
 
                 if (src.texture.Get() == dst.texture.Get() && src.mipLevel == dst.mipLevel) {
@@ -730,9 +733,9 @@
             case Command::BeginRenderPass: {
                 BeginRenderPassCmd* cmd = mCommands.NextCommand<BeginRenderPassCmd>();
 
-                PrepareResourcesForRenderPass(
+                DAWN_TRY(PrepareResourcesForRenderPass(
                     device, recordingContext,
-                    GetResourceUsages().renderPasses[nextRenderPassNumber]);
+                    GetResourceUsages().renderPasses[nextRenderPassNumber]));
 
                 LazyClearRenderPassAttachments(cmd);
                 DAWN_TRY(RecordRenderPass(recordingContext, cmd));
@@ -935,8 +938,8 @@
             case Command::Dispatch: {
                 DispatchCmd* dispatch = mCommands.NextCommand<DispatchCmd>();
 
-                TransitionAndClearForSyncScope(device, recordingContext,
-                                               resourceUsages.dispatchUsages[currentDispatch]);
+                DAWN_TRY(TransitionAndClearForSyncScope(
+                    device, recordingContext, resourceUsages.dispatchUsages[currentDispatch]));
                 descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_COMPUTE);
 
                 device->fn.CmdDispatch(commands, dispatch->x, dispatch->y, dispatch->z);
@@ -948,8 +951,8 @@
                 DispatchIndirectCmd* dispatch = mCommands.NextCommand<DispatchIndirectCmd>();
                 VkBuffer indirectBuffer = ToBackend(dispatch->indirectBuffer)->GetHandle();
 
-                TransitionAndClearForSyncScope(device, recordingContext,
-                                               resourceUsages.dispatchUsages[currentDispatch]);
+                DAWN_TRY(TransitionAndClearForSyncScope(
+                    device, recordingContext, resourceUsages.dispatchUsages[currentDispatch]));
                 descriptorSets.Apply(device, recordingContext, VK_PIPELINE_BIND_POINT_COMPUTE);
 
                 device->fn.CmdDispatchIndirect(commands, indirectBuffer,
diff --git a/src/dawn/native/vulkan/DeviceVk.cpp b/src/dawn/native/vulkan/DeviceVk.cpp
index f420380..370b3b5 100644
--- a/src/dawn/native/vulkan/DeviceVk.cpp
+++ b/src/dawn/native/vulkan/DeviceVk.cpp
@@ -816,7 +816,8 @@
         // Since texture has been overwritten, it has been "initialized"
         dst.texture->SetIsSubresourceContentInitialized(true, range);
     } else {
-        ToBackend(dst.texture)->EnsureSubresourceContentInitialized(recordingContext, range);
+        DAWN_TRY(
+            ToBackend(dst.texture)->EnsureSubresourceContentInitialized(recordingContext, range));
     }
     // Insert pipeline barrier to ensure correct ordering with previous memory operations on the
     // texture.
diff --git a/src/dawn/native/vulkan/TextureVk.cpp b/src/dawn/native/vulkan/TextureVk.cpp
index 68e858c..b15b9be 100644
--- a/src/dawn/native/vulkan/TextureVk.cpp
+++ b/src/dawn/native/vulkan/TextureVk.cpp
@@ -1342,17 +1342,17 @@
     return {};
 }
 
-void Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
-                                                  const SubresourceRange& range) {
+MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
+                                                        const SubresourceRange& range) {
     if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
-        return;
+        return {};
     }
     if (!IsSubresourceContentInitialized(range)) {
         // If subresource has not been initialized, clear it to black as it could contain dirty
         // bits from recycled memory
-        GetDevice()->ConsumedError(
-            ClearTexture(recordingContext, range, TextureBase::ClearValue::Zero));
+        DAWN_TRY(ClearTexture(recordingContext, range, TextureBase::ClearValue::Zero));
     }
+    return {};
 }
 
 void Texture::UpdateExternalSemaphoreHandle(ExternalSemaphoreHandle handle) {
diff --git a/src/dawn/native/vulkan/TextureVk.h b/src/dawn/native/vulkan/TextureVk.h
index 48017bf..0d9a923 100644
--- a/src/dawn/native/vulkan/TextureVk.h
+++ b/src/dawn/native/vulkan/TextureVk.h
@@ -84,8 +84,8 @@
     void TransitionEagerlyForExport(CommandRecordingContext* recordingContext);
     std::vector<VkSemaphore> AcquireWaitRequirements();
 
-    void EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
-                                             const SubresourceRange& range);
+    MaybeError EnsureSubresourceContentInitialized(CommandRecordingContext* recordingContext,
+                                                   const SubresourceRange& range);
 
     VkImageLayout GetCurrentLayoutForSwapChain() const;
 
diff --git a/src/dawn/tests/unittests/SubresourceStorageTests.cpp b/src/dawn/tests/unittests/SubresourceStorageTests.cpp
index fb2759a..2f5fadd 100644
--- a/src/dawn/tests/unittests/SubresourceStorageTests.cpp
+++ b/src/dawn/tests/unittests/SubresourceStorageTests.cpp
@@ -12,14 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <memory>
+#include <string>
 #include <vector>
 
 #include "dawn/common/Log.h"
 #include "dawn/native/SubresourceStorage.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace dawn::native {
 
+using ::testing::HasSubstr;
+
 // A fake class that replicates the behavior of SubresourceStorage but without any compression
 // and is used to compare the results of operations on SubresourceStorage against the "ground
 // truth" of FakeStorage.
@@ -211,6 +216,31 @@
     return a.value == b.value;
 }
 
+// Tests that the MaybeError version of Iterate returns the first error that it encounters.
+TEST(SubresourceStorageTest, IterateMaybeError) {
+    // Create a resource with multiple layers of different data so that we can ensure that the
+    // iterate function runs more than once.
+    constexpr uint32_t kLayers = 4;
+    SubresourceStorage<uint32_t> s(Aspect::Color, kLayers, 1);
+    for (uint32_t layer = 0; layer < kLayers; layer++) {
+        s.Update(SubresourceRange::MakeSingle(Aspect::Color, layer, 0),
+                 [&](const SubresourceRange&, uint32_t* data) { *data = layer + 1; });
+    }
+
+    // Make sure that the first error is returned.
+    uint32_t errorLayer = 0;
+    MaybeError maybeError =
+        s.Iterate([&](const SubresourceRange& range, const uint32_t& layer) -> MaybeError {
+            if (!errorLayer) {
+                errorLayer = layer;
+            }
+            return DAWN_VALIDATION_ERROR("Errored at layer: %d", layer);
+        });
+    ASSERT_TRUE(maybeError.IsError());
+    std::unique_ptr<ErrorData> error = maybeError.AcquireError();
+    EXPECT_THAT(error->GetFormattedMessage(), HasSubstr(std::to_string(errorLayer)));
+}
+
 // Test that the default value is correctly set.
 TEST(SubresourceStorageTest, DefaultValue) {
     // Test setting no default value for a primitive type.