Implement CreateBufferMapped in dawn_native for MAP_WRITE buffers only.

This is the first command to return a struct. This patch also
updates the code generator to support structure return values.

Bug: dawn:7
Change-Id: Ie8acec895c0ec88429672138ffc900032fbbc447
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/4780
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index 6044c96..b7a18ce 100644
--- a/dawn.json
+++ b/dawn.json
@@ -213,6 +213,14 @@
     "char": {
         "category": "native"
     },
+    "create buffer mapped result": {
+        "category": "structure",
+        "members": [
+            {"name": "buffer", "type": "buffer"},
+            {"name": "data length", "type": "uint64_t"},
+            {"name": "data", "type": "uint8_t", "annotation": "*", "length": "data length"}
+        ]
+    },
     "color": {
         "category": "structure",
         "members": [
@@ -411,6 +419,13 @@
                 ]
             },
             {
+                "name": "create buffer mapped",
+                "returns": "create buffer mapped result",
+                "args": [
+                    {"name": "descriptor", "type": "buffer descriptor", "annotation": "const*"}
+                ]
+            },
+            {
                 "name": "create command encoder",
                 "returns": "command encoder"
             },
diff --git a/dawn_wire.json b/dawn_wire.json
index f2c1dce..02338b4 100644
--- a/dawn_wire.json
+++ b/dawn_wire.json
@@ -58,6 +58,7 @@
         ],
         "client_handwritten_commands": [
             "BufferUnmap",
+            "DeviceCreateBufferMapped",
             "QueueCreateFence",
             "FenceGetCompletedValue",
             "QueueSignal"
diff --git a/generator/main.py b/generator/main.py
index 8518e5d..2d63c08 100644
--- a/generator/main.py
+++ b/generator/main.py
@@ -230,9 +230,34 @@
     else:
         return name.CamelCase()
 
+def convert_cType_to_cppType(typ, annotation, arg, indent=0):
+    if typ.category == 'native':
+        return arg
+    if annotation == 'value':
+        if typ.category == 'object':
+            return '{}::Acquire({})'.format(as_cppType(typ.name), arg)
+        elif typ.category == 'structure':
+            converted_members = [
+                convert_cType_to_cppType(
+                    member.type, member.annotation,
+                    '{}.{}'.format(arg, as_varName(member.name)),
+                    indent + 1)
+                for member in typ.members]
+
+            converted_members = [(' ' * 4) + m for m in converted_members ]
+            converted_members = ',\n'.join(converted_members)
+
+            return as_cppType(typ.name) + ' {\n' + converted_members + '\n}'
+        else:
+            return 'static_cast<{}>({})'.format(as_cppType(typ.name), arg)
+    else:
+        return 'reinterpret_cast<{} {}>({})'.format(as_cppType(typ.name), annotation, arg)
+
 def decorate(name, typ, arg):
     if arg.annotation == 'value':
         return typ + ' ' + name
+    elif arg.annotation == '*':
+        return typ + ' * ' + name
     elif arg.annotation == 'const*':
         return typ + ' const * ' + name
     elif arg.annotation == 'const*const*':
@@ -314,6 +339,7 @@
         'as_cProc': as_cProc,
         'as_cType': as_cType,
         'as_cppType': as_cppType,
+        'convert_cType_to_cppType': convert_cType_to_cppType,
         'as_varName': as_varName,
         'decorate': decorate,
     }
diff --git a/generator/templates/apicpp.cpp b/generator/templates/apicpp.cpp
index b353a8b..5b61b2f 100644
--- a/generator/templates/apicpp.cpp
+++ b/generator/templates/apicpp.cpp
@@ -96,13 +96,7 @@
                     {{render_cpp_to_c_method_call(type, method)}};
                 {% else %}
                     auto result = {{render_cpp_to_c_method_call(type, method)}};
-                    {% if method.return_type.category == "native" %} 
-                        return result;
-                    {% elif method.return_type.category == "object" %}
-                        return {{as_cppType(method.return_type.name)}}::Acquire(result);
-                    {% else %}
-                        return static_cast<{{as_cppType(method.return_type.name)}}>(result);
-                    {% endif%}
+                    return {{convert_cType_to_cppType(method.return_type, 'value', 'result') | indent(8)}};
                 {% endif %}
             }
         {% endfor %}
