dawn_wire/client: Encapsulate all buffer-related logic in Buffer.cpp

This CL only moves code, renames client::Buffer members and does
additional casts where needed. No functional changes.

Bug: dawn:445

Change-Id: I2bf83ecc1c9b36d5965d0365360dd981fcd41aac
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/23860
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_wire/client/ApiProcs.cpp b/src/dawn_wire/client/ApiProcs.cpp
index 358e717..da12171 100644
--- a/src/dawn_wire/client/ApiProcs.cpp
+++ b/src/dawn_wire/client/ApiProcs.cpp
@@ -18,214 +18,49 @@
 
 namespace dawn_wire { namespace client {
 
-    namespace {
-        template <typename Handle>
-        void SerializeBufferMapAsync(const Buffer* buffer, uint32_t serial, Handle* handle) {
-            // TODO(enga): Remove the template when Read/Write handles are combined in a tagged
-            // pointer.
-            constexpr bool isWrite =
-                std::is_same<Handle, MemoryTransferService::WriteHandle>::value;
-
-            // Get the serialization size of the handle.
-            size_t handleCreateInfoLength = handle->SerializeCreateSize();
-
-            BufferMapAsyncCmd cmd;
-            cmd.bufferId = buffer->id;
-            cmd.requestSerial = serial;
-            cmd.isWrite = isWrite;
-            cmd.handleCreateInfoLength = handleCreateInfoLength;
-            cmd.handleCreateInfo = nullptr;
-
-            char* writeHandleSpace =
-                buffer->device->GetClient()->SerializeCommand(cmd, handleCreateInfoLength);
-
-            // Serialize the handle into the space after the command.
-            handle->SerializeCreate(writeHandleSpace);
-        }
-    }  // namespace
-
     void ClientHandwrittenBufferMapReadAsync(WGPUBuffer cBuffer,
                                              WGPUBufferMapReadCallback callback,
                                              void* userdata) {
         Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
-
-        uint32_t serial = buffer->requestSerial++;
-        ASSERT(buffer->requests.find(serial) == buffer->requests.end());
-
-        if (buffer->size > std::numeric_limits<size_t>::max()) {
-            // On buffer creation, we check that mappable buffers do not exceed this size.
-            // So this buffer must not have mappable usage. Inject a validation error.
-            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(buffer->device),
-                                    WGPUErrorType_Validation,
-                                    "Buffer needs the correct map usage bit");
-            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
-            return;
-        }
-
-        // Create a ReadHandle for the map request. This is the client's intent to read GPU
-        // memory.
-        MemoryTransferService::ReadHandle* readHandle =
-            buffer->device->GetClient()->GetMemoryTransferService()->CreateReadHandle(
-                static_cast<size_t>(buffer->size));
-        if (readHandle == nullptr) {
-            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(buffer->device),
-                                    WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
-            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
-            return;
-        }
-
-        Buffer::MapRequestData request = {};
-        request.readCallback = callback;
-        request.userdata = userdata;
-        // The handle is owned by the MapRequest until the callback returns.
-        request.readHandle = std::unique_ptr<MemoryTransferService::ReadHandle>(readHandle);
-
-        // Store a mapping from serial -> MapRequest. The client can map/unmap before the map
-        // operations are returned by the server so multiple requests may be in flight.
-        buffer->requests[serial] = std::move(request);
-
-        SerializeBufferMapAsync(buffer, serial, readHandle);
+        buffer->MapReadAsync(callback, userdata);
     }
 
     void ClientHandwrittenBufferMapWriteAsync(WGPUBuffer cBuffer,
                                               WGPUBufferMapWriteCallback callback,
                                               void* userdata) {
         Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
+        buffer->MapWriteAsync(callback, userdata);
+    }
 
-        uint32_t serial = buffer->requestSerial++;
-        ASSERT(buffer->requests.find(serial) == buffer->requests.end());
+    void ClientHandwrittenBufferSetSubData(WGPUBuffer cBuffer,
+                                           uint64_t start,
+                                           uint64_t count,
+                                           const void* data) {
+        Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
+        buffer->SetSubData(start, count, data);
+    }
 
-        if (buffer->size > std::numeric_limits<size_t>::max()) {
-            // On buffer creation, we check that mappable buffers do not exceed this size.
-            // So this buffer must not have mappable usage. Inject a validation error.
-            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(buffer->device),
-                                    WGPUErrorType_Validation,
-                                    "Buffer needs the correct map usage bit");
-            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
-            return;
-        }
+    void ClientHandwrittenBufferUnmap(WGPUBuffer cBuffer) {
+        Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
+        buffer->Unmap();
+    }
 
