Implement RenderBundle in the frontend

This CL implements RenderBundle and RenderBundleEncoder in the frontend
and adds unittests for validation.

Bug: dawn:154
Change-Id: Ice5ecd384cd627ad270b73052408f8139d1ea5f4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9221
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn_native/AttachmentState.cpp b/src/dawn_native/AttachmentState.cpp
index 39ddb4b..00bd10b 100644
--- a/src/dawn_native/AttachmentState.cpp
+++ b/src/dawn_native/AttachmentState.cpp
@@ -21,6 +21,19 @@
 
 namespace dawn_native {
 
+    AttachmentStateBlueprint::AttachmentStateBlueprint(
+        const RenderBundleEncoderDescriptor* descriptor)
+        : mHasDepthStencilAttachment(descriptor->depthStencilFormat != nullptr),
+          mSampleCount(descriptor->sampleCount) {
+        for (uint32_t i = 0; i < descriptor->colorFormatsCount; ++i) {
+            mColorAttachmentsSet.set(i);
+            mColorFormats[i] = descriptor->colorFormats[i];
+        }
+        if (mHasDepthStencilAttachment) {
+            mDepthStencilFormat = *descriptor->depthStencilFormat;
+        }
+    }
+
     AttachmentStateBlueprint::AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor)
         : mHasDepthStencilAttachment(descriptor->depthStencilState != nullptr),
           mSampleCount(descriptor->sampleCount) {
diff --git a/src/dawn_native/AttachmentState.h b/src/dawn_native/AttachmentState.h
index 34f2c1a..a7202fd 100644
--- a/src/dawn_native/AttachmentState.h
+++ b/src/dawn_native/AttachmentState.h
@@ -33,8 +33,9 @@
     class AttachmentStateBlueprint {
       public:
         // Note: Descriptors must be validated before the AttachmentState is constructed.
-        AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor);
-        AttachmentStateBlueprint(const RenderPassDescriptor* descriptor);
+        explicit AttachmentStateBlueprint(const RenderBundleEncoderDescriptor* descriptor);
+        explicit AttachmentStateBlueprint(const RenderPipelineDescriptor* descriptor);
+        explicit AttachmentStateBlueprint(const RenderPassDescriptor* descriptor);
 
         AttachmentStateBlueprint(const AttachmentStateBlueprint& rhs);
 
diff --git a/src/dawn_native/CommandValidation.cpp b/src/dawn_native/CommandValidation.cpp
index 9099a9e..78f8949 100644
--- a/src/dawn_native/CommandValidation.cpp
+++ b/src/dawn_native/CommandValidation.cpp
@@ -19,6 +19,7 @@
 #include "dawn_native/CommandBufferStateTracker.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/PassResourceUsageTracker.h"
+#include "dawn_native/RenderBundle.h"
 #include "dawn_native/RenderPipeline.h"
 
 namespace dawn_native {
@@ -177,6 +178,27 @@
 
     }  // namespace
 
+    MaybeError ValidateRenderBundle(CommandIterator* commands,
+                                    const AttachmentState* attachmentState,
+                                    PassResourceUsage* resourceUsage) {
+        PassResourceUsageTracker usageTracker;
+        CommandBufferStateTracker commandBufferState;
+        unsigned int debugGroupStackSize = 0;
+
+        Command type;
+        while (commands->NextCommandId(&type)) {
+            DAWN_TRY(ValidateRenderBundleCommand(commands, type, &usageTracker, &commandBufferState,
+                                                 attachmentState, &debugGroupStackSize,
+                                                 "Command disallowed inside a render bundle"));
+        }
+
+        DAWN_TRY(usageTracker.ValidateRenderPassUsages());
+        ASSERT(resourceUsage != nullptr);
+        *resourceUsage = usageTracker.AcquireResourceUsage();
+
+        return {};
+    }
+
     MaybeError ValidateRenderPass(CommandIterator* commands,
                                   BeginRenderPassCmd* renderPass,
                                   std::vector<PassResourceUsage>* perPassResourceUsages) {
@@ -217,6 +239,33 @@
                     return {};
                 } break;
 
+                case Command::ExecuteBundles: {
+                    ExecuteBundlesCmd* cmd = commands->NextCommand<ExecuteBundlesCmd>();
+                    auto bundles = commands->NextData<Ref<RenderBundleBase>>(cmd->count);
+                    for (uint32_t i = 0; i < cmd->count; ++i) {
+                        if (DAWN_UNLIKELY(renderPass->attachmentState.Get() !=
+                                          bundles[i]->GetAttachmentState())) {
+                            return DAWN_VALIDATION_ERROR(
+                                "Render bundle is not compatible with render pass");
+                        }
+
+                        const PassResourceUsage& usages = bundles[i]->GetResourceUsage();
+                        for (uint32_t i = 0; i < usages.buffers.size(); ++i) {
+                            usageTracker.BufferUsedAs(usages.buffers[i], usages.bufferUsages[i]);
+                        }
+
+                        for (uint32_t i = 0; i < usages.textures.size(); ++i) {
+                            usageTracker.TextureUsedAs(usages.textures[i], usages.textureUsages[i]);
+                        }
+                    }
+
+                    if (cmd->count > 0) {
+                        // Reset state. It is invalidated after render bundle execution.
+                        commandBufferState = CommandBufferStateTracker{};
+                    }
+
+                } break;
+
                 case Command::SetStencilReference: {
                     commands->NextCommand<SetStencilReferenceCmd>();
                 } break;
diff --git a/src/dawn_native/CommandValidation.h b/src/dawn_native/CommandValidation.h
index 2d29cb3..c90343c 100644
--- a/src/dawn_native/CommandValidation.h
+++ b/src/dawn_native/CommandValidation.h
@@ -22,9 +22,13 @@
 
 namespace dawn_native {
 
+    class AttachmentState;
     struct BeginRenderPassCmd;
     struct PassResourceUsage;
 
+    MaybeError ValidateRenderBundle(CommandIterator* commands,
+                                    const AttachmentState* attachmentState,
+                                    PassResourceUsage* resourceUsage);
     MaybeError ValidateRenderPass(CommandIterator* commands,
                                   BeginRenderPassCmd* renderPass,
                                   std::vector<PassResourceUsage>* perPassResourceUsages);
diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp
index fbc9172..eb11791 100644
--- a/src/dawn_native/Commands.cpp
+++ b/src/dawn_native/Commands.cpp
@@ -18,6 +18,7 @@
 #include "dawn_native/Buffer.h"
 #include "dawn_native/CommandAllocator.h"
 #include "dawn_native/ComputePipeline.h"
+#include "dawn_native/RenderBundle.h"
 #include "dawn_native/RenderPipeline.h"
 #include "dawn_native/Texture.h"
 
@@ -86,6 +87,14 @@
                     EndRenderPassCmd* cmd = commands->NextCommand<EndRenderPassCmd>();
                     cmd->~EndRenderPassCmd();
                 } break;
+                case Command::ExecuteBundles: {
+                    ExecuteBundlesCmd* cmd = commands->NextCommand<ExecuteBundlesCmd>();
+                    auto bundles = commands->NextData<Ref<RenderBundleBase>>(cmd->count);
+                    for (size_t i = 0; i < cmd->count; ++i) {
+                        (&bundles[i])->~Ref<RenderBundleBase>();
+                    }
+                    cmd->~ExecuteBundlesCmd();
+                } break;
                 case Command::InsertDebugMarker: {
                     InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
                     commands->NextData<char>(cmd->length + 1);
@@ -207,6 +216,11 @@
                 commands->NextCommand<EndRenderPassCmd>();
                 break;
 
+            case Command::ExecuteBundles: {
+                auto* cmd = commands->NextCommand<ExecuteBundlesCmd>();
+                commands->NextData<Ref<RenderBundleBase>>(cmd->count);
+            } break;
+
             case Command::InsertDebugMarker: {
                 InsertDebugMarkerCmd* cmd = commands->NextCommand<InsertDebugMarkerCmd>();
                 commands->NextData<char>(cmd->length + 1);
diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h
index 442cc95..18d834d 100644
--- a/src/dawn_native/Commands.h
+++ b/src/dawn_native/Commands.h
@@ -46,6 +46,7 @@
         DrawIndexedIndirect,
         EndComputePass,
         EndRenderPass,
+        ExecuteBundles,
         InsertDebugMarker,
         PopDebugGroup,
         PushDebugGroup,
@@ -170,6 +171,10 @@
 
     struct EndRenderPassCmd {};
 
+    struct ExecuteBundlesCmd {
+        uint32_t count;
+    };
+
     struct InsertDebugMarkerCmd {
         uint32_t length;
     };
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 3eef7e3..b02bda1e 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -29,6 +29,7 @@
 #include "dawn_native/Instance.h"
 #include "dawn_native/PipelineLayout.h"
 #include "dawn_native/Queue.h"
+#include "dawn_native/RenderBundleEncoder.h"
 #include "dawn_native/RenderPipeline.h"
 #include "dawn_native/Sampler.h"
 #include "dawn_native/ShaderModule.h"
@@ -268,33 +269,34 @@
     }
 
     Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
-        const RenderPipelineDescriptor* descriptor) {
-        AttachmentStateBlueprint blueprint(descriptor);
-
-        auto iter = mCaches->attachmentStates.find(&blueprint);
+        AttachmentStateBlueprint* blueprint) {
+        auto iter = mCaches->attachmentStates.find(blueprint);
         if (iter != mCaches->attachmentStates.end()) {
             return static_cast<AttachmentState*>(*iter);
         }
 
-        Ref<AttachmentState> attachmentState = new AttachmentState(this, blueprint);
+        Ref<AttachmentState> attachmentState = new AttachmentState(this, *blueprint);
         attachmentState->Release();
         mCaches->attachmentStates.insert(attachmentState.Get());
         return attachmentState;
     }
 
     Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
+        const RenderBundleEncoderDescriptor* descriptor) {
+        AttachmentStateBlueprint blueprint(descriptor);
+        return GetOrCreateAttachmentState(&blueprint);
+    }
+
+    Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
+        const RenderPipelineDescriptor* descriptor) {
+        AttachmentStateBlueprint blueprint(descriptor);
+        return GetOrCreateAttachmentState(&blueprint);
+    }
+
+    Ref<AttachmentState> DeviceBase::GetOrCreateAttachmentState(
         const RenderPassDescriptor* descriptor) {
         AttachmentStateBlueprint blueprint(descriptor);
-
-        auto iter = mCaches->attachmentStates.find(&blueprint);
-        if (iter != mCaches->attachmentStates.end()) {
-            return static_cast<AttachmentState*>(*iter);
-        }
-
-        Ref<AttachmentState> attachmentState = new AttachmentState(this, blueprint);
-        attachmentState->Release();
-        mCaches->attachmentStates.insert(attachmentState.Get());
-        return attachmentState;
+        return GetOrCreateAttachmentState(&blueprint);
     }
 
     void DeviceBase::UncacheAttachmentState(AttachmentState* obj) {
@@ -428,6 +430,16 @@
 
         return result;
     }
