Factor EncodingContext out of CommandEncoderBase.

This patch factors the CommandAllocator, CommandIterator, and error
handling out of CommandEncoderBase so it can later be used by the
RenderBundleEncoder.

Bug: dawn:154
Change-Id: Ia4f8c3ce7f432f0887b619bd8090aa9bec7330fc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9181
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 42396ce..513c74e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -130,6 +130,8 @@
     "src/dawn_native/Device.h",
     "src/dawn_native/DynamicUploader.cpp",
     "src/dawn_native/DynamicUploader.h",
+    "src/dawn_native/EncodingContext.cpp",
+    "src/dawn_native/EncodingContext.h",
     "src/dawn_native/Error.cpp",
     "src/dawn_native/Error.h",
     "src/dawn_native/ErrorData.cpp",
diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp
index 93c0fe5..c5c351a 100644
--- a/src/dawn_native/CommandEncoder.cpp
+++ b/src/dawn_native/CommandEncoder.cpp
@@ -621,28 +621,8 @@
 
     }  // namespace
 
-    enum class CommandEncoderBase::EncodingState : uint8_t {
-        TopLevel,
-        ComputePass,
-        RenderPass,
-        Finished
-    };
-
     CommandEncoderBase::CommandEncoderBase(DeviceBase* device, const CommandEncoderDescriptor*)
-        : ObjectBase(device), mEncodingState(EncodingState::TopLevel) {
-    }
-
-    CommandEncoderBase::~CommandEncoderBase() {
-        if (!mWereCommandsAcquired) {
-            MoveToIterator();
-            FreeCommands(&mIterator);
-        }
-    }
-
-    CommandIterator CommandEncoderBase::AcquireCommands() {
-        ASSERT(!mWereCommandsAcquired);
-        mWereCommandsAcquired = true;
-        return std::move(mIterator);
+        : ObjectBase(device), mEncodingContext(device, this) {
     }
 
     CommandBufferResourceUsage CommandEncoderBase::AcquireResourceUsages() {
@@ -651,11 +631,8 @@
         return std::move(mResourceUsages);
     }
 
-    void CommandEncoderBase::MoveToIterator() {
-        if (!mWasMovedToIterator) {
-            mIterator = std::move(mAllocator);
-            mWasMovedToIterator = true;
-        }
+    CommandIterator CommandEncoderBase::AcquireCommands() {
+        return mEncodingContext.AcquireCommands();
     }
 
     // Implementation of the API's command recording methods
@@ -663,76 +640,91 @@
     ComputePassEncoderBase* CommandEncoderBase::BeginComputePass(
         const ComputePassDescriptor* descriptor) {
         DeviceBase* device = GetDevice();
-        if (ConsumedError(ValidateCanRecordTopLevelCommands())) {
-            return ComputePassEncoderBase::MakeError(device, this);
+
+        bool success =
+            mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+                DAWN_TRY(ValidateComputePassDescriptor(device, descriptor));
+
+                allocator->Allocate<BeginComputePassCmd>(Command::BeginComputePass);
+
+                return {};
+            });
+
+        if (success) {
+            ComputePassEncoderBase* passEncoder =
+                new ComputePassEncoderBase(device, this, &mEncodingContext);
+            mEncodingContext.EnterPass(passEncoder);
+            return passEncoder;
         }
 
-        if (ConsumedError(ValidateComputePassDescriptor(device, descriptor))) {
-            return ComputePassEncoderBase::MakeError(device, this);
-        }
-
-        mAllocator.Allocate<BeginComputePassCmd>(Command::BeginComputePass);
-
-        mEncodingState = EncodingState::ComputePass;
-        return new ComputePassEncoderBase(device, this, &mAllocator);
+        return ComputePassEncoderBase::MakeError(device, this, &mEncodingContext);
     }
 
     RenderPassEncoderBase* CommandEncoderBase::BeginRenderPass(
         const RenderPassDescriptor* descriptor) {
         DeviceBase* device = GetDevice();
 
-        if (ConsumedError(ValidateCanRecordTopLevelCommands())) {
-            return RenderPassEncoderBase::MakeError(device, this);
+        bool success =
+            mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+                uint32_t width = 0;
+                uint32_t height = 0;
+                uint32_t sampleCount = 0;
+
+                DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height,
+                                                      &sampleCount));
+
+                ASSERT(width > 0 && height > 0 && sampleCount > 0);
+
+                BeginRenderPassCmd* cmd =
+                    allocator->Allocate<BeginRenderPassCmd>(Command::BeginRenderPass);
+
+                for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
+                    if (descriptor->colorAttachments[i] != nullptr) {
+                        cmd->colorAttachmentsSet.set(i);
+                        cmd->colorAttachments[i].view = descriptor->colorAttachments[i]->attachment;
+                        cmd->colorAttachments[i].resolveTarget =
+                            descriptor->colorAttachments[i]->resolveTarget;
+                        cmd->colorAttachments[i].loadOp = descriptor->colorAttachments[i]->loadOp;
+                        cmd->colorAttachments[i].storeOp = descriptor->colorAttachments[i]->storeOp;
+                        cmd->colorAttachments[i].clearColor =
+                            descriptor->colorAttachments[i]->clearColor;
+                    }
+                }
+
+                cmd->hasDepthStencilAttachment = descriptor->depthStencilAttachment != nullptr;
+                if (cmd->hasDepthStencilAttachment) {
+                    cmd->hasDepthStencilAttachment = true;
+                    cmd->depthStencilAttachment.view =
+                        descriptor->depthStencilAttachment->attachment;
+                    cmd->depthStencilAttachment.clearDepth =
+                        descriptor->depthStencilAttachment->clearDepth;
+                    cmd->depthStencilAttachment.clearStencil =
+                        descriptor->depthStencilAttachment->clearStencil;
+                    cmd->depthStencilAttachment.depthLoadOp =
+                        descriptor->depthStencilAttachment->depthLoadOp;
+                    cmd->depthStencilAttachment.depthStoreOp =
+                        descriptor->depthStencilAttachment->depthStoreOp;
+                    cmd->depthStencilAttachment.stencilLoadOp =
+                        descriptor->depthStencilAttachment->stencilLoadOp;
+                    cmd->depthStencilAttachment.stencilStoreOp =
+                        descriptor->depthStencilAttachment->stencilStoreOp;
+                }
+
+                cmd->width = width;
+                cmd->height = height;
+                cmd->sampleCount = sampleCount;
+
+                return {};
+            });
+
+        if (success) {
+            RenderPassEncoderBase* passEncoder =
+                new RenderPassEncoderBase(device, this, &mEncodingContext);
+            mEncodingContext.EnterPass(passEncoder);
+            return passEncoder;
         }
 
-        uint32_t width = 0;
-        uint32_t height = 0;
-        uint32_t sampleCount = 0;
-        if (ConsumedError(
-                ValidateRenderPassDescriptor(device, descriptor, &width, &height, &sampleCount))) {
-            return RenderPassEncoderBase::MakeError(device, this);
-        }
-
-        ASSERT(width > 0 && height > 0 && sampleCount > 0);
-
-        mEncodingState = EncodingState::RenderPass;
-
-        BeginRenderPassCmd* cmd = mAllocator.Allocate<BeginRenderPassCmd>(Command::BeginRenderPass);
-
-        for (uint32_t i = 0; i < descriptor->colorAttachmentCount; ++i) {
-            if (descriptor->colorAttachments[i] != nullptr) {
-                cmd->colorAttachmentsSet.set(i);
-                cmd->colorAttachments[i].view = descriptor->colorAttachments[i]->attachment;
-                cmd->colorAttachments[i].resolveTarget =
-                    descriptor->colorAttachments[i]->resolveTarget;
-                cmd->colorAttachments[i].loadOp = descriptor->colorAttachments[i]->loadOp;
-                cmd->colorAttachments[i].storeOp = descriptor->colorAttachments[i]->storeOp;
-                cmd->colorAttachments[i].clearColor = descriptor->colorAttachments[i]->clearColor;
-            }
-        }
-
-        cmd->hasDepthStencilAttachment = descriptor->depthStencilAttachment != nullptr;
-        if (cmd->hasDepthStencilAttachment) {
-            cmd->hasDepthStencilAttachment = true;
-            cmd->depthStencilAttachment.view = descriptor->depthStencilAttachment->attachment;
-            cmd->depthStencilAttachment.clearDepth = descriptor->depthStencilAttachment->clearDepth;
-            cmd->depthStencilAttachment.clearStencil =
-                descriptor->depthStencilAttachment->clearStencil;
-            cmd->depthStencilAttachment.depthLoadOp =
-                descriptor->depthStencilAttachment->depthLoadOp;
-            cmd->depthStencilAttachment.depthStoreOp =
-                descriptor->depthStencilAttachment->depthStoreOp;
-            cmd->depthStencilAttachment.stencilLoadOp =
-                descriptor->depthStencilAttachment->stencilLoadOp;
-            cmd->depthStencilAttachment.stencilStoreOp =
-                descriptor->depthStencilAttachment->stencilStoreOp;
-        }
-
-        cmd->width = width;
-        cmd->height = height;
-        cmd->sampleCount = sampleCount;
-
-        return new RenderPassEncoderBase(device, this, &mAllocator);
+        return RenderPassEncoderBase::MakeError(device, this, &mEncodingContext);
     }
 
     void CommandEncoderBase::CopyBufferToBuffer(BufferBase* source,
@@ -740,208 +732,147 @@
                                                 BufferBase* destination,
                                                 uint64_t destinationOffset,
                                                 uint64_t size) {
-        if (ConsumedError(ValidateCanRecordTopLevelCommands())) {
-            return;
-        }
+        mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(source));
+            DAWN_TRY(GetDevice()->ValidateObject(destination));
 
