[wgpu-headers] Updates mapAsync callback to match wgpu-header.
- Introduces 2nd userdata.
- Updates status and include message. Note that we are using a new
status enum all together to (1) standardize the enum naming with the
other async entry points, and (2) make it easier to update users.
- Note that usages will be updated in a follow up together with
onSubmittedWorkDone since these callbacks have some dependencies.
Bug: 42241461
Change-Id: Ic42ee38213a2df907c02001f9636dbbdca86ef37
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188444
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/generator/templates/api_cpp.h b/generator/templates/api_cpp.h
index d7d62f3..63e097d 100644
--- a/generator/templates/api_cpp.h
+++ b/generator/templates/api_cpp.h
@@ -280,7 +280,7 @@
{{as_annotated_cppType(arg)}}{{ ", "}}
{%- endif -%}
{%- endfor -%}
- {{as_cppType(types["callback mode"].name)}} mode, F callback, T userdata) const
+ {{as_cppType(types["callback mode"].name)}} callbackMode, F callback, T userdata) const
{%- endmacro %}
//* This rendering macro should ONLY be used for callback info type functions.
@@ -313,7 +313,7 @@
{{as_annotated_cppType(arg)}}{{ ", "}}
{%- endif -%}
{%- endfor -%}
- {{as_cppType(types["callback mode"].name)}} mode, L callback) const
+ {{as_cppType(types["callback mode"].name)}} callbackMode, L callback) const
{%- endmacro %}
//* This rendering macro should NOT be used for callback info type functions.
@@ -366,7 +366,7 @@
{% set CallbackInfoType = (method.arguments|last).type %}
{% set CallbackType = (CallbackInfoType.members|first).type %}
{{as_cType(CallbackInfoType.name)}} callbackInfo = {};
- callbackInfo.mode = static_cast<{{as_cType(types["callback mode"].name)}}>(mode);
+ callbackInfo.mode = static_cast<{{as_cType(types["callback mode"].name)}}>(callbackMode);
callbackInfo.callback = [](
{%- for arg in CallbackType.arguments -%}
{{as_annotated_cType(arg)}}{{", "}}
@@ -402,7 +402,7 @@
);
{{as_cType(CallbackInfoType.name)}} callbackInfo = {};
- callbackInfo.mode = static_cast<{{as_cType(types["callback mode"].name)}}>(mode);
+ callbackInfo.mode = static_cast<{{as_cType(types["callback mode"].name)}}>(callbackMode);
if constexpr (std::is_convertible_v<L, F*>) {
callbackInfo.callback = [](
{%- for arg in CallbackType.arguments -%}
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index f1cf9a6..8188df3 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -618,6 +618,18 @@
]
},
{
+ "name": "map async 2",
+ "_comment": "TODO(crbug.com/dawn/2021): This is dawn/emscripten-only until we rename it to replace the old API. See bug for details.",
+ "tags": ["dawn", "emscripten"],
+ "returns": "future",
+ "args": [
+ {"name": "mode", "type": "map mode"},
+ {"name": "offset", "type": "size_t"},
+ {"name": "size", "type": "size_t"},
+ {"name": "callback info", "type": "buffer map callback info 2"}
+ ]
+ },
+ {
"name": "get mapped range",
"returns": "void *",
"args": [
@@ -697,6 +709,13 @@
{"name": "userdata", "type": "void *"}
]
},
+ "buffer map callback 2": {
+ "category": "callback function",
+ "args": [
+ {"name": "status", "type": "map async status"},
+ {"name": "message", "type": "char", "annotation": "const*", "length": "strlen"}
+ ]
+ },
"buffer map callback info": {
"category": "structure",
"extensible": "in",
@@ -706,6 +725,12 @@
{"name": "userdata", "type": "void *"}
]
},
+ "buffer map callback info 2": {
+ "category": "callback info",
+ "members": [
+ {"name": "callback", "type": "buffer map callback 2"}
+ ]
+ },
"buffer map async status": {
"category": "enum",
"emscripten_no_enum_table": true,
@@ -722,6 +747,17 @@
{"value": 9, "name": "size out of range"}
]
},
+ "map async status": {
+ "category": "enum",
+ "emscripten_no_enum_table": true,
+ "values": [
+ {"value": 0, "name": "success"},
+ {"value": 1, "name": "instance dropped"},
+ {"value": 2, "name": "error"},
+ {"value": 3, "name": "aborted"},
+ {"value": 4, "name": "unknown"}
+ ]
+ },
"buffer map state": {
"category": "enum",
"values": [
diff --git a/src/dawn/dawn_wire.json b/src/dawn/dawn_wire.json
index 88ffb7e..da0aab9 100644
--- a/src/dawn/dawn_wire.json
+++ b/src/dawn/dawn_wire.json
@@ -37,7 +37,8 @@
{ "name": "future", "type": "future" },
{ "name": "mode", "type": "map mode" },
{ "name": "offset", "type": "uint64_t"},
- { "name": "size", "type": "uint64_t"}
+ { "name": "size", "type": "uint64_t"},
+ { "name": "userdata count", "type": "uint8_t", "_comment": "TODO(crbug.com/dawn/2509): Remove this once Chromium overrides the correct functions in the proc table."}
],
"buffer update mapped data": [
{ "name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
@@ -127,6 +128,9 @@
{ "name": "event manager", "type": "ObjectHandle" },
{ "name": "future", "type": "future" },
{ "name": "status", "type": "buffer map async status" },
+ { "name": "status2", "type": "map async status" },
+ { "name": "message", "type": "char", "annotation": "const*", "length": "strlen", "optional": true },
+ { "name": "userdata count", "type": "uint8_t", "_comment": "TODO(crbug.com/dawn/2509): Remove this once Chromium overrides the correct functions in the proc table."},
{ "name": "read data update info length", "type": "uint64_t" },
{ "name": "read data update info", "type": "uint8_t", "annotation": "const*", "length": "read data update info length", "skip_serialize": true }
],
@@ -219,6 +223,7 @@
"AdapterRequestDevice2",
"BufferMapAsync",
"BufferMapAsyncF",
+ "BufferMapAsync2",
"BufferGetConstMappedRange",
"BufferGetMappedRange",
"BufferGetMapState",
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 974ac9b..9cdffb0 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -33,6 +33,7 @@
#include <string>
#include <utility>
+#include "absl/container/flat_hash_map.h"
#include "absl/strings/str_format.h"
#include "dawn/common/Alloc.h"
#include "dawn/common/Assert.h"
@@ -48,6 +49,7 @@
#include "dawn/native/ObjectType_autogen.h"
#include "dawn/native/PhysicalDevice.h"
#include "dawn/native/Queue.h"
+#include "dawn/native/SystemEvent.h"
#include "dawn/native/ValidationUtils_autogen.h"
#include "dawn/platform/DawnPlatform.h"
#include "dawn/platform/tracing/TraceEvent.h"
@@ -165,7 +167,21 @@
} // anonymous namespace
-struct BufferBase::MapAsyncEvent final : public EventManager::TrackedEvent {
+struct BufferBase::MapAsyncEvent : public EventManager::TrackedEvent {
+ MapAsyncEvent(wgpu::CallbackMode mode, QueueBase* queue, ExecutionSerial serial)
+ : TrackedEvent(mode, queue, serial) {}
+ // TODO(crbug.com/42241006): Note that currently this constructor is used for errors because a
+ // dropped device can result in failure when trying to get the queue. While using this fixes the
+ // problem, it causes an unintentional side-effect where it is possible to run into a
+ // UnsupportedMixedSource error on WaitAny even if we are only using a single device with
+ // MapAsync calls. This will be fixed once we correctly implement mixed sources.
+ explicit MapAsyncEvent(wgpu::CallbackMode mode)
+ : TrackedEvent(mode, SystemEvent::CreateSignaled()) {}
+
+ virtual void UnmapEarly(wgpu::BufferMapAsyncStatus status) = 0;
+};
+
+struct BufferBase::MapAsyncEvent1 final : public BufferBase::MapAsyncEvent {
// MapAsyncEvent stores a raw pointer to the buffer so that it can
// update the buffer's map state when it completes.
// If the map completes early (error, unmap, destroy), then the buffer
@@ -180,11 +196,11 @@
raw_ptr<void> mUserdata;
// Create an event backed by the given queue execution serial.
- MapAsyncEvent(DeviceBase* device,
- BufferBase* buffer,
- const BufferMapCallbackInfo& callbackInfo,
- ExecutionSerial serial)
- : TrackedEvent(callbackInfo.mode, device->GetQueue(), serial),
+ MapAsyncEvent1(DeviceBase* device,
+ BufferBase* buffer,
+ const BufferMapCallbackInfo& callbackInfo,
+ ExecutionSerial serial)
+ : MapAsyncEvent(callbackInfo.mode, device->GetQueue(), serial),
mBufferOrEarlyStatus(buffer),
mCallback(callbackInfo.callback),
mUserdata(callbackInfo.userdata) {
@@ -193,10 +209,10 @@
}
// Create an event that's ready at creation (for errors, etc.)
- MapAsyncEvent(DeviceBase* device,
- const BufferMapCallbackInfo& callbackInfo,
- wgpu::BufferMapAsyncStatus earlyStatus)
- : TrackedEvent(callbackInfo.mode, device->GetQueue(), kBeginningOfGPUTime),
+ MapAsyncEvent1(DeviceBase* device,
+ const BufferMapCallbackInfo& callbackInfo,
+ wgpu::BufferMapAsyncStatus earlyStatus)
+ : MapAsyncEvent(callbackInfo.mode, device->GetQueue(), kBeginningOfGPUTime),
mBufferOrEarlyStatus(earlyStatus),
mCallback(callbackInfo.callback),
mUserdata(callbackInfo.userdata) {
@@ -204,7 +220,7 @@
uint64_t(kBeginningOfGPUTime));
}
- ~MapAsyncEvent() override { EnsureComplete(EventCompletionType::Shutdown); }
+ ~MapAsyncEvent1() override { EnsureComplete(EventCompletionType::Shutdown); }
void Complete(EventCompletionType completionType) override {
if (const auto* queueAndSerial = std::get_if<QueueAndSerial>(&GetCompletionData())) {
@@ -245,11 +261,128 @@
// This can race with Complete such that the early status is ignored, but this is OK
// because we will still unmap the buffer. It will be as-if the application called
// Unmap/Destroy just after the map event completed.
- void UnmapEarly(wgpu::BufferMapAsyncStatus status) {
+ void UnmapEarly(wgpu::BufferMapAsyncStatus status) override {
mBufferOrEarlyStatus.Use([&](auto bufferOrEarlyStatus) { *bufferOrEarlyStatus = status; });
}
};
+struct BufferBase::MapAsyncEvent2 final : public BufferBase::MapAsyncEvent {
+ // MapAsyncEvent stores a raw pointer to the buffer so that it can update the buffer's map state
+ // when it completes. If the map completes early (error, unmap, destroy), then the buffer is no
+ // longer needed and we store the early status instead. The raw pointer is safe because the
+ // early status is set to destroyed before the buffer is dropped. Note: this could be an atomic
+ // + spin lock on a sentinel enum if the mutex cost is high.
+ struct BufferErrorData {
+ WGPUMapAsyncStatus status;
+ std::string message;
+ };
+ MutexProtected<std::variant<BufferBase*, BufferErrorData>> mBufferOrError;
+
+ WGPUBufferMapCallback2 mCallback;
+ raw_ptr<void> mUserdata1;
+ raw_ptr<void> mUserdata2;
+
+ // Create an event backed by the given queue execution serial.
+ MapAsyncEvent2(DeviceBase* device,
+ BufferBase* buffer,
+ const WGPUBufferMapCallbackInfo2& callbackInfo,
+ ExecutionSerial serial)
+ : MapAsyncEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode),
+ device->GetQueue(),
+ serial),
+ mBufferOrError(buffer),
+ mCallback(callbackInfo.callback),
+ mUserdata1(callbackInfo.userdata1),
+ mUserdata2(callbackInfo.userdata2) {
+ TRACE_EVENT_ASYNC_BEGIN0(device->GetPlatform(), General, "Buffer::APIMapAsync",
+ uint64_t(serial));
+ }
+
+ // Create an event that's ready at creation (for errors, etc.)
+ MapAsyncEvent2(DeviceBase* device,
+ const WGPUBufferMapCallbackInfo2& callbackInfo,
+ const std::string& message)
+ : MapAsyncEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode)),
+ mBufferOrError(BufferErrorData{WGPUMapAsyncStatus_Error, message}),
+ mCallback(callbackInfo.callback),
+ mUserdata1(callbackInfo.userdata1),
+ mUserdata2(callbackInfo.userdata2) {
+ TRACE_EVENT_ASYNC_BEGIN0(device->GetPlatform(), General, "Buffer::APIMapAsync",
+ uint64_t(kBeginningOfGPUTime));
+ }
+
+ ~MapAsyncEvent2() override { EnsureComplete(EventCompletionType::Shutdown); }
+
+ void Complete(EventCompletionType completionType) override {
+ if (const auto* queueAndSerial = std::get_if<QueueAndSerial>(&GetCompletionData())) {
+ TRACE_EVENT_ASYNC_END0(queueAndSerial->queue->GetDevice()->GetPlatform(), General,
+ "Buffer::APIMapAsync",
+ uint64_t(queueAndSerial->completionSerial));
+ }
+
+ void* userdata1 = mUserdata1.ExtractAsDangling();
+ void* userdata2 = mUserdata2.ExtractAsDangling();
+
+ if (completionType == EventCompletionType::Shutdown) {
+ mCallback(WGPUMapAsyncStatus_InstanceDropped,
+ "A valid external Instance reference no longer exists.", userdata1,
+ userdata2);
+ return;
+ }
+
+ bool error = false;
+ BufferErrorData pendingErrorData;
+ Ref<MapAsyncEvent> pendingMapEvent;
+
+ // Lock the buffer / error. This may race with UnmapEarly which occurs when the buffer is
+ // unmapped or destroyed.
+ mBufferOrError.Use([&](auto bufferOrError) {
+ if (auto* errorData = std::get_if<BufferErrorData>(&*bufferOrError)) {
+ // Assign the early error, if it was set.
+ pendingErrorData = *errorData;
+ error = true;
+ } else if (auto** buffer = std::get_if<BufferBase*>(&*bufferOrError)) {
+ // Set the buffer state to Mapped if this pending map succeeded.
+ // TODO(crbug.com/dawn/831): in order to be thread safe, mutation of the
+ // state and pending map event needs to be atomic w.r.t. UnmapInternal.
+ DAWN_ASSERT((*buffer)->mState == BufferState::PendingMap);
+ (*buffer)->mState = BufferState::Mapped;
+
+ pendingMapEvent = std::move((*buffer)->mPendingMapEvent);
+ }
+ });
+ if (error) {
+ DAWN_ASSERT(!pendingErrorData.message.empty());
+ mCallback(pendingErrorData.status, pendingErrorData.message.c_str(), userdata1,
+ userdata2);
+ } else {
+ mCallback(WGPUMapAsyncStatus_Success, nullptr, userdata1, userdata2);
+ }
+ }
+
+ // Set the buffer early status because it was unmapped early due to Unmap or Destroy.
+ // This can race with Complete such that the early status is ignored, but this is OK
+ // because we will still unmap the buffer. It will be as-if the application called
+ // Unmap/Destroy just after the map event completed.
+ void UnmapEarly(wgpu::BufferMapAsyncStatus status) override {
+ auto StatusToMessage = [](wgpu::BufferMapAsyncStatus status) {
+ switch (status) {
+ case wgpu::BufferMapAsyncStatus::DeviceLost:
+ return "Device was lost.";
+ case wgpu::BufferMapAsyncStatus::DestroyedBeforeCallback:
+ return "Buffer was destroyed before mapping was resolved.";
+ case wgpu::BufferMapAsyncStatus::UnmappedBeforeCallback:
+ return "Buffer was unmapped before mapping was resolved.";
+ default:
+ DAWN_UNREACHABLE();
+ }
+ };
+ mBufferOrError.Use([&](auto bufferOrError) {
+ *bufferOrError = BufferErrorData{WGPUMapAsyncStatus_Aborted, StatusToMessage(status)};
+ });
+ }
+};
+
ResultOrError<UnpackedPtr<BufferDescriptor>> ValidateBufferDescriptor(
DeviceBase* device,
const BufferDescriptor* descriptor) {
@@ -623,14 +756,65 @@
}();
if (earlyStatus) {
- event = AcquireRef(new MapAsyncEvent(GetDevice(), callbackInfo, *earlyStatus));
+ event = AcquireRef(new MapAsyncEvent1(GetDevice(), callbackInfo, *earlyStatus));
} else {
mMapMode = mode;
mMapOffset = offset;
mMapSize = size;
mState = BufferState::PendingMap;
mPendingMapEvent =
- AcquireRef(new MapAsyncEvent(GetDevice(), this, callbackInfo, mLastUsageSerial));
+ AcquireRef(new MapAsyncEvent1(GetDevice(), this, callbackInfo, mLastUsageSerial));
+ event = mPendingMapEvent;
+ }
+ }
+
+ FutureID futureID = GetInstance()->GetEventManager()->TrackEvent(std::move(event));
+ return {futureID};
+}
+
+Future BufferBase::APIMapAsync2(wgpu::MapMode mode,
+ size_t offset,
+ size_t size,
+ const WGPUBufferMapCallbackInfo2& callbackInfo) {
+ // TODO(crbug.com/dawn/2052): Once we always return a future, change this to log to the instance
+ // (note, not raise a validation error to the device) and return the null future.
+ DAWN_ASSERT(callbackInfo.nextInChain == nullptr);
+
+ Ref<EventManager::TrackedEvent> event;
+ {
+ // TODO(crbug.com/dawn/831) Manually acquire device lock instead of relying on code-gen for
+ // re-entrancy.
+ auto deviceLock(GetDevice()->GetScopedLock());
+
+ // Handle the defaulting of size required by WebGPU, even if in webgpu_cpp.h it is not
+ // possible to default the function argument (because there is the callback later in the
+ // argument list)
+ if ((size == wgpu::kWholeMapSize) && (offset <= mSize)) {
+ size = mSize - offset;
+ }
+
+ MaybeError maybeError = [&]() -> MaybeError {
+ DAWN_INVALID_IF(mState == BufferState::PendingMap,
+ "%s already has an outstanding map pending.", this);
+ WGPUBufferMapAsyncStatus status;
+ DAWN_TRY(ValidateMapAsync(mode, offset, size, &status));
+ DAWN_TRY(MapAsyncImpl(mode, offset, size));
+ return {};
+ }();
+
+ if (maybeError.IsError()) {
+ auto error = maybeError.AcquireError();
+ event = AcquireRef(new MapAsyncEvent2(GetDevice(), callbackInfo, error->GetMessage()));
+ [[maybe_unused]] bool hadError = GetDevice()->ConsumedError(
+ std::move(error), "calling %s.MapAsync(%s, %u, %u, ...).", this, mode, offset,
+ size);
+ } else {
+ mMapMode = mode;
+ mMapOffset = offset;
+ mMapSize = size;
+ mState = BufferState::PendingMap;
+ mPendingMapEvent =
+ AcquireRef(new MapAsyncEvent2(GetDevice(), this, callbackInfo, mLastUsageSerial));
event = mPendingMapEvent;
}
}
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h
index e1378ce..24fb24b 100644
--- a/src/dawn/native/Buffer.h
+++ b/src/dawn/native/Buffer.h
@@ -128,6 +128,10 @@
size_t offset,
size_t size,
const BufferMapCallbackInfo& callbackInfo);
+ Future APIMapAsync2(wgpu::MapMode mode,
+ size_t offset,
+ size_t size,
+ const WGPUBufferMapCallbackInfo2& callbackInfo);
void* APIGetMappedRange(size_t offset, size_t size);
const void* APIGetConstMappedRange(size_t offset, size_t size);
void APIUnmap();
@@ -190,6 +194,8 @@
size_t mMapSize = 0;
struct MapAsyncEvent;
+ struct MapAsyncEvent1;
+ struct MapAsyncEvent2;
Ref<MapAsyncEvent> mPendingMapEvent;
};
diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index 6c166a4..c9b1a6f 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -29,6 +29,7 @@
#include <functional>
#include <limits>
+#include <string>
#include <utility>
#include "dawn/wire/BufferConsumer_impl.h"
@@ -209,6 +210,138 @@
raw_ptr<Buffer> mBuffer;
};
+class Buffer::MapAsyncEvent2 : public TrackedEvent {
+ public:
+ static constexpr EventType kType = EventType::MapAsync;
+
+ MapAsyncEvent2(const WGPUBufferMapCallbackInfo2& callbackInfo, Buffer* buffer)
+ : TrackedEvent(callbackInfo.mode),
+ mCallback(callbackInfo.callback),
+ mUserdata1(callbackInfo.userdata1),
+ mUserdata2(callbackInfo.userdata2),
+ mBuffer(buffer) {
+ DAWN_ASSERT(buffer != nullptr);
+ mBuffer->AddRef();
+ }
+
+ ~MapAsyncEvent2() override { mBuffer.ExtractAsDangling()->Release(); }
+
+ EventType GetType() override { return kType; }
+
+ bool IsPendingRequest(FutureID futureID) {
+ return mBuffer->mPendingMapRequest && mBuffer->mPendingMapRequest->futureID == futureID;
+ }
+
+ WireResult ReadyHook(FutureID futureID,
+ WGPUMapAsyncStatus status,
+ const char* message,
+ uint64_t readDataUpdateInfoLength = 0,
+ const uint8_t* readDataUpdateInfo = nullptr) {
+ if (status != WGPUMapAsyncStatus_Success) {
+ mStatus = status;
+ mMessage = message;
+ return WireResult::Success;
+ }
+
+ // If the request was already aborted via the client side, we don't need to actually do
+ // anything, so just return success.
+ if (!IsPendingRequest(futureID)) {
+ return WireResult::Success;
+ }
+
+ auto FailRequest = [this](const char* message) -> WireResult {
+ mStatus = WGPUMapAsyncStatus_Unknown;
+ mMessage = message;
+ return WireResult::FatalError;
+ };
+
+ mStatus = status;
+ DAWN_ASSERT(message == nullptr);
+ const auto& pending = mBuffer->mPendingMapRequest.value();
+ if (!pending.type) {
+ return FailRequest("Invalid map call without a specified mapping type.");
+ }
+ switch (*pending.type) {
+ 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("Invalid data size returned from the server.");
+ }
+
+ // Update user map data with server returned data
+ if (!mBuffer->mReadHandle->DeserializeDataUpdate(
+ readDataUpdateInfo, static_cast<size_t>(readDataUpdateInfoLength),
+ pending.offset, pending.size)) {
+ return FailRequest("Failed to deserialize data returned from the server.");
+ }
+ mBuffer->mMappedData = const_cast<void*>(mBuffer->mReadHandle->GetData());
+ break;
+ }
+ case MapRequestType::Write: {
+ mBuffer->mMappedData = mBuffer->mWriteHandle->GetData();
+ break;
+ }
+ }
+ mBuffer->mMappedOffset = pending.offset;
+ mBuffer->mMappedSize = pending.size;
+
+ return WireResult::Success;
+ }
+
+ private:
+ void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
+ if (completionType == EventCompletionType::Shutdown) {
+ mStatus = WGPUMapAsyncStatus_InstanceDropped;
+ mMessage = "A valid external Instance reference no longer exists.";
+ }
+
+ auto Callback = [this]() {
+ if (mCallback) {
+ mCallback(mStatus, mMessage ? mMessage->c_str() : nullptr,
+ mUserdata1.ExtractAsDangling(), mUserdata2.ExtractAsDangling());
+ }
+ };
+
+ if (!IsPendingRequest(futureID)) {
+ DAWN_ASSERT(mStatus != WGPUMapAsyncStatus_Success);
+ return Callback();
+ }
+
+ if (mStatus == WGPUMapAsyncStatus_Success) {
+ if (mBuffer->mIsDeviceAlive.expired()) {
+ // If the device lost its last ref before this callback was resolved, we want to
+ // overwrite the status. This is necessary because otherwise dropping the last
+ // device reference could race w.r.t what this callback would see.
+ mStatus = WGPUMapAsyncStatus_Aborted;
+ mMessage = "Buffer was destroyed before mapping was resolved.";
+ return Callback();
+ }
+ DAWN_ASSERT(mBuffer->mPendingMapRequest->type);
+ switch (*mBuffer->mPendingMapRequest->type) {
+ case MapRequestType::Read:
+ mBuffer->mMappedState = MapState::MappedForRead;
+ break;
+ case MapRequestType::Write:
+ mBuffer->mMappedState = MapState::MappedForWrite;
+ break;
+ }
+ }
+ mBuffer->mPendingMapRequest = std::nullopt;
+ return Callback();
+ }
+
+ WGPUBufferMapCallback2 mCallback;
+ raw_ptr<void> mUserdata1;
+ raw_ptr<void> mUserdata2;
+
+ WGPUMapAsyncStatus mStatus;
+ std::optional<std::string> mMessage;
+
+ // Strong reference to the buffer so that when we call the callback we can pass the buffer.
+ raw_ptr<Buffer> mBuffer;
+};
+
// static
WGPUBuffer Buffer::Create(Device* device, const WGPUBufferDescriptor* descriptor) {
Client* wireClient = device->GetClient();
@@ -324,8 +457,32 @@
if (!mPendingMapRequest) {
return;
}
- DAWN_CHECK(GetEventManager().SetFutureReady<MapAsyncEvent>(mPendingMapRequest->futureID,
- status) == WireResult::Success);
+
+ FutureID futureID = mPendingMapRequest->futureID;
+ bool isNewEntryPoint = mPendingMapRequest->isNewEntryPoint;
+ mPendingMapRequest = std::nullopt;
+
+ if (isNewEntryPoint) {
+ auto [newStatus, message] =
+ [](WGPUBufferMapAsyncStatus status) -> std::pair<WGPUMapAsyncStatus, const char*> {
+ switch (status) {
+ case WGPUBufferMapAsyncStatus_DestroyedBeforeCallback:
+ return {WGPUMapAsyncStatus_Aborted,
+ "Buffer was destroyed before mapping was resolved."};
+ case WGPUBufferMapAsyncStatus_UnmappedBeforeCallback:
+ return {WGPUMapAsyncStatus_Aborted,
+ "Buffer was unmapped before mapping was resolved."};
+ default:
+ DAWN_UNREACHABLE();
+ }
+ }(status);
+
+ DAWN_CHECK(GetEventManager().SetFutureReady<MapAsyncEvent2>(futureID, newStatus, message) ==
+ WireResult::Success);
+ } else {
+ DAWN_CHECK(GetEventManager().SetFutureReady<MapAsyncEvent>(futureID, status) ==
+ WireResult::Success);
+ }
}
void Buffer::MapAsync(WGPUMapModeFlags mode,
@@ -372,7 +529,7 @@
mapMode = MapRequestType::Write;
}
- mPendingMapRequest = {futureIDInternal, offset, size, mapMode};
+ mPendingMapRequest = {futureIDInternal, offset, size, mapMode, false};
// Serialize the command to send to the server.
BufferMapAsyncCmd cmd;
@@ -382,6 +539,56 @@
cmd.mode = mode;
cmd.offset = offset;
cmd.size = size;
+ cmd.userdataCount = 1;
+
+ client->SerializeCommand(cmd);
+ return {futureIDInternal};
+}
+
+WGPUFuture Buffer::MapAsync2(WGPUMapModeFlags mode,
+ size_t offset,
+ size_t size,
+ const WGPUBufferMapCallbackInfo2& callbackInfo) {
+ DAWN_ASSERT(GetRefcount() != 0);
+
+ Client* client = GetClient();
+ auto [futureIDInternal, tracked] =
+ GetEventManager().TrackEvent(std::make_unique<MapAsyncEvent2>(callbackInfo, this));
+ if (!tracked) {
+ return {futureIDInternal};
+ }
+
+ if (mPendingMapRequest) {
+ [[maybe_unused]] auto id = GetEventManager().SetFutureReady<MapAsyncEvent2>(
+ futureIDInternal, WGPUMapAsyncStatus_Error,
+ "Buffer already has an outstanding map pending.");
+ return {futureIDInternal};
+ }
+
+ // Handle the defaulting of size required by WebGPU.
+ if ((size == WGPU_WHOLE_MAP_SIZE) && (offset <= mSize)) {
+ size = mSize - offset;
+ }
+
+ // Set up the request structure that will hold information while this mapping is in flight.
+ std::optional<MapRequestType> mapMode;
+ if (mode & WGPUMapMode_Read) {
+ mapMode = MapRequestType::Read;
+ } else if (mode & WGPUMapMode_Write) {
+ mapMode = MapRequestType::Write;
+ }
+
+ mPendingMapRequest = {futureIDInternal, offset, size, mapMode, true};
+
+ // Serialize the command to send to the server.
+ BufferMapAsyncCmd cmd;
+ cmd.bufferId = GetWireId();
+ cmd.eventManagerHandle = GetEventManagerHandle();
+ cmd.future = {futureIDInternal};
+ cmd.mode = mode;
+ cmd.offset = offset;
+ cmd.size = size;
+ cmd.userdataCount = 2;
client->SerializeCommand(cmd);
return {futureIDInternal};
@@ -390,11 +597,20 @@
WireResult Client::DoBufferMapAsyncCallback(ObjectHandle eventManager,
WGPUFuture future,
WGPUBufferMapAsyncStatus status,
+ WGPUMapAsyncStatus status2,
+ const char* message,
+ uint8_t userdataCount,
uint64_t readDataUpdateInfoLength,
const uint8_t* readDataUpdateInfo) {
- return GetEventManager(eventManager)
- .SetFutureReady<Buffer::MapAsyncEvent>(future.id, status, readDataUpdateInfoLength,
- readDataUpdateInfo);
+ if (userdataCount == 1) {
+ return GetEventManager(eventManager)
+ .SetFutureReady<Buffer::MapAsyncEvent>(future.id, status, readDataUpdateInfoLength,
+ readDataUpdateInfo);
+ } else {
+ return GetEventManager(eventManager)
+ .SetFutureReady<Buffer::MapAsyncEvent2>(future.id, status2, message,
+ readDataUpdateInfoLength, readDataUpdateInfo);
+ }
}
void* Buffer::GetMappedRange(size_t offset, size_t size) {
diff --git a/src/dawn/wire/client/Buffer.h b/src/dawn/wire/client/Buffer.h
index e1abeac..c82b286 100644
--- a/src/dawn/wire/client/Buffer.h
+++ b/src/dawn/wire/client/Buffer.h
@@ -63,6 +63,10 @@
size_t offset,
size_t size,
const WGPUBufferMapCallbackInfo& callbackInfo);
+ WGPUFuture MapAsync2(WGPUMapModeFlags mode,
+ size_t offset,
+ size_t size,
+ const WGPUBufferMapCallbackInfo2& callbackInfo);
void* GetMappedRange(size_t offset, size_t size);
const void* GetConstMappedRange(size_t offset, size_t size);
void Unmap();
@@ -78,6 +82,7 @@
private:
friend class Client;
class MapAsyncEvent;
+ class MapAsyncEvent2;
// Prepares the callbacks to be called and potentially calls them
void SetFutureStatus(WGPUBufferMapAsyncStatus status);
@@ -103,6 +108,9 @@
// Because validation for request type is validated via the backend, we use an optional type
// here. This is nullopt when an invalid request type is passed to the wire.
std::optional<MapRequestType> type;
+ // Currently needs an additional boolean to indicate which entry point was used for the map.
+ // TODO(crbug.com/42241461): Remove this once we don't need to support both on the wire.
+ bool isNewEntryPoint = false;
};
enum class MapState {
Unmapped,
diff --git a/src/dawn/wire/server/Server.h b/src/dawn/wire/server/Server.h
index a244b9d..207c519 100644
--- a/src/dawn/wire/server/Server.h
+++ b/src/dawn/wire/server/Server.h
@@ -129,6 +129,7 @@
uint64_t offset;
uint64_t size;
WGPUMapModeFlags mode;
+ uint8_t userdataCount;
};
struct ErrorScopeUserdata : CallbackUserdata {
@@ -254,6 +255,9 @@
WGPUErrorType type,
const char* message);
void OnBufferMapAsyncCallback(MapUserdata* userdata, WGPUBufferMapAsyncStatus status);
+ void OnBufferMapAsyncCallback2(MapUserdata* userdata,
+ WGPUMapAsyncStatus status,
+ const char* message);
void OnQueueWorkDone(QueueWorkDoneUserdata* userdata, WGPUQueueWorkDoneStatus status);
void OnCreateComputePipelineAsyncCallback(CreatePipelineAsyncUserData* userdata,
WGPUCreatePipelineAsyncStatus status,
diff --git a/src/dawn/wire/server/ServerBuffer.cpp b/src/dawn/wire/server/ServerBuffer.cpp
index 3a3642f..73f1963 100644
--- a/src/dawn/wire/server/ServerBuffer.cpp
+++ b/src/dawn/wire/server/ServerBuffer.cpp
@@ -70,7 +70,8 @@
WGPUFuture future,
WGPUMapModeFlags mode,
uint64_t offset64,
- uint64_t size64) {
+ uint64_t size64,
+ uint8_t userdataCount) {
// These requests are just forwarded to the buffer, with userdata containing what the
// client will require in the return command.
std::unique_ptr<MapUserdata> userdata = MakeUserdata<MapUserdata>();
@@ -79,6 +80,7 @@
userdata->bufferObj = buffer->handle;
userdata->future = future;
userdata->mode = mode;
+ userdata->userdataCount = userdataCount;
// Make sure that the deserialized offset and size are no larger than
// std::numeric_limits<size_t>::max() so that they are CPU-addressable, and size is not
@@ -100,8 +102,16 @@
userdata->offset = offset;
userdata->size = size;
- mProcs.bufferMapAsync(buffer->handle, mode, offset, size,
- ForwardToServer<&Server::OnBufferMapAsyncCallback>, userdata.release());
+ if (userdataCount == 1) {
+ mProcs.bufferMapAsync(buffer->handle, mode, offset, size,
+ ForwardToServer<&Server::OnBufferMapAsyncCallback>,
+ userdata.release());
+ } else {
+ mProcs.bufferMapAsync2(
+ buffer->handle, mode, offset, size,
+ {nullptr, WGPUCallbackMode_AllowSpontaneous,
+ ForwardToServer2<&Server::OnBufferMapAsyncCallback2>, userdata.release(), nullptr});
+ }
return WireResult::Success;
}
@@ -229,8 +239,68 @@
cmd.eventManager = data->eventManager;
cmd.future = data->future;
cmd.status = status;
+ cmd.message = nullptr;
cmd.readDataUpdateInfoLength = 0;
cmd.readDataUpdateInfo = nullptr;
+ cmd.userdataCount = 1;
+
+ const void* readData = nullptr;
+ size_t readDataUpdateInfoLength = 0;
+ if (isSuccess) {
+ if (isRead) {
+ // Get the serialization size of the message to initialize ReadHandle data.
+ readData = mProcs.bufferGetConstMappedRange(data->bufferObj, data->offset, data->size);
+ readDataUpdateInfoLength =
+ buffer->readHandle->SizeOfSerializeDataUpdate(data->offset, data->size);
+ cmd.readDataUpdateInfoLength = readDataUpdateInfoLength;
+ } else {
+ DAWN_ASSERT(data->mode & WGPUMapMode_Write);
+ // The in-flight map request returned successfully.
+ buffer->mapWriteState = BufferMapWriteState::Mapped;
+ // 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.
+ buffer->writeHandle->SetTarget(static_cast<uint8_t*>(mProcs.bufferGetMappedRange(
+ data->bufferObj, data->offset, data->size)) -
+ data->offset);
+ }
+ }
+
+ SerializeCommand(cmd, CommandExtension{readDataUpdateInfoLength, [&](char* readHandleBuffer) {
+ if (isSuccess && isRead) {
+ // The in-flight map request returned
+ // successfully.
+ buffer->readHandle->SerializeDataUpdate(
+ readData, data->offset, data->size,
+ readHandleBuffer);
+ }
+ }});
+}
+
+void Server::OnBufferMapAsyncCallback2(MapUserdata* data,
+ WGPUMapAsyncStatus status,
+ const char* message) {
+ // Skip sending the callback if the buffer has already been destroyed.
+ Known<WGPUBuffer> buffer;
+ if (Objects<WGPUBuffer>().Get(data->buffer.id, &buffer) != WireResult::Success ||
+ buffer->generation != data->buffer.generation) {
+ return;
+ }
+
+ bool isRead = data->mode & WGPUMapMode_Read;
+ bool isSuccess = status == WGPUMapAsyncStatus_Success;
+
+ ReturnBufferMapAsyncCallbackCmd cmd;
+ cmd.eventManager = data->eventManager;
+ cmd.future = data->future;
+ cmd.status2 = status;
+ cmd.message = message;
+ cmd.readDataUpdateInfoLength = 0;
+ cmd.readDataUpdateInfo = nullptr;
+ cmd.userdataCount = 2;
const void* readData = nullptr;
size_t readDataUpdateInfoLength = 0;
diff --git a/tools/android/BUILD.gn b/tools/android/BUILD.gn
index ccec004..b7caaae 100644
--- a/tools/android/BUILD.gn
+++ b/tools/android/BUILD.gn
@@ -113,6 +113,7 @@
"java/android/dawn/InstanceFeatures.kt",
"java/android/dawn/Limits.kt",
"java/android/dawn/LoadOp.kt",
+ "java/android/dawn/MapAsyncStatus.kt",
"java/android/dawn/MapMode.kt",
"java/android/dawn/MipmapFilterMode.kt",
"java/android/dawn/MultisampleState.kt",