Memory manager: buffer uploads (Vulkan) - Part 2

Manages a single persistently mapped GPU heap which is sub-allocated
inside of ring-buffer for uploads. To handle larger buffers without additional
unused heaps, ring buffers are created on-demand.

BUG=dawn:28
TEST=dawn_unittests

Change-Id: Ic2a5df3142fc24fa772b9a85b38248eea8c7e003
Reviewed-on: https://dawn-review.googlesource.com/c/4260
Commit-Queue: Bryan Bernhart <bryan.bernhart@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index b5f4146..60bda32 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -659,8 +659,6 @@
       "src/dawn_native/vulkan/BindGroupLayoutVk.h",
       "src/dawn_native/vulkan/BindGroupVk.cpp",
       "src/dawn_native/vulkan/BindGroupVk.h",
-      "src/dawn_native/vulkan/BufferUploader.cpp",
-      "src/dawn_native/vulkan/BufferUploader.h",
       "src/dawn_native/vulkan/BufferVk.cpp",
       "src/dawn_native/vulkan/BufferVk.h",
       "src/dawn_native/vulkan/CommandBufferVk.cpp",
@@ -692,6 +690,8 @@
       "src/dawn_native/vulkan/SamplerVk.h",
       "src/dawn_native/vulkan/ShaderModuleVk.cpp",
       "src/dawn_native/vulkan/ShaderModuleVk.h",
+      "src/dawn_native/vulkan/StagingBufferVk.cpp",
+      "src/dawn_native/vulkan/StagingBufferVk.h",
       "src/dawn_native/vulkan/SwapChainVk.cpp",
       "src/dawn_native/vulkan/SwapChainVk.h",
       "src/dawn_native/vulkan/TextureVk.cpp",
diff --git a/src/dawn_native/vulkan/BufferUploader.cpp b/src/dawn_native/vulkan/BufferUploader.cpp
deleted file mode 100644
index e74143f..0000000
--- a/src/dawn_native/vulkan/BufferUploader.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2017 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/vulkan/BufferUploader.h"
-
-#include "dawn_native/vulkan/DeviceVk.h"
-#include "dawn_native/vulkan/FencedDeleter.h"
-#include "dawn_native/vulkan/MemoryAllocator.h"
-
-#include <cstring>
-
-namespace dawn_native { namespace vulkan {
-
-    BufferUploader::BufferUploader(Device* device) : mDevice(device) {
-    }
-
-    BufferUploader::~BufferUploader() {
-    }
-
-    void BufferUploader::BufferSubData(VkBuffer buffer,
-                                       VkDeviceSize offset,
-                                       VkDeviceSize size,
-                                       const void* data) {
-        // TODO(cwallez@chromium.org): this is soooooo bad. We should use some sort of ring buffer
-        // for this.
-
-        // Create a staging buffer
-        VkBufferCreateInfo createInfo;
-        createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
-        createInfo.pNext = nullptr;
-        createInfo.flags = 0;
-        createInfo.size = size;
-        createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
-        createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
-        createInfo.queueFamilyIndexCount = 0;
-        createInfo.pQueueFamilyIndices = 0;
-
-        VkBuffer stagingBuffer = VK_NULL_HANDLE;
-        if (mDevice->fn.CreateBuffer(mDevice->GetVkDevice(), &createInfo, nullptr,
-                                     &stagingBuffer) != VK_SUCCESS) {
-            ASSERT(false);
-        }
-
-        VkMemoryRequirements requirements;
-        mDevice->fn.GetBufferMemoryRequirements(mDevice->GetVkDevice(), stagingBuffer,
-                                                &requirements);
-
-        DeviceMemoryAllocation allocation;
-        if (!mDevice->GetMemoryAllocator()->Allocate(requirements, true, &allocation)) {
-            ASSERT(false);
-        }
-
-        if (mDevice->fn.BindBufferMemory(mDevice->GetVkDevice(), stagingBuffer,
-                                         allocation.GetMemory(),
-                                         allocation.GetMemoryOffset()) != VK_SUCCESS) {
-            ASSERT(false);
-        }
-
-        // Write to the staging buffer
-        ASSERT(allocation.GetMappedPointer() != nullptr);
-        memcpy(allocation.GetMappedPointer(), data, static_cast<size_t>(size));
-
-        // Enqueue host write -> transfer src barrier and copy command
-        VkCommandBuffer commands = mDevice->GetPendingCommandBuffer();
-
-        VkMemoryBarrier barrier;
-        barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
-        barrier.pNext = nullptr;
-        barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
-        barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
-
-        mDevice->fn.CmdPipelineBarrier(commands, VK_PIPELINE_STAGE_HOST_BIT,
-                                       VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &barrier, 0, nullptr,
-                                       0, nullptr);
-
-        VkBufferCopy copy;
-        copy.srcOffset = 0;
-        copy.dstOffset = offset;
-        copy.size = size;
-        mDevice->fn.CmdCopyBuffer(commands, stagingBuffer, buffer, 1, &copy);
-
-        // TODO(cwallez@chromium.org): Buffers must be deleted before the memory.
-        // This happens to work for now, but is fragile.
-        mDevice->GetMemoryAllocator()->Free(&allocation);
-        mDevice->GetFencedDeleter()->DeleteWhenUnused(stagingBuffer);
-    }
-
-    void BufferUploader::Tick(Serial) {
-    }
-
-}}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/BufferUploader.h b/src/dawn_native/vulkan/BufferUploader.h
deleted file mode 100644
index 37ff0d7..0000000
--- a/src/dawn_native/vulkan/BufferUploader.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2017 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_VULKAN_BUFFERUPLOADER_H_
-#define DAWNNATIVE_VULKAN_BUFFERUPLOADER_H_
-
-#include "common/SerialQueue.h"
-#include "common/vulkan_platform.h"
-
-namespace dawn_native { namespace vulkan {
-
-    class Device;
-
-    class BufferUploader {
-      public:
-        BufferUploader(Device* device);
-        ~BufferUploader();
-
-        void BufferSubData(VkBuffer buffer,
-                           VkDeviceSize offset,
-                           VkDeviceSize size,
-                           const void* data);
-
-        void Tick(Serial completedSerial);
-
-      private:
-        Device* mDevice = nullptr;
-    };
-
-}}  // namespace dawn_native::vulkan
-
-#endif  // DAWNNATIVE_VULKAN_BUFFERUPLOADER_H_
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 4175097..0080e76 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -14,7 +14,7 @@
 
 #include "dawn_native/vulkan/BufferVk.h"
 