-        if (ConsumedError(GetDevice()->ValidateObject(source))) {
-            return;
-        }
+            CopyBufferToBufferCmd* copy =
+                allocator->Allocate<CopyBufferToBufferCmd>(Command::CopyBufferToBuffer);
+            copy->source = source;
+            copy->sourceOffset = sourceOffset;
+            copy->destination = destination;
+            copy->destinationOffset = destinationOffset;
+            copy->size = size;
 
-        if (ConsumedError(GetDevice()->ValidateObject(destination))) {
-            return;
-        }
-
-        CopyBufferToBufferCmd* copy =
-            mAllocator.Allocate<CopyBufferToBufferCmd>(Command::CopyBufferToBuffer);
-        copy->source = source;
-        copy->sourceOffset = sourceOffset;
-        copy->destination = destination;
-        copy->destinationOffset = destinationOffset;
-        copy->size = size;
+            return {};
+        });
     }
 
     void CommandEncoderBase::CopyBufferToTexture(const BufferCopyView* source,
                                                  const TextureCopyView* destination,
                                                  const Extent3D* copySize) {
-        if (ConsumedError(ValidateCanRecordTopLevelCommands())) {
-            return;
-        }
+        mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(source->buffer));
+            DAWN_TRY(GetDevice()->ValidateObject(destination->texture));
 
-        if (ConsumedError(GetDevice()->ValidateObject(source->buffer))) {
-            return;
-        }
+            CopyBufferToTextureCmd* copy =
+                allocator->Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
+            copy->source.buffer = source->buffer;
+            copy->source.offset = source->offset;
+            copy->destination.texture = destination->texture;
+            copy->destination.origin = destination->origin;
+            copy->copySize = *copySize;
+            copy->destination.mipLevel = destination->mipLevel;
+            copy->destination.arrayLayer = destination->arrayLayer;
+            if (source->rowPitch == 0) {
+                copy->source.rowPitch =
+                    ComputeDefaultRowPitch(destination->texture->GetFormat(), copySize->width);
+            } else {
+                copy->source.rowPitch = source->rowPitch;
+            }
+            if (source->imageHeight == 0) {
+                copy->source.imageHeight = copySize->height;
+            } else {
+                copy->source.imageHeight = source->imageHeight;
+            }
 
-        if (ConsumedError(GetDevice()->ValidateObject(destination->texture))) {
-            return;
-        }
-
-        CopyBufferToTextureCmd* copy =
-            mAllocator.Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
-        copy->source.buffer = source->buffer;
-        copy->source.offset = source->offset;
-        copy->destination.texture = destination->texture;
-        copy->destination.origin = destination->origin;
-        copy->copySize = *copySize;
-        copy->destination.mipLevel = destination->mipLevel;
-        copy->destination.arrayLayer = destination->arrayLayer;
-        if (source->rowPitch == 0) {
-            copy->source.rowPitch =
-                ComputeDefaultRowPitch(destination->texture->GetFormat(), copySize->width);
-        } else {
-            copy->source.rowPitch = source->rowPitch;
-        }
-        if (source->imageHeight == 0) {
-            copy->source.imageHeight = copySize->height;
-        } else {
-            copy->source.imageHeight = source->imageHeight;
-        }
+            return {};
+        });
     }
 
     void CommandEncoderBase::CopyTextureToBuffer(const TextureCopyView* source,
                                                  const BufferCopyView* destination,
                                                  const Extent3D* copySize) {
-        if (ConsumedError(ValidateCanRecordTopLevelCommands())) {
-            return;
-        }
+        mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(source->texture));
+            DAWN_TRY(GetDevice()->ValidateObject(destination->buffer));
 
-        if (ConsumedError(GetDevice()->ValidateObject(source->texture))) {
-            return;
-        }
+            CopyTextureToBufferCmd* copy =
+                allocator->Allocate<CopyTextureToBufferCmd>(Command::CopyTextureToBuffer);
+            copy->source.texture = source->texture;
+            copy->source.origin = source->origin;
+            copy->copySize = *copySize;
+            copy->source.mipLevel = source->mipLevel;
+            copy->source.arrayLayer = source->arrayLayer;
+            copy->destination.buffer = destination->buffer;
+            copy->destination.offset = destination->offset;
+            if (destination->rowPitch == 0) {
+                copy->destination.rowPitch =
+                    ComputeDefaultRowPitch(source->texture->GetFormat(), copySize->width);
+            } else {
+                copy->destination.rowPitch = destination->rowPitch;
+            }
+            if (destination->imageHeight == 0) {
+                copy->destination.imageHeight = copySize->height;
+            } else {
+                copy->destination.imageHeight = destination->imageHeight;
+            }
 
-        if (ConsumedError(GetDevice()->ValidateObject(destination->buffer))) {
-            return;
-        }
-
-        CopyTextureToBufferCmd* copy =
-            mAllocator.Allocate<CopyTextureToBufferCmd>(Command::CopyTextureToBuffer);
-        copy->source.texture = source->texture;
-        copy->source.origin = source->origin;
-        copy->copySize = *copySize;
-        copy->source.mipLevel = source->mipLevel;
-        copy->source.arrayLayer = source->arrayLayer;
-        copy->destination.buffer = destination->buffer;
-        copy->destination.offset = destination->offset;
-        if (destination->rowPitch == 0) {
-            copy->destination.rowPitch =
-                ComputeDefaultRowPitch(source->texture->GetFormat(), copySize->width);
-        } else {
-            copy->destination.rowPitch = destination->rowPitch;
-        }
-        if (destination->imageHeight == 0) {
-            copy->destination.imageHeight = copySize->height;
-        } else {
-            copy->destination.imageHeight = destination->imageHeight;
-        }
+            return {};
+        });
     }
 
     void CommandEncoderBase::CopyTextureToTexture(const TextureCopyView* source,
                                                   const TextureCopyView* destination,
                                                   const Extent3D* copySize) {
-        if (ConsumedError(ValidateCanRecordTopLevelCommands())) {
-            return;
-        }
+        mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(source->texture));
+            DAWN_TRY(GetDevice()->ValidateObject(destination->texture));
 
-        if (ConsumedError(GetDevice()->ValidateObject(source->texture))) {
-            return;
-        }
+            CopyTextureToTextureCmd* copy =
+                allocator->Allocate<CopyTextureToTextureCmd>(Command::CopyTextureToTexture);
+            copy->source.texture = source->texture;
+            copy->source.origin = source->origin;
+            copy->source.mipLevel = source->mipLevel;
+            copy->source.arrayLayer = source->arrayLayer;
+            copy->destination.texture = destination->texture;
+            copy->destination.origin = destination->origin;
+            copy->destination.mipLevel = destination->mipLevel;
+            copy->destination.arrayLayer = destination->arrayLayer;
+            copy->copySize = *copySize;
 
-        if (ConsumedError(GetDevice()->ValidateObject(destination->texture))) {
-            return;
-        }
-
-        CopyTextureToTextureCmd* copy =
-            mAllocator.Allocate<CopyTextureToTextureCmd>(Command::CopyTextureToTexture);
-        copy->source.texture = source->texture;
-        copy->source.origin = source->origin;
-        copy->source.mipLevel = source->mipLevel;
-        copy->source.arrayLayer = source->arrayLayer;
-        copy->destination.texture = destination->texture;
-        copy->destination.origin = destination->origin;
-        copy->destination.mipLevel = destination->mipLevel;
-        copy->destination.arrayLayer = destination->arrayLayer;
-        copy->copySize = *copySize;
+            return {};
+        });
     }
 
     CommandBufferBase* CommandEncoderBase::Finish(const CommandBufferDescriptor* descriptor) {
         if (GetDevice()->ConsumedError(ValidateFinish(descriptor))) {
             // Even if finish validation fails, it is now invalid to call any encoding commands on
             // this object, so we set its state to finished.
-            mEncodingState = EncodingState::Finished;
             return CommandBufferBase::MakeError(GetDevice());
         }
         ASSERT(!IsError());
 
-        mEncodingState = EncodingState::Finished;
-
-        MoveToIterator();
         return GetDevice()->CreateCommandBuffer(this, descriptor);
     }
 
-    // Implementation of functions to interact with sub-encoders
-
-    void CommandEncoderBase::HandleError(const char* message) {
-        if (mEncodingState != EncodingState::Finished) {
-            if (!mGotError) {
-                mGotError = true;
-                mErrorMessage = message;
-            }
-        } else {
-            GetDevice()->HandleError(message);
-        }
-    }
-
-    void CommandEncoderBase::ConsumeError(ErrorData* error) {
-        HandleError(error->GetMessage().c_str());
-        delete error;
-    }
-
-    void CommandEncoderBase::PassEnded() {
-        // This function may still be called when the command encoder is finished, just do nothing.
-        if (mEncodingState == EncodingState::Finished) {
-            return;
-        }
-
-        if (mEncodingState == EncodingState::ComputePass) {
-            mAllocator.Allocate<EndComputePassCmd>(Command::EndComputePass);
-        } else {
-            ASSERT(mEncodingState == EncodingState::RenderPass);
-            mAllocator.Allocate<EndRenderPassCmd>(Command::EndRenderPass);
-        }
-        mEncodingState = EncodingState::TopLevel;
-    }
-
     // Implementation of the command buffer validation that can be precomputed before submit
 
     MaybeError CommandEncoderBase::ValidateFinish(const CommandBufferDescriptor*) {
         DAWN_TRY(GetDevice()->ValidateObject(this));
 
-        if (mGotError) {
-            return DAWN_VALIDATION_ERROR(mErrorMessage);
-        }
+        // Even if Finish() validation fails, calling it will mutate the internal state of the
+        // encoding context. Subsequent calls to encode commands will generate errors.
+        DAWN_TRY(mEncodingContext.Finish());
 
-        if (mEncodingState != EncodingState::TopLevel) {
-            return DAWN_VALIDATION_ERROR("Command buffer recording ended mid-pass");
-        }
-
-        MoveToIterator();
-        mIterator.Reset();
+        CommandIterator* commands = mEncodingContext.GetIterator();
+        commands->Reset();
 
         Command type;
-        while (mIterator.NextCommandId(&type)) {
+        while (commands->NextCommandId(&type)) {
             switch (type) {
                 case Command::BeginComputePass: {
-                    mIterator.NextCommand<BeginComputePassCmd>();
-                    DAWN_TRY(ValidateComputePass());
+                    commands->NextCommand<BeginComputePassCmd>();
+                    DAWN_TRY(ValidateComputePass(commands));
                 } break;
 
                 case Command::BeginRenderPass: {
-                    BeginRenderPassCmd* cmd = mIterator.NextCommand<BeginRenderPassCmd>();
-                    DAWN_TRY(ValidateRenderPass(cmd));
+                    BeginRenderPassCmd* cmd = commands->NextCommand<BeginRenderPassCmd>();
+                    DAWN_TRY(ValidateRenderPass(commands, cmd));
                 } break;
 
                 case Command::CopyBufferToBuffer: {
-                    CopyBufferToBufferCmd* copy = mIterator.NextCommand<CopyBufferToBufferCmd>();
+                    CopyBufferToBufferCmd* copy = commands->NextCommand<CopyBufferToBufferCmd>();
 
                     DAWN_TRY(
                         ValidateCopySizeFitsInBuffer(copy->source, copy->sourceOffset, copy->size));
@@ -959,7 +890,7 @@
                 } break;
 
                 case Command::CopyBufferToTexture: {
-                    CopyBufferToTextureCmd* copy = mIterator.NextCommand<CopyBufferToTextureCmd>();
+                    CopyBufferToTextureCmd* copy = commands->NextCommand<CopyBufferToTextureCmd>();
 
                     DAWN_TRY(
                         ValidateTextureSampleCountInCopyCommands(copy->destination.texture.Get()));
@@ -994,7 +925,7 @@
                 } break;
 
                 case Command::CopyTextureToBuffer: {
-                    CopyTextureToBufferCmd* copy = mIterator.NextCommand<CopyTextureToBufferCmd>();
+                    CopyTextureToBufferCmd* copy = commands->NextCommand<CopyTextureToBufferCmd>();
 
                     DAWN_TRY(ValidateTextureSampleCountInCopyCommands(copy->source.texture.Get()));
 
@@ -1030,7 +961,7 @@
 
                 case Command::CopyTextureToTexture: {
                     CopyTextureToTextureCmd* copy =
-                        mIterator.NextCommand<CopyTextureToTextureCmd>();
+                        commands->NextCommand<CopyTextureToTextureCmd>();
 
                     DAWN_TRY(ValidateTextureToTextureCopyRestrictions(
                         copy->source, copy->destination, copy->copySize));
@@ -1064,15 +995,15 @@
         return {};
     }
 
-    MaybeError CommandEncoderBase::ValidateComputePass() {
+    MaybeError CommandEncoderBase::ValidateComputePass(CommandIterator* commands) {
         PassResourceUsageTracker usageTracker;
         CommandBufferStateTracker persistentState;
 
         Command type;
-        while (mIterator.NextCommandId(&type)) {
+        while (commands->NextCommandId(&type)) {
             switch (type) {
                 case Command::EndComputePass: {
-                    mIterator.NextCommand<EndComputePassCmd>();
+                    commands->NextCommand<EndComputePassCmd>();
 
                     DAWN_TRY(ValidateDebugGroups(mDebugGroupStackSize));
 
@@ -1082,43 +1013,43 @@
                 } break;
 
                 case Command::Dispatch: {
-                    mIterator.NextCommand<DispatchCmd>();
+                    commands->NextCommand<DispatchCmd>();
                     DAWN_TRY(persistentState.ValidateCanDispatch());
                 } break;
 
                 case Command::DispatchIndirect: {
-                    DispatchIndirectCmd* cmd = mIterator.NextCommand<DispatchIndirectCmd>();
+                    DispatchIndirectCmd* cmd = commands->NextCommand<DispatchIndirectCmd>();
                     DAWN_TRY(persistentState.ValidateCanDispatch());
                     usageTracker.BufferUsedAs(cmd->indirectBuffer.Get(),
                                               dawn::BufferUsageBit::Indirect);
                 } break;
 
                 case Command::InsertDebugMarker: {
-                    InsertDebugMarkerCmd* cmd = mIterator.NextCommand<InsertDebugMarkerCmd>();
-                    mIterator.NextData<char>(cmd->length + 1);
+                    InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
+                    commands->NextData<char>(cmd->length + 1);
                 } break;
 
                 case Command::PopDebugGroup: {
-                    mIterator.NextCommand<PopDebugGroupCmd>();
+                    commands->NextCommand<PopDebugGroupCmd>();
                     DAWN_TRY(PopDebugMarkerStack(&mDebugGroupStackSize));
                 } break;
 
                 case Command::PushDebugGroup: {
-                    PushDebugGroupCmd* cmd = mIterator.NextCommand<PushDebugGroupCmd>();
-                    mIterator.NextData<char>(cmd->length + 1);
+                    PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>();
+                    commands->NextData<char>(cmd->length + 1);
                     DAWN_TRY(PushDebugMarkerStack(&mDebugGroupStackSize));
                 } break;
 
                 case Command::SetComputePipeline: {
-                    SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
+                    SetComputePipelineCmd* cmd = commands->NextCommand<SetComputePipelineCmd>();
                     ComputePipelineBase* pipeline = cmd->pipeline.Get();
                     persistentState.SetComputePipeline(pipeline);
                 } break;
 
                 case Command::SetBindGroup: {
-                    SetBindGroupCmd* cmd = mIterator.NextCommand<SetBindGroupCmd>();
+                    SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>();
                     if (cmd->dynamicOffsetCount > 0) {
-                        mIterator.NextData<uint64_t>(cmd->dynamicOffsetCount);
+                        commands->NextData<uint64_t>(cmd->dynamicOffsetCount);
                     }
 
                     TrackBindGroupResourceUsage(cmd->group.Get(), &usageTracker);
@@ -1134,7 +1065,8 @@
         return DAWN_VALIDATION_ERROR("Unfinished compute pass");
     }
 
-    MaybeError CommandEncoderBase::ValidateRenderPass(BeginRenderPassCmd* renderPass) {
+    MaybeError CommandEncoderBase::ValidateRenderPass(CommandIterator* commands,
+                                                      BeginRenderPassCmd* renderPass) {
         PassResourceUsageTracker usageTracker;
         CommandBufferStateTracker persistentState;
 
@@ -1157,10 +1089,10 @@
         }
 
         Command type;
-        while (mIterator.NextCommandId(&type)) {
+        while (commands->NextCommandId(&type)) {
             switch (type) {
                 case Command::EndRenderPass: {
-                    mIterator.NextCommand<EndRenderPassCmd>();
+                    commands->NextCommand<EndRenderPassCmd>();
 
                     DAWN_TRY(ValidateDebugGroups(mDebugGroupStackSize));
 
@@ -1170,47 +1102,47 @@
                 } break;
 
                 case Command::Draw: {
-                    mIterator.NextCommand<DrawCmd>();
+                    commands->NextCommand<DrawCmd>();
                     DAWN_TRY(persistentState.ValidateCanDraw());
                 } break;
 
                 case Command::DrawIndexed: {
-                    mIterator.NextCommand<DrawIndexedCmd>();
+                    commands->NextCommand<DrawIndexedCmd>();
                     DAWN_TRY(persistentState.ValidateCanDrawIndexed());
                 } break;
 
                 case Command::DrawIndirect: {
-                    DrawIndirectCmd* cmd = mIterator.NextCommand<DrawIndirectCmd>();
+                    DrawIndirectCmd* cmd = commands->NextCommand<DrawIndirectCmd>();
                     DAWN_TRY(persistentState.ValidateCanDraw());
                     usageTracker.BufferUsedAs(cmd->indirectBuffer.Get(),
                                               dawn::BufferUsageBit::Indirect);
                 } break;
 
                 case Command::DrawIndexedIndirect: {
-                    DrawIndexedIndirectCmd* cmd = mIterator.NextCommand<DrawIndexedIndirectCmd>();
+                    DrawIndexedIndirectCmd* cmd = commands->NextCommand<DrawIndexedIndirectCmd>();
                     DAWN_TRY(persistentState.ValidateCanDrawIndexed());
                     usageTracker.BufferUsedAs(cmd->indirectBuffer.Get(),
                                               dawn::BufferUsageBit::Indirect);
                 } break;
 
                 case Command::InsertDebugMarker: {
-                    InsertDebugMarkerCmd* cmd = mIterator.NextCommand<InsertDebugMarkerCmd>();
-                    mIterator.NextData<char>(cmd->length + 1);
+                    InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
+                    commands->NextData<char>(cmd->length + 1);
                 } break;
 
                 case Command::PopDebugGroup: {
-                    mIterator.NextCommand<PopDebugGroupCmd>();
+                    commands->NextCommand<PopDebugGroupCmd>();
                     DAWN_TRY(PopDebugMarkerStack(&mDebugGroupStackSize));
                 } break;
 
                 case Command::PushDebugGroup: {
-                    PushDebugGroupCmd* cmd = mIterator.NextCommand<PushDebugGroupCmd>();
-                    mIterator.NextData<char>(cmd->length + 1);
+                    PushDebugGroupCmd* cmd = commands->NextCommand<PushDebugGroupCmd>();
+                    commands->NextData<char>(cmd->length + 1);
                     DAWN_TRY(PushDebugMarkerStack(&mDebugGroupStackSize));
                 } break;
 
                 case Command::SetRenderPipeline: {
-                    SetRenderPipelineCmd* cmd = mIterator.NextCommand<SetRenderPipelineCmd>();
+                    SetRenderPipelineCmd* cmd = commands->NextCommand<SetRenderPipelineCmd>();
                     RenderPipelineBase* pipeline = cmd->pipeline.Get();
 
                     DAWN_TRY(pipeline->ValidateCompatibleWith(renderPass));
@@ -1218,25 +1150,25 @@
                 } break;
 
                 case Command::SetStencilReference: {
-                    mIterator.NextCommand<SetStencilReferenceCmd>();
+                    commands->NextCommand<SetStencilReferenceCmd>();
                 } break;
 
                 case Command::SetBlendColor: {
-                    mIterator.NextCommand<SetBlendColorCmd>();
+                    commands->NextCommand<SetBlendColorCmd>();
                 } break;
 
                 case Command::SetViewport: {
-                    mIterator.NextCommand<SetViewportCmd>();
+                    commands->NextCommand<SetViewportCmd>();
                 } break;
 
                 case Command::SetScissorRect: {
-                    mIterator.NextCommand<SetScissorRectCmd>();
+                    commands->NextCommand<SetScissorRectCmd>();
                 } break;
 
                 case Command::SetBindGroup: {
-                    SetBindGroupCmd* cmd = mIterator.NextCommand<SetBindGroupCmd>();
+                    SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>();
                     if (cmd->dynamicOffsetCount > 0) {
-                        mIterator.NextData<uint64_t>(cmd->dynamicOffsetCount);
+                        commands->NextData<uint64_t>(cmd->dynamicOffsetCount);
                     }
 
                     TrackBindGroupResourceUsage(cmd->group.Get(), &usageTracker);
@@ -1244,16 +1176,16 @@
                 } break;
 
                 case Command::SetIndexBuffer: {
-                    SetIndexBufferCmd* cmd = mIterator.NextCommand<SetIndexBufferCmd>();
+                    SetIndexBufferCmd* cmd = commands->NextCommand<SetIndexBufferCmd>();
 
                     usageTracker.BufferUsedAs(cmd->buffer.Get(), dawn::BufferUsageBit::Index);
                     persistentState.SetIndexBuffer();
                 } break;
 
                 case Command::SetVertexBuffers: {
-                    SetVertexBuffersCmd* cmd = mIterator.NextCommand<SetVertexBuffersCmd>();
-                    auto buffers = mIterator.NextData<Ref<BufferBase>>(cmd->count);
-                    mIterator.NextData<uint64_t>(cmd->count);
+                    SetVertexBuffersCmd* cmd = commands->NextCommand<SetVertexBuffersCmd>();
+                    auto buffers = commands->NextData<Ref<BufferBase>>(cmd->count);
+                    commands->NextData<uint64_t>(cmd->count);
 
                     for (uint32_t i = 0; i < cmd->count; ++i) {
                         usageTracker.BufferUsedAs(buffers[i].Get(), dawn::BufferUsageBit::Vertex);
@@ -1270,11 +1202,4 @@
         return DAWN_VALIDATION_ERROR("Unfinished render pass");
     }
 
-    MaybeError CommandEncoderBase::ValidateCanRecordTopLevelCommands() const {
-        if (mEncodingState != EncodingState::TopLevel) {
-            return DAWN_VALIDATION_ERROR("Command cannot be recorded inside a pass");
-        }
-        return {};
-    }
-
 }  // namespace dawn_native
diff --git a/src/dawn_native/CommandEncoder.h b/src/dawn_native/CommandEncoder.h
index 2faaa3f..3a59e8e 100644
--- a/src/dawn_native/CommandEncoder.h
+++ b/src/dawn_native/CommandEncoder.h
@@ -17,7 +17,7 @@
 
 #include "dawn_native/dawn_platform.h"
 
-#include "dawn_native/CommandAllocator.h"
+#include "dawn_native/EncodingContext.h"
 #include "dawn_native/Error.h"
 #include "dawn_native/ObjectBase.h"
 #include "dawn_native/PassResourceUsage.h"
@@ -31,7 +31,6 @@
     class CommandEncoderBase : public ObjectBase {
       public:
         CommandEncoderBase(DeviceBase* device, const CommandEncoderDescriptor* descriptor);
-        ~CommandEncoderBase();
 
         CommandIterator AcquireCommands();
         CommandBufferResourceUsage AcquireResourceUsages();
@@ -55,41 +54,17 @@
                                   const Extent3D* copySize);
         CommandBufferBase* Finish(const CommandBufferDescriptor* descriptor);
 
-        // Functions to interact with the encoders
-        void HandleError(const char* message);
-        void ConsumeError(ErrorData* error);
-        bool ConsumedError(MaybeError maybeError) {
-            if (DAWN_UNLIKELY(maybeError.IsError())) {
-                ConsumeError(maybeError.AcquireError());
-                return true;
-            }
-            return false;
-        }
-
-        void PassEnded();
-
       private:
         MaybeError ValidateFinish(const CommandBufferDescriptor* descriptor);
-        MaybeError ValidateComputePass();
-        MaybeError ValidateRenderPass(BeginRenderPassCmd* renderPass);
-        MaybeError ValidateCanRecordTopLevelCommands() const;
+        MaybeError ValidateComputePass(CommandIterator* commands);
+        MaybeError ValidateRenderPass(CommandIterator* commands, BeginRenderPassCmd* renderPass);
 
-        enum class EncodingState : uint8_t;
-        EncodingState mEncodingState;
-
-        void MoveToIterator();
-        CommandAllocator mAllocator;
-        CommandIterator mIterator;
-        bool mWasMovedToIterator = false;
-        bool mWereCommandsAcquired = false;
+        EncodingContext mEncodingContext;
 
         bool mWereResourceUsagesAcquired = false;
         CommandBufferResourceUsage mResourceUsages;
 
         unsigned int mDebugGroupStackSize = 0;
-
-        bool mGotError = false;
-        std::string mErrorMessage;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/ComputePassEncoder.cpp b/src/dawn_native/ComputePassEncoder.cpp
index e039241..2a6eb42d 100644
--- a/src/dawn_native/ComputePassEncoder.cpp
+++ b/src/dawn_native/ComputePassEncoder.cpp
@@ -23,70 +23,76 @@
 namespace dawn_native {
 
     ComputePassEncoderBase::ComputePassEncoderBase(DeviceBase* device,
-                                                   CommandEncoderBase* topLevelEncoder,
-                                                   CommandAllocator* allocator)
-        : ProgrammablePassEncoder(device, topLevelEncoder, allocator) {
+                                                   CommandEncoderBase* commandEncoder,
+                                                   EncodingContext* encodingContext)
+        : ProgrammablePassEncoder(device, encodingContext), mCommandEncoder(commandEncoder) {
     }
 
     ComputePassEncoderBase::ComputePassEncoderBase(DeviceBase* device,
-                                                   CommandEncoderBase* topLevelEncoder,
+                                                   CommandEncoderBase* commandEncoder,
+                                                   EncodingContext* encodingContext,
                                                    ErrorTag errorTag)
-        : ProgrammablePassEncoder(device, topLevelEncoder, errorTag) {
+        : ProgrammablePassEncoder(device, encodingContext, errorTag),
+          mCommandEncoder(commandEncoder) {
     }
 
     ComputePassEncoderBase* ComputePassEncoderBase::MakeError(DeviceBase* device,
-                                                              CommandEncoderBase* topLevelEncoder) {
-        return new ComputePassEncoderBase(device, topLevelEncoder, ObjectBase::kError);
+                                                              CommandEncoderBase* commandEncoder,
+                                                              EncodingContext* encodingContext) {
+        return new ComputePassEncoderBase(device, commandEncoder, encodingContext,
+                                          ObjectBase::kError);
     }
 
     void ComputePassEncoderBase::EndPass() {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        if (mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+                allocator->Allocate<EndComputePassCmd>(Command::EndComputePass);
 
-        mTopLevelEncoder->PassEnded();
-        mAllocator = nullptr;
+                return {};
+            })) {
+            mEncodingContext->ExitPass(this);
+        }
     }
 
     void ComputePassEncoderBase::Dispatch(uint32_t x, uint32_t y, uint32_t z) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DispatchCmd* dispatch = allocator->Allocate<DispatchCmd>(Command::Dispatch);
+            dispatch->x = x;
+            dispatch->y = y;
+            dispatch->z = z;
 
-        DispatchCmd* dispatch = mAllocator->Allocate<DispatchCmd>(Command::Dispatch);
-        dispatch->x = x;
-        dispatch->y = y;
-        dispatch->z = z;
+            return {};
+        });
     }
 
     void ComputePassEncoderBase::DispatchIndirect(BufferBase* indirectBuffer,
                                                   uint64_t indirectOffset) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(indirectBuffer))) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer));
 
-        if (indirectOffset >= indirectBuffer->GetSize() ||
-            indirectOffset + kDispatchIndirectSize > indirectBuffer->GetSize()) {
-            mTopLevelEncoder->HandleError("Indirect offset out of bounds");
-            return;
-        }
+            if (indirectOffset >= indirectBuffer->GetSize() ||
+                indirectOffset + kDispatchIndirectSize > indirectBuffer->GetSize()) {
+                return DAWN_VALIDATION_ERROR("Indirect offset out of bounds");
+            }
 
-        DispatchIndirectCmd* dispatch =
-            mAllocator->Allocate<DispatchIndirectCmd>(Command::DispatchIndirect);
-        dispatch->indirectBuffer = indirectBuffer;
-        dispatch->indirectOffset = indirectOffset;
+            DispatchIndirectCmd* dispatch =
+                allocator->Allocate<DispatchIndirectCmd>(Command::DispatchIndirect);
+            dispatch->indirectBuffer = indirectBuffer;
+            dispatch->indirectOffset = indirectOffset;
+
+            return {};
+        });
     }
 
     void ComputePassEncoderBase::SetPipeline(ComputePipelineBase* pipeline) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(pipeline))) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(pipeline));
 
-        SetComputePipelineCmd* cmd =
-            mAllocator->Allocate<SetComputePipelineCmd>(Command::SetComputePipeline);
-        cmd->pipeline = pipeline;
+            SetComputePipelineCmd* cmd =
+                allocator->Allocate<SetComputePipelineCmd>(Command::SetComputePipeline);
+            cmd->pipeline = pipeline;
+
+            return {};
+        });
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/ComputePassEncoder.h b/src/dawn_native/ComputePassEncoder.h
index 3e645bb..8e4c2f6 100644
--- a/src/dawn_native/ComputePassEncoder.h
+++ b/src/dawn_native/ComputePassEncoder.h
@@ -27,11 +27,12 @@
     class ComputePassEncoderBase : public ProgrammablePassEncoder {
       public:
         ComputePassEncoderBase(DeviceBase* device,
-                               CommandEncoderBase* topLevelEncoder,
-                               CommandAllocator* allocator);
+                               CommandEncoderBase* commandEncoder,
+                               EncodingContext* encodingContext);
 
         static ComputePassEncoderBase* MakeError(DeviceBase* device,
-                                                 CommandEncoderBase* topLevelEncoder);
+                                                 CommandEncoderBase* commandEncoder,
+                                                 EncodingContext* encodingContext);
 
         void EndPass();
 