+    RenderBundleEncoderBase* DeviceBase::CreateRenderBundleEncoder(
+        const RenderBundleEncoderDescriptor* descriptor) {
+        RenderBundleEncoderBase* result = nullptr;
+
+        if (ConsumedError(CreateRenderBundleEncoderInternal(&result, descriptor))) {
+            return RenderBundleEncoderBase::MakeError(this);
+        }
+
+        return result;
+    }
     RenderPipelineBase* DeviceBase::CreateRenderPipeline(
         const RenderPipelineDescriptor* descriptor) {
         RenderPipelineBase* result = nullptr;
@@ -601,6 +613,14 @@
         return {};
     }
 
+    MaybeError DeviceBase::CreateRenderBundleEncoderInternal(
+        RenderBundleEncoderBase** result,
+        const RenderBundleEncoderDescriptor* descriptor) {
+        DAWN_TRY(ValidateRenderBundleEncoderDescriptor(this, descriptor));
+        *result = new RenderBundleEncoderBase(this, descriptor);
+        return {};
+    }
+
     MaybeError DeviceBase::CreateRenderPipelineInternal(
         RenderPipelineBase** result,
         const RenderPipelineDescriptor* descriptor) {
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 8a2b1c8..3ccde37 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -34,6 +34,7 @@
 
     class AdapterBase;
     class AttachmentState;
+    class AttachmentStateBlueprint;
     class FenceSignalTracker;
     class DynamicUploader;
     class StagingBufferBase;
@@ -116,6 +117,9 @@
             const ShaderModuleDescriptor* descriptor);
         void UncacheShaderModule(ShaderModuleBase* obj);
 