-#include "dawn_native/vulkan/BufferUploader.h"
+#include "dawn_native/DynamicUploader.h"
 #include "dawn_native/vulkan/DeviceVk.h"
 #include "dawn_native/vulkan/FencedDeleter.h"
 
@@ -199,11 +199,18 @@
     MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
         Device* device = ToBackend(GetDevice());
 
-        VkCommandBuffer commands = device->GetPendingCommandBuffer();
-        TransitionUsageNow(commands, dawn::BufferUsageBit::TransferDst);
+        DynamicUploader* uploader = nullptr;
+        DAWN_TRY_ASSIGN(uploader, device->GetDynamicUploader());
 
-        BufferUploader* uploader = device->GetBufferUploader();
-        uploader->BufferSubData(mHandle, start, count, data);
+        UploadHandle uploadHandle;
+        DAWN_TRY_ASSIGN(uploadHandle, uploader->Allocate(count, kDefaultAlignment));
+        ASSERT(uploadHandle.mappedBuffer != nullptr);
+
+        memcpy(uploadHandle.mappedBuffer, data, count);
+
+        DAWN_TRY(device->CopyFromStagingToBuffer(uploadHandle.stagingBuffer,
+                                                 uploadHandle.startOffset, this, start, count));
+
         return {};
     }
 
diff --git a/src/dawn_native/vulkan/BufferVk.h b/src/dawn_native/vulkan/BufferVk.h
index ae90b4f..70636fc 100644
--- a/src/dawn_native/vulkan/BufferVk.h
+++ b/src/dawn_native/vulkan/BufferVk.h
@@ -46,6 +46,9 @@
         void MapWriteAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
         void UnmapImpl() override;
 
+        // TODO(b-brber): Remove once alignment constraint is added to validation (dawn:73).
+        static constexpr size_t kDefaultAlignment = 4;  // TODO(b-brber): Figure out this value.
+
         VkBuffer mHandle = VK_NULL_HANDLE;
         DeviceMemoryAllocation mMemoryAllocation;
 
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 32873c3..8fcd392 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -23,7 +23,6 @@
 #include "dawn_native/vulkan/BackendVk.h"
 #include "dawn_native/vulkan/BindGroupLayoutVk.h"
 #include "dawn_native/vulkan/BindGroupVk.h"
-#include "dawn_native/vulkan/BufferUploader.h"
 #include "dawn_native/vulkan/BufferVk.h"
 #include "dawn_native/vulkan/CommandBufferVk.h"
 #include "dawn_native/vulkan/ComputePipelineVk.h"
@@ -36,6 +35,7 @@
 #include "dawn_native/vulkan/RenderPipelineVk.h"
 #include "dawn_native/vulkan/SamplerVk.h"
 #include "dawn_native/vulkan/ShaderModuleVk.h"
+#include "dawn_native/vulkan/StagingBufferVk.h"
 #include "dawn_native/vulkan/SwapChainVk.h"
 #include "dawn_native/vulkan/TextureVk.h"
 #include "dawn_native/vulkan/VulkanError.h"
@@ -61,11 +61,11 @@
         DAWN_TRY(functions->LoadDeviceProcs(mVkDevice, mDeviceInfo));
 
         GatherQueueFromDevice();
-
-        mBufferUploader = std::make_unique<BufferUploader>(this);
         mDeleter = std::make_unique<FencedDeleter>(this);
         mMapRequestTracker = std::make_unique<MapRequestTracker>(this);
         mMemoryAllocator = std::make_unique<MemoryAllocator>(this);
+        mDynamicUploader = std::make_unique<DynamicUploader>(this);
+
         mRenderPassCache = std::make_unique<RenderPassCache>(this);
 
         return {};
@@ -116,7 +116,12 @@
         mUnusedFences.clear();
 
         // Free services explicitly so that they can free Vulkan objects before vkDestroyDevice
-        mBufferUploader = nullptr;
+        mDynamicUploader = nullptr;
+
+        // Releasing the uploader enqueues buffers to be deleted.
+        // Call Tick() again to allow the deleter to clear them prior to being released.
+        mDeleter->Tick(mCompletedSerial);
+
         mDeleter = nullptr;
         mMapRequestTracker = nullptr;
         mMemoryAllocator = nullptr;
@@ -205,7 +210,7 @@
         RecycleCompletedCommands();
 
         mMapRequestTracker->Tick(mCompletedSerial);
-        mBufferUploader->Tick(mCompletedSerial);
+        mDynamicUploader->Tick(mCompletedSerial);
         mMemoryAllocator->Tick(mCompletedSerial);
 
         mDeleter->Tick(mCompletedSerial);
@@ -247,10 +252,6 @@
         return mMemoryAllocator.get();
     }
 
-    BufferUploader* Device::GetBufferUploader() const {
-        return mBufferUploader.get();
-    }
-
     FencedDeleter* Device::GetFencedDeleter() const {
         return mDeleter.get();
     }
@@ -497,7 +498,9 @@
     }
 
     ResultOrError<std::unique_ptr<StagingBufferBase>> Device::CreateStagingBuffer(size_t size) {
-        return DAWN_UNIMPLEMENTED_ERROR("Device unable to create staging buffer.");
+        std::unique_ptr<StagingBufferBase> stagingBuffer =
+            std::make_unique<StagingBuffer>(size, this);
+        return std::move(stagingBuffer);
     }
 
     MaybeError Device::CopyFromStagingToBuffer(StagingBufferBase* source,
@@ -505,7 +508,35 @@
                                                BufferBase* destination,
                                                uint32_t destinationOffset,
                                                uint32_t size) {
-        return DAWN_UNIMPLEMENTED_ERROR("Device unable to copy from staging buffer.");
+        // Insert memory barrier to ensure host write operations are made visible before
+        // copying from the staging buffer. However, this barrier can be removed (see note below).
+        //
+        // Note: Depending on the spec understanding, an explicit barrier may not be required when
+        // used with HOST_COHERENT as vkQueueSubmit does an implicit barrier between host and
+        // device. See "Availability, Visibility, and Domain Operations" in Vulkan spec for details.
+
+        // Insert pipeline barrier to ensure correct ordering with previous memory operations on the
+        // buffer.
+        ToBackend(destination)
+            ->TransitionUsageNow(GetPendingCommandBuffer(), dawn::BufferUsageBit::TransferDst);
+
+        VkBufferCopy copy;
+        copy.srcOffset = sourceOffset;
+        copy.dstOffset = destinationOffset;
+        copy.size = size;
+
+        this->fn.CmdCopyBuffer(GetPendingCommandBuffer(), ToBackend(source)->GetBufferHandle(),
+                               ToBackend(destination)->GetHandle(), 1, &copy);
+
+        return {};
+    }
+
+    ResultOrError<DynamicUploader*> Device::GetDynamicUploader() const {
+        // TODO(b-brber): Refactor this into device init once moved into DeviceBase.
+        if (mDynamicUploader->IsEmpty()) {
+            DAWN_TRY(mDynamicUploader->CreateAndAppendBuffer(kDefaultUploadBufferSize));
+        }
+        return mDynamicUploader.get();
     }
 
 }}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h
