Change render passes from multi to single pass.

This as an API change to get closer to the direction in which WebGPU is
headed. The API change in next.json caused a ton of files to be changed
in the same commit to keep things compiling.

API: the Framebuffer and RenderPass objects are now merged in a single
RenderPassInfo that contains the attachments, loadOps and clear values
for a BeginRenderPass command. The concept of subpass is removed.
The RenderPass creation argument to RenderPipelines is replaced by
explicitly setting the format of attachments for RenderPipeline.

Validation: SetPipeline checks are changed to check that the attachments
info set on a RenderPipeline matches the attachments of the render pass.

Backends: Most changes are simplifications of the backends that no
longer require and indirection to query the current subpass out of the
render pass in BeginSubpass, and don't need to get the attachment info
from a RenderPass when creating RenderPipelines. In the Vulkan backend,
a VkRenderPass cache is added to reuse VkRenderPasses between
RenderPassInfos and RenderPipelines.

Tests and examples: they are updated with the simplified API. Tests
specific to the Framebuffer and RenderPass objects were removed and
validation tests for RenderPassInfo were added.

Tested by running CppHelloTriangle on all backends, end2end tests on all
platforms and all examples on the GL backend.
diff --git a/src/backend/vulkan/CommandBufferVk.cpp b/src/backend/vulkan/CommandBufferVk.cpp
index a1ce476..b85012f 100644
--- a/src/backend/vulkan/CommandBufferVk.cpp
+++ b/src/backend/vulkan/CommandBufferVk.cpp
@@ -18,9 +18,8 @@
 #include "backend/vulkan/BindGroupVk.h"
 #include "backend/vulkan/BufferVk.h"
 #include "backend/vulkan/ComputePipelineVk.h"
-#include "backend/vulkan/FramebufferVk.h"
 #include "backend/vulkan/PipelineLayoutVk.h"
-#include "backend/vulkan/RenderPassVk.h"
+#include "backend/vulkan/RenderPassInfoVk.h"
 #include "backend/vulkan/RenderPipelineVk.h"
 #include "backend/vulkan/TextureVk.h"
 #include "backend/vulkan/VulkanBackend.h"
