Metal: implement BufferMapExtendedUsages feature.

This feature allows MapRead or MapWrite to be combined with other
usages when creating buffer.

For example, a vertex buffer could be mapped with this feature enabled.
Unlike before when it could only be copied to from a mappable buffer.

Bug: dawn:2204
Change-Id: Ia4f177d27ea79d5fcb7f1670321a0e81a6de324e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/161400
Commit-Queue: Quyen Le <lehoangquyen@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/dawn.json b/dawn.json
index 62943b0..00c03d9 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1919,6 +1919,7 @@
             {"value": 1023, "name": "multi planar render targets", "tags": ["dawn"]},
             {"value": 1024, "name": "multi planar format nv12a", "tags": ["dawn"]},
             {"value": 1025, "name": "framebuffer fetch", "tags": ["dawn"]},
+            {"value": 1026, "name": "buffer map extended usages", "tags": ["dawn"]},
 
             {"value": 1100, "name": "shared texture memory vk dedicated allocation", "tags": ["dawn", "native"]},
             {"value": 1101, "name": "shared texture memory a hardware buffer", "tags": ["dawn", "native"]},
diff --git a/docs/dawn/features/buffer_map_extended_usages.md b/docs/dawn/features/buffer_map_extended_usages.md
new file mode 100644
index 0000000..237eb4f
--- /dev/null
+++ b/docs/dawn/features/buffer_map_extended_usages.md
@@ -0,0 +1,22 @@
+# Buffer Map Extended Usages
+
+## Overview:
+ - The `wgpu::Feature::BufferMapExtendedUsages` feature allows creating a buffer with `wgpu::BufferUsage::MapRead` or `wgpu::BufferUsage::MapWrite` and any other `wgpu::BufferUsage`.
+ - Exception is that `wgpu::BufferUsage::MapRead` cannot be combined with `wgpu::BufferUsage::MapWrite` and vice versa.
+
+### Example Usage:
+```
+wgpu::BufferDescriptor descriptor;
+descriptor.size = size;
+descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Uniform;
+wgpu::Buffer uniformBuffer = device.CreateBuffer(&descriptor);
+
+uniformBuffer.MapAsync(wgpu::MapMode::Write, 0, size,
+   [](WGPUBufferMapAsyncStatus status, void* userdata)
+   {
+      wgpu::Buffer* buffer = static_cast<wgpu::Buffer*>(userdata);
+      memcpy(buffer->GetMappedRange(), data, sizeof(data));
+   },
+   &uniformBuffer);
+```
+
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 1114c9e..d969b5e 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -155,21 +155,33 @@
 
     DAWN_INVALID_IF(usage == wgpu::BufferUsage::None, "Buffer usages must not be 0.");
 
-    const wgpu::BufferUsage kMapWriteAllowedUsages =
-        wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
-    DAWN_INVALID_IF(
-        usage & wgpu::BufferUsage::MapWrite && !IsSubset(usage, kMapWriteAllowedUsages),
-        "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed "
-        "usage is %s.",
-        usage, wgpu::BufferUsage::MapWrite, wgpu::BufferUsage::CopySrc);
+    if (device->HasFeature(Feature::BufferMapExtendedUsages)) {
+        // Note with BufferMapExtendedUsages, we only restrict that MapRead & MapWrite cannot be
+        // combined together. This makes it easier to optimize the storage in the backends. For
+        // example, D3D11 has specialized resource usage for GPU write-only or CPU write-only
+        // buffers.
+        DAWN_INVALID_IF(
+            !HasZeroOrOneBits(static_cast<wgpu::BufferUsage>(usage & kMappableBufferUsages)),
+            "Buffer usages (%s) is invalid. A buffer usage can contain either %s or %s "
+            "but not both.",
+            usage, wgpu::BufferUsage::MapRead, wgpu::BufferUsage::MapWrite);
+    } else {
+        const wgpu::BufferUsage kMapWriteAllowedUsages =
+            wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
+        DAWN_INVALID_IF(
+            usage & wgpu::BufferUsage::MapWrite && !IsSubset(usage, kMapWriteAllowedUsages),
+            "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed "
+            "usage is %s.",
+            usage, wgpu::BufferUsage::MapWrite, wgpu::BufferUsage::CopySrc);
 
-    const wgpu::BufferUsage kMapReadAllowedUsages =
-        wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
-    DAWN_INVALID_IF(
-        usage & wgpu::BufferUsage::MapRead && !IsSubset(usage, kMapReadAllowedUsages),
-        "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed "
-        "usage is %s.",
-        usage, wgpu::BufferUsage::MapRead, wgpu::BufferUsage::CopyDst);
+        const wgpu::BufferUsage kMapReadAllowedUsages =
+            wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
+        DAWN_INVALID_IF(
+            usage & wgpu::BufferUsage::MapRead && !IsSubset(usage, kMapReadAllowedUsages),
+            "Buffer usages (%s) is invalid. If a buffer usage contains %s the only other allowed "
+            "usage is %s.",
+            usage, wgpu::BufferUsage::MapRead, wgpu::BufferUsage::CopyDst);
+    }
 
     DAWN_INVALID_IF(descriptor->mappedAtCreation && descriptor->size % 4 != 0,
                     "Buffer is mapped at creation but its size (%u) is not a multiple of 4.",
diff --git a/src/dawn/native/Features.cpp b/src/dawn/native/Features.cpp
index 8b5b9aa..4435f5b 100644
--- a/src/dawn/native/Features.cpp
+++ b/src/dawn/native/Features.cpp
@@ -259,6 +259,12 @@
       "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
       "framebuffer_fetch.md",
       FeatureInfo::FeatureState::Experimental}},