-        // Create a WriteHandle for the map request. This is the client's intent to write GPU
-        // memory.
-        MemoryTransferService::WriteHandle* writeHandle =
-            buffer->device->GetClient()->GetMemoryTransferService()->CreateWriteHandle(
-                static_cast<size_t>(buffer->size));
-        if (writeHandle == nullptr) {
-            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(buffer->device),
-                                    WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
-            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
-            return;
-        }
-
-        Buffer::MapRequestData request = {};
-        request.writeCallback = callback;
-        request.userdata = userdata;
-        // The handle is owned by the MapRequest until the callback returns.
-        request.writeHandle = std::unique_ptr<MemoryTransferService::WriteHandle>(writeHandle);
-
-        // Store a mapping from serial -> MapRequest. The client can map/unmap before the map
-        // operations are returned by the server so multiple requests may be in flight.
-        buffer->requests[serial] = std::move(request);
-
-        SerializeBufferMapAsync(buffer, serial, writeHandle);
+    void ClientHandwrittenBufferDestroy(WGPUBuffer cBuffer) {
+        Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
+        buffer->Destroy();
     }
 
     WGPUBuffer ClientHandwrittenDeviceCreateBuffer(WGPUDevice cDevice,
                                                    const WGPUBufferDescriptor* descriptor) {
         Device* device = reinterpret_cast<Device*>(cDevice);
-        Client* wireClient = device->GetClient();
-
-        if ((descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 &&
-            descriptor->size > std::numeric_limits<size_t>::max()) {
-            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
-                                    "Buffer is too large for map usage");
-            return ClientDeviceCreateErrorBuffer(cDevice);
-        }
-
-        auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device);
-        Buffer* buffer = bufferObjectAndSerial->object.get();
-        // Store the size of the buffer so that mapping operations can allocate a
-        // MemoryTransfer handle of the proper size.
-        buffer->size = descriptor->size;
-
-        DeviceCreateBufferCmd cmd;
-        cmd.self = cDevice;
-        cmd.descriptor = descriptor;
-        cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
-
-        wireClient->SerializeCommand(cmd);
-
-        return reinterpret_cast<WGPUBuffer>(buffer);
+        return Buffer::Create(device, descriptor);
     }
 
     WGPUCreateBufferMappedResult ClientHandwrittenDeviceCreateBufferMapped(
         WGPUDevice cDevice,
         const WGPUBufferDescriptor* descriptor) {
         Device* device = reinterpret_cast<Device*>(cDevice);
-        Client* wireClient = device->GetClient();
-
-        WGPUCreateBufferMappedResult result;
-        result.data = nullptr;
-        result.dataLength = 0;
-
-        // This buffer is too large to be mapped and to make a WriteHandle for.
-        if (descriptor->size > std::numeric_limits<size_t>::max()) {
-            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
-                                    "Buffer is too large for mapping");
-            result.buffer = ClientDeviceCreateErrorBuffer(cDevice);
-            return result;
-        }
-
-        // Create a WriteHandle for the map request. This is the client's intent to write GPU
-        // memory.
-        std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle =
-            std::unique_ptr<MemoryTransferService::WriteHandle>(
-                wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size));
-
-        if (writeHandle == nullptr) {
-            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
-                                    "Buffer mapping allocation failed");
-            result.buffer = ClientDeviceCreateErrorBuffer(cDevice);
-            return result;
-        }
-
-        // CreateBufferMapped is synchronous and the staging buffer for upload should be immediately
-        // available.
-        // Open the WriteHandle. This returns a pointer and size of mapped memory.
-        // |result.data| may be null on error.
-        std::tie(result.data, result.dataLength) = writeHandle->Open();
-        if (result.data == nullptr) {
-            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
-                                    "Buffer mapping allocation failed");
-            result.buffer = ClientDeviceCreateErrorBuffer(cDevice);
-            return result;
-        }
-
-        auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device);
-        Buffer* buffer = bufferObjectAndSerial->object.get();
-        buffer->size = descriptor->size;
-        // Successfully created staging memory. The buffer now owns the WriteHandle.
-        buffer->writeHandle = std::move(writeHandle);
-
-        result.buffer = reinterpret_cast<WGPUBuffer>(buffer);
-
-        // Get the serialization size of the WriteHandle.
-        size_t handleCreateInfoLength = buffer->writeHandle->SerializeCreateSize();
-
-        DeviceCreateBufferMappedCmd cmd;
-        cmd.device = cDevice;
-        cmd.descriptor = descriptor;
-        cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
-        cmd.handleCreateInfoLength = handleCreateInfoLength;
-        cmd.handleCreateInfo = nullptr;
-
-        char* writeHandleSpace =
-            buffer->device->GetClient()->SerializeCommand(cmd, handleCreateInfoLength);
-
-        // Serialize the WriteHandle into the space after the command.
-        buffer->writeHandle->SerializeCreate(writeHandleSpace);
-
-        return result;
+        return Buffer::CreateMapped(device, descriptor);
     }
 
     void ClientHandwrittenDevicePushErrorScope(WGPUDevice cDevice, WGPUErrorFilter filter) {
@@ -269,76 +104,6 @@
         fence->requests.Enqueue(std::move(request), value);
     }
 