@@ -184,43 +183,32 @@
 
                 case Command::BeginRenderPass: {
                     BeginRenderPassCmd* cmd = mCommands.NextCommand<BeginRenderPassCmd>();
-                    Framebuffer* framebuffer = ToBackend(cmd->framebuffer.Get());
-                    RenderPass* renderPass = ToBackend(cmd->renderPass.Get());
+                    RenderPassInfo* info = ToBackend(cmd->info.Get());
 
-                    // NXT has an implicit transition to color attachment on subpasses. Transition
-                    // the attachments now before we start the render pass.
-                    for (uint32_t i = 0; i < renderPass->GetAttachmentCount(); ++i) {
+                    // NXT has an implicit transition to color attachment on render passes.
+                    // Transition the attachments now before we start the render pass.
+                    for (uint32_t i : IterateBitSet(info->GetColorAttachmentMask())) {
                         Texture* attachment =
-                            ToBackend(framebuffer->GetTextureView(i)->GetTexture());
+                            ToBackend(info->GetColorAttachment(i).view->GetTexture());
 
-                        if (attachment->GetUsage() & nxt::TextureUsageBit::OutputAttachment) {
-                            continue;
+                        if (!(attachment->GetUsage() & nxt::TextureUsageBit::OutputAttachment)) {
+                            attachment->RecordBarrier(commands, attachment->GetUsage(),
+                                                      nxt::TextureUsageBit::OutputAttachment);
+                            attachment->UpdateUsageInternal(nxt::TextureUsageBit::OutputAttachment);
                         }
+                    }
+                    if (info->HasDepthStencilAttachment()) {
+                        Texture* attachment =
+                            ToBackend(info->GetDepthStencilAttachment().view->GetTexture());
 
-                        attachment->RecordBarrier(commands, attachment->GetUsage(),
-                                                  nxt::TextureUsageBit::OutputAttachment);
-                        attachment->UpdateUsageInternal(nxt::TextureUsageBit::OutputAttachment);
+                        if (!(attachment->GetUsage() & nxt::TextureUsageBit::OutputAttachment)) {
+                            attachment->RecordBarrier(commands, attachment->GetUsage(),
+                                                      nxt::TextureUsageBit::OutputAttachment);
+                            attachment->UpdateUsageInternal(nxt::TextureUsageBit::OutputAttachment);
+                        }
                     }
 
-                    ASSERT(renderPass->GetSubpassCount() == 1);
-                    ASSERT(renderPass->GetAttachmentCount() <= kMaxColorAttachments + 1);
-
-                    std::array<VkClearValue, kMaxColorAttachments + 1> clearValues;
-                    framebuffer->FillClearValues(clearValues.data());
-
-                    VkRenderPassBeginInfo beginInfo;
-                    beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
-                    beginInfo.pNext = nullptr;
-                    beginInfo.renderPass = renderPass->GetHandle();
-                    beginInfo.framebuffer = framebuffer->GetHandle();
-                    beginInfo.renderArea.offset.x = 0;
-                    beginInfo.renderArea.offset.y = 0;
-                    beginInfo.renderArea.extent.width = framebuffer->GetWidth();
-                    beginInfo.renderArea.extent.height = framebuffer->GetHeight();
-                    beginInfo.clearValueCount = renderPass->GetAttachmentCount();
-                    beginInfo.pClearValues = clearValues.data();
-
-                    device->fn.CmdBeginRenderPass(commands, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
+                    info->RecordBeginRenderPass(commands);
 
                     // Set all the dynamic state just in case.
                     device->fn.CmdSetLineWidth(commands, 1.0f);
@@ -228,32 +216,6 @@
 
                     device->fn.CmdSetStencilReference(commands, VK_STENCIL_FRONT_AND_BACK, 0);
 
-                    // The viewport and scissor default to cover all of the attachments
-                    VkViewport viewport;
-                    viewport.x = 0.0f;
-                    viewport.y = 0.0f;
-                    viewport.width = static_cast<float>(framebuffer->GetWidth());
-                    viewport.height = static_cast<float>(framebuffer->GetHeight());
-                    viewport.minDepth = 0.0f;
-                    viewport.maxDepth = 1.0f;
-                    device->fn.CmdSetViewport(commands, 0, 1, &viewport);
-
-                    VkRect2D scissorRect;
-                    scissorRect.offset.x = 0;
-                    scissorRect.offset.y = 0;
-                    scissorRect.extent.width = framebuffer->GetWidth();
-                    scissorRect.extent.height = framebuffer->GetHeight();
-                    device->fn.CmdSetScissor(commands, 0, 1, &scissorRect);
-
-                    descriptorSets.OnBeginPass();
-                } break;
-
-                case Command::BeginRenderSubpass: {
-                    mCommands.NextCommand<BeginRenderSubpassCmd>();
-                    // Do nothing related to subpasses because the single subpass is started in
-                    // vkBeginRenderPass
-
-                    // Set up the default state
                     float blendConstants[4] = {
                         0.0f,
                         0.0f,
@@ -261,6 +223,25 @@
                         0.0f,
                     };
                     device->fn.CmdSetBlendConstants(commands, blendConstants);
+
+                    // The viewport and scissor default to cover all of the attachments
+                    VkViewport viewport;
+                    viewport.x = 0.0f;
+                    viewport.y = 0.0f;
+                    viewport.width = static_cast<float>(info->GetWidth());
+                    viewport.height = static_cast<float>(info->GetHeight());
+                    viewport.minDepth = 0.0f;
+                    viewport.maxDepth = 1.0f;
+                    device->fn.CmdSetViewport(commands, 0, 1, &viewport);
+
+                    VkRect2D scissorRect;
+                    scissorRect.offset.x = 0;
+                    scissorRect.offset.y = 0;
+                    scissorRect.extent.width = info->GetWidth();
+                    scissorRect.extent.height = info->GetHeight();
+                    device->fn.CmdSetScissor(commands, 0, 1, &scissorRect);
+
+                    descriptorSets.OnBeginPass();
                 } break;
 
                 case Command::DrawArrays: {
@@ -285,11 +266,6 @@
                     device->fn.CmdEndRenderPass(commands);
                 } break;
 
-                case Command::EndRenderSubpass: {
-                    mCommands.NextCommand<EndRenderSubpassCmd>();
-                    // Do nothing because the single subpass is ended in vkEndRenderPass
-                } break;
-
                 case Command::BeginComputePass: {
                     mCommands.NextCommand<BeginComputePassCmd>();
                     descriptorSets.OnBeginPass();
diff --git a/src/backend/vulkan/FramebufferVk.cpp b/src/backend/vulkan/FramebufferVk.cpp
deleted file mode 100644
index f216dea..0000000
--- a/src/backend/vulkan/FramebufferVk.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2018 The NXT 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 "backend/vulkan/FramebufferVk.h"
-
-#include "backend/vulkan/FencedDeleter.h"
-#include "backend/vulkan/RenderPassVk.h"
-#include "backend/vulkan/TextureVk.h"
-#include "backend/vulkan/VulkanBackend.h"
-
-namespace backend { namespace vulkan {
-
-    Framebuffer::Framebuffer(FramebufferBuilder* builder) : FramebufferBase(builder) {
-        ASSERT(GetRenderPass()->GetAttachmentCount() <= kMaxColorAttachments + 1);
-
-        Device* device = ToBackend(GetDevice());
-
-        // Fill in the attachment info that will be chained in the create info.
-        std::array<VkImageView, kMaxColorAttachments + 1> attachments;
-        for (uint32_t i = 0; i < GetRenderPass()->GetAttachmentCount(); ++i) {
-            attachments[i] = ToBackend(GetTextureView(i))->GetHandle();
-        }
-
-        // Chain attachments and create the framebuffer
-        VkFramebufferCreateInfo createInfo;
-        createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
-        createInfo.pNext = nullptr;
-        createInfo.flags = 0;
-        createInfo.renderPass = ToBackend(GetRenderPass())->GetHandle();
-        createInfo.attachmentCount = GetRenderPass()->GetAttachmentCount();
-        createInfo.pAttachments = attachments.data();
-        createInfo.width = GetWidth();
-        createInfo.height = GetHeight();
-        createInfo.layers = 1;
-
-        if (device->fn.CreateFramebuffer(device->GetVkDevice(), &createInfo, nullptr, &mHandle) !=
-            VK_SUCCESS) {
-            ASSERT(false);
-        }
-    }
-
-    Framebuffer::~Framebuffer() {
-        Device* device = ToBackend(GetDevice());
-
-        if (mHandle != VK_NULL_HANDLE) {
-            device->GetFencedDeleter()->DeleteWhenUnused(mHandle);
-            mHandle = VK_NULL_HANDLE;
-        }
-    }
-
-    VkFramebuffer Framebuffer::GetHandle() const {
-        return mHandle;
-    }
-
-    void Framebuffer::FillClearValues(VkClearValue* values) {
-        const RenderPassBase* renderPass = GetRenderPass();
-        for (uint32_t i = 0; i < renderPass->GetAttachmentCount(); ++i) {
-            if (TextureFormatHasDepthOrStencil(renderPass->GetAttachmentInfo(i).format)) {
-                const auto& clearValues = GetClearDepthStencil(i);
-
-                values[i].depthStencil.depth = clearValues.depth;
-                values[i].depthStencil.stencil = clearValues.stencil;
-            } else {
-                const auto& clearValues = GetClearColor(i);
-
-                values[i].color.float32[0] = clearValues.color[0];
-                values[i].color.float32[1] = clearValues.color[1];
-                values[i].color.float32[2] = clearValues.color[2];
-                values[i].color.float32[3] = clearValues.color[3];
-            }
-        }
-    }
-
-}}  // namespace backend::vulkan
diff --git a/src/backend/vulkan/GeneratedCodeIncludes.h b/src/backend/vulkan/GeneratedCodeIncludes.h
index 23b4489..18e96e7 100644
--- a/src/backend/vulkan/GeneratedCodeIncludes.h
+++ b/src/backend/vulkan/GeneratedCodeIncludes.h
@@ -19,10 +19,9 @@
 #include "backend/vulkan/CommandBufferVk.h"
 #include "backend/vulkan/ComputePipelineVk.h"
 #include "backend/vulkan/DepthStencilStateVk.h"
-#include "backend/vulkan/FramebufferVk.h"
 #include "backend/vulkan/InputStateVk.h"
 #include "backend/vulkan/PipelineLayoutVk.h"
-#include "backend/vulkan/RenderPassVk.h"
+#include "backend/vulkan/RenderPassInfoVk.h"
 #include "backend/vulkan/RenderPipelineVk.h"
 #include "backend/vulkan/SamplerVk.h"
 #include "backend/vulkan/ShaderModuleVk.h"
diff --git a/src/backend/vulkan/RenderPassCache.cpp b/src/backend/vulkan/RenderPassCache.cpp
new file mode 100644
index 0000000..0d9e63e
--- /dev/null
+++ b/src/backend/vulkan/RenderPassCache.cpp
@@ -0,0 +1,209 @@
+// Copyright 2018 The NXT 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 "backend/vulkan/RenderPassCache.h"
+
+#include "backend/vulkan/TextureVk.h"
+#include "backend/vulkan/VulkanBackend.h"
+#include "common/BitSetIterator.h"
+#include "common/HashUtils.h"
+
+namespace backend { namespace vulkan {
+
+    namespace {
+        VkAttachmentLoadOp VulkanAttachmentLoadOp(nxt::LoadOp op) {
+            switch (op) {
+                case nxt::LoadOp::Load:
+                    return VK_ATTACHMENT_LOAD_OP_LOAD;
+                case nxt::LoadOp::Clear:
+                    return VK_ATTACHMENT_LOAD_OP_CLEAR;
+                default:
+                    UNREACHABLE();
+            }
+        }
+    }  // anonymous namespace
+
+    // RenderPassCacheQuery
+
+    void RenderPassCacheQuery::SetColor(uint32_t index,
+                                        nxt::TextureFormat format,
+                                        nxt::LoadOp loadOp) {
+        colorMask.set(index);
+        colorFormats[index] = format;
+        colorLoadOp[index] = loadOp;
+    }
+
+    void RenderPassCacheQuery::SetDepthStencil(nxt::TextureFormat format,
+                                               nxt::LoadOp depthLoadOp,
+                                               nxt::LoadOp stencilLoadOp) {
+        hasDepthStencil = true;
+        depthStencilFormat = format;
+        this->depthLoadOp = depthLoadOp;
+        this->stencilLoadOp = stencilLoadOp;
+    }
+
+    // RenderPassCache
+
+    RenderPassCache::RenderPassCache(Device* device) : mDevice(device) {
+    }
+
+    RenderPassCache::~RenderPassCache() {
+        for (auto it : mCache) {
+            mDevice->fn.DestroyRenderPass(mDevice->GetVkDevice(), it.second, nullptr);
+        }
+        mCache.clear();
+    }
+
+    VkRenderPass RenderPassCache::GetRenderPass(const RenderPassCacheQuery& query) {
+        auto it = mCache.find(query);
+        if (it != mCache.end()) {
+            return it->second;
+        }
+
+        VkRenderPass renderPass = CreateRenderPassForQuery(query);
+        mCache.emplace(query, renderPass);
+        return renderPass;
+    }
+
+    VkRenderPass RenderPassCache::CreateRenderPassForQuery(
+        const RenderPassCacheQuery& query) const {
+        // The Vulkan subpasses want to know the layout of the attachments with VkAttachmentRef.
+        // Precompute them as they must be pointer-chained in VkSubpassDescription
+        std::array<VkAttachmentReference, kMaxColorAttachments + 1> attachmentRefs;
+
+        // Contains the attachment description that will be chained in the create info
+        std::array<VkAttachmentDescription, kMaxColorAttachments + 1> attachmentDescs = {};
+
+        uint32_t attachmentCount = 0;
+        for (uint32_t i : IterateBitSet(query.colorMask)) {
+            auto& attachmentRef = attachmentRefs[attachmentCount];
+            auto& attachmentDesc = attachmentDescs[attachmentCount];
+
+            attachmentRef.attachment = attachmentCount;
+            attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+            attachmentDesc.flags = 0;
+            attachmentDesc.format = VulkanImageFormat(query.colorFormats[i]);
+            attachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+            attachmentDesc.loadOp = VulkanAttachmentLoadOp(query.colorLoadOp[i]);
+            attachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+            attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+            attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+            attachmentCount++;
+        }
+        uint32_t colorAttachmentCount = attachmentCount;
+
+        VkAttachmentReference* depthStencilAttachment = nullptr;
+        if (query.hasDepthStencil) {
+            auto& attachmentRef = attachmentRefs[attachmentCount];
+            auto& attachmentDesc = attachmentDescs[attachmentCount];
+
+            depthStencilAttachment = &attachmentRefs[attachmentCount];
+
+            attachmentRef.attachment = attachmentCount;
+            attachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+            attachmentDesc.flags = 0;
+            attachmentDesc.format = VulkanImageFormat(query.depthStencilFormat);
+            attachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+            attachmentDesc.loadOp = VulkanAttachmentLoadOp(query.depthLoadOp);
+            attachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+            attachmentDesc.stencilLoadOp = VulkanAttachmentLoadOp(query.stencilLoadOp);
+            attachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
+            attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+            attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+            attachmentCount++;
+        }
+
+        // Create the VkSubpassDescription that will be chained in the VkRenderPassCreateInfo
+        VkSubpassDescription subpassDesc;
+        subpassDesc.flags = 0;
+        subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+        subpassDesc.inputAttachmentCount = 0;
+        subpassDesc.pInputAttachments = nullptr;
+        subpassDesc.colorAttachmentCount = colorAttachmentCount;
+        subpassDesc.pColorAttachments = attachmentRefs.data();
+        subpassDesc.pResolveAttachments = nullptr;
+        subpassDesc.pDepthStencilAttachment = depthStencilAttachment;
+        subpassDesc.preserveAttachmentCount = 0;
+        subpassDesc.pPreserveAttachments = nullptr;
+
+        // Chain everything in VkRenderPassCreateInfo
+        VkRenderPassCreateInfo createInfo;
+        createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+        createInfo.pNext = nullptr;
+        createInfo.flags = 0;
+        createInfo.attachmentCount = attachmentCount;
+        createInfo.pAttachments = attachmentDescs.data();
+        createInfo.subpassCount = 1;
+        createInfo.pSubpasses = &subpassDesc;
+        createInfo.dependencyCount = 0;
+        createInfo.pDependencies = nullptr;
+
+        // Create the render pass from the zillion parameters
+        VkRenderPass renderPass;
+        if (mDevice->fn.CreateRenderPass(mDevice->GetVkDevice(), &createInfo, nullptr,
+                                         &renderPass) != VK_SUCCESS) {
+            ASSERT(false);
+        }
+
+        return renderPass;
+    }
+
+    // RenderPassCache
+
+    size_t RenderPassCache::CacheFuncs::operator()(const RenderPassCacheQuery& query) const {
+        size_t hash = Hash(query.colorMask);
+
+        for (uint32_t i : IterateBitSet(query.colorMask)) {
+            HashCombine(&hash, query.colorFormats[i], query.colorLoadOp[i]);
+        }
+
+        HashCombine(&hash, query.hasDepthStencil);
+        if (query.hasDepthStencil) {
+            HashCombine(&hash, query.depthStencilFormat, query.depthLoadOp, query.stencilLoadOp);
+        }
+
+        return hash;
+    }
+
+    bool RenderPassCache::CacheFuncs::operator()(const RenderPassCacheQuery& a,
+                                                 const RenderPassCacheQuery& b) const {
+        if (a.colorMask != b.colorMask) {
+            return false;
+        }
+
+        for (uint32_t i : IterateBitSet(a.colorMask)) {
+            if ((a.colorFormats[i] != b.colorFormats[i]) ||
+                (a.colorLoadOp[i] != b.colorLoadOp[i])) {
+                return false;
+            }
+        }
+
+        if (a.hasDepthStencil != b.hasDepthStencil) {
+            return false;
+        }
+
+        if (a.hasDepthStencil) {
+            if ((a.depthStencilFormat != b.depthStencilFormat) ||
+                (a.depthLoadOp != b.depthLoadOp) || (a.stencilLoadOp != b.stencilLoadOp)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}}  // namespace backend::vulkan
diff --git a/src/backend/vulkan/RenderPassCache.h b/src/backend/vulkan/RenderPassCache.h
new file mode 100644
index 0000000..1ac12e3
--- /dev/null
+++ b/src/backend/vulkan/RenderPassCache.h
@@ -0,0 +1,81 @@
+// Copyright 2018 The NXT 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 BACKEND_VULKAN_RENDERPASSCACHE_H_
+#define BACKEND_VULKAN_RENDERPASSCACHE_H_
+
+#include "common/vulkan_platform.h"
+
+#include "common/Constants.h"
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+#include <unordered_map>
+
+namespace backend { namespace vulkan {
+
+    class Device;
+
+    // This is a key to query the RenderPassCache, it can be sparse meaning that only the
+    // information for bits set in colorMask or hasDepthStencil need to be provided and the rest can
+    // be uninintialized.
+    struct RenderPassCacheQuery {
+        // Use these helpers to build the query, they make sure all relevant data is initialized and
+        // masks set.
+        void SetColor(uint32_t index, nxt::TextureFormat format, nxt::LoadOp loadOp);
+        void SetDepthStencil(nxt::TextureFormat format,
+                             nxt::LoadOp depthLoadOp,
+                             nxt::LoadOp stencilLoadOp);
+
+        std::bitset<kMaxColorAttachments> colorMask;
+        std::array<nxt::TextureFormat, kMaxColorAttachments> colorFormats;
+        std::array<nxt::LoadOp, kMaxColorAttachments> colorLoadOp;
+
+        bool hasDepthStencil = false;
+        nxt::TextureFormat depthStencilFormat;
+        nxt::LoadOp depthLoadOp;
+        nxt::LoadOp stencilLoadOp;
+    };
+
+    // Caches VkRenderPasses so that we don't create duplicate ones for every RenderPipeline or
+    // render pass.
+    // TODO(cwallez@chromium.org): Make it an LRU cache somehow?
+    class RenderPassCache {
+      public:
+        RenderPassCache(Device* device);
+        ~RenderPassCache();
+
+        VkRenderPass GetRenderPass(const RenderPassCacheQuery& query);
+
+      private:
+        // Does the actual VkRenderPass creation on a cache miss.
+        VkRenderPass CreateRenderPassForQuery(const RenderPassCacheQuery& query) const;
+
+        // Implements the functors necessary for to use RenderPassCacheQueries as unordered_map
+        // keys.
+        struct CacheFuncs {
+            size_t operator()(const RenderPassCacheQuery& query) const;
+            bool operator()(const RenderPassCacheQuery& a, const RenderPassCacheQuery& b) const;
+        };
+        using Cache =
+            std::unordered_map<RenderPassCacheQuery, VkRenderPass, CacheFuncs, CacheFuncs>;
+
+        Device* mDevice = nullptr;
+        Cache mCache;
+    };
+
+}}  // namespace backend::vulkan
+
+#endif  // BACKEND_VULKAN_RENDERPASSCACHE_H_
diff --git a/src/backend/vulkan/RenderPassInfoVk.cpp b/src/backend/vulkan/RenderPassInfoVk.cpp
new file mode 100644
index 0000000..4f046f5
--- /dev/null
+++ b/src/backend/vulkan/RenderPassInfoVk.cpp
@@ -0,0 +1,122 @@
+// Copyright 2018 The NXT 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 "backend/vulkan/RenderPassInfoVk.h"
+
+#include "backend/vulkan/FencedDeleter.h"
+#include "backend/vulkan/RenderPassCache.h"
+#include "backend/vulkan/TextureVk.h"
+#include "backend/vulkan/VulkanBackend.h"
+#include "common/BitSetIterator.h"
+
+namespace backend { namespace vulkan {
+
+    RenderPassInfo::RenderPassInfo(RenderPassInfoBuilder* builder)
+        : RenderPassInfoBase(builder), mDevice(ToBackend(builder->GetDevice())) {
+    }
+
+    void RenderPassInfo::RecordBeginRenderPass(VkCommandBuffer commands) {
+        // Query a VkRenderPass from the cache
+        VkRenderPass renderPass = VK_NULL_HANDLE;
+        {
+            RenderPassCacheQuery query;
+
+            for (uint32_t i : IterateBitSet(GetColorAttachmentMask())) {
+                const auto& attachmentInfo = GetColorAttachment(i);
+                query.SetColor(i, attachmentInfo.view->GetTexture()->GetFormat(),
+                               attachmentInfo.loadOp);
+            }
+
+            if (HasDepthStencilAttachment()) {
+                const auto& attachmentInfo = GetDepthStencilAttachment();
+                query.SetDepthStencil(attachmentInfo.view->GetTexture()->GetFormat(),
+                                      attachmentInfo.depthLoadOp, attachmentInfo.stencilLoadOp);
+            }
+
+            renderPass = mDevice->GetRenderPassCache()->GetRenderPass(query);
+        }
+
+        // Create a framebuffer that will be used once for the render pass and gather the clear
+        // values for the attachments at the same time.
+        std::array<VkClearValue, kMaxColorAttachments + 1> clearValues;
+        VkFramebuffer framebuffer = VK_NULL_HANDLE;
+        uint32_t attachmentCount = 0;
+        {
+            // Fill in the attachment info that will be chained in the framebuffer create info.
+            std::array<VkImageView, kMaxColorAttachments + 1> attachments;
+
+            for (uint32_t i : IterateBitSet(GetColorAttachmentMask())) {
+                auto& attachmentInfo = GetColorAttachment(i);
+                TextureView* view = ToBackend(attachmentInfo.view.Get());
+
+                attachments[attachmentCount] = view->GetHandle();
+
+                clearValues[attachmentCount].color.float32[0] = attachmentInfo.clearColor[0];
+                clearValues[attachmentCount].color.float32[1] = attachmentInfo.clearColor[1];
+                clearValues[attachmentCount].color.float32[2] = attachmentInfo.clearColor[2];
+                clearValues[attachmentCount].color.float32[3] = attachmentInfo.clearColor[3];
+
+                attachmentCount++;
+            }
+
+            if (HasDepthStencilAttachment()) {
+                auto& attachmentInfo = GetDepthStencilAttachment();
+                TextureView* view = ToBackend(attachmentInfo.view.Get());
+
+                attachments[attachmentCount] = view->GetHandle();
+
+                clearValues[attachmentCount].depthStencil.depth = attachmentInfo.clearDepth;
+                clearValues[attachmentCount].depthStencil.stencil = attachmentInfo.clearStencil;
+
+                attachmentCount++;
+            }
+
+            // Chain attachments and create the framebuffer
+            VkFramebufferCreateInfo createInfo;
+            createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+            createInfo.pNext = nullptr;
+            createInfo.flags = 0;
+            createInfo.renderPass = renderPass;
+            createInfo.attachmentCount = attachmentCount;
+            createInfo.pAttachments = attachments.data();
+            createInfo.width = GetWidth();
+            createInfo.height = GetHeight();
+            createInfo.layers = 1;
+
+            if (mDevice->fn.CreateFramebuffer(mDevice->GetVkDevice(), &createInfo, nullptr,
+                                              &framebuffer) != VK_SUCCESS) {
+                ASSERT(false);
+            }
+
+            // We don't reuse VkFramebuffers so mark the framebuffer for deletion as soon as the
+            // commands currently being recorded are finished.
+            mDevice->GetFencedDeleter()->DeleteWhenUnused(framebuffer);
+        }
+
+        VkRenderPassBeginInfo beginInfo;
+        beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+        beginInfo.pNext = nullptr;
+        beginInfo.renderPass = renderPass;
+        beginInfo.framebuffer = framebuffer;
+        beginInfo.renderArea.offset.x = 0;
+        beginInfo.renderArea.offset.y = 0;
+        beginInfo.renderArea.extent.width = GetWidth();
+        beginInfo.renderArea.extent.height = GetHeight();
+        beginInfo.clearValueCount = attachmentCount;
+        beginInfo.pClearValues = clearValues.data();
+
+        mDevice->fn.CmdBeginRenderPass(commands, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
+    }
+
+}}  // namespace backend::vulkan
diff --git a/src/backend/vulkan/FramebufferVk.h b/src/backend/vulkan/RenderPassInfoVk.h
similarity index 60%
rename from src/backend/vulkan/FramebufferVk.h
rename to src/backend/vulkan/RenderPassInfoVk.h
index 5a979cf..dcbba41 100644
--- a/src/backend/vulkan/FramebufferVk.h
+++ b/src/backend/vulkan/RenderPassInfoVk.h
@@ -12,27 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef BACKEND_VULKAN_FRAMEBUFFERVK_H_
-#define BACKEND_VULKAN_FRAMEBUFFERVK_H_
+#ifndef BACKEND_VULKAN_RENDERPASSINFOVK_H_
+#define BACKEND_VULKAN_RENDERPASSINFOVK_H_
 
-#include "backend/Framebuffer.h"
+#include "backend/RenderPassInfo.h"
 
 #include "common/vulkan_platform.h"
 
 namespace backend { namespace vulkan {
 
-    class Framebuffer : public FramebufferBase {
-      public:
-        Framebuffer(FramebufferBuilder* builder);
-        ~Framebuffer();
+    class Device;
 
-        VkFramebuffer GetHandle() const;
-        void FillClearValues(VkClearValue* values);
+    class RenderPassInfo : public RenderPassInfoBase {
+      public:
+        RenderPassInfo(RenderPassInfoBuilder* builder);
+
+        // Compute all the arguments for, and record the vkCmdBeginRenderPass command.
+        void RecordBeginRenderPass(VkCommandBuffer commands);
 
       private:
-        VkFramebuffer mHandle = VK_NULL_HANDLE;
+        Device* mDevice = nullptr;
     };
 
 }}  // namespace backend::vulkan
 
-#endif  // BACKEND_VULKAN_FRAMEBUFFERVK_H_
+#endif  // BACKEND_VULKAN_RENDERPASSINFOVK_H_
diff --git a/src/backend/vulkan/RenderPassVk.cpp b/src/backend/vulkan/RenderPassVk.cpp
deleted file mode 100644
index d1d35b9..0000000
--- a/src/backend/vulkan/RenderPassVk.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2018 The NXT 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 "backend/vulkan/RenderPassVk.h"
-
-#include "backend/vulkan/FencedDeleter.h"
-#include "backend/vulkan/TextureVk.h"
-#include "backend/vulkan/VulkanBackend.h"
-#include "common/BitSetIterator.h"
-
-namespace backend { namespace vulkan {
-
-    namespace {
-        VkAttachmentLoadOp VulkanAttachmentLoadOp(nxt::LoadOp op) {
-            switch (op) {
-                case nxt::LoadOp::Load:
-                    return VK_ATTACHMENT_LOAD_OP_LOAD;
-                case nxt::LoadOp::Clear:
-                    return VK_ATTACHMENT_LOAD_OP_CLEAR;
-                default:
-                    UNREACHABLE();
-            }
-        }
-    }  // anonymous namespace
-
-    RenderPass::RenderPass(RenderPassBuilder* builder)
-        : RenderPassBase(builder), mDevice(ToBackend(builder->GetDevice())) {
-        // For now we only support single pass render passes.
-        ASSERT(GetSubpassCount() == 1);
-        ASSERT(GetAttachmentCount() <= kMaxColorAttachments + 1);
-
-        const auto& subpass = GetSubpassInfo(0);
-
-        // The Vulkan subpasses want to know the layout of the attachments with VkAttachmentRef.
-        // Precompute them as they must be pointer-chained in VkSubpassDescription
-        std::array<VkAttachmentReference, kMaxColorAttachments + 1> attachmentRefs;
-        attachmentRefs.fill(VkAttachmentReference{VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_UNDEFINED});
-
-        for (uint32_t i : IterateBitSet(subpass.colorAttachmentsSet)) {
-            attachmentRefs[i].attachment = subpass.colorAttachments[i];
-            attachmentRefs[i].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-
-            // TODO(cwallez@chromium.org): need validation rule that attachments are packed
-            ASSERT(i == 0 || subpass.colorAttachmentsSet[i - 1]);
-        }
-        if (subpass.depthStencilAttachment) {
-            attachmentRefs[kMaxColorAttachments].attachment = subpass.depthStencilAttachment;
-            attachmentRefs[kMaxColorAttachments].layout =
-                VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-        }
-
-        // Create the VkSubpassDescription that will be chained in the VkRenderPassCreateInfo
-        VkSubpassDescription subpassDesc;
-        subpassDesc.flags = 0;
-        subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
-        subpassDesc.inputAttachmentCount = 0;
-        subpassDesc.pInputAttachments = nullptr;
-        subpassDesc.colorAttachmentCount =
-            static_cast<uint32_t>(subpass.colorAttachmentsSet.count());
-        subpassDesc.pColorAttachments = attachmentRefs.data();
-        subpassDesc.pResolveAttachments = nullptr;
-        subpassDesc.pDepthStencilAttachment = &attachmentRefs[kMaxColorAttachments];
-        subpassDesc.preserveAttachmentCount = 0;
-        subpassDesc.pPreserveAttachments = nullptr;
-
-        // Create the VkAttachmentDescriptions that will be chained in the VkRenderPassCreateInfo
-        std::array<VkAttachmentDescription, kMaxColorAttachments + 1> attachmentDescs = {};
-        for (uint32_t i = 0; i < GetAttachmentCount(); ++i) {
-            const auto& attachment = GetAttachmentInfo(i);
-            auto& attachmentDesc = attachmentDescs[i];
-
-            attachmentDesc.flags = 0;
-            attachmentDesc.format = VulkanImageFormat(attachment.format);
-            attachmentDesc.samples = VK_SAMPLE_COUNT_1_BIT;
-            if (TextureFormatHasDepthOrStencil(attachment.format)) {
-                attachmentDesc.loadOp = VulkanAttachmentLoadOp(attachment.depthLoadOp);
-                attachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-                attachmentDesc.stencilLoadOp = VulkanAttachmentLoadOp(attachment.stencilLoadOp);
-                attachmentDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
-
-                attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-                attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-            } else {
-                attachmentDesc.loadOp = VulkanAttachmentLoadOp(attachment.colorLoadOp);
-                attachmentDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-
-                attachmentDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-                attachmentDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-            }
-        }
-
-        // Chain everything in VkRenderPassCreateInfo
-        VkRenderPassCreateInfo createInfo;
-        createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
-        createInfo.pNext = nullptr;
-        createInfo.flags = 0;
-        createInfo.attachmentCount = GetAttachmentCount();
-        createInfo.pAttachments = attachmentDescs.data();
-        createInfo.subpassCount = 1;
-        createInfo.pSubpasses = &subpassDesc;
-        createInfo.dependencyCount = 0;
-        createInfo.pDependencies = nullptr;
-
-        // Create the render pass from the zillion parameters
-        if (mDevice->fn.CreateRenderPass(mDevice->GetVkDevice(), &createInfo, nullptr, &mHandle) !=
-            VK_SUCCESS) {
-            ASSERT(false);
-        }
-    }
-
-    RenderPass::~RenderPass() {
-        if (mHandle != VK_NULL_HANDLE) {
-            mDevice->GetFencedDeleter()->DeleteWhenUnused(mHandle);
-            mHandle = VK_NULL_HANDLE;
-        }
-    }
-
-    VkRenderPass RenderPass::GetHandle() const {
-        return mHandle;
-    }
-
-}}  // namespace backend::vulkan
diff --git a/src/backend/vulkan/RenderPassVk.h b/src/backend/vulkan/RenderPassVk.h
deleted file mode 100644
index fe7889f..0000000
--- a/src/backend/vulkan/RenderPassVk.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2018 The NXT 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 BACKEND_VULKAN_RENDERPASSVK_H_
-#define BACKEND_VULKAN_RENDERPASSVK_H_
-
-#include "backend/RenderPass.h"
-
-#include "common/vulkan_platform.h"
-
-namespace backend { namespace vulkan {
-
-    class Device;
-
-    class RenderPass : public RenderPassBase {
-      public:
-        RenderPass(RenderPassBuilder* builder);
-        ~RenderPass();
-
-        // TODO(cwallez@chromium.org): We need a way to ask for a compatible VkRenderPass with the
-        // given load an store operations. Also they should be cached. For now this is hardcoded to
-        // have Load = Clear and Store = Write
-        VkRenderPass GetHandle() const;
-
-      private:
-        VkRenderPass mHandle = VK_NULL_HANDLE;
-        Device* mDevice = nullptr;
-    };
-
-}}  // namespace backend::vulkan
-
-#endif  // BACKEND_VULKAN_PIPELINELAYOUTVK_H_
diff --git a/src/backend/vulkan/RenderPipelineVk.cpp b/src/backend/vulkan/RenderPipelineVk.cpp
index 7fba8e8..03548a0 100644
--- a/src/backend/vulkan/RenderPipelineVk.cpp
+++ b/src/backend/vulkan/RenderPipelineVk.cpp
@@ -19,7 +19,8 @@
 #include "backend/vulkan/FencedDeleter.h"
 #include "backend/vulkan/InputStateVk.h"
 #include "backend/vulkan/PipelineLayoutVk.h"
-#include "backend/vulkan/RenderPassVk.h"
+#include "backend/vulkan/RenderPassCache.h"
+#include "backend/vulkan/RenderPassInfoVk.h"
 #include "backend/vulkan/ShaderModuleVk.h"
 #include "backend/vulkan/VulkanBackend.h"
 
@@ -133,10 +134,8 @@
 
         // Initialize the "blend state info" that will be chained in the "create info" from the data
         // pre-computed in the BlendState
-        const auto& subpassInfo = GetRenderPass()->GetSubpassInfo(GetSubPass());
-
         std::array<VkPipelineColorBlendAttachmentState, kMaxColorAttachments> colorBlendAttachments;
-        for (uint32_t i : IterateBitSet(subpassInfo.colorAttachmentsSet)) {
+        for (uint32_t i : IterateBitSet(GetColorAttachmentsMask())) {
             colorBlendAttachments[i] = ToBackend(GetBlendState(i))->GetState();
         }
         VkPipelineColorBlendStateCreateInfo colorBlend;
@@ -147,7 +146,7 @@
         colorBlend.logicOpEnable = VK_FALSE;
         colorBlend.logicOp = VK_LOGIC_OP_CLEAR;
         // TODO(cwallez@chromium.org): Do we allow holes in the color attachments?
-        colorBlend.attachmentCount = static_cast<uint32_t>(subpassInfo.colorAttachmentsSet.count());
+        colorBlend.attachmentCount = static_cast<uint32_t>(GetColorAttachmentsMask().count());
         colorBlend.pAttachments = colorBlendAttachments.data();
         // The blend constant is always dynamic so we fill in a dummy value
         colorBlend.blendConstants[0] = 0.0f;
@@ -172,6 +171,24 @@
         dynamic.dynamicStateCount = sizeof(dynamicStates) / sizeof(dynamicStates[0]);
         dynamic.pDynamicStates = dynamicStates;
 
+        // Get a VkRenderPass that matches the attachment formats for this pipeline, load ops don't
+        // matter so set them all to LoadOp::Load
+        VkRenderPass renderPass = VK_NULL_HANDLE;
+        {
+            RenderPassCacheQuery query;
+
+            for (uint32_t i : IterateBitSet(GetColorAttachmentsMask())) {
+                query.SetColor(i, GetColorAttachmentFormat(i), nxt::LoadOp::Load);
+            }
+
+            if (HasDepthStencilAttachment()) {
+                query.SetDepthStencil(GetDepthStencilFormat(), nxt::LoadOp::Load,
+                                      nxt::LoadOp::Load);
+            }
+
+            renderPass = mDevice->GetRenderPassCache()->GetRenderPass(query);
+        }
+
         // The create info chains in a bunch of things created on the stack here or inside state
         // objects.
         VkGraphicsPipelineCreateInfo createInfo;
@@ -190,8 +207,8 @@
         createInfo.pColorBlendState = &colorBlend;
         createInfo.pDynamicState = &dynamic;
         createInfo.layout = ToBackend(GetLayout())->GetHandle();
-        createInfo.renderPass = ToBackend(GetRenderPass())->GetHandle();
-        createInfo.subpass = GetSubPass();
+        createInfo.renderPass = renderPass;
+        createInfo.subpass = 0;
         createInfo.basePipelineHandle = VK_NULL_HANDLE;
         createInfo.basePipelineIndex = -1;
 
diff --git a/src/backend/vulkan/VulkanBackend.cpp b/src/backend/vulkan/VulkanBackend.cpp
index ad2b064..e661f7b 100644
--- a/src/backend/vulkan/VulkanBackend.cpp
+++ b/src/backend/vulkan/VulkanBackend.cpp
@@ -24,11 +24,11 @@
 #include "backend/vulkan/ComputePipelineVk.h"
 #include "backend/vulkan/DepthStencilStateVk.h"
 #include "backend/vulkan/FencedDeleter.h"
-#include "backend/vulkan/FramebufferVk.h"
 #include "backend/vulkan/InputStateVk.h"
 #include "backend/vulkan/NativeSwapChainImplVk.h"
 #include "backend/vulkan/PipelineLayoutVk.h"
-#include "backend/vulkan/RenderPassVk.h"
+#include "backend/vulkan/RenderPassCache.h"
+#include "backend/vulkan/RenderPassInfoVk.h"
 #include "backend/vulkan/RenderPipelineVk.h"
 #include "backend/vulkan/SamplerVk.h"
 #include "backend/vulkan/ShaderModuleVk.h"
@@ -146,6 +146,7 @@
         mDeleter = new FencedDeleter(this);
         mMapRequestTracker = new MapRequestTracker(this);
         mMemoryAllocator = new MemoryAllocator(this);
+        mRenderPassCache = new RenderPassCache(this);
     }
 
     Device::~Device() {
@@ -189,6 +190,11 @@
         delete mMemoryAllocator;
         mMemoryAllocator = nullptr;
 
+        // The VkRenderPasses in the cache can be destroyed immediately since all commands referring
+        // to them are guaranteed to be finished executing.
+        delete mRenderPassCache;
+        mRenderPassCache = nullptr;
+
         // VkQueues are destroyed when the VkDevice is destroyed
         if (mVkDevice != VK_NULL_HANDLE) {
             fn.DestroyDevice(mVkDevice, nullptr);
@@ -231,9 +237,6 @@
     DepthStencilStateBase* Device::CreateDepthStencilState(DepthStencilStateBuilder* builder) {
         return new DepthStencilState(builder);
     }
-    FramebufferBase* Device::CreateFramebuffer(FramebufferBuilder* builder) {
-        return new Framebuffer(builder);
-    }
     InputStateBase* Device::CreateInputState(InputStateBuilder* builder) {
         return new InputState(builder);
     }
@@ -243,8 +246,8 @@
     QueueBase* Device::CreateQueue(QueueBuilder* builder) {
         return new Queue(builder);
     }
-    RenderPassBase* Device::CreateRenderPass(RenderPassBuilder* builder) {
-        return new RenderPass(builder);
+    RenderPassInfoBase* Device::CreateRenderPassInfo(RenderPassInfoBuilder* builder) {
+        return new RenderPassInfo(builder);
     }
     RenderPipelineBase* Device::CreateRenderPipeline(RenderPipelineBuilder* builder) {
         return new RenderPipeline(builder);
@@ -325,6 +328,10 @@
         return mDeleter;
     }
 
+    RenderPassCache* Device::GetRenderPassCache() const {
+        return mRenderPassCache;
+    }
+
     Serial Device::GetSerial() const {
         return mNextSerial;
     }
diff --git a/src/backend/vulkan/VulkanBackend.h b/src/backend/vulkan/VulkanBackend.h
index 9612bab..a6844bf 100644
--- a/src/backend/vulkan/VulkanBackend.h
+++ b/src/backend/vulkan/VulkanBackend.h
@@ -39,11 +39,10 @@
     class ComputePipeline;
     class DepthStencilState;
     class Device;
-    class Framebuffer;
     class InputState;
     class PipelineLayout;
     class Queue;
-    class RenderPass;
+    class RenderPassInfo;
     class RenderPipeline;
     class Sampler;
     class ShaderModule;
@@ -55,6 +54,7 @@
     class FencedDeleter;
     class MapRequestTracker;
     class MemoryAllocator;
+    class RenderPassCache;
 
     struct VulkanBackendTraits {
         using BindGroupType = BindGroup;
@@ -66,11 +66,10 @@
         using ComputePipelineType = ComputePipeline;
         using DepthStencilStateType = DepthStencilState;
         using DeviceType = Device;
-        using FramebufferType = Framebuffer;
         using InputStateType = InputState;
         using PipelineLayoutType = PipelineLayout;
         using QueueType = Queue;
-        using RenderPassType = RenderPass;
+        using RenderPassInfoType = RenderPassInfo;
         using RenderPipelineType = RenderPipeline;
         using SamplerType = Sampler;
         using ShaderModuleType = ShaderModule;
@@ -103,6 +102,7 @@
         FencedDeleter* GetFencedDeleter() const;
         MapRequestTracker* GetMapRequestTracker() const;
         MemoryAllocator* GetMemoryAllocator() const;
+        RenderPassCache* GetRenderPassCache() const;
 
         Serial GetSerial() const;
 
@@ -119,11 +119,10 @@
         CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
         ComputePipelineBase* CreateComputePipeline(ComputePipelineBuilder* builder) override;
         DepthStencilStateBase* CreateDepthStencilState(DepthStencilStateBuilder* builder) override;
-        FramebufferBase* CreateFramebuffer(FramebufferBuilder* builder) override;
         InputStateBase* CreateInputState(InputStateBuilder* builder) override;
         PipelineLayoutBase* CreatePipelineLayout(PipelineLayoutBuilder* builder) override;
         QueueBase* CreateQueue(QueueBuilder* builder) override;
-        RenderPassBase* CreateRenderPass(RenderPassBuilder* builder) override;
+        RenderPassInfoBase* CreateRenderPassInfo(RenderPassInfoBuilder* builder) override;
         RenderPipelineBase* CreateRenderPipeline(RenderPipelineBuilder* builder) override;
         SamplerBase* CreateSampler(SamplerBuilder* builder) override;
         ShaderModuleBase* CreateShaderModule(ShaderModuleBuilder* builder) override;
@@ -170,6 +169,7 @@
         FencedDeleter* mDeleter = nullptr;
         MapRequestTracker* mMapRequestTracker = nullptr;
         MemoryAllocator* mMemoryAllocator = nullptr;
+        RenderPassCache* mRenderPassCache = nullptr;
 
         VkFence GetUnusedFence();
         void CheckPassedFences();