@@ -41,8 +42,14 @@
 
       protected:
         ComputePassEncoderBase(DeviceBase* device,
-                               CommandEncoderBase* topLevelEncoder,
+                               CommandEncoderBase* commandEncoder,
+                               EncodingContext* encodingContext,
                                ErrorTag errorTag);
+
+      private:
+        // For render and compute passes, the encoding context is borrowed from the command encoder.
+        // Keep a reference to the encoder to make sure the context isn't freed.
+        Ref<CommandEncoderBase> mCommandEncoder;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/EncodingContext.cpp b/src/dawn_native/EncodingContext.cpp
new file mode 100644
index 0000000..d2c8a75
--- /dev/null
+++ b/src/dawn_native/EncodingContext.cpp
@@ -0,0 +1,101 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn_native/EncodingContext.h"
+
+#include "common/Assert.h"
+#include "dawn_native/Commands.h"
+#include "dawn_native/Device.h"
+#include "dawn_native/ErrorData.h"
+
+namespace dawn_native {
+
+    EncodingContext::EncodingContext(DeviceBase* device, const ObjectBase* initialEncoder)
+        : mDevice(device), mTopLevelEncoder(initialEncoder), mCurrentEncoder(initialEncoder) {
+    }
+
+    EncodingContext::~EncodingContext() {
+        if (!mWereCommandsAcquired) {
+            FreeCommands(GetIterator());
+        }
+    }
+
+    CommandIterator EncodingContext::AcquireCommands() {
+        ASSERT(!mWereCommandsAcquired);
+        mWereCommandsAcquired = true;
+        return std::move(mIterator);
+    }
+
+    CommandIterator* EncodingContext::GetIterator() {
+        if (!mWasMovedToIterator) {
+            mIterator = std::move(mAllocator);
+            mWasMovedToIterator = true;
+        }
+        return &mIterator;
+    }
+
+    void EncodingContext::HandleError(const char* message) {
+        if (!IsFinished()) {
+            // If the encoding context is not finished, errors are deferred until
+            // Finish() is called.
+            if (!mGotError) {
+                mGotError = true;
+                mErrorMessage = message;
+            }
+        } else {
+            mDevice->HandleError(message);
+        }
+    }
+
+    void EncodingContext::EnterPass(const ObjectBase* passEncoder) {
+        // Assert we're at the top level.
+        ASSERT(mCurrentEncoder == mTopLevelEncoder);
+        ASSERT(passEncoder != nullptr);
+
+        mCurrentEncoder = passEncoder;
+    }
+
+    void EncodingContext::ExitPass(const ObjectBase* passEncoder) {
+        // Assert we're not at the top level.
+        ASSERT(mCurrentEncoder != mTopLevelEncoder);
+        // Assert the pass encoder is current.
+        ASSERT(mCurrentEncoder == passEncoder);
+
+        mCurrentEncoder = mTopLevelEncoder;
+    }
+
+    MaybeError EncodingContext::Finish() {
+        const void* currentEncoder = mCurrentEncoder;
+        const void* topLevelEncoder = mTopLevelEncoder;
+
+        // Even if finish validation fails, it is now invalid to call any encoding commands,
+        // so we clear the encoders. Note: mTopLevelEncoder == nullptr is used as a flag for
+        // if Finish() has been called.
+        mCurrentEncoder = nullptr;
+        mTopLevelEncoder = nullptr;
+
+        if (mGotError) {
+            return DAWN_VALIDATION_ERROR(mErrorMessage);
+        }
+        if (currentEncoder != topLevelEncoder) {
+            return DAWN_VALIDATION_ERROR("Command buffer recording ended mid-pass");
+        }
+        return {};
+    }
+
+    bool EncodingContext::IsFinished() const {
+        return mTopLevelEncoder == nullptr;
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/EncodingContext.h b/src/dawn_native/EncodingContext.h
new file mode 100644
index 0000000..a810f50
--- /dev/null
+++ b/src/dawn_native/EncodingContext.h
@@ -0,0 +1,101 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef DAWNNATIVE_ENCODINGCONTEXT_H_
+#define DAWNNATIVE_ENCODINGCONTEXT_H_
+
+#include "dawn_native/CommandAllocator.h"
+#include "dawn_native/Error.h"
+#include "dawn_native/ErrorData.h"
+
+#include <string>
+
+namespace dawn_native {
+
+    class ObjectBase;
+    class DeviceBase;
+
+    // Base class for allocating/iterating commands.
+    // It performs error tracking as well as encoding state for render/compute passes.
+    class EncodingContext {
+      public:
+        EncodingContext(DeviceBase* device, const ObjectBase* initialEncoder);
+        ~EncodingContext();
+
+        CommandIterator AcquireCommands();
+        CommandIterator* GetIterator();
+
+        // Functions to handle encoder errors
+        void HandleError(const char* message);
+
+        inline void ConsumeError(ErrorData* error) {
+            HandleError(error->GetMessage().c_str());
+            delete error;
+        }
+
+        inline bool ConsumedError(MaybeError maybeError) {
+            if (DAWN_UNLIKELY(maybeError.IsError())) {
+                ConsumeError(maybeError.AcquireError());
+                return true;
+            }
+            return false;
+        }
+
+        template <typename EncodeFunction>
+        inline bool TryEncode(const void* encoder, EncodeFunction&& encodeFunction) {
+            if (DAWN_UNLIKELY(encoder != mCurrentEncoder)) {
+                if (mCurrentEncoder != mTopLevelEncoder) {
+                    // The top level encoder was used when a pass encoder was current.
+                    HandleError("Command cannot be recorded inside a pass");
+                } else {
+                    HandleError("Recording in an error or already ended pass encoder");
+                }
+                return false;
+            }
+            ASSERT(!mWasMovedToIterator);
+            return !ConsumedError(encodeFunction(&mAllocator));
+        }
+
+        // Functions to set current encoder state
+        void EnterPass(const ObjectBase* passEncoder);
+        void ExitPass(const ObjectBase* passEncoder);
+        MaybeError Finish();
+
+      private:
+        bool IsFinished() const;
+
+        DeviceBase* mDevice;
+
+        // There can only be two levels of encoders. Top-level and render/compute pass.
+        // The top level encoder is the encoder the EncodingContext is created with.
+        // It doubles as flag to check if encoding has been Finished.
+        const ObjectBase* mTopLevelEncoder;
+        // The current encoder must be the same as the encoder provided to TryEncode,
+        // otherwise an error is produced. It may be nullptr if the EncodingContext is an error.
+        // The current encoder changes with Enter/ExitPass which should be called by
+        // CommandEncoder::Begin/EndPass.
+        const ObjectBase* mCurrentEncoder;
+
+        CommandAllocator mAllocator;
+        CommandIterator mIterator;
+        bool mWasMovedToIterator = false;
+        bool mWereCommandsAcquired = false;
+
+        bool mGotError = false;
+        std::string mErrorMessage;
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_ENCODINGCONTEXT_H_
diff --git a/src/dawn_native/ProgrammablePassEncoder.cpp b/src/dawn_native/ProgrammablePassEncoder.cpp
index 4ba5b11..4210a13 100644
--- a/src/dawn_native/ProgrammablePassEncoder.cpp
+++ b/src/dawn_native/ProgrammablePassEncoder.cpp
@@ -26,109 +26,98 @@
 namespace dawn_native {
 
     ProgrammablePassEncoder::ProgrammablePassEncoder(DeviceBase* device,
-                                                     CommandEncoderBase* topLevelEncoder,
-                                                     CommandAllocator* allocator)
-        : ObjectBase(device), mTopLevelEncoder(topLevelEncoder), mAllocator(allocator) {
-        DAWN_ASSERT(allocator != nullptr);
+                                                     EncodingContext* encodingContext)
+        : ObjectBase(device), mEncodingContext(encodingContext) {
     }
 
     ProgrammablePassEncoder::ProgrammablePassEncoder(DeviceBase* device,
-                                                     CommandEncoderBase* topLevelEncoder,
+                                                     EncodingContext* encodingContext,
                                                      ErrorTag errorTag)
-        : ObjectBase(device, errorTag), mTopLevelEncoder(topLevelEncoder), mAllocator(nullptr) {
+        : ObjectBase(device, errorTag), mEncodingContext(encodingContext) {
     }
 
     void ProgrammablePassEncoder::InsertDebugMarker(const char* groupLabel) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            InsertDebugMarkerCmd* cmd =
+                allocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker);
+            cmd->length = strlen(groupLabel);
 
-        InsertDebugMarkerCmd* cmd =
-            mAllocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker);
-        cmd->length = strlen(groupLabel);
+            char* label = allocator->AllocateData<char>(cmd->length + 1);
+            memcpy(label, groupLabel, cmd->length + 1);
 
-        char* label = mAllocator->AllocateData<char>(cmd->length + 1);
-        memcpy(label, groupLabel, cmd->length + 1);
+            return {};
+        });
     }
 
     void ProgrammablePassEncoder::PopDebugGroup() {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            allocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup);
 
-        mAllocator->Allocate<PopDebugGroupCmd>(Command::PopDebugGroup);
+            return {};
+        });
     }
 
     void ProgrammablePassEncoder::PushDebugGroup(const char* groupLabel) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            PushDebugGroupCmd* cmd =
+                allocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup);
+            cmd->length = strlen(groupLabel);
 
-        PushDebugGroupCmd* cmd = mAllocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup);
-        cmd->length = strlen(groupLabel);
+            char* label = allocator->AllocateData<char>(cmd->length + 1);
+            memcpy(label, groupLabel, cmd->length + 1);
 
-        char* label = mAllocator->AllocateData<char>(cmd->length + 1);
-        memcpy(label, groupLabel, cmd->length + 1);
+            return {};
+        });
     }
 
     void ProgrammablePassEncoder::SetBindGroup(uint32_t groupIndex,
                                                BindGroupBase* group,
                                                uint32_t dynamicOffsetCount,
                                                const uint64_t* dynamicOffsets) {
-        const BindGroupLayoutBase* layout = group->GetLayout();
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            const BindGroupLayoutBase* layout = group->GetLayout();
 
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(group))) {
-            return;
-        }
+            DAWN_TRY(GetDevice()->ValidateObject(group));
 
-        if (groupIndex >= kMaxBindGroups) {
-            mTopLevelEncoder->HandleError("Setting bind group over the max");
-            return;
-        }
-
-        // Dynamic offsets count must match the number required by the layout perfectly.
-        if (layout->GetDynamicBufferCount() != dynamicOffsetCount) {
-            mTopLevelEncoder->HandleError("dynamicOffset count mismatch");
-        }
-
-        for (uint32_t i = 0; i < dynamicOffsetCount; ++i) {
-            if (dynamicOffsets[i] % kMinDynamicBufferOffsetAlignment != 0) {
-                mTopLevelEncoder->HandleError("Dynamic Buffer Offset need to be aligned");
-                return;
+            if (groupIndex >= kMaxBindGroups) {
+                return DAWN_VALIDATION_ERROR("Setting bind group over the max");
             }
 
-            BufferBinding bufferBinding = group->GetBindingAsBufferBinding(i);
-
-            // During BindGroup creation, validation ensures binding offset + binding size <= buffer
-            // size.
-            DAWN_ASSERT(bufferBinding.buffer->GetSize() >= bufferBinding.size);
-            DAWN_ASSERT(bufferBinding.buffer->GetSize() - bufferBinding.size >=
-                        bufferBinding.offset);
-
-            if (dynamicOffsets[i] >
-                bufferBinding.buffer->GetSize() - bufferBinding.offset - bufferBinding.size) {
-                mTopLevelEncoder->HandleError("dynamic offset out of bounds");
-                return;
+            // Dynamic offsets count must match the number required by the layout perfectly.
+            if (layout->GetDynamicBufferCount() != dynamicOffsetCount) {
+                return DAWN_VALIDATION_ERROR("dynamicOffset count mismatch");
             }
-        }
 
-        SetBindGroupCmd* cmd = mAllocator->Allocate<SetBindGroupCmd>(Command::SetBindGroup);
-        cmd->index = groupIndex;
-        cmd->group = group;
-        cmd->dynamicOffsetCount = dynamicOffsetCount;
-        if (dynamicOffsetCount > 0) {
-            uint64_t* offsets = mAllocator->AllocateData<uint64_t>(cmd->dynamicOffsetCount);
-            memcpy(offsets, dynamicOffsets, dynamicOffsetCount * sizeof(uint64_t));
-        }
-    }
+            for (uint32_t i = 0; i < dynamicOffsetCount; ++i) {
+                if (dynamicOffsets[i] % kMinDynamicBufferOffsetAlignment != 0) {
+                    return DAWN_VALIDATION_ERROR("Dynamic Buffer Offset need to be aligned");
+                }
 
-    MaybeError ProgrammablePassEncoder::ValidateCanRecordCommands() const {
-        if (mAllocator == nullptr) {
-            return DAWN_VALIDATION_ERROR("Recording in an error or already ended pass encoder");
-        }
+                BufferBinding bufferBinding = group->GetBindingAsBufferBinding(i);
 
-        return nullptr;
+                // During BindGroup creation, validation ensures binding offset + binding size <=
+                // buffer size.
+                DAWN_ASSERT(bufferBinding.buffer->GetSize() >= bufferBinding.size);
+                DAWN_ASSERT(bufferBinding.buffer->GetSize() - bufferBinding.size >=
+                            bufferBinding.offset);
+
+                if ((dynamicOffsets[i] >
+                     bufferBinding.buffer->GetSize() - bufferBinding.offset - bufferBinding.size)) {
+                    return DAWN_VALIDATION_ERROR("dynamic offset out of bounds");
+                }
+            }
+
+            SetBindGroupCmd* cmd = allocator->Allocate<SetBindGroupCmd>(Command::SetBindGroup);
+            cmd->index = groupIndex;
+            cmd->group = group;
+            cmd->dynamicOffsetCount = dynamicOffsetCount;
+            if (dynamicOffsetCount > 0) {
+                uint64_t* offsets = allocator->AllocateData<uint64_t>(cmd->dynamicOffsetCount);
+                memcpy(offsets, dynamicOffsets, dynamicOffsetCount * sizeof(uint64_t));
+            }
+
+            return {};
+        });
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/ProgrammablePassEncoder.h b/src/dawn_native/ProgrammablePassEncoder.h
index e64592c..fc8f11a 100644
--- a/src/dawn_native/ProgrammablePassEncoder.h
+++ b/src/dawn_native/ProgrammablePassEncoder.h
@@ -29,9 +29,7 @@
     // Base class for shared functionality between ComputePassEncoder and RenderPassEncoder.
     class ProgrammablePassEncoder : public ObjectBase {
       public:
-        ProgrammablePassEncoder(DeviceBase* device,
-                                CommandEncoderBase* topLevelEncoder,
-                                CommandAllocator* allocator);
+        ProgrammablePassEncoder(DeviceBase* device, EncodingContext* encodingContext);
 
         void InsertDebugMarker(const char* groupLabel);
         void PopDebugGroup();
@@ -45,16 +43,10 @@
       protected:
         // Construct an "error" programmable pass encoder.
         ProgrammablePassEncoder(DeviceBase* device,
-                                CommandEncoderBase* topLevelEncoder,
+                                EncodingContext* encodingContext,
                                 ErrorTag errorTag);
 
-        MaybeError ValidateCanRecordCommands() const;
-
-        // The allocator is borrowed from the top level encoder. Keep a reference to the encoder
-        // to make sure the allocator isn't freed.
-        Ref<CommandEncoderBase> mTopLevelEncoder = nullptr;
-        // mAllocator is cleared at the end of the pass so it acts as a tag that EndPass was called
-        CommandAllocator* mAllocator = nullptr;
+        EncodingContext* mEncodingContext = nullptr;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderEncoderBase.cpp b/src/dawn_native/RenderEncoderBase.cpp
index 198428a..ca80aef 100644
--- a/src/dawn_native/RenderEncoderBase.cpp
+++ b/src/dawn_native/RenderEncoderBase.cpp
@@ -26,31 +26,29 @@
 
 namespace dawn_native {
 
-    RenderEncoderBase::RenderEncoderBase(DeviceBase* device,
-                                         CommandEncoderBase* topLevelEncoder,
-                                         CommandAllocator* allocator)
-        : ProgrammablePassEncoder(device, topLevelEncoder, allocator) {
+    RenderEncoderBase::RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext)
+        : ProgrammablePassEncoder(device, encodingContext) {
     }
 
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device,
-                                         CommandEncoderBase* topLevelEncoder,
+                                         EncodingContext* encodingContext,
                                          ErrorTag errorTag)
-        : ProgrammablePassEncoder(device, topLevelEncoder, errorTag) {
+        : ProgrammablePassEncoder(device, encodingContext, errorTag) {
     }
 
     void RenderEncoderBase::Draw(uint32_t vertexCount,
                                  uint32_t instanceCount,
                                  uint32_t firstVertex,
                                  uint32_t firstInstance) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DrawCmd* draw = allocator->Allocate<DrawCmd>(Command::Draw);
+            draw->vertexCount = vertexCount;
+            draw->instanceCount = instanceCount;
+            draw->firstVertex = firstVertex;
+            draw->firstInstance = firstInstance;
 
-        DrawCmd* draw = mAllocator->Allocate<DrawCmd>(Command::Draw);
-        draw->vertexCount = vertexCount;
-        draw->instanceCount = instanceCount;
-        draw->firstVertex = firstVertex;
-        draw->firstInstance = firstInstance;
+            return {};
+        });
     }
 
     void RenderEncoderBase::DrawIndexed(uint32_t indexCount,
@@ -58,102 +56,103 @@
                                         uint32_t firstIndex,
                                         int32_t baseVertex,
                                         uint32_t firstInstance) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DrawIndexedCmd* draw = allocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed);
