Add Buffer::MapReadAsync state-tracking
diff --git a/src/backend/common/Buffer.cpp b/src/backend/common/Buffer.cpp
index b1240f2..8821e5c 100644
--- a/src/backend/common/Buffer.cpp
+++ b/src/backend/common/Buffer.cpp
@@ -30,6 +30,12 @@
           currentUsage(builder->currentUsage) {
     }
 
+    BufferBase::~BufferBase() {
+        if (mapped) {
+            CallMapReadCallback(mapReadSerial, NXT_BUFFER_MAP_READ_STATUS_UNKNOWN, nullptr);
+        }
+    }
+
     BufferViewBuilder* BufferBase::CreateBufferViewBuilder() {
         return new BufferViewBuilder(device, this);
     }
@@ -50,6 +56,13 @@
         return currentUsage;
     }
 
+    void BufferBase::CallMapReadCallback(uint32_t serial, nxtBufferMapReadStatus status, const void* pointer) {
+        if (mapReadCallback && serial == mapReadSerial) {
+            mapReadCallback(status, pointer, mapReadUserdata);
+            mapReadCallback = nullptr;
+        }
+    }
+
     void BufferBase::SetSubData(uint32_t start, uint32_t count, const uint32_t* data) {
         if ((start + count) * sizeof(uint32_t) > GetSize()) {
             device->HandleError("Buffer subdata out of range");
@@ -64,6 +77,46 @@
         SetSubDataImpl(start, count, data);
     }
 
+    void BufferBase::MapReadAsync(uint32_t start, uint32_t size, nxtBufferMapReadCallback callback, nxtCallbackUserdata userdata) {
+        if (start + size > GetSize()) {
+            device->HandleError("Buffer map read out of range");
+            callback(NXT_BUFFER_MAP_READ_STATUS_ERROR, nullptr, userdata);
+            return;
+        }
+
+        if (!(currentUsage & nxt::BufferUsageBit::MapRead)) {
+            device->HandleError("Buffer needs the map read usage bit");
+            callback(NXT_BUFFER_MAP_READ_STATUS_ERROR, nullptr, userdata);
+            return;
+        }
+
+        if (mapped) {
+            device->HandleError("Buffer already mapped");
+            callback(NXT_BUFFER_MAP_READ_STATUS_ERROR, nullptr, userdata);
+            return;
+        }
+
+        // TODO(cwallez@chromium.org): what to do on wraparound? Could cause crashes.
+        mapReadSerial ++;
+        mapReadCallback = callback;
+        mapReadUserdata = userdata;
+        MapReadAsyncImpl(mapReadSerial, start, size);
+        mapped = true;
+    }
+
+    void BufferBase::Unmap() {
+        if (!mapped) {
+            device->HandleError("Buffer wasn't mapped");
+            return;
+        }
+
+        // A map request can only be called once, so this will fire only if the request wasn't
+        // completed before the Unmap
+        CallMapReadCallback(mapReadSerial, NXT_BUFFER_MAP_READ_STATUS_UNKNOWN, nullptr);
+        UnmapImpl();
+        mapped = false;
+    }
+
     bool BufferBase::IsFrozen() const {
         return frozen;
     }
@@ -86,7 +139,7 @@
     }
 
     bool BufferBase::IsTransitionPossible(nxt::BufferUsageBit usage) const {
-        if (frozen) {
+        if (frozen || mapped) {
             return false;
         }
         return IsUsagePossible(allowedUsage, usage);
@@ -138,6 +191,7 @@
             return nullptr;
         }
 
+        // TODO(cwallez@chromium.org) disallow using MapRead with anything else than TransferDst
         return device->CreateBuffer(this);
     }
 
diff --git a/src/backend/common/Buffer.h b/src/backend/common/Buffer.h
index 14f8ac8..b195ca2 100644
--- a/src/backend/common/Buffer.h
+++ b/src/backend/common/Buffer.h
@@ -26,6 +26,7 @@
     class BufferBase : public RefCounted {
         public:
             BufferBase(BufferBuilder* builder);
+            ~BufferBase();
 
             uint32_t GetSize() const;
             nxt::BufferUsageBit GetAllowedUsage() const;
@@ -41,17 +42,30 @@
             // NXT API
             BufferViewBuilder* CreateBufferViewBuilder();
             void SetSubData(uint32_t start, uint32_t count, const uint32_t* data);
+            void MapReadAsync(uint32_t start, uint32_t size, nxtBufferMapReadCallback callback, nxtCallbackUserdata userdata);
+            void Unmap();
             void TransitionUsage(nxt::BufferUsageBit usage);
             void FreezeUsage(nxt::BufferUsageBit usage);
 
+        protected:
+            void CallMapReadCallback(uint32_t serial, nxtBufferMapReadStatus status, const void* pointer);
+
         private:
             virtual void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) = 0;
+            virtual void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t size) = 0;
+            virtual void UnmapImpl() = 0;
 
             DeviceBase* device;
             uint32_t size;
             nxt::BufferUsageBit allowedUsage = nxt::BufferUsageBit::None;
             nxt::BufferUsageBit currentUsage = nxt::BufferUsageBit::None;