+        Ref<AttachmentState> GetOrCreateAttachmentState(AttachmentStateBlueprint* blueprint);
+        Ref<AttachmentState> GetOrCreateAttachmentState(
+            const RenderBundleEncoderDescriptor* descriptor);
         Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPipelineDescriptor* descriptor);
         Ref<AttachmentState> GetOrCreateAttachmentState(const RenderPassDescriptor* descriptor);
         void UncacheAttachmentState(AttachmentState* obj);
@@ -132,6 +136,8 @@
         ComputePipelineBase* CreateComputePipeline(const ComputePipelineDescriptor* descriptor);
         PipelineLayoutBase* CreatePipelineLayout(const PipelineLayoutDescriptor* descriptor);
         QueueBase* CreateQueue();
+        RenderBundleEncoderBase* CreateRenderBundleEncoder(
+            const RenderBundleEncoderDescriptor* descriptor);
         RenderPipelineBase* CreateRenderPipeline(const RenderPipelineDescriptor* descriptor);
         SamplerBase* CreateSampler(const SamplerDescriptor* descriptor);
         ShaderModuleBase* CreateShaderModule(const ShaderModuleDescriptor* descriptor);
@@ -204,6 +210,9 @@
         MaybeError CreatePipelineLayoutInternal(PipelineLayoutBase** result,
                                                 const PipelineLayoutDescriptor* descriptor);
         MaybeError CreateQueueInternal(QueueBase** result);