+            draw->indexCount = indexCount;
+            draw->instanceCount = instanceCount;
+            draw->firstIndex = firstIndex;
+            draw->baseVertex = baseVertex;
+            draw->firstInstance = firstInstance;
 
-        DrawIndexedCmd* draw = mAllocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed);
-        draw->indexCount = indexCount;
-        draw->instanceCount = instanceCount;
-        draw->firstIndex = firstIndex;
-        draw->baseVertex = baseVertex;
-        draw->firstInstance = firstInstance;
+            return {};
+        });
     }
 
     void RenderEncoderBase::DrawIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(indirectBuffer))) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer));
 
-        if (indirectOffset >= indirectBuffer->GetSize() ||
-            indirectOffset + kDrawIndirectSize > indirectBuffer->GetSize()) {
-            mTopLevelEncoder->HandleError("Indirect offset out of bounds");
-            return;
-        }
+            if (indirectOffset >= indirectBuffer->GetSize() ||
+                indirectOffset + kDrawIndirectSize > indirectBuffer->GetSize()) {
+                return DAWN_VALIDATION_ERROR("Indirect offset out of bounds");
+            }
 
-        DrawIndirectCmd* cmd = mAllocator->Allocate<DrawIndirectCmd>(Command::DrawIndirect);
-        cmd->indirectBuffer = indirectBuffer;
-        cmd->indirectOffset = indirectOffset;
+            DrawIndirectCmd* cmd = allocator->Allocate<DrawIndirectCmd>(Command::DrawIndirect);
+            cmd->indirectBuffer = indirectBuffer;
+            cmd->indirectOffset = indirectOffset;
+
+            return {};
+        });
     }
 
     void RenderEncoderBase::DrawIndexedIndirect(BufferBase* indirectBuffer,
                                                 uint64_t indirectOffset) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(indirectBuffer))) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer));
 