+
+            nxtBufferMapReadCallback mapReadCallback = nullptr;
+            nxtCallbackUserdata mapReadUserdata = 0;
+            uint32_t mapReadSerial = 0;
+
             bool frozen = false;
+            bool mapped = false;
     };
 
     class BufferBuilder : public Builder<BufferBase> {
diff --git a/src/backend/d3d12/D3D12Backend.cpp b/src/backend/d3d12/D3D12Backend.cpp
index 033ff92..ab37162 100644
--- a/src/backend/d3d12/D3D12Backend.cpp
+++ b/src/backend/d3d12/D3D12Backend.cpp
@@ -148,6 +148,14 @@
     void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) {
     }
 
+    void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
+        // TODO(cwallez@chromium.org): Implement Map Read for the null backend
+    }
+
+    void Buffer::UnmapImpl() {
+        // TODO(cwallez@chromium.org): Implement Map Read for the null backend
+    }
+
     // BufferView
 
     BufferView::BufferView(Device* device, BufferViewBuilder* builder)
diff --git a/src/backend/d3d12/D3D12Backend.h b/src/backend/d3d12/D3D12Backend.h
index d6375b5..9db94ba 100644
--- a/src/backend/d3d12/D3D12Backend.h
+++ b/src/backend/d3d12/D3D12Backend.h
@@ -141,6 +141,8 @@
 
         private:
             void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
+            void UnmapImpl() override;
 
             Device* device;
     };
diff --git a/src/backend/metal/MetalBackend.h b/src/backend/metal/MetalBackend.h
index 873f116..2dbcec0 100644
--- a/src/backend/metal/MetalBackend.h
+++ b/src/backend/metal/MetalBackend.h
@@ -158,6 +158,8 @@
 
         private:
             void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
+            void UnmapImpl() override;
 
             Device* device;
             std::mutex mutex;
diff --git a/src/backend/metal/MetalBackend.mm b/src/backend/metal/MetalBackend.mm
index 4922dcc..317f1a9 100644
--- a/src/backend/metal/MetalBackend.mm
+++ b/src/backend/metal/MetalBackend.mm
@@ -225,6 +225,14 @@
         [mtlBuffer didModifyRange:NSMakeRange(start * sizeof(uint32_t), count * sizeof(uint32_t))];
     }
 
+    void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
+        // TODO(cwallez@chromium.org): Implement Map Read for the metal backend
+    }
+
+    void Buffer::UnmapImpl() {
+        // TODO(cwallez@chromium.org): Implement Map Read for the metal backend
+    }
+
     // BufferView
 
     BufferView::BufferView(Device* device, BufferViewBuilder* builder)
diff --git a/src/backend/null/NullBackend.cpp b/src/backend/null/NullBackend.cpp
index c710cfe..1f506ac 100644
--- a/src/backend/null/NullBackend.cpp
+++ b/src/backend/null/NullBackend.cpp
@@ -107,6 +107,14 @@
     void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) {
     }
 
+    void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
+        // TODO(cwallez@chromium.org): Implement Map Read for the null backend
+    }
+
+    void Buffer::UnmapImpl() {
+        // TODO(cwallez@chromium.org): Implement Map Read for the null backend
+    }
+
     // Queue
 
     Queue::Queue(QueueBuilder* builder)
diff --git a/src/backend/null/NullBackend.h b/src/backend/null/NullBackend.h
index b412cf8..4cb0a86 100644
--- a/src/backend/null/NullBackend.h
+++ b/src/backend/null/NullBackend.h
@@ -112,6 +112,8 @@
 
         private:
             void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
+            void UnmapImpl() override;
     };
 
     class Queue : public QueueBase {
diff --git a/src/backend/opengl/OpenGLBackend.cpp b/src/backend/opengl/OpenGLBackend.cpp
index 348d03d..7935f9a 100644
--- a/src/backend/opengl/OpenGLBackend.cpp
+++ b/src/backend/opengl/OpenGLBackend.cpp
@@ -133,6 +133,14 @@
         glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(uint32_t), count * sizeof(uint32_t), data);
     }
 
+    void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
+        // TODO(cwallez@chromium.org): Implement Map Read for the GL backend
+    }
+
+    void Buffer::UnmapImpl() {
+        // TODO(cwallez@chromium.org): Implement Map Read for the GL backend
+    }
+
     // BufferView
 
     BufferView::BufferView(Device* device, BufferViewBuilder* builder)
diff --git a/src/backend/opengl/OpenGLBackend.h b/src/backend/opengl/OpenGLBackend.h
index c1d0659..d7765d8 100644
--- a/src/backend/opengl/OpenGLBackend.h
+++ b/src/backend/opengl/OpenGLBackend.h
@@ -124,6 +124,8 @@
 
         private:
             void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
+            void UnmapImpl() override;
 
             Device* device;
             GLuint buffer = 0;