diff --git a/generator/templates/dawn_wire/WireCmd.cpp b/generator/templates/dawn_wire/WireCmd.cpp
index b1c952f..ca7a35f 100644
--- a/generator/templates/dawn_wire/WireCmd.cpp
+++ b/generator/templates/dawn_wire/WireCmd.cpp
@@ -152,7 +152,7 @@
 
     //* Serializes `record` into `transfer`, using `buffer` to get more space for pointed-to data
     //* and `provider` to serialize objects.
-    void {{Return}}{{name}}Serialize(const {{Return}}{{name}}{{Cmd}}& record, {{Return}}{{name}}Transfer* transfer,
+    DAWN_DECLARE_UNUSED void {{Return}}{{name}}Serialize(const {{Return}}{{name}}{{Cmd}}& record, {{Return}}{{name}}Transfer* transfer,
                            char** buffer
         {%- if record.has_dawn_object -%}
             , const ObjectIdProvider& provider
@@ -200,11 +200,12 @@
             }
         {% endfor %}
     }
+    DAWN_UNUSED_FUNC({{Return}}{{name}}Serialize);
 
     //* Deserializes `transfer` into `record` getting more serialized data from `buffer` and `size`
     //* if needed, using `allocator` to store pointed-to values and `resolver` to translate object
     //* Ids to actual objects.
-    DeserializeResult {{Return}}{{name}}Deserialize({{Return}}{{name}}{{Cmd}}* record, const {{Return}}{{name}}Transfer* transfer,
+    DAWN_DECLARE_UNUSED DeserializeResult {{Return}}{{name}}Deserialize({{Return}}{{name}}{{Cmd}}* record, const {{Return}}{{name}}Transfer* transfer,
                                           const char** buffer, size_t* size, DeserializeAllocator* allocator
         {%- if record.has_dawn_object -%}
             , const ObjectIdResolver& resolver
@@ -283,6 +284,7 @@
 
         return DeserializeResult::Success;
     }
+    DAWN_UNUSED_FUNC({{Return}}{{name}}Deserialize);
 {% endmacro %}
 
 {% macro write_command_serialization_methods(command, is_return) %}
diff --git a/src/dawn_native/Buffer.cpp b/src/dawn_native/Buffer.cpp
index 74ac084..08cb871 100644
--- a/src/dawn_native/Buffer.cpp
+++ b/src/dawn_native/Buffer.cpp
@@ -31,7 +31,24 @@
             ErrorBuffer(DeviceBase* device) : BufferBase(device, ObjectBase::kError) {
             }
 
+            static ErrorBuffer* MakeMapped(DeviceBase* device,
+                                           uint64_t size,
+                                           uint8_t** mappedPointer) {
+                ASSERT(mappedPointer != nullptr);
+
+                ErrorBuffer* buffer = new ErrorBuffer(device);
+                buffer->mFakeMappedData = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+                *mappedPointer = buffer->mFakeMappedData.get();
+
+                return buffer;
+            }
+
           private:
+            MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override {
+                UNREACHABLE();
+                return {};
+            }
+
             MaybeError SetSubDataImpl(uint32_t start,
                                       uint32_t count,
                                       const uint8_t* data) override {
@@ -45,11 +62,14 @@
                 UNREACHABLE();
             }
             void UnmapImpl() override {
-                UNREACHABLE();
+                ASSERT(mFakeMappedData);
+                mFakeMappedData.reset();
             }
             void DestroyImpl() override {
                 UNREACHABLE();
             }
+
+            std::unique_ptr<uint8_t[]> mFakeMappedData;
         };
 
     }  // anonymous namespace
@@ -104,6 +124,13 @@
         return new ErrorBuffer(device);
     }
 
+    // static
+    BufferBase* BufferBase::MakeErrorMapped(DeviceBase* device,
+                                            uint64_t size,
+                                            uint8_t** mappedPointer) {
+        return ErrorBuffer::MakeMapped(device, size, mappedPointer);
+    }
+
     uint32_t BufferBase::GetSize() const {
         ASSERT(!IsError());
         return mSize;
@@ -114,6 +141,22 @@
         return mUsage;
     }
 
+    MaybeError BufferBase::MapAtCreation(uint8_t** mappedPointer) {
+        ASSERT(!IsError());
+        ASSERT(mappedPointer != nullptr);
+
+        mState = BufferState::Mapped;
+        if ((mUsage & dawn::BufferUsageBit::MapWrite) == 0) {
+            // TODO(enga): Support non-mappable buffers with a staging buffer.
+            return DAWN_VALIDATION_ERROR("MapWrite usage required");
+        }
+
+        DAWN_TRY(MapAtCreationImpl(mappedPointer));
+        ASSERT(*mappedPointer != nullptr);
+
+        return {};
+    }
+
     MaybeError BufferBase::ValidateCanUseInSubmitNow() const {
         ASSERT(!IsError());
 
@@ -239,6 +282,11 @@
     }
 
     void BufferBase::Unmap() {
+        if (IsError()) {
+            // It is an error to call Unmap() on an ErrorBuffer, but we still need to reclaim the
+            // fake mapped staging data.
+            UnmapImpl();
+        }
         if (GetDevice()->ConsumedError(ValidateUnmap())) {
             return;
         }
diff --git a/src/dawn_native/Buffer.h b/src/dawn_native/Buffer.h
index 46fe1fa..70ae0e5 100644
--- a/src/dawn_native/Buffer.h
+++ b/src/dawn_native/Buffer.h
@@ -45,10 +45,15 @@
         ~BufferBase();
 
         static BufferBase* MakeError(DeviceBase* device);
+        static BufferBase* MakeErrorMapped(DeviceBase* device,
+                                           uint64_t size,
+                                           uint8_t** mappedPointer);
 
         uint32_t GetSize() const;
         dawn::BufferUsageBit GetUsage() const;
 
+        MaybeError MapAtCreation(uint8_t** mappedPointer);
+
         MaybeError ValidateCanUseInSubmitNow() const;
 
         // Dawn API
@@ -73,6 +78,7 @@
         void DestroyInternal();
 
       private:
+        virtual MaybeError MapAtCreationImpl(uint8_t** mappedPointer) = 0;
         virtual MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data);
         virtual void MapReadAsyncImpl(uint32_t serial) = 0;
         virtual void MapWriteAsyncImpl(uint32_t serial) = 0;
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index 753f709..7d7c222 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -237,6 +237,30 @@
 
         return result;
     }
+    DawnCreateBufferMappedResult DeviceBase::CreateBufferMapped(
+        const BufferDescriptor* descriptor) {
+        BufferBase* buffer = nullptr;
+        uint8_t* data = nullptr;
+
+        if (ConsumedError(CreateBufferInternal(&buffer, descriptor)) ||
+            ConsumedError(buffer->MapAtCreation(&data))) {
+            // Map failed. Replace the buffer with an error buffer.
+            if (buffer != nullptr) {
+                delete buffer;
+            }
+            buffer = BufferBase::MakeErrorMapped(this, descriptor->size, &data);
+        }
+
+        ASSERT(buffer != nullptr);
+        ASSERT(data != nullptr);
+
+        DawnCreateBufferMappedResult result = {};
+        result.buffer = reinterpret_cast<DawnBuffer>(buffer);
+        result.data = data;
+        result.dataLength = descriptor->size;
+
+        return result;
+    }
     CommandEncoderBase* DeviceBase::CreateCommandEncoder() {
         return new CommandEncoderBase(this);
     }
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index b9039e4..998e035 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -104,6 +104,7 @@
         BindGroupBase* CreateBindGroup(const BindGroupDescriptor* descriptor);
         BindGroupLayoutBase* CreateBindGroupLayout(const BindGroupLayoutDescriptor* descriptor);
         BufferBase* CreateBuffer(const BufferDescriptor* descriptor);
+        DawnCreateBufferMappedResult CreateBufferMapped(const BufferDescriptor* descriptor);
         CommandEncoderBase* CreateCommandEncoder();
         ComputePipelineBase* CreateComputePipeline(const ComputePipelineDescriptor* descriptor);
         PipelineLayoutBase* CreatePipelineLayout(const PipelineLayoutDescriptor* descriptor);
diff --git a/src/dawn_native/d3d12/BufferD3D12.cpp b/src/dawn_native/d3d12/BufferD3D12.cpp
index b0b5a1e..3a5ed9b 100644
--- a/src/dawn_native/d3d12/BufferD3D12.cpp
+++ b/src/dawn_native/d3d12/BufferD3D12.cpp
@@ -160,6 +160,13 @@
         }
     }
 
+    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
+        mWrittenMappedRange = {0, GetSize()};
+        ASSERT_SUCCESS(
+            mResource->Map(0, &mWrittenMappedRange, reinterpret_cast<void**>(mappedPointer)));
+        return {};
+    }
+
     void Buffer::MapReadAsyncImpl(uint32_t serial) {
         mWrittenMappedRange = {};
         D3D12_RANGE readRange = {0, GetSize()};
diff --git a/src/dawn_native/d3d12/BufferD3D12.h b/src/dawn_native/d3d12/BufferD3D12.h
index 9667085..9208f53 100644
--- a/src/dawn_native/d3d12/BufferD3D12.h
+++ b/src/dawn_native/d3d12/BufferD3D12.h
@@ -44,6 +44,8 @@
         void UnmapImpl() override;
         void DestroyImpl() override;
 
+        virtual MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override;
+
         ComPtr<ID3D12Resource> mResource;
         bool mFixedResourceState = false;
         dawn::BufferUsageBit mLastUsage = dawn::BufferUsageBit::None;
diff --git a/src/dawn_native/metal/BufferMTL.h b/src/dawn_native/metal/BufferMTL.h
index 4cdbb5a..7fb8c35 100644
--- a/src/dawn_native/metal/BufferMTL.h
+++ b/src/dawn_native/metal/BufferMTL.h
@@ -34,11 +34,14 @@
         void OnMapCommandSerialFinished(uint32_t mapSerial, bool isWrite);
 
       private:
+        // Dawn API
         void MapReadAsyncImpl(uint32_t serial) override;
         void MapWriteAsyncImpl(uint32_t serial) override;
         void UnmapImpl() override;
         void DestroyImpl() override;
 
+        MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override;
+
         id<MTLBuffer> mMtlBuffer = nil;
     };
 
diff --git a/src/dawn_native/metal/BufferMTL.mm b/src/dawn_native/metal/BufferMTL.mm
index 1d0828a..7cc999a 100644
--- a/src/dawn_native/metal/BufferMTL.mm
+++ b/src/dawn_native/metal/BufferMTL.mm
@@ -47,6 +47,11 @@
         }
     }
 
+    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
+        *mappedPointer = reinterpret_cast<uint8_t*>([mMtlBuffer contents]);
+        return {};
+    }
+
     void Buffer::MapReadAsyncImpl(uint32_t serial) {
         MapRequestTracker* tracker = ToBackend(GetDevice())->GetMapTracker();
         tracker->Track(this, serial, false);
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 15db5c2..eeaae7e 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -184,13 +184,18 @@
         : BufferBase(device, descriptor) {
         if (GetUsage() & (dawn::BufferUsageBit::TransferDst | dawn::BufferUsageBit::MapRead |
                           dawn::BufferUsageBit::MapWrite)) {
-            mBackingData = std::unique_ptr<char[]>(new char[GetSize()]);
+            mBackingData = std::unique_ptr<uint8_t[]>(new uint8_t[GetSize()]);
         }
     }
 
     Buffer::~Buffer() {
     }
 
+    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
+        *mappedPointer = mBackingData.get();
+        return {};
+    }
+
     void Buffer::MapReadOperationCompleted(uint32_t serial, void* ptr, bool isWrite) {
         if (isWrite) {
             CallMapWriteCallback(serial, DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS, ptr, GetSize());
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index a523f3d..0c770fc 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -138,15 +138,17 @@
         void MapReadOperationCompleted(uint32_t serial, void* ptr, bool isWrite);
 
       private:
+        // Dawn API
         MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
         void MapReadAsyncImpl(uint32_t serial) override;
         void MapWriteAsyncImpl(uint32_t serial) override;
         void UnmapImpl() override;
         void DestroyImpl() override;
 
+        MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override;
         void MapAsyncImplCommon(uint32_t serial, bool isWrite);
 
-        std::unique_ptr<char[]> mBackingData;
+        std::unique_ptr<uint8_t[]> mBackingData;
     };
 
     class CommandBuffer : public CommandBufferBase {
diff --git a/src/dawn_native/opengl/BufferGL.cpp b/src/dawn_native/opengl/BufferGL.cpp
index 225a583..92fa601 100644
--- a/src/dawn_native/opengl/BufferGL.cpp
+++ b/src/dawn_native/opengl/BufferGL.cpp
@@ -35,6 +35,13 @@
         return mBuffer;
     }
 
+    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
+        glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
+        void* data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
+        *mappedPointer = reinterpret_cast<uint8_t*>(data);
+        return {};
+    }
+
     MaybeError Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) {
         glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
         glBufferSubData(GL_ARRAY_BUFFER, start, count, data);
diff --git a/src/dawn_native/opengl/BufferGL.h b/src/dawn_native/opengl/BufferGL.h
index 5a2b4a5..1ad582f 100644
--- a/src/dawn_native/opengl/BufferGL.h
+++ b/src/dawn_native/opengl/BufferGL.h
@@ -31,12 +31,15 @@
         GLuint GetHandle() const;
 
       private:
+        // Dawn API
         MaybeError SetSubDataImpl(uint32_t start, uint32_t count, const uint8_t* data) override;
         void MapReadAsyncImpl(uint32_t serial) override;
         void MapWriteAsyncImpl(uint32_t serial) override;
         void UnmapImpl() override;
         void DestroyImpl() override;
 
+        MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override;
+
         GLuint mBuffer = 0;
     };
 
diff --git a/src/dawn_native/vulkan/BufferVk.cpp b/src/dawn_native/vulkan/BufferVk.cpp
index 137f1ad..01e08bd 100644
--- a/src/dawn_native/vulkan/BufferVk.cpp
+++ b/src/dawn_native/vulkan/BufferVk.cpp
@@ -188,6 +188,11 @@
         mLastUsage = usage;
     }
 
+    MaybeError Buffer::MapAtCreationImpl(uint8_t** mappedPointer) {
+        *mappedPointer = mMemoryAllocation.GetMappedPointer();
+        return {};
+    }
+
     void Buffer::MapReadAsyncImpl(uint32_t serial) {
         Device* device = ToBackend(GetDevice());
 
diff --git a/src/dawn_native/vulkan/BufferVk.h b/src/dawn_native/vulkan/BufferVk.h
index 348f9be..5cfb7c5 100644
--- a/src/dawn_native/vulkan/BufferVk.h
+++ b/src/dawn_native/vulkan/BufferVk.h
@@ -41,11 +41,14 @@
         void TransitionUsageNow(VkCommandBuffer commands, dawn::BufferUsageBit usage);
 
       private:
+        // Dawn API
         void MapReadAsyncImpl(uint32_t serial) override;
         void MapWriteAsyncImpl(uint32_t serial) override;
         void UnmapImpl() override;
         void DestroyImpl() override;
 
+        MaybeError MapAtCreationImpl(uint8_t** mappedPointer) override;
+
         VkBuffer mHandle = VK_NULL_HANDLE;
         DeviceMemoryAllocation mMemoryAllocation;
 
diff --git a/src/dawn_wire/client/ApiProcs.cpp b/src/dawn_wire/client/ApiProcs.cpp
index 4289c17..04c46ff 100644
--- a/src/dawn_wire/client/ApiProcs.cpp
+++ b/src/dawn_wire/client/ApiProcs.cpp
@@ -68,6 +68,14 @@
         cmd.Serialize(allocatedBuffer);
     }
 