-    void ClientHandwrittenBufferSetSubData(WGPUBuffer cBuffer,
-                                           uint64_t start,
-                                           uint64_t count,
-                                           const void* data) {
-        Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
-
-        BufferSetSubDataInternalCmd cmd;
-        cmd.bufferId = buffer->id;
-        cmd.start = start;
-        cmd.count = count;
-        cmd.data = static_cast<const uint8_t*>(data);
-
-        buffer->device->GetClient()->SerializeCommand(cmd);
-    }
-
-    void ClientHandwrittenBufferUnmap(WGPUBuffer cBuffer) {
-        Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
-
-        // Invalidate the local pointer, and cancel all other in-flight requests that would
-        // turn into errors anyway (you can't double map). This prevents race when the following
-        // happens, where the application code would have unmapped a buffer but still receive a
-        // callback:
-        //   - Client -> Server: MapRequest1, Unmap, MapRequest2
-        //   - Server -> Client: Result of MapRequest1
-        //   - Unmap locally on the client
-        //   - Server -> Client: Result of MapRequest2
-        if (buffer->writeHandle) {
-            // Writes need to be flushed before Unmap is sent. Unmap calls all associated
-            // in-flight callbacks which may read the updated data.
-            ASSERT(buffer->readHandle == nullptr);
-
-            // Get the serialization size of metadata to flush writes.
-            size_t writeFlushInfoLength = buffer->writeHandle->SerializeFlushSize();
-
-            BufferUpdateMappedDataCmd cmd;
-            cmd.bufferId = buffer->id;
-            cmd.writeFlushInfoLength = writeFlushInfoLength;
-            cmd.writeFlushInfo = nullptr;
-
-            char* writeHandleSpace =
-                buffer->device->GetClient()->SerializeCommand(cmd, writeFlushInfoLength);
-
-            // Serialize flush metadata into the space after the command.
-            // This closes the handle for writing.
-            buffer->writeHandle->SerializeFlush(writeHandleSpace);
-            buffer->writeHandle = nullptr;
-
-        } else if (buffer->readHandle) {
-            buffer->readHandle = nullptr;
-        }
-        buffer->ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
-
-        BufferUnmapCmd cmd;
-        cmd.self = cBuffer;
-        buffer->device->GetClient()->SerializeCommand(cmd);
-    }
-
-    void ClientHandwrittenBufferDestroy(WGPUBuffer cBuffer) {
-        Buffer* buffer = reinterpret_cast<Buffer*>(cBuffer);
-
-        // Cancel or remove all mappings
-        buffer->writeHandle = nullptr;
-        buffer->readHandle = nullptr;
-        buffer->ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
-
-        BufferDestroyCmd cmd;
-        cmd.self = cBuffer;
-        buffer->device->GetClient()->SerializeCommand(cmd);
-    }
-
     WGPUFence ClientHandwrittenQueueCreateFence(WGPUQueue cSelf,
                                                 WGPUFenceDescriptor const* descriptor) {
         Queue* queue = reinterpret_cast<Queue*>(cSelf);
diff --git a/src/dawn_wire/client/Buffer.cpp b/src/dawn_wire/client/Buffer.cpp
index 3548f51..a4f5fad 100644
--- a/src/dawn_wire/client/Buffer.cpp
+++ b/src/dawn_wire/client/Buffer.cpp
@@ -14,8 +14,136 @@
 
 #include "dawn_wire/client/Buffer.h"
 
+#include "dawn_wire/client/ApiProcs_autogen.h"
+#include "dawn_wire/client/Client.h"
+#include "dawn_wire/client/Device.h"
+
 namespace dawn_wire { namespace client {
 
+    namespace {
+        template <typename Handle>
+        void SerializeBufferMapAsync(const Buffer* buffer, uint32_t serial, Handle* handle) {
+            // TODO(enga): Remove the template when Read/Write handles are combined in a tagged
+            // pointer.
+            constexpr bool isWrite =
+                std::is_same<Handle, MemoryTransferService::WriteHandle>::value;
+
+            // Get the serialization size of the handle.
+            size_t handleCreateInfoLength = handle->SerializeCreateSize();
+
+            BufferMapAsyncCmd cmd;
+            cmd.bufferId = buffer->id;
+            cmd.requestSerial = serial;
+            cmd.isWrite = isWrite;
+            cmd.handleCreateInfoLength = handleCreateInfoLength;
+            cmd.handleCreateInfo = nullptr;
+
+            char* writeHandleSpace =
+                buffer->device->GetClient()->SerializeCommand(cmd, handleCreateInfoLength);
+
+            // Serialize the handle into the space after the command.
+            handle->SerializeCreate(writeHandleSpace);
+        }
+    }  // namespace
+
+    // static
+    WGPUBuffer Buffer::Create(Device* device_, const WGPUBufferDescriptor* descriptor) {
+        WGPUDevice cDevice = reinterpret_cast<WGPUDevice>(device_);
+        Client* wireClient = device_->GetClient();
+
+        if ((descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 &&
+            descriptor->size > std::numeric_limits<size_t>::max()) {
+            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
+                                    "Buffer is too large for map usage");
+            return ClientDeviceCreateErrorBuffer(cDevice);
+        }
+
+        auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device_);
+        Buffer* buffer = bufferObjectAndSerial->object.get();
+        // Store the size of the buffer so that mapping operations can allocate a
+        // MemoryTransfer handle of the proper size.
+        buffer->mSize = descriptor->size;
+
+        DeviceCreateBufferCmd cmd;
+        cmd.self = cDevice;
+        cmd.descriptor = descriptor;
+        cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
+
+        wireClient->SerializeCommand(cmd);
+
+        return reinterpret_cast<WGPUBuffer>(buffer);
+    }
+
+    // static
+    WGPUCreateBufferMappedResult Buffer::CreateMapped(Device* device_,
+                                                      const WGPUBufferDescriptor* descriptor) {
+        WGPUDevice cDevice = reinterpret_cast<WGPUDevice>(device_);
+        Client* wireClient = device_->GetClient();
+
+        WGPUCreateBufferMappedResult result;
+        result.data = nullptr;
+        result.dataLength = 0;
+
+        // This buffer is too large to be mapped and to make a WriteHandle for.
+        if (descriptor->size > std::numeric_limits<size_t>::max()) {
+            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
+                                    "Buffer is too large for mapping");
+            result.buffer = ClientDeviceCreateErrorBuffer(cDevice);
+            return result;
+        }
+
+        // Create a WriteHandle for the map request. This is the client's intent to write GPU
+        // memory.
+        std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle =
+            std::unique_ptr<MemoryTransferService::WriteHandle>(
+                wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size));
+
+        if (writeHandle == nullptr) {
+            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
+                                    "Buffer mapping allocation failed");
+            result.buffer = ClientDeviceCreateErrorBuffer(cDevice);
+            return result;
+        }
+
+        // CreateBufferMapped is synchronous and the staging buffer for upload should be immediately
+        // available.
+        // Open the WriteHandle. This returns a pointer and size of mapped memory.
+        // |result.data| may be null on error.
+        std::tie(result.data, result.dataLength) = writeHandle->Open();
+        if (result.data == nullptr) {
+            ClientDeviceInjectError(cDevice, WGPUErrorType_OutOfMemory,
+                                    "Buffer mapping allocation failed");
+            result.buffer = ClientDeviceCreateErrorBuffer(cDevice);
+            return result;
+        }
+
+        auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device_);
+        Buffer* buffer = bufferObjectAndSerial->object.get();
+        buffer->mSize = descriptor->size;
+        // Successfully created staging memory. The buffer now owns the WriteHandle.
+        buffer->mWriteHandle = std::move(writeHandle);
+
+        result.buffer = reinterpret_cast<WGPUBuffer>(buffer);
+
+        // Get the serialization size of the WriteHandle.
+        size_t handleCreateInfoLength = buffer->mWriteHandle->SerializeCreateSize();
+
+        DeviceCreateBufferMappedCmd cmd;
+        cmd.device = cDevice;
+        cmd.descriptor = descriptor;
+        cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
+        cmd.handleCreateInfoLength = handleCreateInfoLength;
+        cmd.handleCreateInfo = nullptr;
+
+        char* writeHandleSpace =
+            buffer->device->GetClient()->SerializeCommand(cmd, handleCreateInfoLength);
+
+        // Serialize the WriteHandle into the space after the command.
+        buffer->mWriteHandle->SerializeCreate(writeHandleSpace);
+
+        return result;
+    }
+
     Buffer::~Buffer() {
         // Callbacks need to be fired in all cases, as they can handle freeing resources
         // so we call them with "Unknown" status.
@@ -23,14 +151,275 @@
     }
 
     void Buffer::ClearMapRequests(WGPUBufferMapAsyncStatus status) {
-        for (auto& it : requests) {
+        for (auto& it : mRequests) {
             if (it.second.writeHandle) {
                 it.second.writeCallback(status, nullptr, 0, it.second.userdata);
             } else {
                 it.second.readCallback(status, nullptr, 0, it.second.userdata);
             }
         }
-        requests.clear();
+        mRequests.clear();
+    }
+
+    void Buffer::MapReadAsync(WGPUBufferMapReadCallback callback, void* userdata) {
+        uint32_t serial = mRequestSerial++;
+        ASSERT(mRequests.find(serial) == mRequests.end());
+
+        if (mSize > std::numeric_limits<size_t>::max()) {
+            // On buffer creation, we check that mappable buffers do not exceed this size.
+            // So this buffer must not have mappable usage. Inject a validation error.
+            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(device), WGPUErrorType_Validation,
+                                    "Buffer needs the correct map usage bit");
+            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
+            return;
+        }
+
+        // Create a ReadHandle for the map request. This is the client's intent to read GPU
+        // memory.
+        MemoryTransferService::ReadHandle* readHandle =
+            device->GetClient()->GetMemoryTransferService()->CreateReadHandle(
+                static_cast<size_t>(mSize));
+        if (readHandle == nullptr) {
+            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(device), WGPUErrorType_OutOfMemory,
+                                    "Failed to create buffer mapping");
+            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
+            return;
+        }
+
+        Buffer::MapRequestData request = {};
+        request.readCallback = callback;
+        request.userdata = userdata;
+        // The handle is owned by the MapRequest until the callback returns.
+        request.readHandle = std::unique_ptr<MemoryTransferService::ReadHandle>(readHandle);
+
+        // Store a mapping from serial -> MapRequest. The client can map/unmap before the map
+        // operations are returned by the server so multiple requests may be in flight.
+        mRequests[serial] = std::move(request);
+
+        SerializeBufferMapAsync(this, serial, readHandle);
+    }
+
+    void Buffer::MapWriteAsync(WGPUBufferMapWriteCallback callback, void* userdata) {
+        uint32_t serial = mRequestSerial++;
+        ASSERT(mRequests.find(serial) == mRequests.end());
+
+        if (mSize > std::numeric_limits<size_t>::max()) {
+            // On buffer creation, we check that mappable buffers do not exceed this size.
+            // So this buffer must not have mappable usage. Inject a validation error.
+            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(device), WGPUErrorType_Validation,
+                                    "Buffer needs the correct map usage bit");
+            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
+            return;
+        }
+
+        // Create a WriteHandle for the map request. This is the client's intent to write GPU
+        // memory.
+        MemoryTransferService::WriteHandle* writeHandle =
+            device->GetClient()->GetMemoryTransferService()->CreateWriteHandle(
+                static_cast<size_t>(mSize));
+        if (writeHandle == nullptr) {
+            ClientDeviceInjectError(reinterpret_cast<WGPUDevice>(device), WGPUErrorType_OutOfMemory,
+                                    "Failed to create buffer mapping");
+            callback(WGPUBufferMapAsyncStatus_Error, nullptr, 0, userdata);
+            return;
+        }
+
+        Buffer::MapRequestData request = {};
+        request.writeCallback = callback;
+        request.userdata = userdata;
+        // The handle is owned by the MapRequest until the callback returns.
+        request.writeHandle = std::unique_ptr<MemoryTransferService::WriteHandle>(writeHandle);
+
+        // Store a mapping from serial -> MapRequest. The client can map/unmap before the map
+        // operations are returned by the server so multiple requests may be in flight.
+        mRequests[serial] = std::move(request);
+
+        SerializeBufferMapAsync(this, serial, writeHandle);
+    }
+
+    bool Buffer::OnMapReadAsyncCallback(uint32_t requestSerial,
+                                        uint32_t status,
+                                        uint64_t initialDataInfoLength,
+                                        const uint8_t* initialDataInfo) {
+        // The requests can have been deleted via an Unmap so this isn't an error.
+        auto requestIt = mRequests.find(requestSerial);
+        if (requestIt == mRequests.end()) {
+            return true;
+        }
+
+        auto request = std::move(requestIt->second);
+        // Delete the request before calling the callback otherwise the callback could be fired a
+        // second time. If, for example, buffer.Unmap() is called inside the callback.
+        mRequests.erase(requestIt);
+
+        const void* mappedData = nullptr;
+        size_t mappedDataLength = 0;
+
+        auto GetMappedData = [&]() -> bool {
+            // It is an error for the server to call the read callback when we asked for a map write
+            if (request.writeHandle) {
+                return false;
+            }
+
+            if (status == WGPUBufferMapAsyncStatus_Success) {
+                if (mReadHandle || mWriteHandle) {
+                    // Buffer is already mapped.
+                    return false;
+                }
+                if (initialDataInfoLength > std::numeric_limits<size_t>::max()) {
+                    // This is the size of data deserialized from the command stream, which must be
+                    // CPU-addressable.
+                    return false;
+                }
+                ASSERT(request.readHandle != nullptr);
+
+                // The server serializes metadata to initialize the contents of the ReadHandle.
+                // Deserialize the message and return a pointer and size of the mapped data for
+                // reading.
+                if (!request.readHandle->DeserializeInitialData(
+                        initialDataInfo, static_cast<size_t>(initialDataInfoLength), &mappedData,
+                        &mappedDataLength)) {
+                    // Deserialization shouldn't fail. This is a fatal error.
+                    return false;
+                }
+                ASSERT(mappedData != nullptr);
+
+                // The MapRead request was successful. The buffer now owns the ReadHandle until
+                // Unmap().
+                mReadHandle = std::move(request.readHandle);
+            }
+
+            return true;
+        };
+
+        if (!GetMappedData()) {
+            // Dawn promises that all callbacks are called in finite time. Even if a fatal error
+            // occurs, the callback is called.
+            request.readCallback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0, request.userdata);
+            return false;
+        } else {
+            request.readCallback(static_cast<WGPUBufferMapAsyncStatus>(status), mappedData,
+                                 static_cast<uint64_t>(mappedDataLength), request.userdata);
+            return true;
+        }
+    }
+
+    bool Buffer::OnMapWriteAsyncCallback(uint32_t requestSerial, uint32_t status) {
+        // The requests can have been deleted via an Unmap so this isn't an error.
+        auto requestIt = mRequests.find(requestSerial);
+        if (requestIt == mRequests.end()) {
+            return true;
+        }
+
+        auto request = std::move(requestIt->second);
+        // Delete the request before calling the callback otherwise the callback could be fired a
+        // second time. If, for example, buffer.Unmap() is called inside the callback.
+        mRequests.erase(requestIt);
+
+        void* mappedData = nullptr;
+        size_t mappedDataLength = 0;
+
+        auto GetMappedData = [&]() -> bool {
+            // It is an error for the server to call the write callback when we asked for a map read
+            if (request.readHandle) {
+                return false;
+            }
+
+            if (status == WGPUBufferMapAsyncStatus_Success) {
+                if (mReadHandle || mWriteHandle) {
+                    // Buffer is already mapped.
+                    return false;
+                }
+                ASSERT(request.writeHandle != nullptr);
+
+                // Open the WriteHandle. This returns a pointer and size of mapped memory.
+                // On failure, |mappedData| may be null.
+                std::tie(mappedData, mappedDataLength) = request.writeHandle->Open();
+
+                if (mappedData == nullptr) {
+                    return false;
+                }
+
+                // The MapWrite request was successful. The buffer now owns the WriteHandle until
+                // Unmap().
+                mWriteHandle = std::move(request.writeHandle);
+            }
+
+            return true;
+        };
+
+        if (!GetMappedData()) {
+            // Dawn promises that all callbacks are called in finite time. Even if a fatal error
+            // occurs, the callback is called.
+            request.writeCallback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0,
+                                  request.userdata);
+            return false;
+        } else {
+            request.writeCallback(static_cast<WGPUBufferMapAsyncStatus>(status), mappedData,
+                                  static_cast<uint64_t>(mappedDataLength), request.userdata);
+            return true;
+        }
+    }
+
+    void Buffer::Unmap() {
+        // Invalidate the local pointer, and cancel all other in-flight requests that would
+        // turn into errors anyway (you can't double map). This prevents race when the following
+        // happens, where the application code would have unmapped a buffer but still receive a
+        // callback:
+        //   - Client -> Server: MapRequest1, Unmap, MapRequest2
+        //   - Server -> Client: Result of MapRequest1
+        //   - Unmap locally on the client
+        //   - Server -> Client: Result of MapRequest2
+        if (mWriteHandle) {
+            // Writes need to be flushed before Unmap is sent. Unmap calls all associated
+            // in-flight callbacks which may read the updated data.
+            ASSERT(mReadHandle == nullptr);
+
+            // Get the serialization size of metadata to flush writes.
+            size_t writeFlushInfoLength = mWriteHandle->SerializeFlushSize();
+
+            BufferUpdateMappedDataCmd cmd;
+            cmd.bufferId = id;
+            cmd.writeFlushInfoLength = writeFlushInfoLength;
+            cmd.writeFlushInfo = nullptr;
+
+            char* writeHandleSpace =
+                device->GetClient()->SerializeCommand(cmd, writeFlushInfoLength);
+
+            // Serialize flush metadata into the space after the command.
+            // This closes the handle for writing.
+            mWriteHandle->SerializeFlush(writeHandleSpace);
+            mWriteHandle = nullptr;
+
+        } else if (mReadHandle) {
+            mReadHandle = nullptr;
+        }
+        ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
+
+        BufferUnmapCmd cmd;
+        cmd.self = reinterpret_cast<WGPUBuffer>(this);
+        device->GetClient()->SerializeCommand(cmd);
+    }
+
+    void Buffer::Destroy() {
+        // Cancel or remove all mappings
+        mWriteHandle = nullptr;
+        mReadHandle = nullptr;
+        ClearMapRequests(WGPUBufferMapAsyncStatus_Unknown);
+
+        BufferDestroyCmd cmd;
+        cmd.self = reinterpret_cast<WGPUBuffer>(this);
+        device->GetClient()->SerializeCommand(cmd);
+    }
+
+    void Buffer::SetSubData(uint64_t start, uint64_t count, const void* data) {
+        BufferSetSubDataInternalCmd cmd;
+        cmd.bufferId = id;
+        cmd.start = start;
+        cmd.count = count;
+        cmd.data = static_cast<const uint8_t*>(data);
+
+        device->GetClient()->SerializeCommand(cmd);
     }
 
 }}  // namespace dawn_wire::client
diff --git a/src/dawn_wire/client/Buffer.h b/src/dawn_wire/client/Buffer.h
index 09da8e3..96cabf6 100644
--- a/src/dawn_wire/client/Buffer.h
+++ b/src/dawn_wire/client/Buffer.h
@@ -24,12 +24,31 @@
 
 namespace dawn_wire { namespace client {
 
-    struct Buffer : ObjectBase {
+    class Buffer : public ObjectBase {
+      public:
         using ObjectBase::ObjectBase;
 
+        static WGPUBuffer Create(Device* device, const WGPUBufferDescriptor* descriptor);
+        static WGPUCreateBufferMappedResult CreateMapped(Device* device,
+                                                         const WGPUBufferDescriptor* descriptor);
+
         ~Buffer();
         void ClearMapRequests(WGPUBufferMapAsyncStatus status);
 
+        void MapReadAsync(WGPUBufferMapReadCallback callback, void* userdata);
+        void MapWriteAsync(WGPUBufferMapWriteCallback callback, void* userdata);
+        bool OnMapReadAsyncCallback(uint32_t requestSerial,
+                                    uint32_t status,
+                                    uint64_t initialDataInfoLength,
+                                    const uint8_t* initialDataInfo);
+        bool OnMapWriteAsyncCallback(uint32_t requestSerial, uint32_t status);
+        void Unmap();
+
+        void Destroy();
+
+        void SetSubData(uint64_t start, uint64_t count, const void* data);
+
+      private:
         // We want to defer all the validation to the server, which means we could have multiple
         // map request in flight at a single time and need to track them separately.
         // On well-behaved applications, only one request should exist at a single time.
@@ -42,15 +61,15 @@
             std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
             std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
         };
-        std::map<uint32_t, MapRequestData> requests;
-        uint32_t requestSerial = 0;
-        uint64_t size = 0;
+        std::map<uint32_t, MapRequestData> mRequests;
+        uint32_t mRequestSerial = 0;
+        uint64_t mSize = 0;
 
         // Only one mapped pointer can be active at a time because Unmap clears all the in-flight
         // requests.
         // TODO(enga): Use a tagged pointer to save space.
-        std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
-        std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
+        std::unique_ptr<MemoryTransferService::ReadHandle> mReadHandle = nullptr;
+        std::unique_ptr<MemoryTransferService::WriteHandle> mWriteHandle = nullptr;
     };
 
 }}  // namespace dawn_wire::client
diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp
index dd90406..ffc5198 100644
--- a/src/dawn_wire/client/ClientDoers.cpp
+++ b/src/dawn_wire/client/ClientDoers.cpp
@@ -56,67 +56,8 @@
             return true;
         }
 
-        // The requests can have been deleted via an Unmap so this isn't an error.
-        auto requestIt = buffer->requests.find(requestSerial);
-        if (requestIt == buffer->requests.end()) {
-            return true;
-        }
-
-        auto request = std::move(requestIt->second);
-        // Delete the request before calling the callback otherwise the callback could be fired a
-        // second time. If, for example, buffer.Unmap() is called inside the callback.
-        buffer->requests.erase(requestIt);
-
-        const void* mappedData = nullptr;
-        size_t mappedDataLength = 0;
-
-        auto GetMappedData = [&]() -> bool {
-            // It is an error for the server to call the read callback when we asked for a map write
-            if (request.writeHandle) {
-                return false;
-            }
-
-            if (status == WGPUBufferMapAsyncStatus_Success) {
-                if (buffer->readHandle || buffer->writeHandle) {
-                    // Buffer is already mapped.
-                    return false;
-                }
-                if (initialDataInfoLength > std::numeric_limits<size_t>::max()) {
-                    // This is the size of data deserialized from the command stream, which must be
-                    // CPU-addressable.
-                    return false;
-                }
-                ASSERT(request.readHandle != nullptr);
-
-                // The server serializes metadata to initialize the contents of the ReadHandle.
-                // Deserialize the message and return a pointer and size of the mapped data for
-                // reading.
-                if (!request.readHandle->DeserializeInitialData(
-                        initialDataInfo, static_cast<size_t>(initialDataInfoLength), &mappedData,
-                        &mappedDataLength)) {
-                    // Deserialization shouldn't fail. This is a fatal error.
-                    return false;
-                }
-                ASSERT(mappedData != nullptr);
-
-                // The MapRead request was successful. The buffer now owns the ReadHandle until
-                // Unmap().
-                buffer->readHandle = std::move(request.readHandle);
-            }
-
-            return true;
-        };
-
-        if (!GetMappedData()) {
-            // Dawn promises that all callbacks are called in finite time. Even if a fatal error
-            // occurs, the callback is called.
-            request.readCallback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0, request.userdata);
-            return false;
-        } else {
-            request.readCallback(static_cast<WGPUBufferMapAsyncStatus>(status), mappedData,
-                                 static_cast<uint64_t>(mappedDataLength), request.userdata);
-            return true;
-        }
+        return buffer->OnMapReadAsyncCallback(requestSerial, status, initialDataInfoLength,
+                                              initialDataInfo);
     }
 
     bool Client::DoBufferMapWriteAsyncCallback(Buffer* buffer,
@@ -127,60 +68,7 @@
             return true;
         }
 