+    {Feature::BufferMapExtendedUsages,
+     {"Support creating all kinds of buffers with MapRead or MapWrite usage. MapRead and MapWrite "
+      "usages are not allowed to be included together.",
+      "https://dawn.googlesource.com/dawn/+/refs/heads/main/docs/dawn/features/"
+      "buffer_map_extended_usages.md",
+      FeatureInfo::FeatureState::Experimental}},
 };
 
 }  // anonymous namespace
diff --git a/src/dawn/native/metal/BackendMTL.mm b/src/dawn/native/metal/BackendMTL.mm
index 5184c3c..a4de8ca 100644
--- a/src/dawn/native/metal/BackendMTL.mm
+++ b/src/dawn/native/metal/BackendMTL.mm
@@ -608,6 +608,16 @@
         EnableFeature(Feature::Norm16TextureFormats);
 
         EnableFeature(Feature::HostMappedPointer);
+
+#if DAWN_PLATFORM_IS(IOS)
+        EnableFeature(Feature::BufferMapExtendedUsages);
+#else
+        if (@available(macOS 10.15, iOS 13.0, *)) {
+            if ([*mDevice hasUnifiedMemory]) {
+                EnableFeature(Feature::BufferMapExtendedUsages);
+            }
+        }
+#endif
     }
 
     void InitializeVendorArchitectureImpl() override {
diff --git a/src/dawn/tests/end2end/BufferTests.cpp b/src/dawn/tests/end2end/BufferTests.cpp
index 70d1bcf..600b8d7 100644
--- a/src/dawn/tests/end2end/BufferTests.cpp
+++ b/src/dawn/tests/end2end/BufferTests.cpp
@@ -28,9 +28,13 @@
 #include <array>
 #include <cstring>
 #include <limits>
+#include <sstream>
+#include <string>
 #include <vector>
 
 #include "dawn/tests/DawnTest.h"
+#include "dawn/utils/ComboRenderPipelineDescriptor.h"
+#include "dawn/utils/WGPUHelpers.h"
 
 namespace dawn {
 namespace {
@@ -1170,5 +1174,331 @@
                       OpenGLESBackend({"disable_resource_suballocation"}),
                       VulkanBackend({"disable_resource_suballocation"}));
 
+class BufferMapExtendedUsagesTests : public BufferMappingTests {
+  protected:
+    void SetUp() override {
+        BufferMappingTests::SetUp();
+
+        DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+        // Skip all tests if the BufferMapExtendedUsages feature is not supported.
+        DAWN_TEST_UNSUPPORTED_IF(!SupportsFeatures({wgpu::FeatureName::BufferMapExtendedUsages}));
+    }
+
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        std::vector<wgpu::FeatureName> requiredFeatures = {};
+        if (!UsesWire() && SupportsFeatures({wgpu::FeatureName::BufferMapExtendedUsages})) {
+            requiredFeatures.push_back(wgpu::FeatureName::BufferMapExtendedUsages);
+        }
+        return requiredFeatures;
+    }
+
+    wgpu::RenderPipeline CreateRenderPipelineForTest(bool colorFromUniformBuffer) {
+        utils::ComboRenderPipelineDescriptor pipelineDescriptor;
+
+        std::ostringstream vs;
+        vs << R"(
+            struct VertexOut {
+                @location(0) color : vec4f,
+                @builtin(position) position : vec4f,
+            }
+
+            const vertexPos = array(
+                vec2f(-1.0, -1.0),
+                vec2f( 3.0, -1.0),
+                vec2f(-1.0,  3.0));
+        )";
+
+        if (colorFromUniformBuffer) {
+            // Color is from uniform buffer.
+            vs << R"(
+            struct Uniforms {
+                color : vec4f,
+            }
+            @binding(0) @group(0) var<uniform> uniforms : Uniforms;
+
+            @vertex
+            fn main(@builtin(vertex_index) vertexIndex : u32) -> VertexOut {
+                var output : VertexOut;
+                output.position = vec4f(vertexPos[vertexIndex % 3], 0.0, 1.0);
+                output.color = uniforms.color;
+                return output;
+            })";
+        } else {
+            // Color is from vertex buffer.
+            vs << R"(
+            @vertex
+            fn main(@location(0) vertexColor : vec4f,
+                    @builtin(vertex_index) vertexIndex : u32) -> VertexOut {
+                var output : VertexOut;
+                output.position = vec4f(vertexPos[vertexIndex % 3], 0.0, 1.0);
+                output.color = vertexColor;
+                return output;
+            })";
+
+            pipelineDescriptor.vertex.bufferCount = 1;
+            pipelineDescriptor.cBuffers[0].arrayStride = 4;
+            pipelineDescriptor.cBuffers[0].attributeCount = 1;
+            pipelineDescriptor.cBuffers[0].stepMode = wgpu::VertexStepMode::Vertex;
+            pipelineDescriptor.cAttributes[0].format = wgpu::VertexFormat::Unorm8x4;
+        }
+        constexpr char fs[] = R"(
+            @fragment
+            fn main(@location(0) color : vec4f) -> @location(0) vec4f {
+                return color;
+            })";
+
+        pipelineDescriptor.vertex.module = utils::CreateShaderModule(device, vs.str().c_str());
+        pipelineDescriptor.cFragment.module = utils::CreateShaderModule(device, fs);
+
+        pipelineDescriptor.cFragment.targetCount = 1;
+        pipelineDescriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
+
+        wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
+        return pipeline;
+    }
+
+    void EncodeAndSubmitRenderPassForTest(const wgpu::RenderPassDescriptor& renderPass,
+                                          wgpu::RenderPipeline pipeline,
+                                          wgpu::Buffer vertexBuffer,
+                                          wgpu::Buffer indexBuffer,
+                                          wgpu::BindGroup uniformsBindGroup) {
+        wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
+        wgpu::RenderPassEncoder renderPassEncoder = commandEncoder.BeginRenderPass(&renderPass);
+        renderPassEncoder.SetPipeline(pipeline);
+        if (uniformsBindGroup) {
+            renderPassEncoder.SetBindGroup(0, uniformsBindGroup);
+        }
+        if (vertexBuffer) {
+            renderPassEncoder.SetVertexBuffer(0, vertexBuffer);
+        }
+
+        if (indexBuffer) {
+            renderPassEncoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint16);
+            renderPassEncoder.DrawIndexed(3);
+        } else {
+            renderPassEncoder.Draw(3);
+        }
+        renderPassEncoder.End();
+
+        wgpu::CommandBuffer commands = commandEncoder.Finish();
+        queue.Submit(1, &commands);
+    }
+
+    static constexpr wgpu::BufferUsage kNonMapUsages[] = {
+        wgpu::BufferUsage::CopySrc,  wgpu::BufferUsage::CopyDst,      wgpu::BufferUsage::Index,
+        wgpu::BufferUsage::Vertex,   wgpu::BufferUsage::Uniform,      wgpu::BufferUsage::Storage,
+        wgpu::BufferUsage::Indirect, wgpu::BufferUsage::QueryResolve,
+    };
+};
+
+// Test that the map read for any kind of buffer works
+TEST_P(BufferMapExtendedUsagesTests, MapReadWithAnyUsage) {
+    wgpu::BufferDescriptor descriptor;
+    descriptor.size = 4;
+
+    for (const auto otherUsage : kNonMapUsages) {
+        descriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst | otherUsage;
+        wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+
+        uint32_t myData = 0x01020304;
+        constexpr size_t kSize = sizeof(myData);
+        queue.WriteBuffer(buffer, 0, &myData, kSize);
+
+        MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 4);
+        CheckMapping(buffer.GetConstMappedRange(), &myData, kSize);
+        CheckMapping(buffer.GetConstMappedRange(0, kSize), &myData, kSize);
+        buffer.Unmap();
+    }
+}
+
+// Test that the map write for any kind of buffer works
+TEST_P(BufferMapExtendedUsagesTests, MapWriteWithAnyUsage) {
+    wgpu::BufferDescriptor descriptor;
+    descriptor.size = 4;
+
+    for (const auto otherUsage : kNonMapUsages) {
+        descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc | otherUsage;
+        wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
+
+        uint32_t myData = 2934875;
+        MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 4);
+        ASSERT_NE(nullptr, buffer.GetMappedRange());
+        ASSERT_NE(nullptr, buffer.GetConstMappedRange());
+        memcpy(buffer.GetMappedRange(), &myData, sizeof(myData));
+        buffer.Unmap();
+
+        EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
+    }
+}
+
+// Test that mapping a vertex buffer, modifying the data then draw with the buffer works.
+TEST_P(BufferMapExtendedUsagesTests, MapWriteVertexBufferAndDraw) {
+    const utils::RGBA8 kReds[] = {utils::RGBA8::kRed, utils::RGBA8::kRed, utils::RGBA8::kRed};
+    const utils::RGBA8 kGreens[] = {utils::RGBA8::kGreen, utils::RGBA8::kGreen,
+                                    utils::RGBA8::kGreen};
+
+    // Create buffer with initial red color data.
+    wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(
+        device, kReds, sizeof(kReds), wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Vertex);
+
+    wgpu::RenderPipeline renderPipeline =
+        CreateRenderPipelineForTest(/*colorFromUniformBuffer=*/false);
+
+    auto redRenderPass = utils::CreateBasicRenderPass(device, 1, 1);
+    auto greenRenderPass = utils::CreateBasicRenderPass(device, 1, 1);
+
+    // First render pass: draw with red color vertex buffer.
+    EncodeAndSubmitRenderPassForTest(redRenderPass.renderPassInfo, renderPipeline, vertexBuffer,
+                                     nullptr, nullptr);
+
+    // Second render pass: draw with green color vertex buffer.
+    MapAsyncAndWait(vertexBuffer, wgpu::MapMode::Write, 0, sizeof(kGreens));
+    ASSERT_NE(nullptr, vertexBuffer.GetMappedRange());
+    memcpy(vertexBuffer.GetMappedRange(), kGreens, sizeof(kGreens));
+    vertexBuffer.Unmap();
+
+    EncodeAndSubmitRenderPassForTest(greenRenderPass.renderPassInfo, renderPipeline, vertexBuffer,
+                                     nullptr, nullptr);
+
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, redRenderPass.color, 0, 0);
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, greenRenderPass.color, 0, 0);
+}
+
+// Test that mapping a index buffer, modifying the data then draw with the buffer works.
+TEST_P(BufferMapExtendedUsagesTests, MapWriteIndexBufferAndDraw) {
+    const utils::RGBA8 kVertexColors[] = {
+        utils::RGBA8::kRed,   utils::RGBA8::kRed,   utils::RGBA8::kRed,
+        utils::RGBA8::kGreen, utils::RGBA8::kGreen, utils::RGBA8::kGreen,
+    };
+    // Last index is unused. It is only to make sure the index buffer's size is multiple of 4.
+    const uint16_t kRedIndices[] = {0, 1, 2, 0};
+    const uint16_t kGreenIndices[] = {3, 4, 5, 3};
+
+    wgpu::Buffer vertexBuffer = utils::CreateBufferFromData(
+        device, kVertexColors, sizeof(kVertexColors), wgpu::BufferUsage::Vertex);
+    wgpu::Buffer indexBuffer =
+        utils::CreateBufferFromData(device, kRedIndices, sizeof(kRedIndices),
+                                    wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Index);
+
+    wgpu::RenderPipeline renderPipeline =
+        CreateRenderPipelineForTest(/*colorFromUniformBuffer=*/false);
+
+    auto redRenderPass = utils::CreateBasicRenderPass(device, 1, 1);
+    auto greenRenderPass = utils::CreateBasicRenderPass(device, 1, 1);
+
+    // First render pass: draw with red color index buffer.
+    EncodeAndSubmitRenderPassForTest(redRenderPass.renderPassInfo, renderPipeline, vertexBuffer,
+                                     indexBuffer, nullptr);
+
+    // Second render pass: draw with green color index buffer.
+    MapAsyncAndWait(indexBuffer, wgpu::MapMode::Write, 0, sizeof(kGreenIndices));
+    ASSERT_NE(nullptr, indexBuffer.GetMappedRange());
+    memcpy(indexBuffer.GetMappedRange(), kGreenIndices, sizeof(kGreenIndices));
+    indexBuffer.Unmap();
+
+    EncodeAndSubmitRenderPassForTest(greenRenderPass.renderPassInfo, renderPipeline, vertexBuffer,
+                                     indexBuffer, nullptr);
+
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, redRenderPass.color, 0, 0);
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, greenRenderPass.color, 0, 0);
+}
+
+// Test that mapping a uniform buffer, modifying the data then draw with the buffer works.
+TEST_P(BufferMapExtendedUsagesTests, MapWriteUniformBufferAndDraw) {
+    const float kRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
+    const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
+
+    // Create buffer with initial red color data.
+    wgpu::Buffer uniformBuffer = utils::CreateBufferFromData(
+        device, &kRed, sizeof(kRed), wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Uniform);
+
+    wgpu::RenderPipeline renderPipeline =
+        CreateRenderPipelineForTest(/*colorFromUniformBuffer=*/true);
+    wgpu::BindGroup uniformsBindGroup = utils::MakeBindGroup(
+        device, renderPipeline.GetBindGroupLayout(0), {{0, uniformBuffer, 0, sizeof(kRed)}});
+
+    auto redRenderPass = utils::CreateBasicRenderPass(device, 1, 1);
+    auto greenRenderPass = utils::CreateBasicRenderPass(device, 1, 1);
+
+    // First render pass: draw with red color uniform buffer.
+    EncodeAndSubmitRenderPassForTest(redRenderPass.renderPassInfo, renderPipeline, nullptr, nullptr,
+                                     uniformsBindGroup);
+
+    // Second render pass: draw with green color uniform buffer.
+    MapAsyncAndWait(uniformBuffer, wgpu::MapMode::Write, 0, sizeof(kGreen));
+    ASSERT_NE(nullptr, uniformBuffer.GetMappedRange());
+    memcpy(uniformBuffer.GetMappedRange(), &kGreen, sizeof(kGreen));
+    uniformBuffer.Unmap();
+
+    EncodeAndSubmitRenderPassForTest(greenRenderPass.renderPassInfo, renderPipeline, nullptr,
+                                     nullptr, uniformsBindGroup);
+
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, redRenderPass.color, 0, 0);
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, greenRenderPass.color, 0, 0);
+}
+
+// Test that modifying a storage buffer on GPU, then map read it on CPU works.
+TEST_P(BufferMapExtendedUsagesTests, GPUWriteStorageBufferThenMapRead) {
+    const uint32_t kExpectedValue = 1;
+    constexpr size_t kSize = sizeof(kExpectedValue);
+
+    wgpu::ComputePipeline pipeline;
+    {
+        wgpu::ComputePipelineDescriptor csDesc;
+        csDesc.compute.module = utils::CreateShaderModule(device, R"(
+            struct SSBO {
+                value : u32
+            }
+            @group(0) @binding(0) var<storage, read_write> ssbo : SSBO;
+
+            @compute @workgroup_size(1) fn main() {
+                ssbo.value = 1u;
+            })");
+        csDesc.compute.entryPoint = "main";
+
+        pipeline = device.CreateComputePipeline(&csDesc);
+    }
+
+    wgpu::Buffer ssbo;
+    {
+        wgpu::BufferDescriptor descriptor;
+        descriptor.size = kSize;
+
+        descriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::MapRead;
+        ssbo = device.CreateBuffer(&descriptor);
+    }
+
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+
+        ASSERT_NE(nullptr, pipeline.Get());
+        wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0),
+                                                         {
+                                                             {0, ssbo, 0, kSize},
+                                                         });
+        pass.SetBindGroup(0, bindGroup);
+        pass.SetPipeline(pipeline);
+        pass.DispatchWorkgroups(1);
+        pass.End();
+
+        wgpu::CommandBuffer commands = encoder.Finish();
+
+        queue.Submit(1, &commands);
+    }
+
+    MapAsyncAndWait(ssbo, wgpu::MapMode::Read, 0, 4);
+    CheckMapping(ssbo.GetConstMappedRange(0, kSize), &kExpectedValue, kSize);
+    ssbo.Unmap();
+}
+
+DAWN_INSTANTIATE_TEST(BufferMapExtendedUsagesTests,
+                      D3D11Backend(),
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
+
 }  // anonymous namespace
 }  // namespace dawn
diff --git a/src/dawn/tests/unittests/validation/BufferValidationTests.cpp b/src/dawn/tests/unittests/validation/BufferValidationTests.cpp
index 5bb71da..c00283e 100644
--- a/src/dawn/tests/unittests/validation/BufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/BufferValidationTests.cpp
@@ -82,7 +82,6 @@
 
     wgpu::Queue queue;
 
-  private:
     void SetUp() override {
         ValidationTest::SetUp();
 
@@ -1361,3 +1360,69 @@
         EXPECT_EQ(wgpu::BufferMapState::Unmapped, buf.GetMapState());
     }
 }
+
+class BufferMapExtendedUsagesValidationTest : public BufferValidationTest {
+  protected:
+    void SetUp() override {
+        DAWN_SKIP_TEST_IF(UsesWire());
+        BufferValidationTest::SetUp();
+    }
+
+    WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter,
+                                wgpu::DeviceDescriptor descriptor) override {
+        wgpu::FeatureName requiredFeatures[] = {wgpu::FeatureName::BufferMapExtendedUsages};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeatureCount = 1;
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+};
+
+// Test that MapRead or MapWrite can be combined with any other usage when creating
+// a buffer.
+TEST_F(BufferMapExtendedUsagesValidationTest, CreationMapUsageReadOrWriteNoRestrictions) {
+    constexpr wgpu::BufferUsage kNonMapUsages[] = {
+        wgpu::BufferUsage::CopySrc,  wgpu::BufferUsage::CopyDst,      wgpu::BufferUsage::Index,
+        wgpu::BufferUsage::Vertex,   wgpu::BufferUsage::Uniform,      wgpu::BufferUsage::Storage,
+        wgpu::BufferUsage::Indirect, wgpu::BufferUsage::QueryResolve,
+    };
+
+    // MapRead with anything is ok
+    {
+        wgpu::BufferDescriptor descriptor;
+        descriptor.size = 4;
+
+        for (const auto otherUsage : kNonMapUsages) {
+            descriptor.usage = wgpu::BufferUsage::MapRead | otherUsage;
+
+            device.CreateBuffer(&descriptor);
+        }
+    }
+
+    // MapWrite with anything is ok
+    {
+        wgpu::BufferDescriptor descriptor;
+        descriptor.size = 4;
+
+        for (const auto otherUsage : kNonMapUsages) {
+            descriptor.usage = wgpu::BufferUsage::MapWrite | otherUsage;
+
+            device.CreateBuffer(&descriptor);
+        }
+    }
+}
+
+// Test that a buffer creation with both MapRead and MapWrite will fail
+TEST_F(BufferMapExtendedUsagesValidationTest, CreationMapUsageReadAndWriteFails) {
+    // MapRead | MapWrite cannot be combined
+    {
+        wgpu::BufferDescriptor descriptor;
+        descriptor.size = 4;
+
+        descriptor.usage =
+            wgpu::BufferUsage::MapRead | wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopyDst;
+
+        ASSERT_DEVICE_ERROR(
+            device.CreateBuffer(&descriptor),
+            testing::HasSubstr("either BufferUsage::MapRead or BufferUsage::MapWrite"));
+    }
+}
diff --git a/src/dawn/wire/SupportedFeatures.cpp b/src/dawn/wire/SupportedFeatures.cpp
index 051ab54..b053d2c 100644
--- a/src/dawn/wire/SupportedFeatures.cpp
+++ b/src/dawn/wire/SupportedFeatures.cpp
@@ -54,6 +54,7 @@
         case WGPUFeatureName_SharedFenceVkSemaphoreZirconHandle:
         case WGPUFeatureName_SharedFenceDXGISharedHandle:
         case WGPUFeatureName_SharedFenceMTLSharedEvent:
+        case WGPUFeatureName_BufferMapExtendedUsages:
             return false;
         case WGPUFeatureName_Depth32FloatStencil8:
         case WGPUFeatureName_TimestampQuery: