1 // Copyright 2019 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "dawn_wire/client/Buffer.h"
16 
17 #include "dawn_wire/client/Client.h"
18 #include "dawn_wire/client/Device.h"
19 
20 namespace dawn_wire { namespace client {
21 
22     // static
Create(Device * device_,const WGPUBufferDescriptor * descriptor)23     WGPUBuffer Buffer::Create(Device* device_, const WGPUBufferDescriptor* descriptor) {
24         Client* wireClient = device_->GetClient();
25 
26         bool mappable =
27             (descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 ||
28             descriptor->mappedAtCreation;
29         if (mappable && descriptor->size >= std::numeric_limits<size_t>::max()) {
30             device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer is too large for map usage");
31             return device_->CreateErrorBuffer();
32         }
33 
34         std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
35         void* writeData = nullptr;
36         size_t writeHandleCreateInfoLength = 0;
37 
38         // If the buffer is mapped at creation, create a write handle that will represent the
39         // mapping of the whole buffer.
40         if (descriptor->mappedAtCreation) {
41             // Create the handle.
42             writeHandle.reset(
43                 wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size));
44             if (writeHandle == nullptr) {
45                 device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer mapping allocation failed");
46                 return device_->CreateErrorBuffer();
47             }
48 
49             // Open the handle, it may fail by returning a nullptr in writeData.
50             size_t writeDataLength = 0;
51             std::tie(writeData, writeDataLength) = writeHandle->Open();
52             if (writeData == nullptr) {
53                 device_->InjectError(WGPUErrorType_OutOfMemory, "Buffer mapping allocation failed");
54                 return device_->CreateErrorBuffer();
55             }
56             ASSERT(writeDataLength == descriptor->size);
57 
58             // Get the serialization size of the write handle.
59             writeHandleCreateInfoLength = writeHandle->SerializeCreateSize();
60         }
61 
62         // Create the buffer and send the creation command.
63         auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(device_);
64         Buffer* buffer = bufferObjectAndSerial->object.get();
65         buffer->mSize = descriptor->size;
66 
67         DeviceCreateBufferCmd cmd;
68         cmd.device = ToAPI(device_);
69         cmd.descriptor = descriptor;
70         cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
71         cmd.handleCreateInfoLength = writeHandleCreateInfoLength;
72         cmd.handleCreateInfo = nullptr;
73 
74         wireClient->SerializeCommand(cmd, writeHandleCreateInfoLength, [&](char* cmdSpace) {
75             if (descriptor->mappedAtCreation) {
76                 // Serialize the WriteHandle into the space after the command.
77                 writeHandle->SerializeCreate(cmdSpace);
78 
79                 // Set the buffer state for the mapping at creation. The buffer now owns the write
80                 // handle..
81                 buffer->mWriteHandle = std::move(writeHandle);
82                 buffer->mMappedData = writeData;
83                 buffer->mMapOffset = 0;
84                 buffer->mMapSize = buffer->mSize;
85             }
86         });
87         return ToAPI(buffer);
88     }
89 
90     // static
CreateError(Device * device_)91     WGPUBuffer Buffer::CreateError(Device* device_) {
92         auto* allocation = device_->GetClient()->BufferAllocator().New(device_);
93 
94         DeviceCreateErrorBufferCmd cmd;
95         cmd.self = ToAPI(device_);
96         cmd.result = ObjectHandle{allocation->object->id, allocation->generation};
97         device_->GetClient()->SerializeCommand(cmd);
98 
99         return ToAPI(allocation->object.get());
100     }
101 
~Buffer()102     Buffer::~Buffer() {
103         // Callbacks need to be fired in all cases, as they can handle freeing resources
104         // so we call them with "DestroyedBeforeCallback" status.
105         for (auto& it : mRequests) {
106             if (it.second.callback) {
107                 it.second.callback(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback, it.second.userdata);
108             }
109         }
110         mRequests.clear();
111     }
112 
CancelCallbacksForDisconnect()113     void Buffer::CancelCallbacksForDisconnect() {
114         for (auto& it : mRequests) {
115             if (it.second.callback) {
116                 it.second.callback(WGPUBufferMapAsyncStatus_DeviceLost, it.second.userdata);
117             }
118         }
119         mRequests.clear();
120     }
121 
MapAsync(WGPUMapModeFlags mode,size_t offset,size_t size,WGPUBufferMapCallback callback,void * userdata)122     void Buffer::MapAsync(WGPUMapModeFlags mode,
123                           size_t offset,
124                           size_t size,
125                           WGPUBufferMapCallback callback,
126                           void* userdata) {
127         if (device->GetClient()->IsDisconnected()) {
128             return callback(WGPUBufferMapAsyncStatus_DeviceLost, userdata);
129         }
130 
131         // Handle the defaulting of size required by WebGPU.
132         if (size == 0 && offset < mSize) {
133             size = mSize - offset;
134         }
135 
136         bool isReadMode = mode & WGPUMapMode_Read;
137         bool isWriteMode = mode & WGPUMapMode_Write;
138 
139         // Step 1. Do early validation of READ ^ WRITE because the server rejects mode = 0.
140         if (!(isReadMode ^ isWriteMode)) {
141             device->InjectError(WGPUErrorType_Validation, "MapAsync error (you figure out :P)");
142             if (callback != nullptr) {
143                 callback(WGPUBufferMapAsyncStatus_Error, userdata);
144             }
145             return;
146         }
147 
148         // Step 2. Create the request structure that will hold information while this mapping is
149         // in flight.
150         uint32_t serial = mRequestSerial++;
151         ASSERT(mRequests.find(serial) == mRequests.end());
152 
153         Buffer::MapRequestData request = {};
154         request.callback = callback;
155         request.userdata = userdata;
156         request.size = size;
157         request.offset = offset;
158 
159         // Step 2a: Create the read / write handles for this request.
160         if (isReadMode) {
161             request.readHandle.reset(
162                 device->GetClient()->GetMemoryTransferService()->CreateReadHandle(size));
163             if (request.readHandle == nullptr) {
164                 device->InjectError(WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
165                 callback(WGPUBufferMapAsyncStatus_Error, userdata);
166                 return;
167             }
168         } else {
169             ASSERT(isWriteMode);
170             request.writeHandle.reset(
171                 device->GetClient()->GetMemoryTransferService()->CreateWriteHandle(size));
172             if (request.writeHandle == nullptr) {
173                 device->InjectError(WGPUErrorType_OutOfMemory, "Failed to create buffer mapping");
174                 callback(WGPUBufferMapAsyncStatus_Error, userdata);
175                 return;
176             }
177         }
178 
179         // Step 3. Serialize the command to send to the server.
180         BufferMapAsyncCmd cmd;
181         cmd.bufferId = this->id;
182         cmd.requestSerial = serial;
183         cmd.mode = mode;
184         cmd.offset = offset;
185         cmd.size = size;
186         cmd.handleCreateInfo = nullptr;
187 
188         // Step 3a. Fill the handle create info in the command.
189         if (isReadMode) {
190             cmd.handleCreateInfoLength = request.readHandle->SerializeCreateSize();
191             device->GetClient()->SerializeCommand(
192                 cmd, cmd.handleCreateInfoLength,
193                 [&](char* cmdSpace) { request.readHandle->SerializeCreate(cmdSpace); });
194         } else {
195             ASSERT(isWriteMode);
196             cmd.handleCreateInfoLength = request.writeHandle->SerializeCreateSize();
197             device->GetClient()->SerializeCommand(
198                 cmd, cmd.handleCreateInfoLength,
199                 [&](char* cmdSpace) { request.writeHandle->SerializeCreate(cmdSpace); });
200         }
201 
202         // Step 4. Register this request so that we can retrieve it from its serial when the server
203         // sends the callback.
204         mRequests[serial] = std::move(request);
205     }
206 
OnMapAsyncCallback(uint32_t requestSerial,uint32_t status,uint64_t readInitialDataInfoLength,const uint8_t * readInitialDataInfo)207     bool Buffer::OnMapAsyncCallback(uint32_t requestSerial,
208                                     uint32_t status,
209                                     uint64_t readInitialDataInfoLength,
210                                     const uint8_t* readInitialDataInfo) {
211         auto requestIt = mRequests.find(requestSerial);
212         if (requestIt == mRequests.end()) {
213             return false;
214         }
215 
216         auto request = std::move(requestIt->second);
217         // Delete the request before calling the callback otherwise the callback could be fired a
218         // second time. If, for example, buffer.Unmap() is called inside the callback.
219         mRequests.erase(requestIt);
220 
221         auto FailRequest = [&request]() -> bool {
222             if (request.callback != nullptr) {
223                 request.callback(WGPUBufferMapAsyncStatus_DeviceLost, request.userdata);
224             }
225             return false;
226         };
227 
228         bool isRead = request.readHandle != nullptr;
229         bool isWrite = request.writeHandle != nullptr;
230         ASSERT(isRead != isWrite);
231 
232         // Take into account the client-side status of the request if the server says it is a success.
233         if (status == WGPUBufferMapAsyncStatus_Success) {
234             status = request.clientStatus;
235         }
236 
237         size_t mappedDataLength = 0;
238         const void* mappedData = nullptr;
239         if (status == WGPUBufferMapAsyncStatus_Success) {
240             if (mReadHandle || mWriteHandle) {
241                 // Buffer is already mapped.
242                 return FailRequest();
243             }
244 
245             if (isRead) {
246                 if (readInitialDataInfoLength > std::numeric_limits<size_t>::max()) {
247                     // This is the size of data deserialized from the command stream, which must be
248                     // CPU-addressable.
249                     return FailRequest();
250                 }
251 
252                 // The server serializes metadata to initialize the contents of the ReadHandle.
253                 // Deserialize the message and return a pointer and size of the mapped data for
254                 // reading.
255                 if (!request.readHandle->DeserializeInitialData(
256                         readInitialDataInfo, static_cast<size_t>(readInitialDataInfoLength),
257                         &mappedData, &mappedDataLength)) {
258                     // Deserialization shouldn't fail. This is a fatal error.
259                     return FailRequest();
260                 }
261                 ASSERT(mappedData != nullptr);
262 
263             } else {
264                 // Open the WriteHandle. This returns a pointer and size of mapped memory.
265                 // On failure, |mappedData| may be null.
266                 std::tie(mappedData, mappedDataLength) = request.writeHandle->Open();
267 
268                 if (mappedData == nullptr) {
269                     return FailRequest();
270                 }
271             }
272 
273             // The MapAsync request was successful. The buffer now owns the Read/Write handles
274             // until Unmap().
275             mReadHandle = std::move(request.readHandle);
276             mWriteHandle = std::move(request.writeHandle);
277         }
278 
279         mMapOffset = request.offset;
280         mMapSize = request.size;
281         mMappedData = const_cast<void*>(mappedData);
282         if (request.callback) {
283             request.callback(static_cast<WGPUBufferMapAsyncStatus>(status), request.userdata);
284         }
285 
286         return true;
287     }
288 
GetMappedRange(size_t offset,size_t size)289     void* Buffer::GetMappedRange(size_t offset, size_t size) {
290         if (!IsMappedForWriting() || !CheckGetMappedRangeOffsetSize(offset, size)) {
291             return nullptr;
292         }
293         return static_cast<uint8_t*>(mMappedData) + (offset - mMapOffset);
294     }
295 
GetConstMappedRange(size_t offset,size_t size)296     const void* Buffer::GetConstMappedRange(size_t offset, size_t size) {
297         if (!(IsMappedForWriting() || IsMappedForReading()) ||
298             !CheckGetMappedRangeOffsetSize(offset, size)) {
299             return nullptr;
300         }
301         return static_cast<uint8_t*>(mMappedData) + (offset - mMapOffset);
302     }
303 
Unmap()304     void Buffer::Unmap() {
305         // Invalidate the local pointer, and cancel all other in-flight requests that would
306         // turn into errors anyway (you can't double map). This prevents race when the following
307         // happens, where the application code would have unmapped a buffer but still receive a
308         // callback:
309         //   - Client -> Server: MapRequest1, Unmap, MapRequest2
310         //   - Server -> Client: Result of MapRequest1
311         //   - Unmap locally on the client
312         //   - Server -> Client: Result of MapRequest2
313         if (mWriteHandle) {
314             // Writes need to be flushed before Unmap is sent. Unmap calls all associated
315             // in-flight callbacks which may read the updated data.
316             ASSERT(mReadHandle == nullptr);
317 
318             // Get the serialization size of metadata to flush writes.
319             size_t writeFlushInfoLength = mWriteHandle->SerializeFlushSize();
320 
321             BufferUpdateMappedDataCmd cmd;
322             cmd.bufferId = id;
323             cmd.writeFlushInfoLength = writeFlushInfoLength;
324             cmd.writeFlushInfo = nullptr;
325 
326             device->GetClient()->SerializeCommand(cmd, writeFlushInfoLength, [&](char* cmdSpace) {
327                 // Serialize flush metadata into the space after the command.
328                 // This closes the handle for writing.
329                 mWriteHandle->SerializeFlush(cmdSpace);
330             });
331             mWriteHandle = nullptr;
332 
333         } else if (mReadHandle) {
334             mReadHandle = nullptr;
335         }
336 
337         mMappedData = nullptr;
338         mMapOffset = 0;
339         mMapSize = 0;
340 
341         // Tag all mapping requests still in flight as unmapped before callback.
342         for (auto& it : mRequests) {
343             if (it.second.clientStatus == WGPUBufferMapAsyncStatus_Success) {
344                 it.second.clientStatus = WGPUBufferMapAsyncStatus_UnmappedBeforeCallback;
345             }
346         }
347 
348         BufferUnmapCmd cmd;
349         cmd.self = ToAPI(this);
350         device->GetClient()->SerializeCommand(cmd);
351     }
352 
Destroy()353     void Buffer::Destroy() {
354         // Remove the current mapping.
355         mWriteHandle = nullptr;
356         mReadHandle = nullptr;
357         mMappedData = nullptr;
358 
359         // Tag all mapping requests still in flight as destroyed before callback.
360         for (auto& it : mRequests) {
361             if (it.second.clientStatus == WGPUBufferMapAsyncStatus_Success) {
362                 it.second.clientStatus = WGPUBufferMapAsyncStatus_DestroyedBeforeCallback;
363             }
364         }
365 
366         BufferDestroyCmd cmd;
367         cmd.self = ToAPI(this);
368         device->GetClient()->SerializeCommand(cmd);
369     }
370 
IsMappedForReading() const371     bool Buffer::IsMappedForReading() const {
372         return mReadHandle != nullptr;
373     }
374 
IsMappedForWriting() const375     bool Buffer::IsMappedForWriting() const {
376         return mWriteHandle != nullptr;
377     }
378 
CheckGetMappedRangeOffsetSize(size_t offset,size_t size) const379     bool Buffer::CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const {
380         if (offset % 8 != 0 || size % 4 != 0) {
381             return false;
382         }
383 
384         if (size > mMapSize || offset < mMapOffset) {
385             return false;
386         }
387 
388         size_t offsetInMappedRange = offset - mMapOffset;
389         return offsetInMappedRange <= mMapSize - size;
390     }
391 }}  // namespace dawn_wire::client
392