index aefd00d..ffa2a0d 100644
--- a/src/dawn_native/vulkan/DeviceVk.h
+++ b/src/dawn_native/vulkan/DeviceVk.h
@@ -81,6 +81,8 @@
                                            uint32_t destinationOffset,
                                            uint32_t size) override;
 
+        ResultOrError<DynamicUploader*> GetDynamicUploader() const;
+
       private:
         ResultOrError<BindGroupBase*> CreateBindGroupImpl(
             const BindGroupDescriptor* descriptor) override;
@@ -114,7 +116,7 @@
         uint32_t mQueueFamily = 0;
         VkQueue mQueue = VK_NULL_HANDLE;
 
-        std::unique_ptr<BufferUploader> mBufferUploader;
+        std::unique_ptr<DynamicUploader> mDynamicUploader;
         std::unique_ptr<FencedDeleter> mDeleter;
         std::unique_ptr<MapRequestTracker> mMapRequestTracker;
         std::unique_ptr<MemoryAllocator> mMemoryAllocator;
@@ -145,6 +147,9 @@
         std::vector<CommandPoolAndBuffer> mUnusedCommands;
         CommandPoolAndBuffer mPendingCommands;
         std::vector<VkSemaphore> mWaitSemaphores;
+
+        static constexpr size_t kDefaultUploadBufferSize =
+            64000;  // TODO(b-brber): Figure out this value.
     };
 
 }}  // namespace dawn_native::vulkan
diff --git a/src/dawn_native/vulkan/Forward.h b/src/dawn_native/vulkan/Forward.h
index aade94e..5e6cac6 100644
--- a/src/dawn_native/vulkan/Forward.h
+++ b/src/dawn_native/vulkan/Forward.h
@@ -33,6 +33,7 @@
     class RenderPipeline;
     class Sampler;
     class ShaderModule;
+    class StagingBuffer;
     class SwapChain;
     class Texture;
     class TextureView;
@@ -52,6 +53,7 @@
         using RenderPipelineType = RenderPipeline;
         using SamplerType = Sampler;
         using ShaderModuleType = ShaderModule;
+        using StagingBufferType = StagingBuffer;
         using SwapChainType = SwapChain;
         using TextureType = Texture;
         using TextureViewType = TextureView;
diff --git a/src/dawn_native/vulkan/MemoryAllocator.cpp b/src/dawn_native/vulkan/MemoryAllocator.cpp
index e71adcb..6d7cf07 100644
--- a/src/dawn_native/vulkan/MemoryAllocator.cpp
+++ b/src/dawn_native/vulkan/MemoryAllocator.cpp
@@ -60,6 +60,12 @@
                 continue;
             }
 