+    DawnCreateBufferMappedResult ClientDeviceCreateBufferMapped(
+        DawnDevice cDevice,
+        const DawnBufferDescriptor* descriptor) {
+        // TODO(enga): Not implemented
+        UNREACHABLE();
+        return {};
+    }
+
     uint64_t ClientFenceGetCompletedValue(DawnFence cSelf) {
         auto fence = reinterpret_cast<Fence*>(cSelf);
         return fence->completedValue;
diff --git a/src/tests/end2end/BufferTests.cpp b/src/tests/end2end/BufferTests.cpp
index eac536c..5872f41 100644
--- a/src/tests/end2end/BufferTests.cpp
+++ b/src/tests/end2end/BufferTests.cpp
@@ -229,3 +229,48 @@
                      MetalBackend,
                      OpenGLBackend,
                      VulkanBackend);
+
+class CreateBufferMappedTests : public DawnTest {};
+
+// Test that the simplest CreateBufferMapped works.
+TEST_P(CreateBufferMappedTests, SmallSyncWrite) {
+    dawn::BufferDescriptor descriptor;
+    descriptor.nextInChain = nullptr;
+    descriptor.size = 4;
+    descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc;
+
+    uint32_t myData = 230502;
+    dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor);
+    ASSERT_EQ(result.dataLength, descriptor.size);
+    memcpy(result.data, &myData, sizeof(myData));
+    result.buffer.Unmap();
+
+    EXPECT_BUFFER_U32_EQ(myData, result.buffer, 0);
+}
+
+// Test CreateBufferMapped for a large buffer
+TEST_P(CreateBufferMappedTests, LargeSyncWrite) {
+    constexpr uint64_t kDataSize = 1000 * 1000;
+    std::vector<uint32_t> myData;
+    for (uint32_t i = 0; i < kDataSize; ++i) {
+        myData.push_back(i);
+    }
+
+    dawn::BufferDescriptor descriptor;
+    descriptor.nextInChain = nullptr;
+    descriptor.size = static_cast<uint64_t>(kDataSize * sizeof(uint32_t));
+    descriptor.usage = dawn::BufferUsageBit::MapWrite | dawn::BufferUsageBit::TransferSrc;
+
+    dawn::CreateBufferMappedResult result = device.CreateBufferMapped(&descriptor);
+    ASSERT_EQ(result.dataLength, descriptor.size);
+    memcpy(result.data, myData.data(), kDataSize * sizeof(uint32_t));
+    result.buffer.Unmap();
+
+    EXPECT_BUFFER_U32_RANGE_EQ(myData.data(), result.buffer, 0, kDataSize);
+}
+
+DAWN_INSTANTIATE_TEST(CreateBufferMappedTests,
+                      D3D12Backend,
+                      MetalBackend,
+                      OpenGLBackend,
+                      VulkanBackend);
diff --git a/src/tests/unittests/validation/BufferValidationTests.cpp b/src/tests/unittests/validation/BufferValidationTests.cpp
index 3d25d67..69efe5d 100644
--- a/src/tests/unittests/validation/BufferValidationTests.cpp
+++ b/src/tests/unittests/validation/BufferValidationTests.cpp
@@ -82,6 +82,15 @@
             return device.CreateBuffer(&descriptor);
         }
 
+        dawn::CreateBufferMappedResult CreateBufferMapped(uint64_t size,
+                                                          dawn::BufferUsageBit usage) {
+            dawn::BufferDescriptor descriptor;
+            descriptor.size = size;
+            descriptor.usage = usage;
+
+            return device.CreateBufferMapped(&descriptor);
+        }
+
         dawn::Queue queue;
 
     private:
@@ -183,6 +192,14 @@
     buf.Unmap();
 }
 
+// Test the success case for CreateBufferMapped
+TEST_F(BufferValidationTest, CreateBufferMappedSuccess) {
+    dawn::CreateBufferMappedResult result = CreateBufferMapped(4, dawn::BufferUsageBit::MapWrite);
+    ASSERT_NE(result.data, nullptr);
+    ASSERT_EQ(result.dataLength, 4u);
+    result.buffer.Unmap();
+}
+
 // Test map reading a buffer with wrong current usage
 TEST_F(BufferValidationTest, MapReadWrongUsage) {
     dawn::BufferDescriptor descriptor;