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: