From bae16b4df9d7383834745a5d31f8d4b0d745a455 Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Tue, 30 Jun 2020 09:48:14 +0000 Subject: [PATCH] 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 Reviewed-by: Austin Eng Commit-Queue: Corentin Wallez --- src/dawn_wire/client/ApiProcs.cpp | 273 ++----------------- src/dawn_wire/client/Buffer.cpp | 393 ++++++++++++++++++++++++++- src/dawn_wire/client/Buffer.h | 31 ++- src/dawn_wire/client/ClientDoers.cpp | 118 +------- 4 files changed, 438 insertions(+), 377 deletions(-) diff --git a/src/dawn_wire/client/ApiProcs.cpp b/src/dawn_wire/client/ApiProcs.cpp index 358e717525..da121719de 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 - 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::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(cBuffer); - - uint32_t serial = buffer->requestSerial++; - ASSERT(buffer->requests.find(serial) == buffer->requests.end()); - - if (buffer->size > std::numeric_limits::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(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(buffer->size)); - if (readHandle == nullptr) { - ClientDeviceInjectError(reinterpret_cast(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(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(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(cBuffer); + buffer->SetSubData(start, count, data); + } - if (buffer->size > std::numeric_limits::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(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(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(buffer->size)); - if (writeHandle == nullptr) { - ClientDeviceInjectError(reinterpret_cast(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(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(cBuffer); + buffer->Destroy(); } WGPUBuffer ClientHandwrittenDeviceCreateBuffer(WGPUDevice cDevice, const WGPUBufferDescriptor* descriptor) { Device* device = reinterpret_cast(cDevice); - Client* wireClient = device->GetClient(); - - if ((descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 && - descriptor->size > std::numeric_limits::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(buffer); + return Buffer::Create(device, descriptor); } WGPUCreateBufferMappedResult ClientHandwrittenDeviceCreateBufferMapped( WGPUDevice cDevice, const WGPUBufferDescriptor* descriptor) { Device* device = reinterpret_cast(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::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 writeHandle = - std::unique_ptr( - 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(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 @@ namespace dawn_wire { namespace client { fence->requests.Enqueue(std::move(request), value); } - void ClientHandwrittenBufferSetSubData(WGPUBuffer cBuffer, - uint64_t start, - uint64_t count, - const void* data) { - Buffer* buffer = reinterpret_cast(cBuffer); - - BufferSetSubDataInternalCmd cmd; - cmd.bufferId = buffer->id; - cmd.start = start; - cmd.count = count; - cmd.data = static_cast(data); - - buffer->device->GetClient()->SerializeCommand(cmd); - } - - void ClientHandwrittenBufferUnmap(WGPUBuffer cBuffer) { - Buffer* buffer = reinterpret_cast(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(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(cSelf); diff --git a/src/dawn_wire/client/Buffer.cpp b/src/dawn_wire/client/Buffer.cpp index 3548f5133f..a4f5fad4a8 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 + 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::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(device_); + Client* wireClient = device_->GetClient(); + + if ((descriptor->usage & (WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite)) != 0 && + descriptor->size > std::numeric_limits::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(buffer); + } + + // static + WGPUCreateBufferMappedResult Buffer::CreateMapped(Device* device_, + const WGPUBufferDescriptor* descriptor) { + WGPUDevice cDevice = reinterpret_cast(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::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 writeHandle = + std::unique_ptr( + 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(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 @@ namespace dawn_wire { namespace client { } 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::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(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(mSize)); + if (readHandle == nullptr) { + ClientDeviceInjectError(reinterpret_cast(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(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::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(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(mSize)); + if (writeHandle == nullptr) { + ClientDeviceInjectError(reinterpret_cast(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(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::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(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(status), mappedData, + static_cast(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(status), mappedData, + static_cast(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(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(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(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 09da8e3497..96cabf6f66 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 @@ namespace dawn_wire { namespace client { std::unique_ptr readHandle = nullptr; std::unique_ptr writeHandle = nullptr; }; - std::map requests; - uint32_t requestSerial = 0; - uint64_t size = 0; + std::map 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 readHandle = nullptr; - std::unique_ptr writeHandle = nullptr; + std::unique_ptr mReadHandle = nullptr; + std::unique_ptr mWriteHandle = nullptr; }; }} // namespace dawn_wire::client diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp index dd90406869..ffc5198e18 100644 --- a/src/dawn_wire/client/ClientDoers.cpp +++ b/src/dawn_wire/client/ClientDoers.cpp @@ -56,67 +56,8 @@ namespace dawn_wire { namespace client { 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::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(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(status), mappedData, - static_cast(mappedDataLength), request.userdata); - return true; - } + return buffer->OnMapReadAsyncCallback(requestSerial, status, initialDataInfoLength, + initialDataInfo); } bool Client::DoBufferMapWriteAsyncCallback(Buffer* buffer, @@ -127,60 +68,7 @@ namespace dawn_wire { namespace client { 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(status), mappedData, - static_cast(mappedDataLength), request.userdata); - return true; - } + return buffer->OnMapWriteAsyncCallback(requestSerial, status); } bool Client::DoFenceUpdateCompletedValue(Fence* fence, uint64_t value) {