+            // Mappable must also be host coherent.
+            if (mappable &&
+                (info.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
+                continue;
+            }
+
             // Found the first candidate memory type
             if (bestType == -1) {
                 bestType = static_cast<int>(i);
diff --git a/src/dawn_native/vulkan/StagingBufferVk.cpp b/src/dawn_native/vulkan/StagingBufferVk.cpp
new file mode 100644
index 0000000..4e96f85
--- /dev/null
+++ b/src/dawn_native/vulkan/StagingBufferVk.cpp
@@ -0,0 +1,72 @@
+// Copyright 2018 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/vulkan/StagingBufferVk.h"
+#include "dawn_native/vulkan/DeviceVk.h"
+#include "dawn_native/vulkan/FencedDeleter.h"
+#include "dawn_native/vulkan/MemoryAllocator.h"
+
+namespace dawn_native { namespace vulkan {
+
+    StagingBuffer::StagingBuffer(size_t size, Device* device)
+        : StagingBufferBase(size), mDevice(device) {
+    }
+
+    MaybeError StagingBuffer::Initialize() {
+        VkBufferCreateInfo createInfo;
+        createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+        createInfo.pNext = nullptr;
+        createInfo.flags = 0;
+        createInfo.size = GetSize();
+        createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+        createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        createInfo.queueFamilyIndexCount = 0;
+        createInfo.pQueueFamilyIndices = 0;
+
+        if (mDevice->fn.CreateBuffer(mDevice->GetVkDevice(), &createInfo, nullptr, &mBuffer) !=
+            VK_SUCCESS) {
+            return DAWN_CONTEXT_LOST_ERROR("Unable to create staging buffer.");
+        }
+
+        VkMemoryRequirements requirements;
+        mDevice->fn.GetBufferMemoryRequirements(mDevice->GetVkDevice(), mBuffer, &requirements);
+
+        if (!mDevice->GetMemoryAllocator()->Allocate(requirements, true, &mAllocation)) {
+            return DAWN_CONTEXT_LOST_ERROR("Unable to allocate memory for staging buffer.");
+        }
+
+        if (mDevice->fn.BindBufferMemory(mDevice->GetVkDevice(), mBuffer, mAllocation.GetMemory(),
+                                         mAllocation.GetMemoryOffset()) != VK_SUCCESS) {
+            return DAWN_CONTEXT_LOST_ERROR("Unable to attach memory to the staging buffer.");
+        }
+
+        mMappedPointer = mAllocation.GetMappedPointer();
+        if (mMappedPointer == nullptr) {
+            return DAWN_CONTEXT_LOST_ERROR("Unable to map staging buffer.");
+        }
+
+        return {};
+    }
+
+    StagingBuffer::~StagingBuffer() {
+        mMappedPointer = nullptr;
+        mDevice->GetFencedDeleter()->DeleteWhenUnused(mBuffer);
+        mDevice->GetMemoryAllocator()->Free(&mAllocation);
+    }
+
+    VkBuffer StagingBuffer::GetBufferHandle() const {
+        return mBuffer;
+    }
+
+}}  // namespace dawn_native::vulkan
\ No newline at end of file
diff --git a/src/dawn_native/vulkan/StagingBufferVk.h b/src/dawn_native/vulkan/StagingBufferVk.h
new file mode 100644
index 0000000..618c5ed
--- /dev/null
+++ b/src/dawn_native/vulkan/StagingBufferVk.h
@@ -0,0 +1,41 @@
+// Copyright 2018 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_STAGINGBUFFERVK_H_
+#define DAWNNATIVE_STAGINGBUFFERVK_H_
+
+#include "dawn_native/StagingBuffer.h"
+#include "dawn_native/vulkan/MemoryAllocator.h"
+
+namespace dawn_native { namespace vulkan {
+
+    class Device;
+
+    class StagingBuffer : public StagingBufferBase {
+      public:
+        StagingBuffer(size_t size, Device* device);
+        ~StagingBuffer();
+
+        VkBuffer GetBufferHandle() const;
+
+        MaybeError Initialize() override;
+
+      private:
+        Device* mDevice;
+        VkBuffer mBuffer;
+        DeviceMemoryAllocation mAllocation;
+    };
+}}  // namespace dawn_native::vulkan
+
+#endif  // DAWNNATIVE_STAGINGBUFFERVK_H_
diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp
index 70dfac8..399994f 100644
--- a/src/tests/end2end/BufferTests.cpp
+++ b/src/tests/end2end/BufferTests.cpp
@@ -222,7 +222,7 @@
 TEST_P(BufferSetSubDataTests, ManySetSubData) {
     // TODO(cwallez@chromium.org): Use ringbuffers for SetSubData on explicit APIs.
     // otherwise this creates too many resources and can take freeze the driver(?)
-    DAWN_SKIP_TEST_IF(IsMetal() || IsVulkan());
+    DAWN_SKIP_TEST_IF(IsMetal());
 
     // Note: Increasing the size of the buffer will likely cause timeout issues.
     // In D3D12, timeout detection occurs when the GPU scheduler tries but cannot preempt the task