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/BufferConsumer_impl.h" 18 #include "dawn_wire/WireCmd_autogen.h" 19 #include "dawn_wire/client/Client.h" 20 #include "dawn_wire/client/Device.h" 21 22 namespace dawn_wire { namespace client { 23 24 // static Create(Device * device,const WGPUBufferDescriptor * descriptor)25 WGPUBuffer Buffer::Create(Device* device, const WGPUBufferDescriptor* descriptor) { 26 Client* wireClient = device->client; 27 28 bool mappable = 29 (descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 || 30 descriptor->mappedAtCreation; 31 if (mappable && descriptor->size >= std::numeric_limits<size_t>::max()) { 32 device->InjectError(WGPUErrorType_OutOfMemory, "Buffer is too large for map usage"); 33 return device->CreateErrorBuffer(); 34 } 35 36 std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr; 37 std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr; 38 39 DeviceCreateBufferCmd cmd; 40 cmd.deviceId = device->id; 41 cmd.descriptor = descriptor; 42 cmd.readHandleCreateInfoLength = 0; 43 cmd.readHandleCreateInfo = nullptr; 44 cmd.writeHandleCreateInfoLength = 0; 45 cmd.writeHandleCreateInfo = nullptr; 46 47 if (mappable) { 48 if ((descriptor->usage & WGPUBufferUsage_MapRead) != 0) { 49 // Create the read handle on buffer creation. 50 readHandle.reset( 51 wireClient->GetMemoryTransferService()->CreateReadHandle(descriptor->size)); 52 if (readHandle == nullptr) { 53 device->InjectError(WGPUErrorType_OutOfMemory, 54 "Failed to create buffer mapping"); 55 return device->CreateErrorBuffer(); 56 } 57 cmd.readHandleCreateInfoLength = readHandle->SerializeCreateSize(); 58 } 59 60 if ((descriptor->usage & WGPUBufferUsage_MapWrite) != 0 || 61 descriptor->mappedAtCreation) { 62 // Create the write handle on buffer creation. 63 writeHandle.reset( 64 wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size)); 65 if (writeHandle == nullptr) { 66 device->InjectError(WGPUErrorType_OutOfMemory, 67 "Failed to create buffer mapping"); 68 return device->CreateErrorBuffer(); 69 } 70 cmd.writeHandleCreateInfoLength = writeHandle->SerializeCreateSize(); 71 } 72 } 73 74 // Create the buffer and send the creation command. 75 // This must happen after any potential device->CreateErrorBuffer() 76 // as server expects allocating ids to be monotonically increasing 77 auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(wireClient); 78 Buffer* buffer = bufferObjectAndSerial->object.get(); 79 buffer->mDevice = device; 80 buffer->mDeviceIsAlive = device->GetAliveWeakPtr(); 81 buffer->mSize = descriptor->size; 82 buffer->mDestructWriteHandleOnUnmap = false; 83 84 if (descriptor->mappedAtCreation) { 85 // If the buffer is mapped at creation, a write handle is created and will be 86 // destructed on unmap if the buffer doesn't have MapWrite usage 87 // The buffer is mapped right now. 88 buffer->mMapState = MapState::MappedAtCreation; 89 90 // This flag is for write handle created by mappedAtCreation 91 // instead of MapWrite usage. We don't have such a case for read handle 92 buffer->mDestructWriteHandleOnUnmap = 93 (descriptor->usage & WGPUBufferUsage_MapWrite) == 0; 94 95 buffer->mMapOffset = 0; 96 buffer->mMapSize = buffer->mSize; 97 ASSERT(writeHandle != nullptr); 98 buffer->mMappedData = writeHandle->GetData(); 99 } 100 101 cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation}; 102 103 wireClient->SerializeCommand( 104 cmd, cmd.readHandleCreateInfoLength + cmd.writeHandleCreateInfoLength, 105 [&](SerializeBuffer* serializeBuffer) { 106 if (readHandle != nullptr) { 107 char* readHandleBuffer; 108 WIRE_TRY( 109 serializeBuffer->NextN(cmd.readHandleCreateInfoLength, &readHandleBuffer)); 110 // Serialize the ReadHandle into the space after the command. 111 readHandle->SerializeCreate(readHandleBuffer); 112 buffer->mReadHandle = std::move(readHandle); 113 } 114 if (writeHandle != nullptr) { 115 char* writeHandleBuffer; 116 WIRE_TRY(serializeBuffer->NextN(cmd.writeHandleCreateInfoLength, 117 &writeHandleBuffer)); 118 // Serialize the WriteHandle into the space after the command. 119 writeHandle->SerializeCreate(writeHandleBuffer); 120 buffer->mWriteHandle = std::move(writeHandle); 121 } 122 123 return WireResult::Success; 124 }); 125 return ToAPI(buffer); 126 } 127 128 // static CreateError(Device * device)129 WGPUBuffer Buffer::CreateError(Device* device) { 130 auto* allocation = device->client->BufferAllocator().New(device->client); 131 allocation->object->mDevice = device; 132 allocation->object->mDeviceIsAlive = device->GetAliveWeakPtr(); 133 134 DeviceCreateErrorBufferCmd cmd; 135 cmd.self = ToAPI(device); 136 cmd.result = ObjectHandle{allocation->object->id, allocation->generation}; 137 device->client->SerializeCommand(cmd); 138 139 return ToAPI(allocation->object.get()); 140 } 141 ~Buffer()142 Buffer::~Buffer() { 143 ClearAllCallbacks(WGPUBufferMapAsyncStatus_DestroyedBeforeCallback); 144 FreeMappedData(); 145 } 146 CancelCallbacksForDisconnect()147 void Buffer::CancelCallbacksForDisconnect() { 148 ClearAllCallbacks(WGPUBufferMapAsyncStatus_DeviceLost); 149 } 150 ClearAllCallbacks(WGPUBufferMapAsyncStatus status)151 void Buffer::ClearAllCallbacks(WGPUBufferMapAsyncStatus status) { 152 mRequests.CloseAll([status](MapRequestData* request) { 153 if (request->callback != nullptr) { 154 request->callback(status, request->userdata); 155 } 156 }); 157 } 158 MapAsync(WGPUMapModeFlags mode,size_t offset,size_t size,WGPUBufferMapCallback callback,void * userdata)159 void Buffer::MapAsync(WGPUMapModeFlags mode, 160 size_t offset, 161 size_t size, 162 WGPUBufferMapCallback callback, 163 void* userdata) { 164 if (client->IsDisconnected()) { 165 return callback(WGPUBufferMapAsyncStatus_DeviceLost, userdata); 166 } 167 168 // Handle the defaulting of size required by WebGPU. 169 if ((size == WGPU_WHOLE_MAP_SIZE) && (offset <= mSize)) { 170 size = mSize - offset; 171 } 172 173 // Create the request structure that will hold information while this mapping is 174 // in flight. 175 MapRequestData request = {}; 176 request.callback = callback; 177 request.userdata = userdata; 178 request.offset = offset; 179 request.size = size; 180 if (mode & WGPUMapMode_Read) { 181 request.type = MapRequestType::Read; 182 } else if (mode & WGPUMapMode_Write) { 183 request.type = MapRequestType::Write; 184 } 185 186 uint64_t serial = mRequests.Add(std::move(request)); 187 188 // Serialize the command to send to the server. 189 BufferMapAsyncCmd cmd; 190 cmd.bufferId = this->id; 191 cmd.requestSerial = serial; 192 cmd.mode = mode; 193 cmd.offset = offset; 194 cmd.size = size; 195 196 client->SerializeCommand(cmd); 197 } 198 OnMapAsyncCallback(uint64_t requestSerial,uint32_t status,uint64_t readDataUpdateInfoLength,const uint8_t * readDataUpdateInfo)199 bool Buffer::OnMapAsyncCallback(uint64_t requestSerial, 200 uint32_t status, 201 uint64_t readDataUpdateInfoLength, 202 const uint8_t* readDataUpdateInfo) { 203 MapRequestData request; 204 if (!mRequests.Acquire(requestSerial, &request)) { 205 return false; 206 } 207 208 auto FailRequest = [&request]() -> bool { 209 if (request.callback != nullptr) { 210 request.callback(WGPUBufferMapAsyncStatus_DeviceLost, request.userdata); 211 } 212 return false; 213 }; 214 215 // Take into account the client-side status of the request if the server says it is a success. 216 if (status == WGPUBufferMapAsyncStatus_Success) { 217 status = request.clientStatus; 218 } 219 220 if (status == WGPUBufferMapAsyncStatus_Success) { 221 switch (request.type) { 222 case MapRequestType::Read: { 223 if (readDataUpdateInfoLength > std::numeric_limits<size_t>::max()) { 224 // This is the size of data deserialized from the command stream, which must 225 // be CPU-addressable. 226 return FailRequest(); 227 } 228 229 // Validate to prevent bad map request; buffer destroyed during map request 230 if (mReadHandle == nullptr) { 231 return FailRequest(); 232 } 233 // Update user map data with server returned data 234 if (!mReadHandle->DeserializeDataUpdate( 235 readDataUpdateInfo, static_cast<size_t>(readDataUpdateInfoLength), 236 request.offset, request.size)) { 237 return FailRequest(); 238 } 239 mMapState = MapState::MappedForRead; 240 mMappedData = const_cast<void*>(mReadHandle->GetData()); 241 break; 242 } 243 case MapRequestType::Write: { 244 if (mWriteHandle == nullptr) { 245 return FailRequest(); 246 } 247 mMapState = MapState::MappedForWrite; 248 mMappedData = mWriteHandle->GetData(); 249 break; 250 } 251 default: 252 UNREACHABLE(); 253 } 254 255 mMapOffset = request.offset; 256 mMapSize = request.size; 257 } 258 259 if (request.callback) { 260 request.callback(static_cast<WGPUBufferMapAsyncStatus>(status), request.userdata); 261 } 262 263 return true; 264 } 265 GetMappedRange(size_t offset,size_t size)266 void* Buffer::GetMappedRange(size_t offset, size_t size) { 267 if (!IsMappedForWriting() || !CheckGetMappedRangeOffsetSize(offset, size)) { 268 return nullptr; 269 } 270 return static_cast<uint8_t*>(mMappedData) + offset; 271 } 272 GetConstMappedRange(size_t offset,size_t size)273 const void* Buffer::GetConstMappedRange(size_t offset, size_t size) { 274 if (!(IsMappedForWriting() || IsMappedForReading()) || 275 !CheckGetMappedRangeOffsetSize(offset, size)) { 276 return nullptr; 277 } 278 return static_cast<uint8_t*>(mMappedData) + offset; 279 } 280 Unmap()281 void Buffer::Unmap() { 282 // Invalidate the local pointer, and cancel all other in-flight requests that would 283 // turn into errors anyway (you can't double map). This prevents race when the following 284 // happens, where the application code would have unmapped a buffer but still receive a 285 // callback: 286 // - Client -> Server: MapRequest1, Unmap, MapRequest2 287 // - Server -> Client: Result of MapRequest1 288 // - Unmap locally on the client 289 // - Server -> Client: Result of MapRequest2 290 291 // mWriteHandle can still be nullptr if buffer has been destroyed before unmap 292 if ((mMapState == MapState::MappedForWrite || mMapState == MapState::MappedAtCreation) && 293 mWriteHandle != nullptr) { 294 // Writes need to be flushed before Unmap is sent. Unmap calls all associated 295 // in-flight callbacks which may read the updated data. 296 297 // Get the serialization size of data update writes. 298 size_t writeDataUpdateInfoLength = 299 mWriteHandle->SizeOfSerializeDataUpdate(mMapOffset, mMapSize); 300 301 BufferUpdateMappedDataCmd cmd; 302 cmd.bufferId = id; 303 cmd.writeDataUpdateInfoLength = writeDataUpdateInfoLength; 304 cmd.writeDataUpdateInfo = nullptr; 305 cmd.offset = mMapOffset; 306 cmd.size = mMapSize; 307 308 client->SerializeCommand( 309 cmd, writeDataUpdateInfoLength, [&](SerializeBuffer* serializeBuffer) { 310 char* writeHandleBuffer; 311 WIRE_TRY(serializeBuffer->NextN(writeDataUpdateInfoLength, &writeHandleBuffer)); 312 313 // Serialize flush metadata into the space after the command. 314 // This closes the handle for writing. 315 mWriteHandle->SerializeDataUpdate(writeHandleBuffer, cmd.offset, cmd.size); 316 317 return WireResult::Success; 318 }); 319 320 // If mDestructWriteHandleOnUnmap is true, that means the write handle is merely 321 // for mappedAtCreation usage. It is destroyed on unmap after flush to server 322 // instead of at buffer destruction. 323 if (mMapState == MapState::MappedAtCreation && mDestructWriteHandleOnUnmap) { 324 mWriteHandle = nullptr; 325 if (mReadHandle) { 326 // If it's both mappedAtCreation and MapRead we need to reset 327 // mMappedData to readHandle's GetData(). This could be changed to 328 // merging read/write handle in future 329 mMappedData = const_cast<void*>(mReadHandle->GetData()); 330 } 331 } 332 } 333 334 // Free map access tokens 335 mMapState = MapState::Unmapped; 336 mMapOffset = 0; 337 mMapSize = 0; 338 339 // Tag all mapping requests still in flight as unmapped before callback. 340 mRequests.ForAll([](MapRequestData* request) { 341 if (request->clientStatus == WGPUBufferMapAsyncStatus_Success) { 342 request->clientStatus = WGPUBufferMapAsyncStatus_UnmappedBeforeCallback; 343 } 344 }); 345 346 BufferUnmapCmd cmd; 347 cmd.self = ToAPI(this); 348 client->SerializeCommand(cmd); 349 } 350 Destroy()351 void Buffer::Destroy() { 352 // Remove the current mapping and destroy Read/WriteHandles. 353 FreeMappedData(); 354 355 // Tag all mapping requests still in flight as destroyed before callback. 356 mRequests.ForAll([](MapRequestData* request) { 357 if (request->clientStatus == WGPUBufferMapAsyncStatus_Success) { 358 request->clientStatus = WGPUBufferMapAsyncStatus_DestroyedBeforeCallback; 359 } 360 }); 361 362 BufferDestroyCmd cmd; 363 cmd.self = ToAPI(this); 364 client->SerializeCommand(cmd); 365 } 366 IsMappedForReading() const367 bool Buffer::IsMappedForReading() const { 368 return mMapState == MapState::MappedForRead; 369 } 370 IsMappedForWriting() const371 bool Buffer::IsMappedForWriting() const { 372 return mMapState == MapState::MappedForWrite || mMapState == MapState::MappedAtCreation; 373 } 374 CheckGetMappedRangeOffsetSize(size_t offset,size_t size) const375 bool Buffer::CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const { 376 if (offset % 8 != 0 || size % 4 != 0) { 377 return false; 378 } 379 380 if (size > mMapSize || offset < mMapOffset) { 381 return false; 382 } 383 384 size_t offsetInMappedRange = offset - mMapOffset; 385 return offsetInMappedRange <= mMapSize - size; 386 } 387 FreeMappedData()388 void Buffer::FreeMappedData() { 389 #if defined(DAWN_ENABLE_ASSERTS) 390 // When in "debug" mode, 0xCA-out the mapped data when we free it so that in we can detect 391 // use-after-free of the mapped data. This is particularly useful for WebGPU test about the 392 // interaction of mapping and GC. 393 if (mMappedData) { 394 memset(static_cast<uint8_t*>(mMappedData) + mMapOffset, 0xCA, mMapSize); 395 } 396 #endif // defined(DAWN_ENABLE_ASSERTS) 397 398 mMapOffset = 0; 399 mMapSize = 0; 400 mReadHandle = nullptr; 401 mWriteHandle = nullptr; 402 mMappedData = nullptr; 403 } 404 405 }} // namespace dawn_wire::client 406