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