+        MaybeError CreateRenderBundleEncoderInternal(
+            RenderBundleEncoderBase** result,
+            const RenderBundleEncoderDescriptor* descriptor);
         MaybeError CreateRenderPipelineInternal(RenderPipelineBase** result,
                                                 const RenderPipelineDescriptor* descriptor);
         MaybeError CreateSamplerInternal(SamplerBase** result, const SamplerDescriptor* descriptor);
diff --git a/src/dawn_native/EncodingContext.cpp b/src/dawn_native/EncodingContext.cpp
index d2c8a75..d36ccef 100644
--- a/src/dawn_native/EncodingContext.cpp
+++ b/src/dawn_native/EncodingContext.cpp
@@ -76,6 +76,10 @@
     }
 
     MaybeError EncodingContext::Finish() {
+        if (IsFinished()) {
+            return DAWN_VALIDATION_ERROR("Command encoding already finished");
+        }
+
         const void* currentEncoder = mCurrentEncoder;
         const void* topLevelEncoder = mTopLevelEncoder;
 
diff --git a/src/dawn_native/Forward.h b/src/dawn_native/Forward.h
index b4c3e83..1ba29da 100644
--- a/src/dawn_native/Forward.h
+++ b/src/dawn_native/Forward.h
@@ -32,6 +32,8 @@
     class PipelineBase;
     class PipelineLayoutBase;
     class QueueBase;
+    class RenderBundleBase;
+    class RenderBundleEncoderBase;
     class RenderPassEncoderBase;
     class RenderPipelineBase;
     class SamplerBase;
diff --git a/src/dawn_native/RenderBundle.cpp b/src/dawn_native/RenderBundle.cpp
new file mode 100644
index 0000000..9cd08ea
--- /dev/null
+++ b/src/dawn_native/RenderBundle.cpp
@@ -0,0 +1,61 @@
+// 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/RenderBundle.h"
+
+#include "common/BitSetIterator.h"
+#include "dawn_native/Commands.h"
+#include "dawn_native/Device.h"
+#include "dawn_native/RenderBundleEncoder.h"
+
+namespace dawn_native {
+
+    RenderBundleBase::RenderBundleBase(RenderBundleEncoderBase* encoder,
+                                       const RenderBundleDescriptor* descriptor,
+                                       AttachmentState* attachmentState,
+                                       PassResourceUsage resourceUsage)
+        : ObjectBase(encoder->GetDevice()),
+          mCommands(encoder->AcquireCommands()),
+          mAttachmentState(attachmentState),
+          mResourceUsage(std::move(resourceUsage)) {
+    }
+
+    RenderBundleBase::~RenderBundleBase() {
+        FreeCommands(&mCommands);
+    }
+
+    // static
+    RenderBundleBase* RenderBundleBase::MakeError(DeviceBase* device) {
+        return new RenderBundleBase(device, ObjectBase::kError);
+    }
+
+    RenderBundleBase::RenderBundleBase(DeviceBase* device, ErrorTag errorTag)
+        : ObjectBase(device, errorTag) {
+    }
+
+    CommandIterator* RenderBundleBase::GetCommands() {
+        return &mCommands;
+    }
+
+    const AttachmentState* RenderBundleBase::GetAttachmentState() const {
+        ASSERT(!IsError());
+        return mAttachmentState.Get();
+    }
+
+    const PassResourceUsage& RenderBundleBase::GetResourceUsage() const {
+        ASSERT(!IsError());
+        return mResourceUsage;
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/RenderBundle.h b/src/dawn_native/RenderBundle.h
new file mode 100644
index 0000000..26db850
--- /dev/null
+++ b/src/dawn_native/RenderBundle.h
@@ -0,0 +1,60 @@
+// 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_RENDERBUNDLE_H_
+#define DAWNNATIVE_RENDERBUNDLE_H_
+
+#include "common/Constants.h"
+#include "dawn_native/AttachmentState.h"
+#include "dawn_native/CommandAllocator.h"
+#include "dawn_native/Error.h"
+#include "dawn_native/ObjectBase.h"
+#include "dawn_native/PassResourceUsage.h"
+
+#include "dawn_native/dawn_platform.h"
+
+#include <bitset>
+
+namespace dawn_native {
+
+    struct BeginRenderPassCmd;
+    struct RenderBundleDescriptor;
+    class RenderBundleEncoderBase;
+
+    class RenderBundleBase : public ObjectBase {
+      public:
+        RenderBundleBase(RenderBundleEncoderBase* encoder,
+                         const RenderBundleDescriptor* descriptor,
+                         AttachmentState* attachmentState,
+                         PassResourceUsage resourceUsage);
+        ~RenderBundleBase() override;
+
+        static RenderBundleBase* MakeError(DeviceBase* device);
+
+        CommandIterator* GetCommands();
+
+        const AttachmentState* GetAttachmentState() const;
+        const PassResourceUsage& GetResourceUsage() const;
+
+      private:
+        RenderBundleBase(DeviceBase* device, ErrorTag errorTag);
+
+        CommandIterator mCommands;
+        Ref<AttachmentState> mAttachmentState;
+        PassResourceUsage mResourceUsage;
+    };
+
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_RENDERBUNDLE_H_
diff --git a/src/dawn_native/RenderBundleEncoder.cpp b/src/dawn_native/RenderBundleEncoder.cpp
new file mode 100644
index 0000000..b4febef
--- /dev/null
+++ b/src/dawn_native/RenderBundleEncoder.cpp
@@ -0,0 +1,99 @@
+// 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/RenderBundleEncoder.h"
+
+#include "dawn_native/CommandValidation.h"
+#include "dawn_native/Commands.h"
+#include "dawn_native/Device.h"
+#include "dawn_native/RenderPipeline.h"
+#include "dawn_native/ValidationUtils_autogen.h"
+
+namespace dawn_native {
+
+    MaybeError ValidateRenderBundleEncoderDescriptor(
+        const DeviceBase* device,
+        const RenderBundleEncoderDescriptor* descriptor) {
+        if (!IsValidSampleCount(descriptor->sampleCount)) {
+            return DAWN_VALIDATION_ERROR("Sample count is not supported");
+        }
+
+        if (descriptor->colorFormatsCount > kMaxColorAttachments) {
+            return DAWN_VALIDATION_ERROR("Color formats count exceeds maximum");
+        }
+
+        if (descriptor->colorFormatsCount == 0 && !descriptor->depthStencilFormat) {
+            return DAWN_VALIDATION_ERROR("Should have at least one attachment format");
+        }
+
+        for (uint32_t i = 0; i < descriptor->colorFormatsCount; ++i) {
+            DAWN_TRY(ValidateTextureFormat(descriptor->colorFormats[i]));
+        }
+
+        if (descriptor->depthStencilFormat != nullptr) {
+            DAWN_TRY(ValidateTextureFormat(*descriptor->depthStencilFormat));
+        }
+
+        return {};
+    }
+
+    RenderBundleEncoderBase::RenderBundleEncoderBase(
+        DeviceBase* device,
+        const RenderBundleEncoderDescriptor* descriptor)
+        : RenderEncoderBase(device, &mEncodingContext),
+          mEncodingContext(device, this),
+          mAttachmentState(device->GetOrCreateAttachmentState(descriptor)) {
+    }
+
+    RenderBundleEncoderBase::RenderBundleEncoderBase(DeviceBase* device, ErrorTag errorTag)
+        : RenderEncoderBase(device, &mEncodingContext, errorTag), mEncodingContext(device, this) {
+    }
+
+    // static
+    RenderBundleEncoderBase* RenderBundleEncoderBase::MakeError(DeviceBase* device) {
+        return new RenderBundleEncoderBase(device, ObjectBase::kError);
+    }
+
+    const AttachmentState* RenderBundleEncoderBase::GetAttachmentState() const {
+        return mAttachmentState.Get();
+    }
+
+    CommandIterator RenderBundleEncoderBase::AcquireCommands() {
+        return mEncodingContext.AcquireCommands();
+    }
+
+    RenderBundleBase* RenderBundleEncoderBase::Finish(const RenderBundleDescriptor* descriptor) {
+        if (GetDevice()->ConsumedError(ValidateFinish(descriptor))) {
+            return RenderBundleBase::MakeError(GetDevice());
+        }
+        ASSERT(!IsError());
+
+        return new RenderBundleBase(this, descriptor, mAttachmentState.Get(),
+                                    std::move(mResourceUsage));
+    }
+
+    MaybeError RenderBundleEncoderBase::ValidateFinish(const RenderBundleDescriptor* descriptor) {
+        DAWN_TRY(GetDevice()->ValidateObject(this));
+
+        // 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());
+
+        CommandIterator* commands = mEncodingContext.GetIterator();
+
+        DAWN_TRY(ValidateRenderBundle(commands, mAttachmentState.Get(), &mResourceUsage));
+        return {};
+    }
+
+}  // namespace dawn_native
diff --git a/src/dawn_native/RenderBundleEncoder.h b/src/dawn_native/RenderBundleEncoder.h
new file mode 100644
index 0000000..aa32201
--- /dev/null
+++ b/src/dawn_native/RenderBundleEncoder.h
@@ -0,0 +1,53 @@
+// 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_RENDERBUNDLEENCODER_H_
+#define DAWNNATIVE_RENDERBUNDLEENCODER_H_
+
+#include "dawn_native/AttachmentState.h"
+#include "dawn_native/EncodingContext.h"
+#include "dawn_native/Error.h"
+#include "dawn_native/RenderBundle.h"
+#include "dawn_native/RenderEncoderBase.h"
+
+namespace dawn_native {
+
+    MaybeError ValidateRenderBundleEncoderDescriptor(
+        const DeviceBase* device,
+        const RenderBundleEncoderDescriptor* descriptor);
+    class RenderBundleEncoderBase : public RenderEncoderBase {
+      public:
+        RenderBundleEncoderBase(DeviceBase* device,
+                                const RenderBundleEncoderDescriptor* descriptor);
+
+        static RenderBundleEncoderBase* MakeError(DeviceBase* device);
+
+        const AttachmentState* GetAttachmentState() const;
+
+        RenderBundleBase* Finish(const RenderBundleDescriptor* descriptor);
+
+        CommandIterator AcquireCommands();
+
+      private:
+        RenderBundleEncoderBase(DeviceBase* device, ErrorTag errorTag);
+
+        MaybeError ValidateFinish(const RenderBundleDescriptor* descriptor);
+
+        EncodingContext mEncodingContext;
+        Ref<AttachmentState> mAttachmentState;
+        PassResourceUsage mResourceUsage;
+    };
+}  // namespace dawn_native
+
+#endif  // DAWNNATIVE_RENDERBUNDLEENCODER_H_
diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp
index 54d5db5..27f5df3 100644
--- a/src/dawn_native/RenderPassEncoder.cpp
+++ b/src/dawn_native/RenderPassEncoder.cpp
@@ -19,6 +19,7 @@
 #include "dawn_native/CommandEncoder.h"
 #include "dawn_native/Commands.h"
 #include "dawn_native/Device.h"
+#include "dawn_native/RenderBundle.h"
 #include "dawn_native/RenderPipeline.h"
 
 #include <math.h>
@@ -130,4 +131,24 @@
         });
     }
 
+    void RenderPassEncoderBase::ExecuteBundles(uint32_t count,
+                                               RenderBundleBase* const* renderBundles) {
+        mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            for (uint32_t i = 0; i < count; ++i) {
+                DAWN_TRY(GetDevice()->ValidateObject(renderBundles[i]));
+            }
+
+            ExecuteBundlesCmd* cmd =
+                allocator->Allocate<ExecuteBundlesCmd>(Command::ExecuteBundles);
+            cmd->count = count;
+
+            Ref<RenderBundleBase>* bundles = allocator->AllocateData<Ref<RenderBundleBase>>(count);
+            for (uint32_t i = 0; i < count; ++i) {
+                bundles[i] = renderBundles[i];
+            }
+
+            return {};
+        });
+    }
+
 }  // namespace dawn_native
diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h
index b961079..4b7c06d 100644
--- a/src/dawn_native/RenderPassEncoder.h
+++ b/src/dawn_native/RenderPassEncoder.h
@@ -20,10 +20,12 @@
 
 namespace dawn_native {
 
+    class RenderBundleBase;
+
     // This is called RenderPassEncoderBase to match the code generator expectations. Note that it
     // is a pure frontend type to record in its parent CommandEncoder and never has a backend
     // implementation.
-    // TODO(cwallez@chromium.org): Remove that generator limitation and rename to ComputePassEncoder
+    // TODO(cwallez@chromium.org): Remove that generator limitation and rename to RenderPassEncoder
     class RenderPassEncoderBase : public RenderEncoderBase {
       public:
         RenderPassEncoderBase(DeviceBase* device,
@@ -45,6 +47,7 @@
                          float minDepth,
                          float maxDepth);
         void SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height);
+        void ExecuteBundles(uint32_t count, RenderBundleBase* const* renderBundles);
 
       protected:
         RenderPassEncoderBase(DeviceBase* device,
diff --git a/src/dawn_native/RenderPipeline.h b/src/dawn_native/RenderPipeline.h
index 7a00e3a..490d178 100644
--- a/src/dawn_native/RenderPipeline.h
+++ b/src/dawn_native/RenderPipeline.h
@@ -28,6 +28,7 @@
     struct BeginRenderPassCmd;
 
     class DeviceBase;
+    class RenderBundleEncoderBase;
 
     MaybeError ValidateRenderPipelineDescriptor(const DeviceBase* device,
                                                 const RenderPipelineDescriptor* descriptor);