MapAsync update: keep read/write handle through lifetime of mappable buffers

Change dawn read/write handle for buffer mapping to be created at buffer
creation time instead of at mapAsync time. Update related buffer mapping
tests and wire tests.

Bug: dawn:773
Change-Id: I7dd423c94e1bc15cfe561ea33ec9e348ddf2bfe0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/51164
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Shrek Shao <shrekshao@google.com>
This commit is contained in:
shrekshao 2021-07-08 22:48:57 +00:00 committed by Dawn LUCI CQ
parent 15838b98af
commit 6e680fc56f
20 changed files with 936 additions and 772 deletions

View File

@ -20,24 +20,26 @@
"commands": { "commands": {
"buffer map async": [ "buffer map async": [
{ "name": "buffer id", "type": "ObjectId" }, { "name": "buffer id", "type": "ObjectId" },
{ "name": "request serial", "type": "uint32_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "mode", "type": "map mode" }, { "name": "mode", "type": "map mode" },
{ "name": "offset", "type": "uint64_t"}, { "name": "offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"}, { "name": "size", "type": "uint64_t"}
{ "name": "handle create info length", "type": "uint64_t" },
{ "name": "handle create info", "type": "uint8_t", "annotation": "const*", "length": "handle create info length", "skip_serialize": true}
], ],
"buffer update mapped data": [ "buffer update mapped data": [
{ "name": "buffer id", "type": "ObjectId" }, { "name": "buffer id", "type": "ObjectId" },
{ "name": "write flush info length", "type": "uint64_t" }, { "name": "write data update info length", "type": "uint64_t" },
{ "name": "write flush info", "type": "uint8_t", "annotation": "const*", "length": "write flush info length", "skip_serialize": true} { "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true},
{ "name": "offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"}
], ],
"device create buffer": [ "device create buffer": [
{ "name": "device id", "type": "ObjectId" }, { "name": "device id", "type": "ObjectId" },
{ "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" }, { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" },
{ "name": "result", "type": "ObjectHandle", "handle_type": "buffer" }, { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" },
{ "name": "handle create info length", "type": "uint64_t" }, { "name": "read handle create info length", "type": "uint64_t" },
{ "name": "handle create info", "type": "uint8_t", "annotation": "const*", "length": "handle create info length", "skip_serialize": true} { "name": "read handle create info", "type": "uint8_t", "annotation": "const*", "length": "read handle create info length", "skip_serialize": true},
{ "name": "write handle create info length", "type": "uint64_t" },
{ "name": "write handle create info", "type": "uint8_t", "annotation": "const*", "length": "write handle create info length", "skip_serialize": true}
], ],
"device create compute pipeline async": [ "device create compute pipeline async": [
{ "name": "device id", "type": "ObjectId" }, { "name": "device id", "type": "ObjectId" },
@ -87,10 +89,10 @@
"return commands": { "return commands": {
"buffer map async callback": [ "buffer map async callback": [
{ "name": "buffer", "type": "ObjectHandle", "handle_type": "buffer" }, { "name": "buffer", "type": "ObjectHandle", "handle_type": "buffer" },
{ "name": "request serial", "type": "uint32_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "status", "type": "uint32_t" }, { "name": "status", "type": "uint32_t" },
{ "name": "read initial data info length", "type": "uint64_t" }, { "name": "read data update info length", "type": "uint64_t" },
{ "name": "read initial data info", "type": "uint8_t", "annotation": "const*", "length": "read initial data info length", "skip_serialize": true } { "name": "read data update info", "type": "uint8_t", "annotation": "const*", "length": "read data update info length", "skip_serialize": true }
], ],
"device create compute pipeline async callback": [ "device create compute pipeline async callback": [
{ "name": "device", "type": "ObjectHandle", "handle_type": "device" }, { "name": "device", "type": "ObjectHandle", "handle_type": "device" },

View File

@ -68,8 +68,10 @@ namespace dawn_wire {
MemoryTransferService::WriteHandle::~WriteHandle() = default; MemoryTransferService::WriteHandle::~WriteHandle() = default;
void MemoryTransferService::WriteHandle::SetTarget(void* data, size_t dataLength) { void MemoryTransferService::WriteHandle::SetTarget(void* data) {
mTargetData = data; mTargetData = data;
}
void MemoryTransferService::WriteHandle::SetDataLength(size_t dataLength) {
mDataLength = dataLength; mDataLength = dataLength;
} }
} // namespace server } // namespace server

View File

@ -33,64 +33,93 @@ namespace dawn_wire { namespace client {
return device->CreateErrorBuffer(); return device->CreateErrorBuffer();
} }
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr; std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
void* writeData = nullptr;
size_t writeHandleCreateInfoLength = 0;
// If the buffer is mapped at creation, create a write handle that will represent the DeviceCreateBufferCmd cmd;
// mapping of the whole buffer. cmd.deviceId = device->id;
if (descriptor->mappedAtCreation) { cmd.descriptor = descriptor;
// Create the handle. cmd.readHandleCreateInfoLength = 0;
cmd.readHandleCreateInfo = nullptr;
cmd.writeHandleCreateInfoLength = 0;
cmd.writeHandleCreateInfo = nullptr;
if (mappable) {
if ((descriptor->usage & WGPUBufferUsage_MapRead) != 0) {
// Create the read handle on buffer creation.
readHandle.reset(
wireClient->GetMemoryTransferService()->CreateReadHandle(descriptor->size));
if (readHandle == nullptr) {
device->InjectError(WGPUErrorType_OutOfMemory,
"Failed to create buffer mapping");
return device->CreateErrorBuffer();
}
cmd.readHandleCreateInfoLength = readHandle->SerializeCreateSize();
}
if ((descriptor->usage & WGPUBufferUsage_MapWrite) != 0 ||
descriptor->mappedAtCreation) {
// Create the write handle on buffer creation.
writeHandle.reset( writeHandle.reset(
wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size)); wireClient->GetMemoryTransferService()->CreateWriteHandle(descriptor->size));
if (writeHandle == nullptr) { if (writeHandle == nullptr) {
device->InjectError(WGPUErrorType_OutOfMemory, "Buffer mapping allocation failed"); device->InjectError(WGPUErrorType_OutOfMemory,
"Failed to create buffer mapping");
return device->CreateErrorBuffer(); return device->CreateErrorBuffer();
} }
cmd.writeHandleCreateInfoLength = writeHandle->SerializeCreateSize();
// Open the handle, it may fail by returning a nullptr in writeData.
size_t writeDataLength = 0;
std::tie(writeData, writeDataLength) = writeHandle->Open();
if (writeData == nullptr) {
device->InjectError(WGPUErrorType_OutOfMemory, "Buffer mapping allocation failed");
return device->CreateErrorBuffer();
} }
ASSERT(writeDataLength == descriptor->size);
// Get the serialization size of the write handle.
writeHandleCreateInfoLength = writeHandle->SerializeCreateSize();
} }
// Create the buffer and send the creation command. // Create the buffer and send the creation command.
// This must happen after any potential device->CreateErrorBuffer()
// as server expects allocating ids to be monotonically increasing
auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(wireClient); auto* bufferObjectAndSerial = wireClient->BufferAllocator().New(wireClient);
Buffer* buffer = bufferObjectAndSerial->object.get(); Buffer* buffer = bufferObjectAndSerial->object.get();
buffer->mDevice = device; buffer->mDevice = device;
buffer->mDeviceIsAlive = device->GetAliveWeakPtr(); buffer->mDeviceIsAlive = device->GetAliveWeakPtr();
buffer->mSize = descriptor->size; buffer->mSize = descriptor->size;
buffer->mDestructWriteHandleOnUnmap = false;
DeviceCreateBufferCmd cmd;
cmd.deviceId = device->id;
cmd.descriptor = descriptor;
cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
cmd.handleCreateInfoLength = writeHandleCreateInfoLength;
cmd.handleCreateInfo = nullptr;
wireClient->SerializeCommand(
cmd, writeHandleCreateInfoLength, [&](SerializeBuffer* serializeBuffer) {
if (descriptor->mappedAtCreation) { if (descriptor->mappedAtCreation) {
char* writeHandleBuffer; // If the buffer is mapped at creation, a write handle is created and will be
WIRE_TRY( // destructed on unmap if the buffer doesn't have MapWrite usage
serializeBuffer->NextN(writeHandleCreateInfoLength, &writeHandleBuffer)); // The buffer is mapped right now.
// Serialize the WriteHandle into the space after the command. buffer->mMapState = MapState::MappedAtCreation;
writeHandle->SerializeCreate(writeHandleBuffer);
// This flag is for write handle created by mappedAtCreation
// instead of MapWrite usage. We don't have such a case for read handle
buffer->mDestructWriteHandleOnUnmap =
(descriptor->usage & WGPUBufferUsage_MapWrite) == 0;
// Set the buffer state for the mapping at creation. The buffer now owns the
// write handle..
buffer->mWriteHandle = std::move(writeHandle);
buffer->mMappedData = writeData;
buffer->mMapOffset = 0; buffer->mMapOffset = 0;
buffer->mMapSize = buffer->mSize; buffer->mMapSize = buffer->mSize;
ASSERT(writeHandle != nullptr);
buffer->mMappedData = writeHandle->GetData();
} }
cmd.result = ObjectHandle{buffer->id, bufferObjectAndSerial->generation};
wireClient->SerializeCommand(
cmd, cmd.readHandleCreateInfoLength + cmd.writeHandleCreateInfoLength,
[&](SerializeBuffer* serializeBuffer) {
if (readHandle != nullptr) {
char* readHandleBuffer;
WIRE_TRY(
serializeBuffer->NextN(cmd.readHandleCreateInfoLength, &readHandleBuffer));
// Serialize the ReadHandle into the space after the command.
readHandle->SerializeCreate(readHandleBuffer);
buffer->mReadHandle = std::move(readHandle);
}
if (writeHandle != nullptr) {
char* writeHandleBuffer;
WIRE_TRY(serializeBuffer->NextN(cmd.writeHandleCreateInfoLength,
&writeHandleBuffer));
// Serialize the WriteHandle into the space after the command.
writeHandle->SerializeCreate(writeHandleBuffer);
buffer->mWriteHandle = std::move(writeHandle);
}
return WireResult::Success; return WireResult::Success;
}); });
return ToAPI(buffer); return ToAPI(buffer);
@ -120,7 +149,7 @@ namespace dawn_wire { namespace client {
} }
mRequests.clear(); mRequests.clear();
FreeMappedData(true); FreeMappedData();
} }
void Buffer::CancelCallbacksForDisconnect() { void Buffer::CancelCallbacksForDisconnect() {
@ -146,97 +175,41 @@ namespace dawn_wire { namespace client {
size = mSize - offset; size = mSize - offset;
} }
bool isReadMode = mode & WGPUMapMode_Read; // Create the request structure that will hold information while this mapping is
bool isWriteMode = mode & WGPUMapMode_Write;
// Step 1. Do early validation of READ ^ WRITE because the server rejects mode = 0.
if (!(isReadMode ^ isWriteMode)) {
if (!mDeviceIsAlive.expired()) {
mDevice->InjectError(WGPUErrorType_Validation,
"MapAsync mode must be exactly one of Read or Write");
}
if (callback != nullptr) {
callback(WGPUBufferMapAsyncStatus_Error, userdata);
}
return;
}
// Step 2. Create the request structure that will hold information while this mapping is
// in flight. // in flight.
uint32_t serial = mRequestSerial++; uint64_t serial = mRequestSerial++;
ASSERT(mRequests.find(serial) == mRequests.end()); ASSERT(mRequests.find(serial) == mRequests.end());
Buffer::MapRequestData request = {}; Buffer::MapRequestData request = {};
request.callback = callback; request.callback = callback;
request.userdata = userdata; request.userdata = userdata;
request.size = size;
request.offset = offset; request.offset = offset;
request.size = size;
// Step 2a: Create the read / write handles for this request. if (mode & WGPUMapMode_Read) {
if (isReadMode) { request.type = MapRequestType::Read;
request.readHandle.reset(client->GetMemoryTransferService()->CreateReadHandle(size)); } else if (mode & WGPUMapMode_Write) {
if (request.readHandle == nullptr) { request.type = MapRequestType::Write;
if (!mDeviceIsAlive.expired()) {
mDevice->InjectError(WGPUErrorType_OutOfMemory,
"Failed to create buffer mapping");
}
callback(WGPUBufferMapAsyncStatus_Error, userdata);
return;
}
} else {
ASSERT(isWriteMode);
request.writeHandle.reset(client->GetMemoryTransferService()->CreateWriteHandle(size));
if (request.writeHandle == nullptr) {
if (!mDeviceIsAlive.expired()) {
mDevice->InjectError(WGPUErrorType_OutOfMemory,
"Failed to create buffer mapping");
}
callback(WGPUBufferMapAsyncStatus_Error, userdata);
return;
}
} }
// Step 3. Serialize the command to send to the server. // Serialize the command to send to the server.
BufferMapAsyncCmd cmd; BufferMapAsyncCmd cmd;
cmd.bufferId = this->id; cmd.bufferId = this->id;
cmd.requestSerial = serial; cmd.requestSerial = serial;
cmd.mode = mode; cmd.mode = mode;
cmd.offset = offset; cmd.offset = offset;
cmd.size = size; cmd.size = size;
cmd.handleCreateInfo = nullptr;
// Step 3a. Fill the handle create info in the command. client->SerializeCommand(cmd);
if (isReadMode) {
cmd.handleCreateInfoLength = request.readHandle->SerializeCreateSize();
client->SerializeCommand(
cmd, cmd.handleCreateInfoLength, [&](SerializeBuffer* serializeBuffer) {
char* readHandleBuffer;
WIRE_TRY(serializeBuffer->NextN(cmd.handleCreateInfoLength, &readHandleBuffer));
request.readHandle->SerializeCreate(readHandleBuffer);
return WireResult::Success;
});
} else {
ASSERT(isWriteMode);
cmd.handleCreateInfoLength = request.writeHandle->SerializeCreateSize();
client->SerializeCommand(
cmd, cmd.handleCreateInfoLength, [&](SerializeBuffer* serializeBuffer) {
char* writeHandleBuffer;
WIRE_TRY(
serializeBuffer->NextN(cmd.handleCreateInfoLength, &writeHandleBuffer));
request.writeHandle->SerializeCreate(writeHandleBuffer);
return WireResult::Success;
});
}
// Step 4. Register this request so that we can retrieve it from its serial when the server // Register this request so that we can retrieve it from its serial when the server
// sends the callback. // sends the callback.
mRequests[serial] = std::move(request); mRequests[serial] = std::move(request);
} }
bool Buffer::OnMapAsyncCallback(uint32_t requestSerial, bool Buffer::OnMapAsyncCallback(uint64_t requestSerial,
uint32_t status, uint32_t status,
uint64_t readInitialDataInfoLength, uint64_t readDataUpdateInfoLength,
const uint8_t* readInitialDataInfo) { const uint8_t* readDataUpdateInfo) {
auto requestIt = mRequests.find(requestSerial); auto requestIt = mRequests.find(requestSerial);
if (requestIt == mRequests.end()) { if (requestIt == mRequests.end()) {
return false; return false;
@ -254,60 +227,50 @@ namespace dawn_wire { namespace client {
return false; return false;
}; };
bool isRead = request.readHandle != nullptr;
bool isWrite = request.writeHandle != nullptr;
ASSERT(isRead != isWrite);
// Take into account the client-side status of the request if the server says it is a success. // Take into account the client-side status of the request if the server says it is a success.
if (status == WGPUBufferMapAsyncStatus_Success) { if (status == WGPUBufferMapAsyncStatus_Success) {
status = request.clientStatus; status = request.clientStatus;
} }
size_t mappedDataLength = 0;
const void* mappedData = nullptr;
if (status == WGPUBufferMapAsyncStatus_Success) { if (status == WGPUBufferMapAsyncStatus_Success) {
if (mReadHandle || mWriteHandle) { switch (request.type) {
// Buffer is already mapped. case MapRequestType::Read: {
if (readDataUpdateInfoLength > std::numeric_limits<size_t>::max()) {
// This is the size of data deserialized from the command stream, which must
// be CPU-addressable.
return FailRequest(); return FailRequest();
} }
if (isRead) { // Validate to prevent bad map request; buffer destroyed during map request
if (readInitialDataInfoLength > std::numeric_limits<size_t>::max()) { if (mReadHandle == nullptr) {
// This is the size of data deserialized from the command stream, which must be
// CPU-addressable.
return FailRequest(); return FailRequest();
} }
// Update user map data with server returned data
// The server serializes metadata to initialize the contents of the ReadHandle. if (!mReadHandle->DeserializeDataUpdate(
// Deserialize the message and return a pointer and size of the mapped data for readDataUpdateInfo, static_cast<size_t>(readDataUpdateInfoLength),
// reading. request.offset, request.size)) {
if (!request.readHandle->DeserializeInitialData(
readInitialDataInfo, static_cast<size_t>(readInitialDataInfoLength),
&mappedData, &mappedDataLength)) {
// Deserialization shouldn't fail. This is a fatal error.
return FailRequest(); return FailRequest();
} }
ASSERT(mappedData != nullptr); mMapState = MapState::MappedForRead;
mMappedData = const_cast<void*>(mReadHandle->GetData());
} else { break;
// Open the WriteHandle. This returns a pointer and size of mapped memory. }
// On failure, |mappedData| may be null. case MapRequestType::Write: {
std::tie(mappedData, mappedDataLength) = request.writeHandle->Open(); if (mWriteHandle == nullptr) {
if (mappedData == nullptr) {
return FailRequest(); return FailRequest();
} }
mMapState = MapState::MappedForWrite;
mMappedData = mWriteHandle->GetData();
break;
} }
default:
// The MapAsync request was successful. The buffer now owns the Read/Write handles UNREACHABLE();
// until Unmap().
mReadHandle = std::move(request.readHandle);
mWriteHandle = std::move(request.writeHandle);
} }
mMapOffset = request.offset; mMapOffset = request.offset;
mMapSize = request.size; mMapSize = request.size;
mMappedData = const_cast<void*>(mappedData); }
if (request.callback) { if (request.callback) {
request.callback(static_cast<WGPUBufferMapAsyncStatus>(status), request.userdata); request.callback(static_cast<WGPUBufferMapAsyncStatus>(status), request.userdata);
} }
@ -319,7 +282,7 @@ namespace dawn_wire { namespace client {
if (!IsMappedForWriting() || !CheckGetMappedRangeOffsetSize(offset, size)) { if (!IsMappedForWriting() || !CheckGetMappedRangeOffsetSize(offset, size)) {
return nullptr; return nullptr;
} }
return static_cast<uint8_t*>(mMappedData) + (offset - mMapOffset); return static_cast<uint8_t*>(mMappedData) + offset;
} }
const void* Buffer::GetConstMappedRange(size_t offset, size_t size) { const void* Buffer::GetConstMappedRange(size_t offset, size_t size) {
@ -327,7 +290,7 @@ namespace dawn_wire { namespace client {
!CheckGetMappedRangeOffsetSize(offset, size)) { !CheckGetMappedRangeOffsetSize(offset, size)) {
return nullptr; return nullptr;
} }
return static_cast<uint8_t*>(mMappedData) + (offset - mMapOffset); return static_cast<uint8_t*>(mMappedData) + offset;
} }
void Buffer::Unmap() { void Buffer::Unmap() {
@ -339,32 +302,57 @@ namespace dawn_wire { namespace client {
// - Server -> Client: Result of MapRequest1 // - Server -> Client: Result of MapRequest1
// - Unmap locally on the client // - Unmap locally on the client
// - Server -> Client: Result of MapRequest2 // - Server -> Client: Result of MapRequest2
if (mWriteHandle) {
// TODO(dawn:608): mDevice->InjectError(WGPUErrorType_Validation) for map oom failure
// and separate from buffer destroyed before unmap case
// mWriteHandle can still be nullptr if buffer has been destroyed before unmap
if ((mMapState == MapState::MappedForWrite || mMapState == MapState::MappedAtCreation) &&
mWriteHandle != nullptr) {
// Writes need to be flushed before Unmap is sent. Unmap calls all associated // Writes need to be flushed before Unmap is sent. Unmap calls all associated
// in-flight callbacks which may read the updated data. // in-flight callbacks which may read the updated data.
ASSERT(mReadHandle == nullptr);
// Get the serialization size of metadata to flush writes. // Get the serialization size of data update writes.
size_t writeFlushInfoLength = mWriteHandle->SerializeFlushSize(); size_t writeDataUpdateInfoLength =
mWriteHandle->SizeOfSerializeDataUpdate(mMapOffset, mMapSize);
BufferUpdateMappedDataCmd cmd; BufferUpdateMappedDataCmd cmd;
cmd.bufferId = id; cmd.bufferId = id;
cmd.writeFlushInfoLength = writeFlushInfoLength; cmd.writeDataUpdateInfoLength = writeDataUpdateInfoLength;
cmd.writeFlushInfo = nullptr; cmd.writeDataUpdateInfo = nullptr;
cmd.offset = mMapOffset;
cmd.size = mMapSize;
client->SerializeCommand( client->SerializeCommand(
cmd, writeFlushInfoLength, [&](SerializeBuffer* serializeBuffer) { cmd, writeDataUpdateInfoLength, [&](SerializeBuffer* serializeBuffer) {
char* writeHandleBuffer; char* writeHandleBuffer;
WIRE_TRY(serializeBuffer->NextN(writeFlushInfoLength, &writeHandleBuffer)); WIRE_TRY(serializeBuffer->NextN(writeDataUpdateInfoLength, &writeHandleBuffer));
// Serialize flush metadata into the space after the command. // Serialize flush metadata into the space after the command.
// This closes the handle for writing. // This closes the handle for writing.
mWriteHandle->SerializeFlush(writeHandleBuffer); mWriteHandle->SerializeDataUpdate(writeHandleBuffer, cmd.offset, cmd.size);
return WireResult::Success; return WireResult::Success;
}); });
// If mDestructWriteHandleOnUnmap is true, that means the write handle is merely
// for mappedAtCreation usage. It is destroyed on unmap after flush to server
// instead of at buffer destruction.
if (mMapState == MapState::MappedAtCreation && mDestructWriteHandleOnUnmap) {
mWriteHandle = nullptr;
if (mReadHandle) {
// If it's both mappedAtCreation and MapRead we need to reset
// mMappedData to readHandle's GetData(). This could be changed to
// merging read/write handle in future
mMappedData = const_cast<void*>(mReadHandle->GetData());
}
}
} }
FreeMappedData(false); // Free map access tokens
mMapState = MapState::Unmapped;
mMapOffset = 0;
mMapSize = 0;
// Tag all mapping requests still in flight as unmapped before callback. // Tag all mapping requests still in flight as unmapped before callback.
for (auto& it : mRequests) { for (auto& it : mRequests) {
@ -376,11 +364,13 @@ namespace dawn_wire { namespace client {
BufferUnmapCmd cmd; BufferUnmapCmd cmd;
cmd.self = ToAPI(this); cmd.self = ToAPI(this);
client->SerializeCommand(cmd); client->SerializeCommand(cmd);
// TODO(dawn:608): add tracking mapped status and change to return bool for returning unmap
// success/failure
} }
void Buffer::Destroy() { void Buffer::Destroy() {
// Remove the current mapping. // Remove the current mapping and destroy Read/WriteHandles.
FreeMappedData(true); FreeMappedData();
// Tag all mapping requests still in flight as destroyed before callback. // Tag all mapping requests still in flight as destroyed before callback.
for (auto& it : mRequests) { for (auto& it : mRequests) {
@ -395,11 +385,11 @@ namespace dawn_wire { namespace client {
} }
bool Buffer::IsMappedForReading() const { bool Buffer::IsMappedForReading() const {
return mReadHandle != nullptr; return mMapState == MapState::MappedForRead;
} }
bool Buffer::IsMappedForWriting() const { bool Buffer::IsMappedForWriting() const {
return mWriteHandle != nullptr; return mMapState == MapState::MappedForWrite || mMapState == MapState::MappedAtCreation;
} }
bool Buffer::CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const { bool Buffer::CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const {
@ -415,20 +405,20 @@ namespace dawn_wire { namespace client {
return offsetInMappedRange <= mMapSize - size; return offsetInMappedRange <= mMapSize - size;
} }
void Buffer::FreeMappedData(bool destruction) { void Buffer::FreeMappedData() {
#if defined(DAWN_ENABLE_ASSERTS) #if defined(DAWN_ENABLE_ASSERTS)
// When in "debug" mode, 0xCA-out the mapped data when we free it so that in we can detect // When in "debug" mode, 0xCA-out the mapped data when we free it so that in we can detect
// use-after-free of the mapped data. This is particularly useful for WebGPU test about the // use-after-free of the mapped data. This is particularly useful for WebGPU test about the
// interaction of mapping and GC. // interaction of mapping and GC.
if (mMappedData && destruction) { if (mMappedData) {
memset(mMappedData, 0xCA, mMapSize); memset(static_cast<uint8_t*>(mMappedData) + mMapOffset, 0xCA, mMapSize);
} }
#endif // defined(DAWN_ENABLE_ASSERTS) #endif // defined(DAWN_ENABLE_ASSERTS)
mMapOffset = 0; mMapOffset = 0;
mMapSize = 0; mMapSize = 0;
mWriteHandle = nullptr;
mReadHandle = nullptr; mReadHandle = nullptr;
mWriteHandle = nullptr;
mMappedData = nullptr; mMappedData = nullptr;
} }

View File

@ -35,10 +35,10 @@ namespace dawn_wire { namespace client {
~Buffer(); ~Buffer();
bool OnMapAsyncCallback(uint32_t requestSerial, bool OnMapAsyncCallback(uint64_t requestSerial,
uint32_t status, uint32_t status,
uint64_t readInitialDataInfoLength, uint64_t readDataUpdateInfoLength,
const uint8_t* readInitialDataInfo); const uint8_t* readDataUpdateInfo);
void MapAsync(WGPUMapModeFlags mode, void MapAsync(WGPUMapModeFlags mode,
size_t offset, size_t offset,
size_t size, size_t size,
@ -57,10 +57,19 @@ namespace dawn_wire { namespace client {
bool IsMappedForWriting() const; bool IsMappedForWriting() const;
bool CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const; bool CheckGetMappedRangeOffsetSize(size_t offset, size_t size) const;
void FreeMappedData(bool destruction); void FreeMappedData();
Device* mDevice; Device* mDevice;
enum class MapRequestType { None, Read, Write };
enum class MapState {
Unmapped,
MappedForRead,
MappedForWrite,
MappedAtCreation,
};
// We want to defer all the validation to the server, which means we could have multiple // 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. // 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. // On well-behaved applications, only one request should exist at a single time.
@ -75,12 +84,10 @@ namespace dawn_wire { namespace client {
// from the server take precedence over the client-side status. // from the server take precedence over the client-side status.
WGPUBufferMapAsyncStatus clientStatus = WGPUBufferMapAsyncStatus_Success; WGPUBufferMapAsyncStatus clientStatus = WGPUBufferMapAsyncStatus_Success;
// TODO(enga): Use a tagged pointer to save space. MapRequestType type = MapRequestType::None;
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
}; };
std::map<uint32_t, MapRequestData> mRequests; std::map<uint64_t, MapRequestData> mRequests;
uint32_t mRequestSerial = 0; uint64_t mRequestSerial = 0;
uint64_t mSize = 0; uint64_t mSize = 0;
// Only one mapped pointer can be active at a time because Unmap clears all the in-flight // Only one mapped pointer can be active at a time because Unmap clears all the in-flight
@ -88,6 +95,9 @@ namespace dawn_wire { namespace client {
// TODO(enga): Use a tagged pointer to save space. // TODO(enga): Use a tagged pointer to save space.
std::unique_ptr<MemoryTransferService::ReadHandle> mReadHandle = nullptr; std::unique_ptr<MemoryTransferService::ReadHandle> mReadHandle = nullptr;
std::unique_ptr<MemoryTransferService::WriteHandle> mWriteHandle = nullptr; std::unique_ptr<MemoryTransferService::WriteHandle> mWriteHandle = nullptr;
MapState mMapState = MapState::Unmapped;
bool mDestructWriteHandleOnUnmap = false;
void* mMappedData = nullptr; void* mMappedData = nullptr;
size_t mMapOffset = 0; size_t mMapOffset = 0;
size_t mMapSize = 0; size_t mMapSize = 0;

View File

@ -73,16 +73,16 @@ namespace dawn_wire { namespace client {
} }
bool Client::DoBufferMapAsyncCallback(Buffer* buffer, bool Client::DoBufferMapAsyncCallback(Buffer* buffer,
uint32_t requestSerial, uint64_t requestSerial,
uint32_t status, uint32_t status,
uint64_t readInitialDataInfoLength, uint64_t readDataUpdateInfoLength,
const uint8_t* readInitialDataInfo) { const uint8_t* readDataUpdateInfo) {
// The buffer might have been deleted or recreated so this isn't an error. // The buffer might have been deleted or recreated so this isn't an error.
if (buffer == nullptr) { if (buffer == nullptr) {
return true; return true;
} }
return buffer->OnMapAsyncCallback(requestSerial, status, readInitialDataInfoLength, return buffer->OnMapAsyncCallback(requestSerial, status, readDataUpdateInfoLength,
readInitialDataInfo); readDataUpdateInfo);
} }
bool Client::DoQueueWorkDoneCallback(Queue* queue, bool Client::DoQueueWorkDoneCallback(Queue* queue,

View File

@ -24,7 +24,8 @@ namespace dawn_wire { namespace client {
class InlineMemoryTransferService : public MemoryTransferService { class InlineMemoryTransferService : public MemoryTransferService {
class ReadHandleImpl : public ReadHandle { class ReadHandleImpl : public ReadHandle {
public: public:
explicit ReadHandleImpl(size_t size) : mSize(size) { explicit ReadHandleImpl(std::unique_ptr<uint8_t[]> stagingData, size_t size)
: mStagingData(std::move(stagingData)), mSize(size) {
} }
~ReadHandleImpl() override = default; ~ReadHandleImpl() override = default;
@ -36,36 +37,36 @@ namespace dawn_wire { namespace client {
void SerializeCreate(void*) override { void SerializeCreate(void*) override {
} }
bool DeserializeInitialData(const void* deserializePointer, const void* GetData() override {
return mStagingData.get();
}
bool DeserializeDataUpdate(const void* deserializePointer,
size_t deserializeSize, size_t deserializeSize,
const void** data, size_t offset,
size_t* dataLength) override { size_t size) override {
if (deserializeSize != mSize || deserializePointer == nullptr) { if (deserializeSize != size || deserializePointer == nullptr) {
return false; return false;
} }
mStagingData = std::unique_ptr<uint8_t[]>(AllocNoThrow<uint8_t>(mSize)); if (offset > mSize || size > mSize - offset) {
if (!mStagingData) {
return false; return false;
} }
memcpy(mStagingData.get(), deserializePointer, mSize);
ASSERT(data != nullptr);
ASSERT(dataLength != nullptr);
*data = mStagingData.get();
*dataLength = mSize;
void* start = static_cast<uint8_t*>(mStagingData.get()) + offset;
memcpy(start, deserializePointer, size);
return true; return true;
} }
private: private:
size_t mSize;
std::unique_ptr<uint8_t[]> mStagingData; std::unique_ptr<uint8_t[]> mStagingData;
size_t mSize;
}; };
class WriteHandleImpl : public WriteHandle { class WriteHandleImpl : public WriteHandle {
public: public:
explicit WriteHandleImpl(size_t size) : mSize(size) { explicit WriteHandleImpl(std::unique_ptr<uint8_t[]> stagingData, size_t size)
: mStagingData(std::move(stagingData)), mSize(size) {
} }
~WriteHandleImpl() override = default; ~WriteHandleImpl() override = default;
@ -77,28 +78,27 @@ namespace dawn_wire { namespace client {
void SerializeCreate(void*) override { void SerializeCreate(void*) override {
} }
std::pair<void*, size_t> Open() override { void* GetData() override {
mStagingData = std::unique_ptr<uint8_t[]>(AllocNoThrow<uint8_t>(mSize)); return mStagingData.get();
if (!mStagingData) {
return std::make_pair(nullptr, 0);
}
memset(mStagingData.get(), 0, mSize);
return std::make_pair(mStagingData.get(), mSize);
} }
size_t SerializeFlushSize() override { size_t SizeOfSerializeDataUpdate(size_t offset, size_t size) override {
return mSize; ASSERT(offset <= mSize);
ASSERT(size <= mSize - offset);
return size;
} }
void SerializeFlush(void* serializePointer) override { void SerializeDataUpdate(void* serializePointer, size_t offset, size_t size) override {
ASSERT(mStagingData != nullptr); ASSERT(mStagingData != nullptr);
ASSERT(serializePointer != nullptr); ASSERT(serializePointer != nullptr);
memcpy(serializePointer, mStagingData.get(), mSize); ASSERT(offset <= mSize);
ASSERT(size <= mSize - offset);
memcpy(serializePointer, static_cast<uint8_t*>(mStagingData.get()) + offset, size);
} }
private: private:
size_t mSize;
std::unique_ptr<uint8_t[]> mStagingData; std::unique_ptr<uint8_t[]> mStagingData;
size_t mSize;
}; };
public: public:
@ -107,11 +107,20 @@ namespace dawn_wire { namespace client {
~InlineMemoryTransferService() override = default; ~InlineMemoryTransferService() override = default;
ReadHandle* CreateReadHandle(size_t size) override { ReadHandle* CreateReadHandle(size_t size) override {
return new ReadHandleImpl(size); auto stagingData = std::unique_ptr<uint8_t[]>(AllocNoThrow<uint8_t>(size));
if (stagingData) {
return new ReadHandleImpl(std::move(stagingData), size);
}
return nullptr;
} }
WriteHandle* CreateWriteHandle(size_t size) override { WriteHandle* CreateWriteHandle(size_t size) override {
return new WriteHandleImpl(size); auto stagingData = std::unique_ptr<uint8_t[]>(AllocNoThrow<uint8_t>(size));
if (stagingData) {
memset(stagingData.get(), 0, size);
return new WriteHandleImpl(std::move(stagingData), size);
}
return nullptr;
} }
}; };

View File

@ -35,15 +35,19 @@ namespace dawn_wire { namespace client {
mService->OnReadHandleSerializeCreate(this, serializePointer); mService->OnReadHandleSerializeCreate(this, serializePointer);
} }
bool MockMemoryTransferService::MockReadHandle::DeserializeInitialData( const void* MockMemoryTransferService::MockReadHandle::GetData() {
return mService->OnReadHandleGetData(this);
}
bool MockMemoryTransferService::MockReadHandle::DeserializeDataUpdate(
const void* deserializePointer, const void* deserializePointer,
size_t deserializeSize, size_t deserializeSize,
const void** data, size_t offset,
size_t* dataLength) { size_t size) {
ASSERT(deserializeSize % sizeof(uint32_t) == 0); ASSERT(deserializeSize % sizeof(uint32_t) == 0);
return mService->OnReadHandleDeserializeInitialData( return mService->OnReadHandleDeserializeDataUpdate(
this, reinterpret_cast<const uint32_t*>(deserializePointer), deserializeSize, data, this, reinterpret_cast<const uint32_t*>(deserializePointer), deserializeSize, offset,
dataLength); size);
} }
MockMemoryTransferService::MockWriteHandle::MockWriteHandle(MockMemoryTransferService* service) MockMemoryTransferService::MockWriteHandle::MockWriteHandle(MockMemoryTransferService* service)
@ -62,16 +66,19 @@ namespace dawn_wire { namespace client {
mService->OnWriteHandleSerializeCreate(this, serializePointer); mService->OnWriteHandleSerializeCreate(this, serializePointer);
} }
std::pair<void*, size_t> MockMemoryTransferService::MockWriteHandle::Open() { void* MockMemoryTransferService::MockWriteHandle::GetData() {
return mService->OnWriteHandleOpen(this); return mService->OnWriteHandleGetData(this);
} }
size_t MockMemoryTransferService::MockWriteHandle::SerializeFlushSize() { size_t MockMemoryTransferService::MockWriteHandle::SizeOfSerializeDataUpdate(size_t offset,
return mService->OnWriteHandleSerializeFlushSize(this); size_t size) {
return mService->OnWriteHandleSizeOfSerializeDataUpdate(this, offset, size);
} }
void MockMemoryTransferService::MockWriteHandle::SerializeFlush(void* serializePointer) { void MockMemoryTransferService::MockWriteHandle::SerializeDataUpdate(void* serializePointer,
mService->OnWriteHandleSerializeFlush(this, serializePointer); size_t offset,
size_t size) {
mService->OnWriteHandleSerializeDataUpdate(this, serializePointer, offset, size);
} }
MockMemoryTransferService::MockMemoryTransferService() = default; MockMemoryTransferService::MockMemoryTransferService() = default;

View File

@ -31,10 +31,11 @@ namespace dawn_wire { namespace client {
size_t SerializeCreateSize() override; size_t SerializeCreateSize() override;
void SerializeCreate(void* serializePointer) override; void SerializeCreate(void* serializePointer) override;
bool DeserializeInitialData(const void* deserializePointer, const void* GetData() override;
bool DeserializeDataUpdate(const void* deserializePointer,
size_t deserializeSize, size_t deserializeSize,
const void** data, size_t offset,
size_t* dataLength) override; size_t size) override;
private: private:
MockMemoryTransferService* mService; MockMemoryTransferService* mService;
@ -47,9 +48,9 @@ namespace dawn_wire { namespace client {
size_t SerializeCreateSize() override; size_t SerializeCreateSize() override;
void SerializeCreate(void* serializePointer) override; void SerializeCreate(void* serializePointer) override;
std::pair<void*, size_t> Open() override; void* GetData() override;
size_t SerializeFlushSize() override; size_t SizeOfSerializeDataUpdate(size_t offset, size_t size) override;
void SerializeFlush(void* serializePointer) override; void SerializeDataUpdate(void* serializePointer, size_t offset, size_t size) override;
private: private:
MockMemoryTransferService* mService; MockMemoryTransferService* mService;
@ -69,24 +70,27 @@ namespace dawn_wire { namespace client {
MOCK_METHOD(size_t, OnReadHandleSerializeCreateSize, (const ReadHandle*)); MOCK_METHOD(size_t, OnReadHandleSerializeCreateSize, (const ReadHandle*));
MOCK_METHOD(void, OnReadHandleSerializeCreate, (const ReadHandle*, void* serializePointer)); MOCK_METHOD(void, OnReadHandleSerializeCreate, (const ReadHandle*, void* serializePointer));
MOCK_METHOD((const void*), OnReadHandleGetData, (const ReadHandle*));
MOCK_METHOD(bool, MOCK_METHOD(bool,
OnReadHandleDeserializeInitialData, OnReadHandleDeserializeDataUpdate,
(const ReadHandle*, (const ReadHandle*,
const uint32_t* deserializePointer, const uint32_t* deserializePointer,
size_t deserializeSize, size_t deserializeSize,
const void** data, size_t offset,
size_t* dataLength)); size_t size));
MOCK_METHOD(void, OnReadHandleDestroy, (const ReadHandle*)); MOCK_METHOD(void, OnReadHandleDestroy, (const ReadHandle*));
MOCK_METHOD(size_t, OnWriteHandleSerializeCreateSize, (const void* WriteHandle)); MOCK_METHOD(size_t, OnWriteHandleSerializeCreateSize, (const void* WriteHandle));
MOCK_METHOD(void, MOCK_METHOD(void,
OnWriteHandleSerializeCreate, OnWriteHandleSerializeCreate,
(const void* WriteHandle, void* serializePointer)); (const void* WriteHandle, void* serializePointer));
MOCK_METHOD((std::pair<void*, size_t>), OnWriteHandleOpen, (const void* WriteHandle)); MOCK_METHOD((void*), OnWriteHandleGetData, (const void* WriteHandle));
MOCK_METHOD(size_t, OnWriteHandleSerializeFlushSize, (const void* WriteHandle)); MOCK_METHOD(size_t,
MOCK_METHOD(void, OnWriteHandleSizeOfSerializeDataUpdate,
OnWriteHandleSerializeFlush, (const void* WriteHandle, size_t offset, size_t size));
(const void* WriteHandle, void* serializePointer)); MOCK_METHOD(size_t,
OnWriteHandleSerializeDataUpdate,
(const void* WriteHandle, void* serializePointer, size_t offset, size_t size));
MOCK_METHOD(void, OnWriteHandleDestroy, (const void* WriteHandle)); MOCK_METHOD(void, OnWriteHandleDestroy, (const void* WriteHandle));
}; };

View File

@ -62,6 +62,9 @@ namespace dawn_wire { namespace server {
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle; std::unique_ptr<MemoryTransferService::ReadHandle> readHandle;
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle; std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle;
BufferMapWriteState mapWriteState = BufferMapWriteState::Unmapped; BufferMapWriteState mapWriteState = BufferMapWriteState::Unmapped;
WGPUBufferUsageFlags usage = WGPUBufferUsage_None;
// Indicate if writeHandle needs to be destroyed on unmap
bool mappedAtCreation = false;
}; };
// Pack the ObjectType and ObjectId as a single value for storage in // Pack the ObjectType and ObjectId as a single value for storage in

View File

@ -111,13 +111,10 @@ namespace dawn_wire { namespace server {
ObjectHandle buffer; ObjectHandle buffer;
WGPUBuffer bufferObj; WGPUBuffer bufferObj;
uint32_t requestSerial; uint64_t requestSerial;
uint64_t offset; uint64_t offset;
uint64_t size; uint64_t size;
WGPUMapModeFlags mode; WGPUMapModeFlags mode;
// TODO(enga): Use a tagged pointer to save space.
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
}; };
struct ErrorScopeUserdata : CallbackUserdata { struct ErrorScopeUserdata : CallbackUserdata {

View File

@ -25,9 +25,13 @@ namespace dawn_wire { namespace server {
auto* buffer = BufferObjects().Get(cmd.selfId); auto* buffer = BufferObjects().Get(cmd.selfId);
DAWN_ASSERT(buffer != nullptr); DAWN_ASSERT(buffer != nullptr);
// The buffer was unmapped. Clear the Read/WriteHandle. if (buffer->mappedAtCreation && !(buffer->usage & WGPUMapMode_Write)) {
buffer->readHandle = nullptr; // This indicates the writeHandle is for mappedAtCreation only. Destroy on unmap
// writeHandle could have possibly been deleted if buffer is already destroyed so we
// don't assert it's non-null
buffer->writeHandle = nullptr; buffer->writeHandle = nullptr;
}
buffer->mapWriteState = BufferMapWriteState::Unmapped; buffer->mapWriteState = BufferMapWriteState::Unmapped;
return true; return true;
@ -47,12 +51,10 @@ namespace dawn_wire { namespace server {
} }
bool Server::DoBufferMapAsync(ObjectId bufferId, bool Server::DoBufferMapAsync(ObjectId bufferId,
uint32_t requestSerial, uint64_t requestSerial,
WGPUMapModeFlags mode, WGPUMapModeFlags mode,
uint64_t offset64, uint64_t offset64,
uint64_t size64, uint64_t size64) {
uint64_t handleCreateInfoLength,
const uint8_t* handleCreateInfo) {
// These requests are just forwarded to the buffer, with userdata containing what the // These requests are just forwarded to the buffer, with userdata containing what the
// client will require in the return command. // client will require in the return command.
@ -66,13 +68,6 @@ namespace dawn_wire { namespace server {
return false; return false;
} }
// The server only knows how to deal with write XOR read. Validate that.
bool isReadMode = mode & WGPUMapMode_Read;
bool isWriteMode = mode & WGPUMapMode_Write;
if (!(isReadMode ^ isWriteMode)) {
return false;
}
std::unique_ptr<MapUserdata> userdata = MakeUserdata<MapUserdata>(); std::unique_ptr<MapUserdata> userdata = MakeUserdata<MapUserdata>();
userdata->buffer = ObjectHandle{bufferId, buffer->generation}; userdata->buffer = ObjectHandle{bufferId, buffer->generation};
userdata->bufferObj = buffer->handle; userdata->bufferObj = buffer->handle;
@ -80,8 +75,7 @@ namespace dawn_wire { namespace server {
userdata->mode = mode; userdata->mode = mode;
if (offset64 > std::numeric_limits<size_t>::max() || if (offset64 > std::numeric_limits<size_t>::max() ||
size64 > std::numeric_limits<size_t>::max() || size64 > std::numeric_limits<size_t>::max()) {
handleCreateInfoLength > std::numeric_limits<size_t>::max()) {
OnBufferMapAsyncCallback(WGPUBufferMapAsyncStatus_Error, userdata.get()); OnBufferMapAsyncCallback(WGPUBufferMapAsyncStatus_Error, userdata.get());
return true; return true;
} }
@ -92,32 +86,6 @@ namespace dawn_wire { namespace server {
userdata->offset = offset; userdata->offset = offset;
userdata->size = size; userdata->size = size;
// The handle will point to the mapped memory or staging memory for the mapping.
// Store it on the map request.
if (isWriteMode) {
// Deserialize metadata produced from the client to create a companion server handle.
MemoryTransferService::WriteHandle* writeHandle = nullptr;
if (!mMemoryTransferService->DeserializeWriteHandle(
handleCreateInfo, static_cast<size_t>(handleCreateInfoLength), &writeHandle)) {
return false;
}
ASSERT(writeHandle != nullptr);
userdata->writeHandle =
std::unique_ptr<MemoryTransferService::WriteHandle>(writeHandle);
} else {
ASSERT(isReadMode);
// Deserialize metadata produced from the client to create a companion server handle.
MemoryTransferService::ReadHandle* readHandle = nullptr;
if (!mMemoryTransferService->DeserializeReadHandle(
handleCreateInfo, static_cast<size_t>(handleCreateInfoLength), &readHandle)) {
return false;
}
ASSERT(readHandle != nullptr);
userdata->readHandle = std::unique_ptr<MemoryTransferService::ReadHandle>(readHandle);
}
mProcs.bufferMapAsync( mProcs.bufferMapAsync(
buffer->handle, mode, offset, size, buffer->handle, mode, offset, size,
ForwardToServer<decltype( ForwardToServer<decltype(
@ -130,8 +98,10 @@ namespace dawn_wire { namespace server {
bool Server::DoDeviceCreateBuffer(ObjectId deviceId, bool Server::DoDeviceCreateBuffer(ObjectId deviceId,
const WGPUBufferDescriptor* descriptor, const WGPUBufferDescriptor* descriptor,
ObjectHandle bufferResult, ObjectHandle bufferResult,
uint64_t handleCreateInfoLength, uint64_t readHandleCreateInfoLength,
const uint8_t* handleCreateInfo) { const uint8_t* readHandleCreateInfo,
uint64_t writeHandleCreateInfoLength,
const uint8_t* writeHandleCreateInfo) {
auto* device = DeviceObjects().Get(deviceId); auto* device = DeviceObjects().Get(deviceId);
if (device == nullptr) { if (device == nullptr) {
return false; return false;
@ -145,56 +115,84 @@ namespace dawn_wire { namespace server {
resultData->generation = bufferResult.generation; resultData->generation = bufferResult.generation;
resultData->handle = mProcs.deviceCreateBuffer(device->handle, descriptor); resultData->handle = mProcs.deviceCreateBuffer(device->handle, descriptor);
resultData->deviceInfo = device->info.get(); resultData->deviceInfo = device->info.get();
resultData->usage = descriptor->usage;
resultData->mappedAtCreation = descriptor->mappedAtCreation;
if (!TrackDeviceChild(resultData->deviceInfo, ObjectType::Buffer, bufferResult.id)) { if (!TrackDeviceChild(resultData->deviceInfo, ObjectType::Buffer, bufferResult.id)) {
return false; return false;
} }
// If the buffer isn't mapped at creation, we are done. // isReadMode and isWriteMode could be true at the same time if usage contains
if (!descriptor->mappedAtCreation) { // WGPUMapMode_Read and buffer is mappedAtCreation
return handleCreateInfoLength == 0; bool isReadMode = descriptor->usage & WGPUMapMode_Read;
} bool isWriteMode = descriptor->usage & WGPUMapMode_Write || descriptor->mappedAtCreation;
// This is the size of data deserialized from the command stream to create the write handle, // This is the size of data deserialized from the command stream to create the read/write
// which must be CPU-addressable. // handle, which must be CPU-addressable.
if (handleCreateInfoLength > std::numeric_limits<size_t>::max()) { if (readHandleCreateInfoLength > std::numeric_limits<size_t>::max() ||
writeHandleCreateInfoLength > std::numeric_limits<size_t>::max() ||
readHandleCreateInfoLength >
std::numeric_limits<size_t>::max() - writeHandleCreateInfoLength) {
return false; return false;
} }
void* mapping = mProcs.bufferGetMappedRange(resultData->handle, 0, descriptor->size); if (isWriteMode) {
MemoryTransferService::WriteHandle* writeHandle = nullptr;
// Deserialize metadata produced from the client to create a companion server handle.
if (!mMemoryTransferService->DeserializeWriteHandle(
writeHandleCreateInfo, static_cast<size_t>(writeHandleCreateInfoLength),
&writeHandle)) {
return false;
}
ASSERT(writeHandle != nullptr);
resultData->writeHandle.reset(writeHandle);
writeHandle->SetDataLength(descriptor->size);
if (descriptor->mappedAtCreation) {
void* mapping =
mProcs.bufferGetMappedRange(resultData->handle, 0, descriptor->size);
if (mapping == nullptr) { if (mapping == nullptr) {
// A zero mapping is used to indicate an allocation error of an error buffer. This is a // A zero mapping is used to indicate an allocation error of an error buffer.
// valid case and isn't fatal. Remember the buffer is an error so as to skip subsequent // This is a valid case and isn't fatal. Remember the buffer is an error so as
// mapping operations. // to skip subsequent mapping operations.
resultData->mapWriteState = BufferMapWriteState::MapError; resultData->mapWriteState = BufferMapWriteState::MapError;
return true; return true;
} }
ASSERT(mapping != nullptr);
// Deserialize metadata produced from the client to create a companion server handle. writeHandle->SetTarget(mapping);
MemoryTransferService::WriteHandle* writeHandle = nullptr;
if (!mMemoryTransferService->DeserializeWriteHandle(
handleCreateInfo, static_cast<size_t>(handleCreateInfoLength), &writeHandle)) {
return false;
}
// Set the target of the WriteHandle to the mapped GPU memory.
ASSERT(writeHandle != nullptr);
writeHandle->SetTarget(mapping, descriptor->size);
resultData->mapWriteState = BufferMapWriteState::Mapped; resultData->mapWriteState = BufferMapWriteState::Mapped;
resultData->writeHandle.reset(writeHandle); }
}
if (isReadMode) {
MemoryTransferService::ReadHandle* readHandle = nullptr;
// Deserialize metadata produced from the client to create a companion server handle.
if (!mMemoryTransferService->DeserializeReadHandle(
readHandleCreateInfo, static_cast<size_t>(readHandleCreateInfoLength),
&readHandle)) {
return false;
}
ASSERT(readHandle != nullptr);
resultData->readHandle.reset(readHandle);
}
return true; return true;
} }
bool Server::DoBufferUpdateMappedData(ObjectId bufferId, bool Server::DoBufferUpdateMappedData(ObjectId bufferId,
uint64_t writeFlushInfoLength, uint64_t writeDataUpdateInfoLength,
const uint8_t* writeFlushInfo) { const uint8_t* writeDataUpdateInfo,
uint64_t offset,
uint64_t size) {
// The null object isn't valid as `self` // The null object isn't valid as `self`
if (bufferId == 0) { if (bufferId == 0) {
return false; return false;
} }
if (writeFlushInfoLength > std::numeric_limits<size_t>::max()) { if (writeDataUpdateInfoLength > std::numeric_limits<size_t>::max() ||
offset > std::numeric_limits<size_t>::max() ||
size > std::numeric_limits<size_t>::max()) {
return false; return false;
} }
@ -220,8 +218,9 @@ namespace dawn_wire { namespace server {
// Deserialize the flush info and flush updated data from the handle into the target // Deserialize the flush info and flush updated data from the handle into the target
// of the handle. The target is set via WriteHandle::SetTarget. // of the handle. The target is set via WriteHandle::SetTarget.
return buffer->writeHandle->DeserializeFlush(writeFlushInfo, return buffer->writeHandle->DeserializeDataUpdate(
static_cast<size_t>(writeFlushInfoLength)); writeDataUpdateInfo, static_cast<size_t>(writeDataUpdateInfoLength),
static_cast<size_t>(offset), static_cast<size_t>(size));
} }
void Server::OnBufferMapAsyncCallback(WGPUBufferMapAsyncStatus status, MapUserdata* data) { void Server::OnBufferMapAsyncCallback(WGPUBufferMapAsyncStatus status, MapUserdata* data) {
@ -238,40 +237,42 @@ namespace dawn_wire { namespace server {
cmd.buffer = data->buffer; cmd.buffer = data->buffer;
cmd.requestSerial = data->requestSerial; cmd.requestSerial = data->requestSerial;
cmd.status = status; cmd.status = status;
cmd.readInitialDataInfoLength = 0; cmd.readDataUpdateInfoLength = 0;
cmd.readInitialDataInfo = nullptr; cmd.readDataUpdateInfo = nullptr;
const void* readData = nullptr; const void* readData = nullptr;
if (isSuccess && isRead) {
// Get the serialization size of the message to initialize ReadHandle data.
readData = mProcs.bufferGetConstMappedRange(data->bufferObj, data->offset, data->size);
cmd.readInitialDataInfoLength =
data->readHandle->SerializeInitialDataSize(readData, data->size);
}
SerializeCommand(cmd, cmd.readInitialDataInfoLength, [&](SerializeBuffer* serializeBuffer) {
if (isSuccess) { if (isSuccess) {
if (isRead) { if (isRead) {
char* readHandleBuffer; // Get the serialization size of the message to initialize ReadHandle data.
WIRE_TRY( readData =
serializeBuffer->NextN(cmd.readInitialDataInfoLength, &readHandleBuffer)); mProcs.bufferGetConstMappedRange(data->bufferObj, data->offset, data->size);
cmd.readDataUpdateInfoLength =
// Serialize the initialization message into the space after the command. bufferData->readHandle->SizeOfSerializeDataUpdate(data->offset, data->size);
data->readHandle->SerializeInitialData(readData, data->size, readHandleBuffer);
// The in-flight map request returned successfully.
// Move the ReadHandle so it is owned by the buffer.
bufferData->readHandle = std::move(data->readHandle);
} else { } else {
ASSERT(data->mode & WGPUMapMode_Write);
// The in-flight map request returned successfully. // The in-flight map request returned successfully.
// Move the WriteHandle so it is owned by the buffer.
bufferData->writeHandle = std::move(data->writeHandle);
bufferData->mapWriteState = BufferMapWriteState::Mapped; bufferData->mapWriteState = BufferMapWriteState::Mapped;
// Set the target of the WriteHandle to the mapped buffer data. // Set the target of the WriteHandle to the mapped buffer data.
// writeHandle Target always refers to the buffer base address.
// but we call getMappedRange exactly with the range of data that is potentially
// modified (i.e. we don't want getMappedRange(0, wholeBufferSize) if only a
// subset of the buffer is actually mapped) in case the implementation does some
// range tracking.
bufferData->writeHandle->SetTarget( bufferData->writeHandle->SetTarget(
mProcs.bufferGetMappedRange(data->bufferObj, data->offset, data->size), static_cast<uint8_t*>(
data->size); mProcs.bufferGetMappedRange(data->bufferObj, data->offset, data->size)) -
data->offset);
} }
} }
SerializeCommand(cmd, cmd.readDataUpdateInfoLength, [&](SerializeBuffer* serializeBuffer) {
if (isSuccess && isRead) {
char* readHandleBuffer;
WIRE_TRY(serializeBuffer->NextN(cmd.readDataUpdateInfoLength, &readHandleBuffer));
// The in-flight map request returned successfully.
bufferData->readHandle->SerializeDataUpdate(readData, data->offset, data->size,
readHandleBuffer);
}
return WireResult::Success; return WireResult::Success;
}); });
} }

View File

@ -28,17 +28,18 @@ namespace dawn_wire { namespace server {
} }
~ReadHandleImpl() override = default; ~ReadHandleImpl() override = default;
size_t SerializeInitialDataSize(const void* data, size_t dataLength) override { size_t SizeOfSerializeDataUpdate(size_t offset, size_t size) override {
return dataLength; return size;
} }
void SerializeInitialData(const void* data, void SerializeDataUpdate(const void* data,
size_t dataLength, size_t offset,
size_t size,
void* serializePointer) override { void* serializePointer) override {
if (dataLength > 0) { if (size > 0) {
ASSERT(data != nullptr); ASSERT(data != nullptr);
ASSERT(serializePointer != nullptr); ASSERT(serializePointer != nullptr);
memcpy(serializePointer, data, dataLength); memcpy(serializePointer, data, size);
} }
} }
}; };
@ -49,12 +50,18 @@ namespace dawn_wire { namespace server {
} }
~WriteHandleImpl() override = default; ~WriteHandleImpl() override = default;
bool DeserializeFlush(const void* deserializePointer, size_t deserializeSize) override { bool DeserializeDataUpdate(const void* deserializePointer,
if (deserializeSize != mDataLength || mTargetData == nullptr || size_t deserializeSize,
size_t offset,
size_t size) override {
if (deserializeSize != size || mTargetData == nullptr ||
deserializePointer == nullptr) { deserializePointer == nullptr) {
return false; return false;
} }
memcpy(mTargetData, deserializePointer, mDataLength); if ((offset >= mDataLength && offset > 0) || size > mDataLength - offset) {
return false;
}
memcpy(static_cast<uint8_t*>(mTargetData) + offset, deserializePointer, size);
return true; return true;
} }
}; };

View File

@ -26,15 +26,16 @@ namespace dawn_wire { namespace server {
mService->OnReadHandleDestroy(this); mService->OnReadHandleDestroy(this);
} }
size_t MockMemoryTransferService::MockReadHandle::SerializeInitialDataSize(const void* data, size_t MockMemoryTransferService::MockReadHandle::SizeOfSerializeDataUpdate(size_t offset,
size_t dataLength) { size_t size) {
return mService->OnReadHandleSerializeInitialDataSize(this, data, dataLength); return mService->OnReadHandleSizeOfSerializeDataUpdate(this, offset, size);
} }
void MockMemoryTransferService::MockReadHandle::SerializeInitialData(const void* data, void MockMemoryTransferService::MockReadHandle::SerializeDataUpdate(const void* data,
size_t dataLength, size_t offset,
size_t size,
void* serializePointer) { void* serializePointer) {
mService->OnReadHandleSerializeInitialData(this, data, dataLength, serializePointer); mService->OnReadHandleSerializeDataUpdate(this, data, offset, size, serializePointer);
} }
MockMemoryTransferService::MockWriteHandle::MockWriteHandle(MockMemoryTransferService* service) MockMemoryTransferService::MockWriteHandle::MockWriteHandle(MockMemoryTransferService* service)
@ -45,18 +46,21 @@ namespace dawn_wire { namespace server {
mService->OnWriteHandleDestroy(this); mService->OnWriteHandleDestroy(this);
} }
bool MockMemoryTransferService::MockWriteHandle::DeserializeFlush(
const void* deserializePointer,
size_t deserializeSize) {
ASSERT(deserializeSize % sizeof(uint32_t) == 0);
return mService->OnWriteHandleDeserializeFlush(
this, reinterpret_cast<const uint32_t*>(deserializePointer), deserializeSize);
}
const uint32_t* MockMemoryTransferService::MockWriteHandle::GetData() const { const uint32_t* MockMemoryTransferService::MockWriteHandle::GetData() const {
return reinterpret_cast<const uint32_t*>(mTargetData); return reinterpret_cast<const uint32_t*>(mTargetData);
} }
bool MockMemoryTransferService::MockWriteHandle::DeserializeDataUpdate(
const void* deserializePointer,
size_t deserializeSize,
size_t offset,
size_t size) {
ASSERT(deserializeSize % sizeof(uint32_t) == 0);
return mService->OnWriteHandleDeserializeDataUpdate(
this, reinterpret_cast<const uint32_t*>(deserializePointer), deserializeSize, offset,
size);
}
MockMemoryTransferService::MockMemoryTransferService() = default; MockMemoryTransferService::MockMemoryTransferService() = default;
MockMemoryTransferService::~MockMemoryTransferService() = default; MockMemoryTransferService::~MockMemoryTransferService() = default;

View File

@ -29,9 +29,10 @@ namespace dawn_wire { namespace server {
MockReadHandle(MockMemoryTransferService* service); MockReadHandle(MockMemoryTransferService* service);
~MockReadHandle() override; ~MockReadHandle() override;
size_t SerializeInitialDataSize(const void* data, size_t dataLength) override; size_t SizeOfSerializeDataUpdate(size_t offset, size_t size) override;
void SerializeInitialData(const void* data, void SerializeDataUpdate(const void* data,
size_t dataLength, size_t offset,
size_t size,
void* serializePointer) override; void* serializePointer) override;
private: private:
@ -43,7 +44,10 @@ namespace dawn_wire { namespace server {
MockWriteHandle(MockMemoryTransferService* service); MockWriteHandle(MockMemoryTransferService* service);
~MockWriteHandle() override; ~MockWriteHandle() override;
bool DeserializeFlush(const void* deserializePointer, size_t deserializeSize) override; bool DeserializeDataUpdate(const void* deserializePointer,
size_t deserializeSize,
size_t offset,
size_t size) override;
const uint32_t* GetData() const; const uint32_t* GetData() const;
@ -78,21 +82,24 @@ namespace dawn_wire { namespace server {
WriteHandle** writeHandle)); WriteHandle** writeHandle));
MOCK_METHOD(size_t, MOCK_METHOD(size_t,
OnReadHandleSerializeInitialDataSize, OnReadHandleSizeOfSerializeDataUpdate,
(const ReadHandle* readHandle, const void* data, size_t dataLength)); (const ReadHandle* readHandle, size_t offset, size_t size));
MOCK_METHOD(void, MOCK_METHOD(void,
OnReadHandleSerializeInitialData, OnReadHandleSerializeDataUpdate,
(const ReadHandle* readHandle, (const ReadHandle* readHandle,
const void* data, const void* data,
size_t dataLength, size_t offset,
size_t size,
void* serializePointer)); void* serializePointer));
MOCK_METHOD(void, OnReadHandleDestroy, (const ReadHandle* readHandle)); MOCK_METHOD(void, OnReadHandleDestroy, (const ReadHandle* readHandle));
MOCK_METHOD(bool, MOCK_METHOD(bool,
OnWriteHandleDeserializeFlush, OnWriteHandleDeserializeDataUpdate,
(const WriteHandle* writeHandle, (const WriteHandle* writeHandle,
const uint32_t* deserializePointer, const uint32_t* deserializePointer,
size_t deserializeSize)); size_t deserializeSize,
size_t offset,
size_t size));
MOCK_METHOD(void, OnWriteHandleDestroy, (const WriteHandle* writeHandle)); MOCK_METHOD(void, OnWriteHandleDestroy, (const WriteHandle* writeHandle));
}; };

View File

@ -121,6 +121,7 @@ namespace dawn_wire {
// deserialize the data update and apply // deserialize the data update and apply
// it to the range (offset, offset + size) of allocation // it to the range (offset, offset + size) of allocation
// There could be nothing to be deserialized (if using shared memory) // There could be nothing to be deserialized (if using shared memory)
// Needs to check potential offset/size OOB and overflow
// TODO(dawn:773): change to pure virtual after update on chromium side. // TODO(dawn:773): change to pure virtual after update on chromium side.
virtual bool DeserializeDataUpdate(const void* deserializePointer, virtual bool DeserializeDataUpdate(const void* deserializePointer,
size_t deserializeSize, size_t deserializeSize,

View File

@ -132,8 +132,9 @@ namespace dawn_wire {
// Set the target for writes from the client. DeserializeFlush should copy data // Set the target for writes from the client. DeserializeFlush should copy data
// into the target. // into the target.
// TODO(dawn:773): only set backing buffer pointer data void SetTarget(void* data);
void SetTarget(void* data, size_t dataLength); // Set Staging data length for OOB check
void SetDataLength(size_t dataLength);
// TODO(dawn:773): remove after update on chromium side. // TODO(dawn:773): remove after update on chromium side.
virtual bool DeserializeFlush(const void* deserializePointer, virtual bool DeserializeFlush(const void* deserializePointer,
@ -143,6 +144,7 @@ namespace dawn_wire {
// This function takes in the serialized result of // This function takes in the serialized result of
// client::MemoryTransferService::WriteHandle::SerializeDataUpdate. // client::MemoryTransferService::WriteHandle::SerializeDataUpdate.
// Needs to check potential offset/size OOB and overflow
virtual bool DeserializeDataUpdate(const void* deserializePointer, virtual bool DeserializeDataUpdate(const void* deserializePointer,
size_t deserializeSize, size_t deserializeSize,
size_t offset, size_t offset,
@ -152,7 +154,6 @@ namespace dawn_wire {
protected: protected:
void* mTargetData = nullptr; void* mTargetData = nullptr;
// TODO(dawn:773): only set backing buffer pointer data
size_t mDataLength = 0; size_t mDataLength = 0;
private: private:

View File

@ -113,6 +113,21 @@ TEST_P(BufferMappingTests, MapRead_Twice) {
buffer.Unmap(); buffer.Unmap();
} }
// Map read and test multiple get mapped range data
TEST_P(BufferMappingTests, MapRead_MultipleMappedRange) {
wgpu::Buffer buffer = CreateMapReadBuffer(12);
uint32_t myData[] = {0x00010203, 0x04050607, 0x08090a0b};
queue.WriteBuffer(buffer, 0, &myData, 12);
MapAsyncAndWait(buffer, wgpu::MapMode::Read, 0, 12);
ASSERT_EQ(myData[0], *static_cast<const uint32_t*>(buffer.GetConstMappedRange(0)));
ASSERT_EQ(myData[1], *(static_cast<const uint32_t*>(buffer.GetConstMappedRange(0)) + 1));
ASSERT_EQ(myData[2], *(static_cast<const uint32_t*>(buffer.GetConstMappedRange(0)) + 2));
ASSERT_EQ(myData[2], *static_cast<const uint32_t*>(buffer.GetConstMappedRange(8)));
buffer.Unmap();
}
// Test map-reading a large buffer. // Test map-reading a large buffer.
TEST_P(BufferMappingTests, MapRead_Large) { TEST_P(BufferMappingTests, MapRead_Large) {
constexpr uint32_t kDataSize = 1000 * 1000; constexpr uint32_t kDataSize = 1000 * 1000;
@ -257,6 +272,70 @@ TEST_P(BufferMappingTests, MapWrite_Twice) {
EXPECT_BUFFER_U32_EQ(myData, buffer, 0); EXPECT_BUFFER_U32_EQ(myData, buffer, 0);
} }
// Map write and unmap twice with different ranges and make sure the first write is preserved
TEST_P(BufferMappingTests, MapWrite_TwicePreserve) {
wgpu::Buffer buffer = CreateMapWriteBuffer(12);
uint32_t data1 = 0x08090a0b;
size_t offset1 = 8;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset1, sizeof(data1));
memcpy(buffer.GetMappedRange(offset1), &data1, sizeof(data1));
buffer.Unmap();
uint32_t data2 = 0x00010203;
size_t offset2 = 0;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset2, sizeof(data2));
memcpy(buffer.GetMappedRange(offset2), &data2, sizeof(data2));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(data1, buffer, offset1);
EXPECT_BUFFER_U32_EQ(data2, buffer, offset2);
}
// Map write and unmap twice with overlapping ranges and make sure data is updated correctly
TEST_P(BufferMappingTests, MapWrite_TwiceRangeOverlap) {
wgpu::Buffer buffer = CreateMapWriteBuffer(16);
uint32_t data1[] = {0x01234567, 0x89abcdef};
size_t offset1 = 8;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset1, 8);
memcpy(buffer.GetMappedRange(offset1), data1, 8);
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(0x00000000, buffer, 0);
EXPECT_BUFFER_U32_EQ(0x00000000, buffer, 4);
EXPECT_BUFFER_U32_EQ(0x01234567, buffer, 8);
EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 12);
uint32_t data2[] = {0x01234567, 0x89abcdef, 0x55555555};
size_t offset2 = 0;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, offset2, 12);
memcpy(buffer.GetMappedRange(offset2), data2, 12);
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(0x01234567, buffer, 0);
EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 4);
EXPECT_BUFFER_U32_EQ(0x55555555, buffer, 8);
EXPECT_BUFFER_U32_EQ(0x89abcdef, buffer, 12);
}
// Map write and test multiple mapped range data get updated correctly
TEST_P(BufferMappingTests, MapWrite_MultipleMappedRange) {
wgpu::Buffer buffer = CreateMapWriteBuffer(12);
uint32_t data1 = 0x08090a0b;
size_t offset1 = 8;
uint32_t data2 = 0x00010203;
size_t offset2 = 0;
MapAsyncAndWait(buffer, wgpu::MapMode::Write, 0, 12);
memcpy(buffer.GetMappedRange(offset1), &data1, sizeof(data1));
memcpy(buffer.GetMappedRange(offset2), &data2, sizeof(data2));
buffer.Unmap();
EXPECT_BUFFER_U32_EQ(data1, buffer, offset1);
EXPECT_BUFFER_U32_EQ(data2, buffer, offset2);
}
// Test mapping a large buffer. // Test mapping a large buffer.
TEST_P(BufferMappingTests, MapWrite_Large) { TEST_P(BufferMappingTests, MapWrite_Large) {
constexpr uint32_t kDataSize = 1000 * 1000; constexpr uint32_t kDataSize = 1000 * 1000;
@ -330,20 +409,26 @@ TEST_P(BufferMappingTests, OffsetNotUpdatedOnError) {
queue.WriteBuffer(buffer, 0, data, sizeof(data)); queue.WriteBuffer(buffer, 0, data, sizeof(data));
// Map the buffer but do not wait on the result yet. // Map the buffer but do not wait on the result yet.
bool done = false; bool done1 = false;
bool done2 = false;
buffer.MapAsync( buffer.MapAsync(
wgpu::MapMode::Read, 8, 4, wgpu::MapMode::Read, 8, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) { [](WGPUBufferMapAsyncStatus status, void* userdata) {
ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status); ASSERT_EQ(WGPUBufferMapAsyncStatus_Success, status);
*static_cast<bool*>(userdata) = true; *static_cast<bool*>(userdata) = true;
}, },
&done); &done1);
// Call MapAsync another time, it is an error because the buffer is already being mapped so // Call MapAsync another time, it is an error because the buffer is already being mapped so
// mMapOffset is not updated. // mMapOffset is not updated.
ASSERT_DEVICE_ERROR(buffer.MapAsync(wgpu::MapMode::Read, 0, 4, nullptr, nullptr)); ASSERT_DEVICE_ERROR(buffer.MapAsync(
wgpu::MapMode::Read, 0, 4,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
*static_cast<bool*>(userdata) = true;
},
&done2));
while (!done) { while (!done1 || !done2) {
WaitABit(); WaitABit();
} }

View File

@ -47,17 +47,7 @@ class WireBufferMappingTests : public WireTest {
WireTest::SetUp(); WireTest::SetUp();
mockBufferMapCallback = std::make_unique<StrictMock<MockBufferMapCallback>>(); mockBufferMapCallback = std::make_unique<StrictMock<MockBufferMapCallback>>();
WGPUBufferDescriptor descriptor = {};
descriptor.size = kBufferSize;
apiBuffer = api.GetNewBuffer(); apiBuffer = api.GetNewBuffer();
buffer = wgpuDeviceCreateBuffer(device, &descriptor);
EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _))
.WillOnce(Return(apiBuffer))
.RetiresOnSaturation();
FlushClient();
} }
void TearDown() override { void TearDown() override {
@ -77,6 +67,19 @@ class WireBufferMappingTests : public WireTest {
Mock::VerifyAndClearExpectations(&mockBufferMapCallback); Mock::VerifyAndClearExpectations(&mockBufferMapCallback);
} }
void SetupBuffer(WGPUBufferUsageFlags usage) {
WGPUBufferDescriptor descriptor = {};
descriptor.size = kBufferSize;
descriptor.usage = usage;
buffer = wgpuDeviceCreateBuffer(device, &descriptor);
EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _))
.WillOnce(Return(apiBuffer))
.RetiresOnSaturation();
FlushClient();
}
protected: protected:
static constexpr uint64_t kBufferSize = sizeof(uint32_t); static constexpr uint64_t kBufferSize = sizeof(uint32_t);
// A successfully created buffer // A successfully created buffer
@ -85,9 +88,21 @@ class WireBufferMappingTests : public WireTest {
}; };
// Tests specific to mapping for reading // Tests specific to mapping for reading
class WireBufferMappingReadTests : public WireBufferMappingTests {
public:
WireBufferMappingReadTests() {
}
~WireBufferMappingReadTests() override = default;
void SetUp() override {
WireBufferMappingTests::SetUp();
SetupBuffer(WGPUBufferUsage_MapRead);
}
};
// Check mapping for reading a succesfully created buffer // Check mapping for reading a succesfully created buffer
TEST_F(WireBufferMappingTests, MappingForReadSuccessBuffer) { TEST_F(WireBufferMappingReadTests, MappingForReadSuccessBuffer) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -115,7 +130,7 @@ TEST_F(WireBufferMappingTests, MappingForReadSuccessBuffer) {
// Check that things work correctly when a validation error happens when mapping the buffer for // Check that things work correctly when a validation error happens when mapping the buffer for
// reading // reading
TEST_F(WireBufferMappingTests, ErrorWhileMappingForRead) { TEST_F(WireBufferMappingReadTests, ErrorWhileMappingForRead) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _)) EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
@ -133,7 +148,7 @@ TEST_F(WireBufferMappingTests, ErrorWhileMappingForRead) {
// Check that the map read callback is called with UNKNOWN when the buffer is destroyed before the // Check that the map read callback is called with UNKNOWN when the buffer is destroyed before the
// request is finished // request is finished
TEST_F(WireBufferMappingTests, DestroyBeforeReadRequestEnd) { TEST_F(WireBufferMappingReadTests, DestroyBeforeReadRequestEnd) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
// Return success // Return success
@ -158,7 +173,7 @@ TEST_F(WireBufferMappingTests, DestroyBeforeReadRequestEnd) {
// Check the map read callback is called with "UnmappedBeforeCallback" when the map request would // Check the map read callback is called with "UnmappedBeforeCallback" when the map request would
// have worked, but Unmap was called // have worked, but Unmap was called
TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForRead) { TEST_F(WireBufferMappingReadTests, UnmapCalledTooEarlyForRead) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -186,7 +201,7 @@ TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForRead) {
// Check that even if Unmap() was called early client-side, we correctly surface server-side // Check that even if Unmap() was called early client-side, we correctly surface server-side
// validation errors. // validation errors.
TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForReadButServerSideError) { TEST_F(WireBufferMappingReadTests, UnmapCalledTooEarlyForReadButServerSideError) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _)) EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
@ -209,7 +224,7 @@ TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForReadButServerSideError) {
// Check the map read callback is called with "DestroyedBeforeCallback" when the map request would // Check the map read callback is called with "DestroyedBeforeCallback" when the map request would
// have worked, but Destroy was called // have worked, but Destroy was called
TEST_F(WireBufferMappingTests, DestroyCalledTooEarlyForRead) { TEST_F(WireBufferMappingReadTests, DestroyCalledTooEarlyForRead) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -237,7 +252,7 @@ TEST_F(WireBufferMappingTests, DestroyCalledTooEarlyForRead) {
// Check that even if Destroy() was called early client-side, we correctly surface server-side // Check that even if Destroy() was called early client-side, we correctly surface server-side
// validation errors. // validation errors.
TEST_F(WireBufferMappingTests, DestroyCalledTooEarlyForReadButServerSideError) { TEST_F(WireBufferMappingReadTests, DestroyCalledTooEarlyForReadButServerSideError) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _)) EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _, _))
@ -258,8 +273,9 @@ TEST_F(WireBufferMappingTests, DestroyCalledTooEarlyForReadButServerSideError) {
FlushServer(); FlushServer();
} }
// Check that an error map read callback gets nullptr while a buffer is already mapped // Check that an error map read while a buffer is already mapped won't changed the result of get
TEST_F(WireBufferMappingTests, MappingForReadingErrorWhileAlreadyMappedGetsNullptr) { // mapped range
TEST_F(WireBufferMappingReadTests, MappingForReadingErrorWhileAlreadyMappedUnchangeMapData) {
// Successful map // Successful map
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
@ -289,11 +305,12 @@ TEST_F(WireBufferMappingTests, MappingForReadingErrorWhileAlreadyMappedGetsNullp
FlushServer(); FlushServer();
EXPECT_EQ(nullptr, wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)); EXPECT_EQ(bufferContent,
*static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
} }
// Test that the MapReadCallback isn't fired twice when unmap() is called inside the callback // Test that the MapReadCallback isn't fired twice when unmap() is called inside the callback
TEST_F(WireBufferMappingTests, UnmapInsideMapReadCallback) { TEST_F(WireBufferMappingReadTests, UnmapInsideMapReadCallback) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -318,7 +335,7 @@ TEST_F(WireBufferMappingTests, UnmapInsideMapReadCallback) {
// Test that the MapReadCallback isn't fired twice the buffer external refcount reaches 0 in the // Test that the MapReadCallback isn't fired twice the buffer external refcount reaches 0 in the
// callback // callback
TEST_F(WireBufferMappingTests, DestroyInsideMapReadCallback) { TEST_F(WireBufferMappingReadTests, DestroyInsideMapReadCallback) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Read, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -342,9 +359,21 @@ TEST_F(WireBufferMappingTests, DestroyInsideMapReadCallback) {
} }
// Tests specific to mapping for writing // Tests specific to mapping for writing
class WireBufferMappingWriteTests : public WireBufferMappingTests {
public:
WireBufferMappingWriteTests() {
}
~WireBufferMappingWriteTests() override = default;
void SetUp() override {
WireBufferMappingTests::SetUp();
SetupBuffer(WGPUBufferUsage_MapWrite);
}
};
// Check mapping for writing a succesfully created buffer // Check mapping for writing a succesfully created buffer
TEST_F(WireBufferMappingTests, MappingForWriteSuccessBuffer) { TEST_F(WireBufferMappingWriteTests, MappingForWriteSuccessBuffer) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t serverBufferContent = 31337; uint32_t serverBufferContent = 31337;
@ -382,7 +411,7 @@ TEST_F(WireBufferMappingTests, MappingForWriteSuccessBuffer) {
// Check that things work correctly when a validation error happens when mapping the buffer for // Check that things work correctly when a validation error happens when mapping the buffer for
// writing // writing
TEST_F(WireBufferMappingTests, ErrorWhileMappingForWrite) { TEST_F(WireBufferMappingWriteTests, ErrorWhileMappingForWrite) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _)) EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
@ -400,7 +429,7 @@ TEST_F(WireBufferMappingTests, ErrorWhileMappingForWrite) {
// Check that the map write callback is called with "DestroyedBeforeCallback" when the buffer is // Check that the map write callback is called with "DestroyedBeforeCallback" when the buffer is
// destroyed before the request is finished // destroyed before the request is finished
TEST_F(WireBufferMappingTests, DestroyBeforeWriteRequestEnd) { TEST_F(WireBufferMappingWriteTests, DestroyBeforeWriteRequestEnd) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
// Return success // Return success
@ -423,9 +452,9 @@ TEST_F(WireBufferMappingTests, DestroyBeforeWriteRequestEnd) {
FlushServer(); FlushServer();
} }
// Check the map read callback is called with "UnmappedBeforeCallback" when the map request would // Check the map write callback is called with "UnmappedBeforeCallback" when the map request would
// have worked, but Unmap was called // have worked, but Unmap was called
TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForWrite) { TEST_F(WireBufferMappingWriteTests, UnmapCalledTooEarlyForWrite) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -447,8 +476,8 @@ TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForWrite) {
FlushServer(); FlushServer();
} }
// Check that an error map read callback gets nullptr while a buffer is already mapped // Check that an error map write while a buffer is already mapped
TEST_F(WireBufferMappingTests, MappingForWritingErrorWhileAlreadyMappedGetsNullptr) { TEST_F(WireBufferMappingWriteTests, MappingForWritingErrorWhileAlreadyMapped) {
// Successful map // Successful map
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
@ -478,11 +507,12 @@ TEST_F(WireBufferMappingTests, MappingForWritingErrorWhileAlreadyMappedGetsNullp
FlushServer(); FlushServer();
EXPECT_EQ(nullptr, wgpuBufferGetMappedRange(buffer, 0, kBufferSize)); EXPECT_NE(nullptr,
static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
} }
// Test that the MapWriteCallback isn't fired twice when unmap() is called inside the callback // Test that the MapWriteCallback isn't fired twice when unmap() is called inside the callback
TEST_F(WireBufferMappingTests, UnmapInsideMapWriteCallback) { TEST_F(WireBufferMappingWriteTests, UnmapInsideMapWriteCallback) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -507,7 +537,7 @@ TEST_F(WireBufferMappingTests, UnmapInsideMapWriteCallback) {
// Test that the MapWriteCallback isn't fired twice the buffer external refcount reaches 0 in the // Test that the MapWriteCallback isn't fired twice the buffer external refcount reaches 0 in the
// callback // callback
TEST_F(WireBufferMappingTests, DestroyInsideMapWriteCallback) { TEST_F(WireBufferMappingWriteTests, DestroyInsideMapWriteCallback) {
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, nullptr);
uint32_t bufferContent = 31337; uint32_t bufferContent = 31337;
@ -578,6 +608,7 @@ TEST_F(WireBufferMappingTests, MappedAtCreationReleaseBeforeUnmap) {
TEST_F(WireBufferMappingTests, MappedAtCreationThenMapSuccess) { TEST_F(WireBufferMappingTests, MappedAtCreationThenMapSuccess) {
WGPUBufferDescriptor descriptor = {}; WGPUBufferDescriptor descriptor = {};
descriptor.size = 4; descriptor.size = 4;
descriptor.usage = WGPUMapMode_Write;
descriptor.mappedAtCreation = true; descriptor.mappedAtCreation = true;
WGPUBuffer apiBuffer = api.GetNewBuffer(); WGPUBuffer apiBuffer = api.GetNewBuffer();
@ -639,7 +670,8 @@ TEST_F(WireBufferMappingTests, MappedAtCreationThenMapFailure) {
FlushServer(); FlushServer();
EXPECT_EQ(nullptr, wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)); EXPECT_NE(nullptr,
static_cast<const uint32_t*>(wgpuBufferGetConstMappedRange(buffer, 0, kBufferSize)));
wgpuBufferUnmap(buffer); wgpuBufferUnmap(buffer);
EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
@ -694,6 +726,7 @@ TEST_F(WireBufferMappingTests, MaxSizeMappableBufferOOMDirectly) {
// Test that registering a callback then wire disconnect calls the callback with // Test that registering a callback then wire disconnect calls the callback with
// DeviceLost. // DeviceLost.
TEST_F(WireBufferMappingTests, MapThenDisconnect) { TEST_F(WireBufferMappingTests, MapThenDisconnect) {
SetupBuffer(WGPUMapMode_Write);
wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, this); wgpuBufferMapAsync(buffer, WGPUMapMode_Write, 0, kBufferSize, ToMockBufferMapCallback, this);
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _)) EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _, _))
@ -711,6 +744,8 @@ TEST_F(WireBufferMappingTests, MapThenDisconnect) {
// Test that registering a callback after wire disconnect calls the callback with // Test that registering a callback after wire disconnect calls the callback with
// DeviceLost. // DeviceLost.
TEST_F(WireBufferMappingTests, MapAfterDisconnect) { TEST_F(WireBufferMappingTests, MapAfterDisconnect) {
SetupBuffer(WGPUMapMode_Read);
GetWireClient()->Disconnect(); GetWireClient()->Disconnect();
EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DeviceLost, this)).Times(1); EXPECT_CALL(*mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_DeviceLost, this)).Times(1);

View File

@ -56,58 +56,3 @@ TEST_F(WireDestroyObjectTests, DestroyDeviceDestroysChildren) {
wgpuCommandEncoderFinish(encoder, nullptr); wgpuCommandEncoderFinish(encoder, nullptr);
FlushClient(false); FlushClient(false);
} }
// Test that calling a function that would generate an InjectError doesn't crash after
// the device is destroyed.
TEST_F(WireDestroyObjectTests, ImplicitInjectErrorAfterDestroyDevice) {
WGPUBufferDescriptor bufferDesc = {};
bufferDesc.size = 4;
WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
WGPUBuffer apiBuffer = api.GetNewBuffer();
EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
FlushClient();
{
// Control case: MapAsync errors on invalid WGPUMapMode.
MockCallback<WGPUBufferMapCallback> mockBufferMapCallback;
EXPECT_CALL(api, DeviceInjectError(apiDevice, WGPUErrorType_Validation, _));
EXPECT_CALL(mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Error, this));
wgpuBufferMapAsync(buffer, WGPUMapMode(0), 0, 4, mockBufferMapCallback.Callback(),
mockBufferMapCallback.MakeUserdata(this));
FlushClient();
}
{
// Now, release the device. InjectError shouldn't happen.
wgpuDeviceRelease(device);
MockCallback<WGPUBufferMapCallback> mockBufferMapCallback;
EXPECT_CALL(mockBufferMapCallback, Call(WGPUBufferMapAsyncStatus_Error, this + 1));
wgpuBufferMapAsync(buffer, WGPUMapMode(0), 0, 4, mockBufferMapCallback.Callback(),
mockBufferMapCallback.MakeUserdata(this + 1));
Sequence s1, s2;
// The device and child objects alre also released.
EXPECT_CALL(api, BufferRelease(apiBuffer)).InSequence(s1);
EXPECT_CALL(api, QueueRelease(apiQueue)).InSequence(s2);
EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr))
.Times(1)
.InSequence(s1, s2);
EXPECT_CALL(api, DeviceRelease(apiDevice)).InSequence(s1, s2);
FlushClient();
// Signal that we already released and cleared callbacks for |apiDevice|
DefaultApiDeviceWasReleased();
}
}

File diff suppressed because it is too large Load Diff