-        if (indirectOffset >= indirectBuffer->GetSize() ||
-            indirectOffset + kDrawIndexedIndirectSize > indirectBuffer->GetSize()) {
-            mTopLevelEncoder->HandleError("Indirect offset out of bounds");
-            return;
-        }
+            if ((indirectOffset >= indirectBuffer->GetSize() ||
+                 indirectOffset + kDrawIndexedIndirectSize > indirectBuffer->GetSize())) {
+                return DAWN_VALIDATION_ERROR("Indirect offset out of bounds");
+            }
 
-        DrawIndexedIndirectCmd* cmd =
-            mAllocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect);
-        cmd->indirectBuffer = indirectBuffer;
-        cmd->indirectOffset = indirectOffset;
+            DrawIndexedIndirectCmd* cmd =
+                allocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect);
+            cmd->indirectBuffer = indirectBuffer;
+            cmd->indirectOffset = indirectOffset;
+
+            return {};
+        });
     }
 
     void RenderEncoderBase::SetPipeline(RenderPipelineBase* pipeline) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(pipeline))) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(pipeline));
 
-        SetRenderPipelineCmd* cmd =
-            mAllocator->Allocate<SetRenderPipelineCmd>(Command::SetRenderPipeline);
-        cmd->pipeline = pipeline;
+            SetRenderPipelineCmd* cmd =
+                allocator->Allocate<SetRenderPipelineCmd>(Command::SetRenderPipeline);
+            cmd->pipeline = pipeline;
+
+            return {};
+        });
     }
 
     void RenderEncoderBase::SetIndexBuffer(BufferBase* buffer, uint64_t offset) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands()) ||
-            mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(buffer))) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            DAWN_TRY(GetDevice()->ValidateObject(buffer));
 
-        SetIndexBufferCmd* cmd = mAllocator->Allocate<SetIndexBufferCmd>(Command::SetIndexBuffer);
-        cmd->buffer = buffer;
-        cmd->offset = offset;
+            SetIndexBufferCmd* cmd =
+                allocator->Allocate<SetIndexBufferCmd>(Command::SetIndexBuffer);
+            cmd->buffer = buffer;
+            cmd->offset = offset;
+
+            return {};
+        });
     }
 
     void RenderEncoderBase::SetVertexBuffers(uint32_t startSlot,
                                              uint32_t count,
                                              BufferBase* const* buffers,
                                              uint64_t const* offsets) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
-
-        for (size_t i = 0; i < count; ++i) {
-            if (mTopLevelEncoder->ConsumedError(GetDevice()->ValidateObject(buffers[i]))) {
-                return;
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            for (size_t i = 0; i < count; ++i) {
+                DAWN_TRY(GetDevice()->ValidateObject(buffers[i]));
             }
-        }
 
-        SetVertexBuffersCmd* cmd =
-            mAllocator->Allocate<SetVertexBuffersCmd>(Command::SetVertexBuffers);
-        cmd->startSlot = startSlot;
-        cmd->count = count;
+            SetVertexBuffersCmd* cmd =
+                allocator->Allocate<SetVertexBuffersCmd>(Command::SetVertexBuffers);
+            cmd->startSlot = startSlot;
+            cmd->count = count;
 
-        Ref<BufferBase>* cmdBuffers = mAllocator->AllocateData<Ref<BufferBase>>(count);
-        for (size_t i = 0; i < count; ++i) {
-            cmdBuffers[i] = buffers[i];
-        }
+            Ref<BufferBase>* cmdBuffers = allocator->AllocateData<Ref<BufferBase>>(count);
+            for (size_t i = 0; i < count; ++i) {
+                cmdBuffers[i] = buffers[i];
+            }
 
-        uint64_t* cmdOffsets = mAllocator->AllocateData<uint64_t>(count);
-        memcpy(cmdOffsets, offsets, count * sizeof(uint64_t));
+            uint64_t* cmdOffsets = allocator->AllocateData<uint64_t>(count);
+            memcpy(cmdOffsets, offsets, count * sizeof(uint64_t));
+
+            return {};
+        });
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderEncoderBase.h b/src/dawn_native/RenderEncoderBase.h
index adeead3..19061bc 100644
--- a/src/dawn_native/RenderEncoderBase.h
+++ b/src/dawn_native/RenderEncoderBase.h
@@ -22,9 +22,7 @@
 
     class RenderEncoderBase : public ProgrammablePassEncoder {
       public:
-        RenderEncoderBase(DeviceBase* device,
-                          CommandEncoderBase* topLevelEncoder,
-                          CommandAllocator* allocator);
+        RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext);
 
         void Draw(uint32_t vertexCount,
                   uint32_t instanceCount,
@@ -57,9 +55,7 @@
 
       protected:
         // Construct an "error" render encoder base.
-        RenderEncoderBase(DeviceBase* device,
-                          CommandEncoderBase* topLevelEncoder,
-                          ErrorTag errorTag);
+        RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext, ErrorTag errorTag);
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp
index 07b1be8..54d5db5 100644
--- a/src/dawn_native/RenderPassEncoder.cpp
+++ b/src/dawn_native/RenderPassEncoder.cpp
@@ -27,48 +27,52 @@
 namespace dawn_native {
 
     RenderPassEncoderBase::RenderPassEncoderBase(DeviceBase* device,
-                                                 CommandEncoderBase* topLevelEncoder,
-                                                 CommandAllocator* allocator)
-        : RenderEncoderBase(device, topLevelEncoder, allocator) {
+                                                 CommandEncoderBase* commandEncoder,
+                                                 EncodingContext* encodingContext)
+        : RenderEncoderBase(device, encodingContext), mCommandEncoder(commandEncoder) {
     }
 
     RenderPassEncoderBase::RenderPassEncoderBase(DeviceBase* device,
-                                                 CommandEncoderBase* topLevelEncoder,
+                                                 CommandEncoderBase* commandEncoder,
+                                                 EncodingContext* encodingContext,
                                                  ErrorTag errorTag)
-        : RenderEncoderBase(device, topLevelEncoder, errorTag) {
+        : RenderEncoderBase(device, encodingContext, errorTag), mCommandEncoder(commandEncoder) {
     }
 
     RenderPassEncoderBase* RenderPassEncoderBase::MakeError(DeviceBase* device,
-                                                            CommandEncoderBase* topLevelEncoder) {
-        return new RenderPassEncoderBase(device, topLevelEncoder, ObjectBase::kError);
+                                                            CommandEncoderBase* commandEncoder,
+                                                            EncodingContext* encodingContext) {
+        return new RenderPassEncoderBase(device, commandEncoder, encodingContext,
+                                         ObjectBase::kError);
     }
 
     void RenderPassEncoderBase::EndPass() {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        if (mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+                allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass);
 
-        mTopLevelEncoder->PassEnded();
-        mAllocator = nullptr;
+                return {};
+            })) {
+            mEncodingContext->ExitPass(this);
+        }
     }
 
     void RenderPassEncoderBase::SetStencilReference(uint32_t reference) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            SetStencilReferenceCmd* cmd =
