Optimize Dawn Wire WriteTexture
Reduces the number of memcpys that are done when uploading texture data
via WriteTexture using the wire client. Does so by directly managing the
allocation of shared memory and writing to that rather than copying the
data to the wire command buffer first, which may involve chunking and
additional copies.
In tests so far this performs up to 2X better than the previous
WriteTexture implementation, depending on data size. (Larger uploads
typically see a bigger improvement.)
Very similar to recent WriteBuffer optimizations proposed by Loko:
https://dawn-review.googlesource.com/c/dawn/+/271535
Bug: 441900745
Change-Id: Ie85e2d38e4db9ad85465640a4f0d65f07c69fa78
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/273396
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/dawn_wire.json b/src/dawn/dawn_wire.json
index 055e6c0..fbce45f 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -84,9 +84,9 @@
{ "name": "future", "type": "future" }
],
"queue write buffer": [
- {"name": "queue id", "type": "ObjectId", "id_type": "queue" },
- {"name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
- {"name": "buffer offset", "type": "uint64_t"},
+ { "name": "queue id", "type": "ObjectId", "id_type": "queue" },
+ { "name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
+ { "name": "buffer offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"},
{ "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},
@@ -94,12 +94,15 @@
{ "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true}
],
"queue write texture": [
- {"name": "queue id", "type": "ObjectId", "id_type": "queue" },
- {"name": "destination", "type": "texel copy texture info", "annotation": "const*"},
- {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "data size", "wire_is_data_only": true},
- {"name": "data size", "type": "uint64_t"},
- {"name": "data layout", "type": "texel copy buffer layout", "annotation": "const*"},
- {"name": "writeSize", "type": "extent 3D", "annotation": "const*"}
+ { "name": "queue id", "type": "ObjectId", "id_type": "queue" },
+ { "name": "destination", "type": "texel copy texture info", "annotation": "const*"},
+ { "name": "data size", "type": "uint64_t"},
+ { "name": "data layout", "type": "texel copy buffer layout", "annotation": "const*"},
+ { "name": "writeSize", "type": "extent 3D", "annotation": "const*"},
+ { "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},
+ { "name": "write data update info length", "type": "uint64_t" },
+ { "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true}
],
"shader module get compilation info": [
{ "name": "shader module id", "type": "ObjectId", "id_type": "shader module" },
diff --git a/src/dawn/wire/client/Queue.cpp b/src/dawn/wire/client/Queue.cpp
index cf8803c..82b5cd0 100644
--- a/src/dawn/wire/client/Queue.cpp
+++ b/src/dawn/wire/client/Queue.cpp
@@ -174,15 +174,47 @@
size_t dataSize,
const WGPUTexelCopyBufferLayout* dataLayout,
const WGPUExtent3D* writeSize) {
+ Client* client = GetClient();
+
+ // Create write handle and prepare to serialize command.
+ size_t writeHandleCreateInfoLength = 0;
+ std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle(
+ client->GetMemoryTransferService()->CreateWriteHandle(dataSize));
+ if (writeHandle == nullptr) {
+ // Trigger a device loss.
+ client->Disconnect();
+ return;
+ }
+ writeHandleCreateInfoLength = writeHandle->SerializeCreateSize();
+
+ // Write the data to the allocated memory.
+ memcpy(writeHandle->GetData(), data, dataSize);
+
+ // Prepare to serialize data update command.
+ size_t writeDataUpdateInfoLength = writeHandle->SizeOfSerializeDataUpdate(0u, dataSize);
+
QueueWriteTextureCmd cmd;
cmd.queueId = GetWireHandle(GetClient()).id;
cmd.destination = destination;
- cmd.data = static_cast<const uint8_t*>(data);
cmd.dataSize = dataSize;
cmd.dataLayout = dataLayout;
cmd.writeSize = writeSize;
+ // Set the pointer lengths, but the pointed-to data itself won't be serialized as usual (due
+ // to skip_serialize). Instead, the custom CommandExtensions below fill that memory. [*]
+ cmd.writeHandleCreateInfoLength = writeHandleCreateInfoLength;
+ cmd.writeHandleCreateInfo = nullptr; // Skipped by skip_serialize.
+ cmd.writeDataUpdateInfoLength = writeDataUpdateInfoLength;
+ cmd.writeDataUpdateInfo = nullptr; // Skipped by skip_serialize.
- GetClient()->SerializeCommand(cmd);
+ client->SerializeCommand(
+ cmd,
+ // Extensions to replace fields skipped by skip_serialize.
+ CommandExtension{
+ writeHandleCreateInfoLength,
+ [&](char* writeHandleBuffer) { writeHandle->SerializeCreate(writeHandleBuffer); }},
+ CommandExtension{writeDataUpdateInfoLength, [&](char* writeHandleBuffer) {
+ writeHandle->SerializeDataUpdate(writeHandleBuffer, 0u, cmd.dataSize);
+ }});
}
} // namespace dawn::wire::client
diff --git a/src/dawn/wire/server/ServerQueue.cpp b/src/dawn/wire/server/ServerQueue.cpp
index d35355f..9c08eca 100644
--- a/src/dawn/wire/server/ServerQueue.cpp
+++ b/src/dawn/wire/server/ServerQueue.cpp
@@ -108,16 +108,49 @@
WireResult Server::DoQueueWriteTexture(Known<WGPUQueue> queue,
const WGPUTexelCopyTextureInfo* destination,
- const uint8_t* data,
uint64_t dataSize,
const WGPUTexelCopyBufferLayout* dataLayout,
- const WGPUExtent3D* writeSize) {
+ const WGPUExtent3D* writeSize,
+ uint64_t writeHandleCreateInfoLength,
+ const uint8_t* writeHandleCreateInfo,
+ uint64_t writeDataUpdateInfoLength,
+ const uint8_t* writeDataUpdateInfo) {
if (dataSize > std::numeric_limits<size_t>::max()) {
return WireResult::FatalError;
}
- mProcs.queueWriteTexture(queue->handle, destination, data, static_cast<size_t>(dataSize),
- dataLayout, writeSize);
+ 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 WireResult::FatalError;
+ }
+
+ // Try first to use GetSourceData if the memory transfer service implements
+ // it. If so, we can avoid a copy.
+ uint8_t* sourceData = writeHandle->GetSourceData();
+ if (sourceData) {
+ mProcs.queueWriteTexture(queue->handle, destination, sourceData,
+ static_cast<size_t>(dataSize), dataLayout, writeSize);
+ return WireResult::Success;
+ }
+
+ // Otherwise, fall back to DeserializeDataUpdate.
+ auto backingData = std::make_unique<char[]>(dataSize);
+ writeHandle->SetTarget(backingData.get());
+ writeHandle->SetDataLength(dataSize);
+
+ // Deserialize the flush info and flush updated data from the handle into the target
+ // of the handle that's just a temporary allocation from above right now.
+ if (!writeHandle->DeserializeDataUpdate(writeDataUpdateInfo,
+ static_cast<size_t>(writeDataUpdateInfoLength), 0u,
+ static_cast<size_t>(dataSize))) {
+ return WireResult::FatalError;
+ }
+
+ mProcs.queueWriteTexture(queue->handle, destination, backingData.get(),
+ static_cast<size_t>(dataSize), dataLayout, writeSize);
return WireResult::Success;
}