Make CommandBufferValidation use Error.h
diff --git a/src/backend/CommandBuffer.cpp b/src/backend/CommandBuffer.cpp
index 04ec426..2d140ab 100644
--- a/src/backend/CommandBuffer.cpp
+++ b/src/backend/CommandBuffer.cpp
@@ -32,12 +32,10 @@
 
     namespace {
 
-        bool ValidateCopyLocationFitsInTexture(CommandBufferBuilder* builder,
-                                               const TextureCopyLocation& location) {
+        MaybeError ValidateCopyLocationFitsInTexture(const TextureCopyLocation& location) {
             const TextureBase* texture = location.texture.Get();
             if (location.level >= texture->GetNumMipLevels()) {
-                builder->HandleError("Copy mip-level out of range");
-                return false;
+                NXT_RETURN_ERROR("Copy mip-level out of range");
             }
 
             // All texture dimensions are in uint32_t so by doing checks in uint64_t we avoid
@@ -47,18 +45,16 @@
                     (static_cast<uint64_t>(texture->GetWidth()) >> level) ||
                 uint64_t(location.y) + uint64_t(location.height) >
                     (static_cast<uint64_t>(texture->GetHeight()) >> level)) {
-                builder->HandleError("Copy would touch outside of the texture");
-                return false;
+                NXT_RETURN_ERROR("Copy would touch outside of the texture");
             }
 
             // TODO(cwallez@chromium.org): Check the depth bound differently for 2D arrays and 3D
             // textures
             if (location.z != 0 || location.depth != 1) {
-                builder->HandleError("No support for z != 0 and depth != 1 for now");
-                return false;
+                NXT_RETURN_ERROR("No support for z != 0 and depth != 1 for now");
             }
 
-            return true;
+            return {};
         }
 
         bool FitsInBuffer(const BufferBase* buffer, uint32_t offset, uint32_t size) {
@@ -66,38 +62,33 @@
             return offset <= bufferSize && (size <= (bufferSize - offset));
         }
 
-        bool ValidateCopySizeFitsInBuffer(CommandBufferBuilder* builder,
-                                          const BufferCopyLocation& location,
-                                          uint32_t dataSize) {
+        MaybeError ValidateCopySizeFitsInBuffer(const BufferCopyLocation& location,
+                                                uint32_t dataSize) {
             if (!FitsInBuffer(location.buffer.Get(), location.offset, dataSize)) {
-                builder->HandleError("Copy would overflow the buffer");
-                return false;
+                NXT_RETURN_ERROR("Copy would overflow the buffer");
             }
 
-            return true;
+            return {};
         }
 
-        bool ValidateTexelBufferOffset(CommandBufferBuilder* builder,
-                                       TextureBase* texture,
-                                       const BufferCopyLocation& location) {
+        MaybeError ValidateTexelBufferOffset(TextureBase* texture,
+                                             const BufferCopyLocation& location) {
             uint32_t texelSize =
                 static_cast<uint32_t>(TextureFormatPixelSize(texture->GetFormat()));
             if (location.offset % texelSize != 0) {
-                builder->HandleError("Buffer offset must be a multiple of the texel size");
-                return false;
+                NXT_RETURN_ERROR("Buffer offset must be a multiple of the texel size");
             }
 
-            return true;
+            return {};
         }
 
-        bool ComputeTextureCopyBufferSize(CommandBufferBuilder*,
-                                          const TextureCopyLocation& location,
-                                          uint32_t rowPitch,
-                                          uint32_t* bufferSize) {
+        MaybeError ComputeTextureCopyBufferSize(const TextureCopyLocation& location,
+                                                uint32_t rowPitch,
+                                                uint32_t* bufferSize) {
             // TODO(cwallez@chromium.org): check for overflows
             *bufferSize = (rowPitch * (location.height - 1) + location.width) * location.depth;
 
-            return true;
+            return {};
         }
 
         uint32_t ComputeDefaultRowPitch(TextureBase* texture, uint32_t width) {
@@ -105,45 +96,35 @@
             return texelSize * width;
         }
 
-        bool ValidateRowPitch(CommandBufferBuilder* builder,
-                              const TextureCopyLocation& location,
-                              uint32_t rowPitch) {
+        MaybeError ValidateRowPitch(const TextureCopyLocation& location, uint32_t rowPitch) {
             if (rowPitch % kTextureRowPitchAlignment != 0) {
-                builder->HandleError("Row pitch must be a multiple of 256");
-                return false;
+                NXT_RETURN_ERROR("Row pitch must be a multiple of 256");
             }
 
             uint32_t texelSize = TextureFormatPixelSize(location.texture.Get()->GetFormat());
             if (rowPitch < location.width * texelSize) {
-                builder->HandleError("Row pitch must not be less than the number of bytes per row");
-                return false;
+                NXT_RETURN_ERROR("Row pitch must not be less than the number of bytes per row");
             }
 
-            return true;
+            return {};
         }
 
-        bool ValidateCanUseAs(CommandBufferBuilder* builder,
-                              BufferBase* buffer,
-                              nxt::BufferUsageBit usage) {
+        MaybeError ValidateCanUseAs(BufferBase* buffer, nxt::BufferUsageBit usage) {
             ASSERT(HasZeroOrOneBits(usage));
             if (!(buffer->GetAllowedUsage() & usage)) {
-                builder->HandleError("buffer doesn't have the required usage.");
-                return false;
+                NXT_RETURN_ERROR("buffer doesn't have the required usage.");
             }
 
-            return true;
+            return {};
         }
 
-        bool ValidateCanUseAs(CommandBufferBuilder* builder,
-                              TextureBase* texture,
-                              nxt::TextureUsageBit usage) {
+        MaybeError ValidateCanUseAs(TextureBase* texture, nxt::TextureUsageBit usage) {
             ASSERT(HasZeroOrOneBits(usage));
             if (!(texture->GetAllowedUsage() & usage)) {
-                builder->HandleError("texture doesn't have the required usage.");
-                return false;
+                NXT_RETURN_ERROR("texture doesn't have the required usage.");
             }
 
-            return true;
+            return {};
         }
 
         enum class PassType {
@@ -184,10 +165,10 @@
             }
 
             // Performs the per-pass usage validation checks
-            bool AreUsagesValid(PassType pass) const {
+            MaybeError ValidateUsages(PassType pass) const {
                 // Storage resources cannot be used twice in the same compute pass
                 if (pass == PassType::Compute && mStorageUsedMultipleTimes) {
-                    return false;
+                    NXT_RETURN_ERROR("Storage resource used multiple times in compute pass");
                 }
 
                 // Buffers can only be used as single-write or multiple read.
@@ -196,14 +177,15 @@
                     nxt::BufferUsageBit usage = it.second;
 
                     if (usage & ~buffer->GetAllowedUsage()) {
-                        return false;
+                        NXT_RETURN_ERROR("Buffer missing usage for the pass");
                     }
 
                     bool readOnly = (usage & kReadOnlyBufferUsages) == usage;
                     bool singleUse = nxt::HasZeroOrOneBits(usage);
 
                     if (!readOnly && !singleUse) {
-                        return false;
+                        NXT_RETURN_ERROR(
+                            "Buffer used as writeable usage and another usage in pass");
                     }
                 }
 
@@ -214,17 +196,17 @@
                     nxt::TextureUsageBit usage = it.second;
 
                     if (usage & ~texture->GetAllowedUsage()) {
-                        return false;
+                        NXT_RETURN_ERROR("Texture missing usage for the pass");
                     }
 
                     // For textures the only read-only usage in a pass is Sampled, so checking the
                     // usage constraint simplifies to checking a single usage bit is set.
                     if (!nxt::HasZeroOrOneBits(it.second)) {
-                        return false;
+                        NXT_RETURN_ERROR("Texture used with more than one usage in pass");
                     }
                 }
 
-                return true;
+                return {};
             }
 
             // Returns the per-pass usage for use by backends for APIs with explicit barriers.
@@ -297,7 +279,7 @@
     // CommandBufferBuilder
 
     CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device)
-        : Builder(device), mState(std::make_unique<CommandBufferStateTracker>(this)) {
+        : Builder(device), mState(std::make_unique<CommandBufferStateTracker>()) {
     }
 
     CommandBufferBuilder::~CommandBufferBuilder() {
@@ -333,7 +315,7 @@
 
     // Implementation of the command buffer validation that can be precomputed before submit
 
-    bool CommandBufferBuilder::ValidateGetResult() {
+    MaybeError CommandBufferBuilder::ValidateGetResult() {
         MoveToIterator();
         mIterator.Reset();
 
@@ -342,78 +324,73 @@
             switch (type) {
                 case Command::BeginComputePass: {
                     mIterator.NextCommand<BeginComputePassCmd>();
-                    if (!ValidateComputePass()) {
-                        return false;
-                    }
+                    NXT_TRY(ValidateComputePass());
                 } break;
 
                 case Command::BeginRenderPass: {
                     BeginRenderPassCmd* cmd = mIterator.NextCommand<BeginRenderPassCmd>();
-                    if (!ValidateRenderPass(cmd->info.Get())) {
-                        return false;
-                    }
+                    NXT_TRY(ValidateRenderPass(cmd->info.Get()));
                 } break;
 
                 case Command::CopyBufferToBuffer: {
                     CopyBufferToBufferCmd* copy = mIterator.NextCommand<CopyBufferToBufferCmd>();
-                    if (!ValidateCopySizeFitsInBuffer(this, copy->source, copy->size) ||
-                        !ValidateCopySizeFitsInBuffer(this, copy->destination, copy->size) ||
-                        !ValidateCanUseAs(this, copy->source.buffer.Get(),
-                                          nxt::BufferUsageBit::TransferSrc) ||
-                        !ValidateCanUseAs(this, copy->destination.buffer.Get(),
-                                          nxt::BufferUsageBit::TransferDst)) {
-                        return false;
-                    }
+
+                    NXT_TRY(ValidateCopySizeFitsInBuffer(copy->source, copy->size));
+                    NXT_TRY(ValidateCopySizeFitsInBuffer(copy->destination, copy->size));
+
+                    NXT_TRY(ValidateCanUseAs(copy->source.buffer.Get(),
+                                             nxt::BufferUsageBit::TransferSrc));
+                    NXT_TRY(ValidateCanUseAs(copy->destination.buffer.Get(),
+                                             nxt::BufferUsageBit::TransferDst));
                 } break;
 
                 case Command::CopyBufferToTexture: {
                     CopyBufferToTextureCmd* copy = mIterator.NextCommand<CopyBufferToTextureCmd>();
 
                     uint32_t bufferCopySize = 0;
-                    if (!ValidateRowPitch(this, copy->destination, copy->rowPitch) ||
-                        !ComputeTextureCopyBufferSize(this, copy->destination, copy->rowPitch,
-                                                      &bufferCopySize) ||
-                        !ValidateCopyLocationFitsInTexture(this, copy->destination) ||
-                        !ValidateCopySizeFitsInBuffer(this, copy->source, bufferCopySize) ||
-                        !ValidateTexelBufferOffset(this, copy->destination.texture.Get(),
-                                                   copy->source) ||
-                        !ValidateCanUseAs(this, copy->source.buffer.Get(),
-                                          nxt::BufferUsageBit::TransferSrc) ||
-                        !ValidateCanUseAs(this, copy->destination.texture.Get(),
-                                          nxt::TextureUsageBit::TransferDst)) {
-                        return false;
-                    }
+                    NXT_TRY(ValidateRowPitch(copy->destination, copy->rowPitch));
+                    NXT_TRY(ComputeTextureCopyBufferSize(copy->destination, copy->rowPitch,
+                                                         &bufferCopySize));
+
+                    NXT_TRY(ValidateCopyLocationFitsInTexture(copy->destination));
+                    NXT_TRY(ValidateCopySizeFitsInBuffer(copy->source, bufferCopySize));
+                    NXT_TRY(
+                        ValidateTexelBufferOffset(copy->destination.texture.Get(), copy->source));
+
+                    NXT_TRY(ValidateCanUseAs(copy->source.buffer.Get(),
+                                             nxt::BufferUsageBit::TransferSrc));
+                    NXT_TRY(ValidateCanUseAs(copy->destination.texture.Get(),
+                                             nxt::TextureUsageBit::TransferDst));
                 } break;
 
                 case Command::CopyTextureToBuffer: {
                     CopyTextureToBufferCmd* copy = mIterator.NextCommand<CopyTextureToBufferCmd>();
 
                     uint32_t bufferCopySize = 0;
-                    if (!ValidateRowPitch(this, copy->source, copy->rowPitch) ||
-                        !ComputeTextureCopyBufferSize(this, copy->source, copy->rowPitch,
-                                                      &bufferCopySize) ||
-                        !ValidateCopyLocationFitsInTexture(this, copy->source) ||
-                        !ValidateCopySizeFitsInBuffer(this, copy->destination, bufferCopySize) ||
-                        !ValidateTexelBufferOffset(this, copy->source.texture.Get(),
-                                                   copy->destination) ||
-                        !ValidateCanUseAs(this, copy->source.texture.Get(),
-                                          nxt::TextureUsageBit::TransferSrc) ||
-                        !ValidateCanUseAs(this, copy->destination.buffer.Get(),
-                                          nxt::BufferUsageBit::TransferDst)) {
-                        return false;
-                    }
+                    NXT_TRY(ValidateRowPitch(copy->source, copy->rowPitch));
+                    NXT_TRY(ComputeTextureCopyBufferSize(copy->source, copy->rowPitch,
+                                                         &bufferCopySize));
+
+                    NXT_TRY(ValidateCopyLocationFitsInTexture(copy->source));
+                    NXT_TRY(ValidateCopySizeFitsInBuffer(copy->destination, bufferCopySize));
+                    NXT_TRY(
+                        ValidateTexelBufferOffset(copy->source.texture.Get(), copy->destination));
+
+                    NXT_TRY(ValidateCanUseAs(copy->source.texture.Get(),
+                                             nxt::TextureUsageBit::TransferSrc));
+                    NXT_TRY(ValidateCanUseAs(copy->destination.buffer.Get(),
+                                             nxt::BufferUsageBit::TransferDst));
                 } break;
 
                 default:
-                    HandleError("Command disallowed outside of a pass");
-                    return false;
+                    NXT_RETURN_ERROR("Command disallowed outside of a pass");
             }
         }
 
-        return true;
+        return {};
     }
 
-    bool CommandBufferBuilder::ValidateComputePass() {
+    MaybeError CommandBufferBuilder::ValidateComputePass() {
         PassResourceUsageTracker usageTracker;
 
         Command type;
@@ -422,28 +399,22 @@
                 case Command::EndComputePass: {
                     mIterator.NextCommand<EndComputePassCmd>();
 
-                    if (!usageTracker.AreUsagesValid(PassType::Compute)) {
-                        return false;
-                    }
+                    NXT_TRY(usageTracker.ValidateUsages(PassType::Compute));
                     mPassResourceUsages.push_back(usageTracker.AcquireResourceUsage());
 
                     mState->EndPass();
-                    return true;
+                    return {};
                 } break;
 
                 case Command::Dispatch: {
                     mIterator.NextCommand<DispatchCmd>();
-                    if (!mState->ValidateCanDispatch()) {
-                        return false;
-                    }
+                    NXT_TRY(mState->ValidateCanDispatch());
                 } break;
 
                 case Command::SetComputePipeline: {
                     SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
                     ComputePipelineBase* pipeline = cmd->pipeline.Get();
-                    if (!mState->SetComputePipeline(pipeline)) {
-                        return false;
-                    }
+                    mState->SetComputePipeline(pipeline);
                 } break;
 
                 case Command::SetPushConstants: {
@@ -453,9 +424,8 @@
                     // recorded because it impacts the size of an allocation in the
                     // CommandAllocator.
                     if (cmd->stages & ~nxt::ShaderStageBit::Compute) {
-                        HandleError(
+                        NXT_RETURN_ERROR(
                             "SetPushConstants stage must be compute or 0 in compute passes");
-                        return false;
                     }
                 } break;
 
@@ -467,16 +437,14 @@
                 } break;
 
                 default:
-                    HandleError("Command disallowed inside a compute pass");
-                    return false;
+                    NXT_RETURN_ERROR("Command disallowed inside a compute pass");
             }
         }
 
-        HandleError("Unfinished compute pass");
-        return false;
+        NXT_RETURN_ERROR("Unfinished compute pass");
     }
 
-    bool CommandBufferBuilder::ValidateRenderPass(RenderPassDescriptorBase* renderPass) {
+    MaybeError CommandBufferBuilder::ValidateRenderPass(RenderPassDescriptorBase* renderPass) {
         PassResourceUsageTracker usageTracker;
 
         // Track usage of the render pass attachments
@@ -496,27 +464,21 @@
                 case Command::EndRenderPass: {
                     mIterator.NextCommand<EndRenderPassCmd>();
 
-                    if (!usageTracker.AreUsagesValid(PassType::Render)) {
-                        return false;
-                    }
+                    NXT_TRY(usageTracker.ValidateUsages(PassType::Render));
                     mPassResourceUsages.push_back(usageTracker.AcquireResourceUsage());
 
                     mState->EndPass();
-                    return true;
+                    return {};
                 } break;
 
                 case Command::DrawArrays: {
                     mIterator.NextCommand<DrawArraysCmd>();
-                    if (!mState->ValidateCanDrawArrays()) {
-                        return false;
-                    }
+                    NXT_TRY(mState->ValidateCanDrawArrays());
                 } break;
 
                 case Command::DrawElements: {
                     mIterator.NextCommand<DrawElementsCmd>();
-                    if (!mState->ValidateCanDrawElements()) {
-                        return false;
-                    }
+                    NXT_TRY(mState->ValidateCanDrawElements());
                 } break;
 
                 case Command::SetRenderPipeline: {
@@ -524,13 +486,10 @@
                     RenderPipelineBase* pipeline = cmd->pipeline.Get();
 
                     if (!pipeline->IsCompatibleWith(renderPass)) {
-                        HandleError("Pipeline is incompatible with this render pass");
-                        return false;
+                        NXT_RETURN_ERROR("Pipeline is incompatible with this render pass");
                     }
 
-                    if (!mState->SetRenderPipeline(pipeline)) {
-                        return false;
-                    }
+                    mState->SetRenderPipeline(pipeline);
                 } break;
 
                 case Command::SetPushConstants: {
@@ -541,10 +500,9 @@
                     // CommandAllocator.
                     if (cmd->stages &
                         ~(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment)) {
-                        HandleError(
+                        NXT_RETURN_ERROR(
                             "SetPushConstants stage must be a subset of (vertex|fragment) in "
                             "render passes");
-                        return false;
                     }
                 } break;
 
@@ -571,9 +529,7 @@
                     SetIndexBufferCmd* cmd = mIterator.NextCommand<SetIndexBufferCmd>();
 
                     usageTracker.BufferUsedAs(cmd->buffer.Get(), nxt::BufferUsageBit::Index);
-                    if (!mState->SetIndexBuffer()) {
-                        return false;
-                    }
+                    NXT_TRY(mState->SetIndexBuffer());
                 } break;
 
                 case Command::SetVertexBuffers: {
@@ -583,18 +539,16 @@
 
                     for (uint32_t i = 0; i < cmd->count; ++i) {
                         usageTracker.BufferUsedAs(buffers[i].Get(), nxt::BufferUsageBit::Vertex);
-                        mState->SetVertexBuffer(cmd->startSlot + i);
+                        NXT_TRY(mState->SetVertexBuffer(cmd->startSlot + i));
                     }
                 } break;
 
                 default:
-                    HandleError("Command disallowed inside a render pass");
-                    return false;
+                    NXT_RETURN_ERROR("Command disallowed inside a render pass");
             }
         }
 
-        HandleError("Unfinished render pass");
-        return false;
+        NXT_RETURN_ERROR("Unfinished render pass");
     }
 
     // Implementation of the API's command recording methods
diff --git a/src/backend/CommandBuffer.h b/src/backend/CommandBuffer.h
index 7c0ef96..926353c 100644
--- a/src/backend/CommandBuffer.h
+++ b/src/backend/CommandBuffer.h
@@ -19,6 +19,7 @@
 
 #include "backend/Builder.h"
 #include "backend/CommandAllocator.h"
+#include "backend/Error.h"
 #include "backend/PassResourceUsage.h"
 #include "backend/RefCounted.h"
 
@@ -54,7 +55,7 @@
         CommandBufferBuilder(DeviceBase* device);
         ~CommandBufferBuilder();
 
-        bool ValidateGetResult();
+        MaybeError ValidateGetResult();
 
         CommandIterator AcquireCommands();
         std::vector<PassResourceUsage> AcquirePassResourceUsage();
@@ -134,8 +135,8 @@
         CommandBufferBase* GetResultImpl() override;
         void MoveToIterator();
 
-        bool ValidateComputePass();
-        bool ValidateRenderPass(RenderPassDescriptorBase* renderPass);
+        MaybeError ValidateComputePass();
+        MaybeError ValidateRenderPass(RenderPassDescriptorBase* renderPass);
 
         std::unique_ptr<CommandBufferStateTracker> mState;
         CommandAllocator mAllocator;
diff --git a/src/backend/CommandBufferStateTracker.cpp b/src/backend/CommandBufferStateTracker.cpp
index d8a60eb..5ffcac3 100644
--- a/src/backend/CommandBufferStateTracker.cpp
+++ b/src/backend/CommandBufferStateTracker.cpp
@@ -29,54 +29,47 @@
 
 namespace backend {
 
-    CommandBufferStateTracker::CommandBufferStateTracker(CommandBufferBuilder* mBuilder)
-        : mBuilder(mBuilder) {
-    }
-
-    bool CommandBufferStateTracker::ValidateCanDispatch() {
+    MaybeError CommandBufferStateTracker::ValidateCanDispatch() {
         constexpr ValidationAspects requiredAspects =
             1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS;
         if ((requiredAspects & ~mAspects).none()) {
             // Fast return-true path if everything is good
-            return true;
+            return {};
         }
 
         if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
-            mBuilder->HandleError("No active compute pipeline");
-            return false;
+            NXT_RETURN_ERROR("No active compute pipeline");
         }
         // Compute the lazily computed mAspects
         if (!RecomputeHaveAspectBindGroups()) {
-            mBuilder->HandleError("Bind group state not valid");
-            return false;
+            NXT_RETURN_ERROR("Bind group state not valid");
         }
-        return true;
+        return {};
     }
 
-    bool CommandBufferStateTracker::ValidateCanDrawArrays() {
+    MaybeError CommandBufferStateTracker::ValidateCanDrawArrays() {
         constexpr ValidationAspects requiredAspects = 1 << VALIDATION_ASPECT_PIPELINE |
                                                       1 << VALIDATION_ASPECT_BIND_GROUPS |
                                                       1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
         if ((requiredAspects & ~mAspects).none()) {
             // Fast return-true path if everything is good
-            return true;
+            return {};
         }
 
         return RevalidateCanDraw();
     }
 
-    bool CommandBufferStateTracker::ValidateCanDrawElements() {
+    MaybeError CommandBufferStateTracker::ValidateCanDrawElements() {
         constexpr ValidationAspects requiredAspects =
             1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS |
             1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_INDEX_BUFFER;
         if ((requiredAspects & ~mAspects).none()) {
             // Fast return-true path if everything is good
-            return true;
+            return {};
         }
 
         if (!mAspects[VALIDATION_ASPECT_INDEX_BUFFER]) {
-            mBuilder->HandleError("Cannot DrawElements without index buffer set");
-            return false;
+            NXT_RETURN_ERROR("Cannot DrawElements without index buffer set");
         }
         return RevalidateCanDraw();
     }
@@ -87,15 +80,13 @@
         mBindgroups.fill(nullptr);
     }
 
-    bool CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) {
+    void CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) {
         SetPipelineCommon(pipeline);
-        return true;
     }
 
-    bool CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) {
+    void CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) {
         mLastRenderPipeline = pipeline;
         SetPipelineCommon(pipeline);
-        return true;
     }
 
     void CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) {
@@ -103,24 +94,22 @@
         mBindgroups[index] = bindgroup;
     }
 
-    bool CommandBufferStateTracker::SetIndexBuffer() {
+    MaybeError CommandBufferStateTracker::SetIndexBuffer() {
         if (!HavePipeline()) {
-            mBuilder->HandleError("Can't set the index buffer without a pipeline");
-            return false;
+            NXT_RETURN_ERROR("Can't set the index buffer without a pipeline");
         }
 
         mAspects.set(VALIDATION_ASPECT_INDEX_BUFFER);
-        return true;
+        return {};
     }
 
-    bool CommandBufferStateTracker::SetVertexBuffer(uint32_t index) {
+    MaybeError CommandBufferStateTracker::SetVertexBuffer(uint32_t index) {
         if (!HavePipeline()) {
-            mBuilder->HandleError("Can't set vertex buffers without a pipeline");
-            return false;
+            NXT_RETURN_ERROR("Can't set vertex buffers without a pipeline");
         }
 
         mInputsSet.set(index);
-        return true;
+        return {};
     }
 
     bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() {
@@ -161,21 +150,18 @@
         return mAspects[VALIDATION_ASPECT_PIPELINE];
     }
 
-    bool CommandBufferStateTracker::RevalidateCanDraw() {
+    MaybeError CommandBufferStateTracker::RevalidateCanDraw() {
         if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
-            mBuilder->HandleError("No active render pipeline");
-            return false;
+            NXT_RETURN_ERROR("No active render pipeline");
         }
         // Compute the lazily computed mAspects
         if (!RecomputeHaveAspectBindGroups()) {
-            mBuilder->HandleError("Bind group state not valid");
-            return false;
+            NXT_RETURN_ERROR("Bind group state not valid");
         }
         if (!RecomputeHaveAspectVertexBuffers()) {
-            mBuilder->HandleError("Some vertex buffers are not set");
-            return false;
+            NXT_RETURN_ERROR("Some vertex buffers are not set");
         }
-        return true;
+        return {};
     }
 
     void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
diff --git a/src/backend/CommandBufferStateTracker.h b/src/backend/CommandBufferStateTracker.h
index 5a5f561..b097ad9 100644
--- a/src/backend/CommandBufferStateTracker.h
+++ b/src/backend/CommandBufferStateTracker.h
@@ -27,21 +27,19 @@
 
     class CommandBufferStateTracker {
       public:
-        explicit CommandBufferStateTracker(CommandBufferBuilder* builder);
-
         // Non-state-modifying validation functions
-        bool ValidateCanCopy() const;
-        bool ValidateCanDispatch();
-        bool ValidateCanDrawArrays();
-        bool ValidateCanDrawElements();
+        MaybeError ValidateCanCopy() const;
+        MaybeError ValidateCanDispatch();
+        MaybeError ValidateCanDrawArrays();
+        MaybeError ValidateCanDrawElements();
 
         // State-modifying methods
         void EndPass();
-        bool SetComputePipeline(ComputePipelineBase* pipeline);
-        bool SetRenderPipeline(RenderPipelineBase* pipeline);
+        void SetComputePipeline(ComputePipelineBase* pipeline);
+        void SetRenderPipeline(RenderPipelineBase* pipeline);
         void SetBindGroup(uint32_t index, BindGroupBase* bindgroup);
-        bool SetIndexBuffer();
-        bool SetVertexBuffer(uint32_t index);
+        MaybeError SetIndexBuffer();
+        MaybeError SetVertexBuffer(uint32_t index);
 
       private:
         enum ValidationAspect {
@@ -59,12 +57,10 @@
         bool RecomputeHaveAspectVertexBuffers();
 
         bool HavePipeline() const;
-        bool RevalidateCanDraw();
+        MaybeError RevalidateCanDraw();
 
         void SetPipelineCommon(PipelineBase* pipeline);
 
-        CommandBufferBuilder* mBuilder;
-
         ValidationAspects mAspects;
 
         std::bitset<kMaxBindGroups> mBindgroupsSet;
diff --git a/src/backend/Queue.cpp b/src/backend/Queue.cpp
index 7b3d26d..837b572 100644
--- a/src/backend/Queue.cpp
+++ b/src/backend/Queue.cpp
@@ -28,9 +28,9 @@
         return mDevice;
     }
 
-    bool QueueBase::ValidateSubmitCommand(CommandBufferBase*) {
+    MaybeError QueueBase::ValidateSubmitCommand(CommandBufferBase*) {
         // TODO(cwallez@chromium.org): Validate resources referenced by command buffers can be used
-        return true;
+        return {};
     }
 
 }  // namespace backend
diff --git a/src/backend/Queue.h b/src/backend/Queue.h
index 3ae10ef..86d1ba6 100644
--- a/src/backend/Queue.h
+++ b/src/backend/Queue.h
@@ -16,6 +16,7 @@
 #define BACKEND_QUEUE_H_
 
 #include "backend/Builder.h"
+#include "backend/Error.h"
 #include "backend/Forward.h"
 #include "backend/RefCounted.h"
 
@@ -30,20 +31,18 @@
         DeviceBase* GetDevice();
 
         template <typename T>
-        bool ValidateSubmit(uint32_t numCommands, T* const* commands) {
+        MaybeError ValidateSubmit(uint32_t numCommands, T* const* commands) {
             static_assert(std::is_base_of<CommandBufferBase, T>::value,
                           "invalid command buffer type");
 
             for (uint32_t i = 0; i < numCommands; ++i) {
-                if (!ValidateSubmitCommand(commands[i])) {
-                    return false;
-                }
+                NXT_TRY(ValidateSubmitCommand(commands[i]));
             }
-            return true;
+            return {};
         }
 
       private:
-        bool ValidateSubmitCommand(CommandBufferBase* command);
+        MaybeError ValidateSubmitCommand(CommandBufferBase* command);
 
         DeviceBase* mDevice;
     };