+                allocator->Allocate<SetStencilReferenceCmd>(Command::SetStencilReference);
+            cmd->reference = reference;
 
-        SetStencilReferenceCmd* cmd =
-            mAllocator->Allocate<SetStencilReferenceCmd>(Command::SetStencilReference);
-        cmd->reference = reference;
+            return {};
+        });
     }
 
     void RenderPassEncoderBase::SetBlendColor(const Color* color) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            SetBlendColorCmd* cmd = allocator->Allocate<SetBlendColorCmd>(Command::SetBlendColor);
+            cmd->color = *color;
 
-        SetBlendColorCmd* cmd = mAllocator->Allocate<SetBlendColorCmd>(Command::SetBlendColor);
-        cmd->color = *color;
+            return {};
+        });
     }
 
     void RenderPassEncoderBase::SetViewport(float x,
@@ -77,55 +81,53 @@
                                             float height,
                                             float minDepth,
                                             float maxDepth) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if ((isnan(x) || isnan(y) || isnan(width) || isnan(height) || isnan(minDepth) ||
+                 isnan(maxDepth))) {
+                return DAWN_VALIDATION_ERROR("NaN is not allowed.");
+            }
 
-        if (isnan(x) || isnan(y) || isnan(width) || isnan(height) || isnan(minDepth) ||
-            isnan(maxDepth)) {
-            mTopLevelEncoder->HandleError("NaN is not allowed.");
-            return;
-        }
+            // TODO(yunchao.he@intel.com): there are more restrictions for x, y, width and height in
+            // Vulkan, and height can be a negative value in Vulkan 1.1. Revisit this part later
+            // (say, for WebGPU v1).
+            if (width <= 0 || height <= 0) {
+                return DAWN_VALIDATION_ERROR("Width and height must be greater than 0.");
+            }
 
-        // TODO(yunchao.he@intel.com): there are more restrictions for x, y, width and height in
-        // Vulkan, and height can be a negative value in Vulkan 1.1. Revisit this part later (say,
-        // for WebGPU v1).
-        if (width <= 0 || height <= 0) {
-            mTopLevelEncoder->HandleError("Width and height must be greater than 0.");
-            return;
-        }
+            if (minDepth < 0 || minDepth > 1 || maxDepth < 0 || maxDepth > 1) {
+                return DAWN_VALIDATION_ERROR("minDepth and maxDepth must be in [0, 1].");
+            }
 
-        if (minDepth < 0 || minDepth > 1 || maxDepth < 0 || maxDepth > 1) {
-            mTopLevelEncoder->HandleError("minDepth and maxDepth must be in [0, 1].");
-            return;
-        }
+            SetViewportCmd* cmd = allocator->Allocate<SetViewportCmd>(Command::SetViewport);
+            cmd->x = x;
+            cmd->y = y;
+            cmd->width = width;
+            cmd->height = height;
+            cmd->minDepth = minDepth;
+            cmd->maxDepth = maxDepth;
 
-        SetViewportCmd* cmd = mAllocator->Allocate<SetViewportCmd>(Command::SetViewport);
-        cmd->x = x;
-        cmd->y = y;
-        cmd->width = width;
-        cmd->height = height;
-        cmd->minDepth = minDepth;
-        cmd->maxDepth = maxDepth;
+            return {};
+        });
     }
 
     void RenderPassEncoderBase::SetScissorRect(uint32_t x,
                                                uint32_t y,
                                                uint32_t width,
                                                uint32_t height) {
-        if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) {
-            return;
-        }
-        if (width == 0 || height == 0) {
-            mTopLevelEncoder->HandleError("Width and height must be greater than 0.");
-            return;
-        }
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if (width == 0 || height == 0) {
+                return DAWN_VALIDATION_ERROR("Width and height must be greater than 0.");
+            }
 
-        SetScissorRectCmd* cmd = mAllocator->Allocate<SetScissorRectCmd>(Command::SetScissorRect);
-        cmd->x = x;
-        cmd->y = y;
-        cmd->width = width;
-        cmd->height = height;
+            SetScissorRectCmd* cmd =
+                allocator->Allocate<SetScissorRectCmd>(Command::SetScissorRect);
+            cmd->x = x;
+            cmd->y = y;
+            cmd->width = width;
+            cmd->height = height;
+
+            return {};
+        });
     }
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h
index ed947e5..b961079 100644
--- a/src/dawn_native/RenderPassEncoder.h
+++ b/src/dawn_native/RenderPassEncoder.h
@@ -27,11 +27,12 @@
     class RenderPassEncoderBase : public RenderEncoderBase {
       public:
         RenderPassEncoderBase(DeviceBase* device,
-                              CommandEncoderBase* topLevelEncoder,
-                              CommandAllocator* allocator);
+                              CommandEncoderBase* commandEncoder,
+                              EncodingContext* encodingContext);
 
         static RenderPassEncoderBase* MakeError(DeviceBase* device,
-                                                CommandEncoderBase* topLevelEncoder);
+                                                CommandEncoderBase* commandEncoder,
+                                                EncodingContext* encodingContext);
 
         void EndPass();
 
@@ -47,8 +48,14 @@
 
       protected:
         RenderPassEncoderBase(DeviceBase* device,
-                              CommandEncoderBase* topLevelEncoder,
+                              CommandEncoderBase* commandEncoder,
+                              EncodingContext* encodingContext,
                               ErrorTag errorTag);
+
+      private:
+        // For render and compute passes, the encoding context is borrowed from the command encoder.
+        // Keep a reference to the encoder to make sure the context isn't freed.
+        Ref<CommandEncoderBase> mCommandEncoder;
     };
 
 }  // namespace dawn_native
diff --git a/src/tests/unittests/validation/CommandBufferValidationTests.cpp b/src/tests/unittests/validation/CommandBufferValidationTests.cpp
index 9fe2863..b8415bd 100644
--- a/src/tests/unittests/validation/CommandBufferValidationTests.cpp
+++ b/src/tests/unittests/validation/CommandBufferValidationTests.cpp
@@ -49,9 +49,7 @@
         dawn::CommandEncoder encoder = device.CreateCommandEncoder();
         dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
         ASSERT_DEVICE_ERROR(encoder.Finish());
-        // TODO(cwallez@chromium.org) this should probably be a device error, but currently it
-        // produces a encoder error.
-        pass.EndPass();
+        ASSERT_DEVICE_ERROR(pass.EndPass());
     }
 }
 
@@ -78,9 +76,7 @@
         dawn::CommandEncoder encoder = device.CreateCommandEncoder();
         dawn::ComputePassEncoder pass = encoder.BeginComputePass();
         ASSERT_DEVICE_ERROR(encoder.Finish());
-        // TODO(cwallez@chromium.org) this should probably be a device error, but currently it
-        // produces a encoder error.
-        pass.EndPass();
+        ASSERT_DEVICE_ERROR(pass.EndPass());
     }
 }
 
@@ -101,8 +97,6 @@
         dawn::CommandEncoder encoder = device.CreateCommandEncoder();
         dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
         pass.EndPass();
-        // TODO(cwallez@chromium.org) this should probably be a device error, but currently it
-        // produces a encoder error.
         pass.EndPass();
         ASSERT_DEVICE_ERROR(encoder.Finish());
     }
@@ -123,8 +117,6 @@
         dawn::CommandEncoder encoder = device.CreateCommandEncoder();
         dawn::ComputePassEncoder pass = encoder.BeginComputePass();
         pass.EndPass();
-        // TODO(cwallez@chromium.org) this should probably be a device error, but currently it
-        // produces a encoder error.
         pass.EndPass();
         ASSERT_DEVICE_ERROR(encoder.Finish());
     }