-        // The requests can have been deleted via an Unmap so this isn't an error.
-        auto requestIt = buffer->requests.find(requestSerial);
-        if (requestIt == buffer->requests.end()) {
-            return true;
-        }
-
-        auto request = std::move(requestIt->second);
-        // Delete the request before calling the callback otherwise the callback could be fired a
-        // second time. If, for example, buffer.Unmap() is called inside the callback.
-        buffer->requests.erase(requestIt);
-
-        void* mappedData = nullptr;
-        size_t mappedDataLength = 0;
-
-        auto GetMappedData = [&]() -> bool {
-            // It is an error for the server to call the write callback when we asked for a map read
-            if (request.readHandle) {
-                return false;
-            }
-
-            if (status == WGPUBufferMapAsyncStatus_Success) {
-                if (buffer->readHandle || buffer->writeHandle) {
-                    // Buffer is already mapped.
-                    return false;
-                }
-                ASSERT(request.writeHandle != nullptr);
-
-                // Open the WriteHandle. This returns a pointer and size of mapped memory.
-                // On failure, |mappedData| may be null.
-                std::tie(mappedData, mappedDataLength) = request.writeHandle->Open();
-
-                if (mappedData == nullptr) {
-                    return false;
-                }
-
-                // The MapWrite request was successful. The buffer now owns the WriteHandle until
-                // Unmap().
-                buffer->writeHandle = std::move(request.writeHandle);
-            }
-
-            return true;
-        };
-
-        if (!GetMappedData()) {
-            // Dawn promises that all callbacks are called in finite time. Even if a fatal error
-            // occurs, the callback is called.
-            request.writeCallback(WGPUBufferMapAsyncStatus_DeviceLost, nullptr, 0,
-                                  request.userdata);
-            return false;
-        } else {
-            request.writeCallback(static_cast<WGPUBufferMapAsyncStatus>(status), mappedData,
-                                  static_cast<uint64_t>(mappedDataLength), request.userdata);
-            return true;
-        }
+        return buffer->OnMapWriteAsyncCallback(requestSerial, status);
     }
 
     bool Client::DoFenceUpdateCompletedValue(Fence* fence, uint64_t value) {