[dawn] Make spontaneous device callbacks thread safe in native also. - Unifies the code used for a lot of the device callbacks between the wire client and native. Note that I am currently using dawn/common for convenience, but it might be better in the long run to use another target or something since I needed to explicitly exclude the helpers for WASM builds since the logging callback is currently native only. Change-Id: Ia244a3dab0c9244d6a277c31d857210c9d3fc554 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/297575 Reviewed-by: Kai Ninomiya <kainino@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/common/BUILD.gn b/src/dawn/common/BUILD.gn index 6b5264a..d6b5c5e 100644 --- a/src/dawn/common/BUILD.gn +++ b/src/dawn/common/BUILD.gn
@@ -401,6 +401,13 @@ ] sources += get_target_outputs(":dawn_gpu_info_gen") + if (!is_wasm) { + sources += [ + "WGPUDeviceCallbackInfos.cpp", + "WGPUDeviceCallbackInfos.h", + ] + } + public_deps = [ ":dawn_gpu_info_gen", ":dawn_version_gen",
diff --git a/src/dawn/common/CMakeLists.txt b/src/dawn/common/CMakeLists.txt index bc70590..953f412 100644 --- a/src/dawn/common/CMakeLists.txt +++ b/src/dawn/common/CMakeLists.txt
@@ -133,7 +133,7 @@ set(conditional_private_depends) if (WIN32) - list(APPEND headers + list(APPEND private_headers "windows_with_undefs.h" "WindowsUtils.h" ) @@ -141,7 +141,7 @@ "WindowsUtils.cpp" ) elseif(APPLE) - list(APPEND headers + list(APPEND private_headers "IOSurfaceUtils.h" ) list(APPEND sources @@ -154,6 +154,15 @@ ) endif() +if (NOT EMSCRIPTEN) + list(APPEND private_headers + "WGPUDeviceCallbackInfos.h" + ) + list(APPEND sources + "WGPUDeviceCallbackInfos.cpp" + ) +endif() + if (CMAKE_SYSTEM_NAME STREQUAL "Android") find_library(log_lib log) list(APPEND conditional_private_depends
diff --git a/src/dawn/common/WGPUDeviceCallbackInfos.cpp b/src/dawn/common/WGPUDeviceCallbackInfos.cpp new file mode 100644 index 0000000..63b6d95 --- /dev/null +++ b/src/dawn/common/WGPUDeviceCallbackInfos.cpp
@@ -0,0 +1,181 @@ +// Copyright 2026 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "dawn/common/WGPUDeviceCallbackInfos.h" + +#include "dawn/common/Log.h" + +namespace dawn { +namespace { +// Default callback infos depending on the build type. +#ifdef DAWN_ENABLE_ASSERTS +static constexpr WGPUDeviceLostCallbackInfo kDefaultDeviceLostCallbackInfo = { + nullptr, WGPUCallbackMode_AllowSpontaneous, + [](WGPUDevice const*, WGPUDeviceLostReason, WGPUStringView, void*, void*) { + static std::once_flag flag; + std::call_once(flag, []() { + dawn::WarningLog() << "No Dawn device lost callback was set. This is probably not " + "intended. If you really want to ignore device lost " + "and suppress this message, set the callback explicitly."; + }); + }, + nullptr, nullptr}; +static constexpr WGPUUncapturedErrorCallbackInfo kDefaultUncapturedErrorCallbackInfo = { + nullptr, + [](WGPUDevice const*, WGPUErrorType, WGPUStringView, void*, void*) { + static std::once_flag flag; + std::call_once(flag, []() { + dawn::WarningLog() << "No Dawn device uncaptured error callback was set. This is " + "probably not intended. If you really want to ignore errors " + "and suppress this message, set the callback explicitly."; + }); + }, + nullptr, nullptr}; +static constexpr WGPULoggingCallbackInfo kDefaultLoggingCallbackInfo = { + nullptr, + [](WGPULoggingType, WGPUStringView, void*, void*) { + static std::once_flag flag; + std::call_once(flag, []() { + dawn::WarningLog() << "No Dawn device logging callback callback was set. This is " + "probably not intended. If you really want to ignore logs " + "and suppress this message, set the callback explicitly."; + }); + }, + nullptr, nullptr}; +#else +static constexpr WGPUDeviceLostCallbackInfo kDefaultDeviceLostCallbackInfo = { + nullptr, WGPUCallbackMode_AllowSpontaneous, nullptr, nullptr, nullptr}; +static constexpr WGPUUncapturedErrorCallbackInfo kDefaultUncapturedErrorCallbackInfo = { + nullptr, nullptr, nullptr, nullptr}; +static constexpr WGPULoggingCallbackInfo kDefaultLoggingCallbackInfo = {nullptr, nullptr, nullptr, + nullptr}; +#endif // DAWN_ENABLE_ASSERTS + +const WGPUUncapturedErrorCallbackInfo& GetUncapturedErrorCallbackInfoOrDefault( + const WGPUDeviceDescriptor* descriptor) { + if (descriptor != nullptr && descriptor->uncapturedErrorCallbackInfo.callback != nullptr) { + return descriptor->uncapturedErrorCallbackInfo; + } + return kDefaultUncapturedErrorCallbackInfo; +} +} // namespace + +const WGPUDeviceLostCallbackInfo& GetDeviceLostCallbackInfoOrDefault( + const WGPUDeviceDescriptor* descriptor) { + if (descriptor != nullptr && descriptor->deviceLostCallbackInfo.callback != nullptr) { + return descriptor->deviceLostCallbackInfo; + } + return kDefaultDeviceLostCallbackInfo; +} + +WGPUDeviceCallbackInfos::CallbackInfos::CallbackInfos() = default; + +WGPUDeviceCallbackInfos::CallbackInfos::CallbackInfos(const WGPUUncapturedErrorCallbackInfo& error, + const WGPULoggingCallbackInfo& logging) { + if (error.callback != nullptr) { + this->error = error; + } + if (logging.callback != nullptr) { + this->logging = logging; + } +} + +WGPUDeviceCallbackInfos::WGPUDeviceCallbackInfos() = default; + +WGPUDeviceCallbackInfos::WGPUDeviceCallbackInfos(const WGPUDeviceDescriptor* descriptor) + : mCallbackInfos(GetUncapturedErrorCallbackInfoOrDefault(descriptor), + kDefaultLoggingCallbackInfo) {} + +void WGPUDeviceCallbackInfos::CallErrorCallback(WGPUDevice const* device, + WGPUErrorType type, + WGPUStringView message) { + std::optional<WGPUUncapturedErrorCallbackInfo> callbackInfo; + mCallbackInfos.Use<NotifyType::None>([&](auto callbackInfos) { + callbackInfo = callbackInfos->error; + if (callbackInfo) { + callbackInfos->semaphore += 1; + } + }); + + // If we don't have a callback info, we can just return. + if (!callbackInfo) { + return; + } + + // Call the callback without holding the lock to prevent any re-entrant issues. + DAWN_ASSERT(callbackInfo->callback != nullptr); + callbackInfo->callback(device, type, message, callbackInfo->userdata1, callbackInfo->userdata2); + + mCallbackInfos.Use([&](auto callbackInfos) { + DAWN_ASSERT(callbackInfos->semaphore > 0); + callbackInfos->semaphore -= 1; + }); +} + +void WGPUDeviceCallbackInfos::CallLoggingCallback(WGPULoggingType type, WGPUStringView message) { + std::optional<WGPULoggingCallbackInfo> callbackInfo; + mCallbackInfos.Use<NotifyType::None>([&](auto callbackInfos) { + callbackInfo = callbackInfos->logging; + if (callbackInfo) { + callbackInfos->semaphore += 1; + } + }); + + // If we don't have a callback info, we can just return. + if (!callbackInfo) { + return; + } + + // Call the callback without holding the lock to prevent any re-entrant issues. + DAWN_ASSERT(callbackInfo->callback != nullptr); + callbackInfo->callback(type, message, callbackInfo->userdata1, callbackInfo->userdata2); + + mCallbackInfos.Use([&](auto callbackInfos) { + DAWN_ASSERT(callbackInfos->semaphore > 0); + callbackInfos->semaphore -= 1; + }); +} + +void WGPUDeviceCallbackInfos::SetLoggingCallbackInfo(const WGPULoggingCallbackInfo& callbackInfo) { + mCallbackInfos.Use<NotifyType::None>( + [&](auto callbackInfos) { callbackInfos->logging = callbackInfo; }); +} + +void WGPUDeviceCallbackInfos::Clear() { + mCallbackInfos.Use<NotifyType::None>([](auto callbackInfos) { + callbackInfos->error = std::nullopt; + callbackInfos->logging = std::nullopt; + + // The uncaptured error and logging callbacks are spontaneous and must not be called + // after we call the device lost's |mCallback| below. Although we have cleared those + // callbacks, we need to wait for any remaining outstanding callbacks to finish before + // continuing. + callbackInfos.Wait([](auto& x) { return x.semaphore == 0; }); + }); +} + +} // namespace dawn
diff --git a/src/dawn/common/WGPUDeviceCallbackInfos.h b/src/dawn/common/WGPUDeviceCallbackInfos.h new file mode 100644 index 0000000..e804f7b --- /dev/null +++ b/src/dawn/common/WGPUDeviceCallbackInfos.h
@@ -0,0 +1,85 @@ +// Copyright 2026 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_DAWN_COMMON_WGPUDEVICECALLBACKINFOS_H_ +#define SRC_DAWN_COMMON_WGPUDEVICECALLBACKINFOS_H_ + +#include <webgpu/webgpu.h> + +#include <optional> + +#include "dawn/common/MutexProtected.h" + +namespace dawn { + +const WGPUDeviceLostCallbackInfo& GetDeviceLostCallbackInfoOrDefault( + const WGPUDeviceDescriptor* descriptor); + +// Device level unconditionally spontaneous callbacks need to be synchronized so this class provides +// a common wrapper for those callback infos so that the implementation can be shared across native +// and wire client. +class WGPUDeviceCallbackInfos { + public: + WGPUDeviceCallbackInfos(); + explicit WGPUDeviceCallbackInfos(const WGPUDeviceDescriptor* descriptor); + + // APIs to call the callbacks. + void CallErrorCallback(WGPUDevice const* device, WGPUErrorType type, WGPUStringView message); + void CallLoggingCallback(WGPULoggingType type, WGPUStringView message); + + // The logging callback currently needs to support a setter. + void SetLoggingCallbackInfo(const WGPULoggingCallbackInfo& callbackInfo); + + // Used when the device is lost and we want to clear out the callbacks. This helper waits until + // there are no other places using the callbacks before returning. This is important since this + // is generally used when completing the device lost event which users may use to clean up the + // uncaptured error and logging callbacks. + void Clear(); + + private: + struct CallbackInfos { + CallbackInfos(); + CallbackInfos(const WGPUUncapturedErrorCallbackInfo& error, + const WGPULoggingCallbackInfo& logging); + + // The callback infos are optional because once the device is lost, they are set to + // std::nullopt and no longer do anything. + std::optional<WGPUUncapturedErrorCallbackInfo> error = std::nullopt; + std::optional<WGPULoggingCallbackInfo> logging = std::nullopt; + + // Counter that tracks how many places are currently using callback infos. This is used to + // ensure that before we call the device lost callback (which may deallocate the uncaptured + // error and logging callbacks), we have ensured that there are no outstanding references to + // those callbacks. + uint32_t semaphore = 0; + }; + MutexCondVarProtected<CallbackInfos> mCallbackInfos; +}; + +} // namespace dawn + +#endif // SRC_DAWN_COMMON_WGPUDEVICECALLBACKINFOS_H_
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp index 6430b9d..4fc1c46 100644 --- a/src/dawn/native/Device.cpp +++ b/src/dawn/native/Device.cpp
@@ -143,11 +143,6 @@ namespace { -static constexpr WGPUUncapturedErrorCallbackInfo kEmptyUncapturedErrorCallbackInfo = { - nullptr, nullptr, nullptr, nullptr}; -static constexpr WGPULoggingCallbackInfo kEmptyLoggingCallbackInfo = {nullptr, nullptr, nullptr, - nullptr}; - void TrimErrorScopeStacks( absl::flat_hash_map<ThreadUniqueId, std::unique_ptr<ErrorScopeStack>>& errorScopeStacks) { for (auto it = errorScopeStacks.begin(); it != errorScopeStacks.end();) { @@ -176,30 +171,8 @@ Ref<DeviceBase::DeviceLostEvent> DeviceBase::DeviceLostEvent::Create( const DeviceDescriptor* descriptor) { DAWN_ASSERT(descriptor != nullptr); - -#if defined(DAWN_ENABLE_ASSERTS) - static constexpr WGPUDeviceLostCallbackInfo kDefaultDeviceLostCallbackInfo = { - nullptr, WGPUCallbackMode_AllowSpontaneous, - [](WGPUDevice const*, WGPUDeviceLostReason, WGPUStringView, void*, void*) { - static bool calledOnce = false; - if (!calledOnce) { - calledOnce = true; - dawn::WarningLog() << "No Dawn device lost callback was set. This is probably not " - "intended. If you really want to ignore device lost and " - "suppress this message, set the callback explicitly."; - } - }, - nullptr, nullptr}; -#else - static constexpr WGPUDeviceLostCallbackInfo kDefaultDeviceLostCallbackInfo = { - nullptr, WGPUCallbackMode_AllowProcessEvents, nullptr, nullptr, nullptr}; -#endif // DAWN_ENABLE_ASSERTS - - WGPUDeviceLostCallbackInfo deviceLostCallbackInfo = kDefaultDeviceLostCallbackInfo; - if (descriptor->deviceLostCallbackInfo.callback != nullptr) { - deviceLostCallbackInfo = descriptor->deviceLostCallbackInfo; - } - return AcquireRef(new DeviceBase::DeviceLostEvent(deviceLostCallbackInfo)); + return AcquireRef( + new DeviceBase::DeviceLostEvent(GetDeviceLostCallbackInfoOrDefault(ToAPI(descriptor)))); } void DeviceBase::DeviceLostEvent::SetLost(EventManager* eventManager, @@ -216,15 +189,11 @@ mMessage = "A valid external Instance reference no longer exists."; } - // Some users may use the device lost callback to deallocate resources allocated for the - // uncaptured error and logging callbacks, so reset these callbacks before calling the - // device lost callback. if (mDevice != nullptr) { - mDevice->mUncapturedErrorCallbackInfo = kEmptyUncapturedErrorCallbackInfo; - { - std::lock_guard<std::shared_mutex> lock(mDevice->mLoggingMutex); - mDevice->mLoggingCallbackInfo = kEmptyLoggingCallbackInfo; - } + // The uncaptured error and logging callbacks are spontaneous and must not be called + // after we call the device lost's |mCallback| below, so we clear them and wait for them to + // be no longer referenced before moving forwards. + mDevice->mCallbackInfos.Clear(); } auto device = ToAPI(mDevice.Get()); @@ -301,6 +270,7 @@ const TogglesState& deviceToggles, Ref<DeviceLostEvent>&& lostEvent) : mLostEvent(std::move(lostEvent)), + mCallbackInfos(ToAPI(*descriptor)), mAdapter(adapter), mToggles(deviceToggles), mNextPipelineCompatibilityToken(1) { @@ -309,45 +279,6 @@ DAWN_ASSERT(mLostEvent); mLostEvent->mDevice = this; -#if defined(DAWN_ENABLE_ASSERTS) - static constexpr WGPUUncapturedErrorCallbackInfo kDefaultUncapturedErrorCallbackInfo = { - nullptr, - [](WGPUDevice const*, WGPUErrorType, WGPUStringView, void*, void*) { - static bool calledOnce = false; - if (!calledOnce) { - calledOnce = true; - dawn::WarningLog() << "No Dawn device uncaptured error callback was set. This is " - "probably not intended. If you really want to ignore errors " - "and suppress this message, set the callback explicitly."; - } - }, - nullptr, nullptr}; - static constexpr WGPULoggingCallbackInfo kDefaultLoggingCallbackInfo = { - nullptr, - [](WGPULoggingType, WGPUStringView, void*, void*) { - static bool calledOnce = false; - if (!calledOnce) { - calledOnce = true; - dawn::WarningLog() << "No Dawn device logging callback callback was set. This is " - "probably not intended. If you really want to ignore logs " - "and suppress this message, set the callback explicitly."; - } - }, - nullptr, nullptr}; -#else - static constexpr WGPUUncapturedErrorCallbackInfo kDefaultUncapturedErrorCallbackInfo = - kEmptyUncapturedErrorCallbackInfo; - static constexpr WGPULoggingCallbackInfo kDefaultLoggingCallbackInfo = - kEmptyLoggingCallbackInfo; -#endif // DAWN_ENABLE_ASSERTS - - mUncapturedErrorCallbackInfo = kDefaultUncapturedErrorCallbackInfo; - if (descriptor->uncapturedErrorCallbackInfo.callback != nullptr) { - mUncapturedErrorCallbackInfo = descriptor->uncapturedErrorCallbackInfo; - } - - mLoggingCallbackInfo = kDefaultLoggingCallbackInfo; - AdapterInfo adapterInfo; adapter->APIGetInfo(&adapterInfo); @@ -573,11 +504,7 @@ // Reset callbacks since after dropping the last external reference, the application may have // freed any device-scope memory needed to run the callback. - mUncapturedErrorCallbackInfo = kEmptyUncapturedErrorCallbackInfo; - { - std::lock_guard<std::shared_mutex> lock(mLoggingMutex); - mLoggingCallbackInfo = kEmptyLoggingCallbackInfo; - } + mCallbackInfos.Clear(); GetInstance()->RemoveDevice(this); @@ -823,15 +750,13 @@ if (forwardToErrorScope == ForwardToErrorScope::Yes) { captured = GetErrorScopeStack()->HandleError(ToWGPUErrorType(type), messageStr); } - - // Only call the uncaptured error callback if the device is alive. After the - // device is lost, the uncaptured error callback should cease firing. - if (!captured && mUncapturedErrorCallbackInfo.callback != nullptr && mState == State::Alive) { - auto device = ToAPI(this); - mUncapturedErrorCallbackInfo.callback( - &device, ToAPI(ToWGPUErrorType(type)), ToOutputStringView(messageStr), - mUncapturedErrorCallbackInfo.userdata1, mUncapturedErrorCallbackInfo.userdata2); + if (captured || mState != State::Alive) { + return; } + + auto device = ToAPI(this); + mCallbackInfos.CallErrorCallback(&device, ToAPI(ToWGPUErrorType(type)), + ToOutputStringView(messageStr)); } void DeviceBase::HandleErrorGeneratingAsyncTask(Ref<ErrorGeneratingAsyncTask> task, @@ -870,11 +795,10 @@ } void DeviceBase::APISetLoggingCallback(const WGPULoggingCallbackInfo& callbackInfo) { - if (mState != State::Alive) { + if (mState != State::Alive || callbackInfo.callback == nullptr) { return; } - std::lock_guard<std::shared_mutex> lock(mLoggingMutex); - mLoggingCallbackInfo = callbackInfo; + mCallbackInfos.SetLoggingCallbackInfo(callbackInfo); } ErrorScopeStack* DeviceBase::GetErrorScopeStack() { @@ -1952,16 +1876,7 @@ } void DeviceBase::EmitLog(wgpu::LoggingType type, std::string_view message) { - // Acquire a shared lock. This allows multiple threads to emit logs, - // or even logs to be emitted re-entrantly. It will block if there is a call - // to SetLoggingCallback. Applications should not call SetLoggingCallback inside - // the logging callback or they will deadlock. - std::shared_lock<std::shared_mutex> lock(mLoggingMutex); - if (mLoggingCallbackInfo.callback) { - mLoggingCallbackInfo.callback(ToAPI(type), ToOutputStringView(message), - mLoggingCallbackInfo.userdata1, - mLoggingCallbackInfo.userdata2); - } + mCallbackInfos.CallLoggingCallback(ToAPI(type), ToOutputStringView(message)); } wgpu::Status DeviceBase::APIGetAHardwareBufferProperties(void* handle,
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h index 50ce8e7..a03ffda 100644 --- a/src/dawn/native/Device.h +++ b/src/dawn/native/Device.h
@@ -44,6 +44,7 @@ #include "dawn/common/RefCountedWithExternalCount.h" #include "dawn/common/StackAllocated.h" #include "dawn/common/ThreadLocal.h" +#include "dawn/common/WGPUDeviceCallbackInfos.h" #include "dawn/native/AsyncTask.h" #include "dawn/native/CacheKey.h" #include "dawn/native/Commands.h" @@ -620,11 +621,7 @@ const TextureCopy& dst, const Extent3D& copySizePixels) = 0; - WGPUUncapturedErrorCallbackInfo mUncapturedErrorCallbackInfo = - WGPU_UNCAPTURED_ERROR_CALLBACK_INFO_INIT; - - std::shared_mutex mLoggingMutex; - WGPULoggingCallbackInfo mLoggingCallbackInfo = WGPU_LOGGING_CALLBACK_INFO_INIT; + WGPUDeviceCallbackInfos mCallbackInfos; // Error scopes need to be thread local, but also need to be cleaned up when the device is // destroyed. To do this, we can't use thread_local natively because we wouldn't have a way to
diff --git a/src/dawn/wire/client/Device.cpp b/src/dawn/wire/client/Device.cpp index 9d3c9c4..c430429 100644 --- a/src/dawn/wire/client/Device.cpp +++ b/src/dawn/wire/client/Device.cpp
@@ -156,78 +156,8 @@ EventType::CreateRenderPipeline, WGPUCreateRenderPipelineAsyncCallbackInfo>; -// Default callback infos depending on the build type. -#ifdef DAWN_ENABLE_ASSERTS -static constexpr WGPUDeviceLostCallbackInfo kDefaultDeviceLostCallbackInfo = { - nullptr, WGPUCallbackMode_AllowSpontaneous, - [](WGPUDevice const*, WGPUDeviceLostReason, WGPUStringView, void*, void*) { - static std::once_flag flag; - std::call_once(flag, []() { - dawn::WarningLog() << "No Dawn device lost callback was set. This is probably not " - "intended. If you really want to ignore device lost " - "and suppress this message, set the callback explicitly."; - }); - }, - nullptr, nullptr}; -static constexpr WGPUUncapturedErrorCallbackInfo kDefaultUncapturedErrorCallbackInfo = { - nullptr, - [](WGPUDevice const*, WGPUErrorType, WGPUStringView, void*, void*) { - static std::once_flag flag; - std::call_once(flag, []() { - dawn::WarningLog() << "No Dawn device uncaptured error callback was set. This is " - "probably not intended. If you really want to ignore errors " - "and suppress this message, set the callback explicitly."; - }); - }, - nullptr, nullptr}; -static constexpr WGPULoggingCallbackInfo kDefaultLoggingCallbackInfo = { - nullptr, - [](WGPULoggingType, WGPUStringView, void*, void*) { - static std::once_flag flag; - std::call_once(flag, []() { - dawn::WarningLog() << "No Dawn device logging callback callback was set. This is " - "probably not intended. If you really want to ignore logs " - "and suppress this message, set the callback explicitly."; - }); - }, - nullptr, nullptr}; -#else -static constexpr WGPUDeviceLostCallbackInfo kDefaultDeviceLostCallbackInfo = { - nullptr, WGPUCallbackMode_AllowSpontaneous, nullptr, nullptr, nullptr}; -static constexpr WGPUUncapturedErrorCallbackInfo kDefaultUncapturedErrorCallbackInfo = { - nullptr, nullptr, nullptr, nullptr}; -static constexpr WGPULoggingCallbackInfo kDefaultLoggingCallbackInfo = {nullptr, nullptr, nullptr, - nullptr}; -#endif // DAWN_ENABLE_ASSERTS - -const WGPUDeviceLostCallbackInfo& GetDeviceLostCallbackInfo( - const WGPUDeviceDescriptor* descriptor) { - if (descriptor != nullptr && descriptor->deviceLostCallbackInfo.callback != nullptr) { - return descriptor->deviceLostCallbackInfo; - } - return kDefaultDeviceLostCallbackInfo; -} - -const WGPUUncapturedErrorCallbackInfo& GetUncapturedErrorCallbackInfo( - const WGPUDeviceDescriptor* descriptor) { - if (descriptor != nullptr && descriptor->uncapturedErrorCallbackInfo.callback != nullptr) { - return descriptor->uncapturedErrorCallbackInfo; - } - return kDefaultUncapturedErrorCallbackInfo; -} - } // namespace -Device::CallbackInfos::CallbackInfos(const WGPUUncapturedErrorCallbackInfo& error, - const WGPULoggingCallbackInfo& logging) { - if (error.callback != nullptr) { - this->error = error; - } - if (logging.callback != nullptr) { - this->logging = logging; - } -} - class Device::DeviceLostEvent : public TrackedEvent { public: static constexpr EventType kType = EventType::DeviceLost; @@ -258,16 +188,10 @@ mMessage = "A valid external Instance reference no longer exists."; } - mDevice->mCallbackInfos.Use<NotifyType::None>([](auto callbackInfos) { - callbackInfos->error = std::nullopt; - callbackInfos->logging = std::nullopt; - - // The uncaptured error and logging callbacks are spontaneous and must not be called - // after we call the device lost's |mCallback| below. Although we have cleared those - // callbacks, we need to wait for any remaining outstanding callbacks to finish before - // continuing. - callbackInfos.Wait([](auto& x) { return x.semaphore == 0; }); - }); + // The uncaptured error and logging callbacks are spontaneous and must not be called + // after we call the device lost's |mCallback| below, so we clear them and wait for them to + // be no longer referenced before moving forwards. + mDevice->mCallbackInfos.Clear(); void* userdata1 = mUserdata1.ExtractAsDangling(); void* userdata2 = mUserdata2.ExtractAsDangling(); @@ -295,8 +219,9 @@ Adapter* adapter, const WGPUDeviceDescriptor* descriptor) : RefCountedWithExternalCount<ObjectWithEventsBase>(params, eventManagerHandle), - mDeviceLostInfo(AcquireRef(new DeviceLostEvent(GetDeviceLostCallbackInfo(descriptor), this))), - mCallbackInfos(GetUncapturedErrorCallbackInfo(descriptor), kDefaultLoggingCallbackInfo), + mDeviceLostInfo( + AcquireRef(new DeviceLostEvent(GetDeviceLostCallbackInfoOrDefault(descriptor), this))), + mCallbackInfos(descriptor), mAdapter(adapter) {} ObjectType Device::GetObjectType() const { @@ -361,53 +286,12 @@ } void Device::HandleError(WGPUErrorType errorType, WGPUStringView message) { - std::optional<WGPUUncapturedErrorCallbackInfo> callbackInfo; - mCallbackInfos.Use<NotifyType::None>([&](auto callbackInfos) { - callbackInfo = callbackInfos->error; - if (callbackInfo) { - callbackInfos->semaphore += 1; - } - }); - - // If we don't have a callback info, we can just return. - if (!callbackInfo) { - return; - } - - // Call the callback without holding the lock to prevent any re-entrant issues. - DAWN_ASSERT(callbackInfo->callback != nullptr); const auto device = ToAPI(this); - callbackInfo->callback(&device, errorType, message, callbackInfo->userdata1, - callbackInfo->userdata2); - - mCallbackInfos.Use([&](auto callbackInfos) { - DAWN_ASSERT(callbackInfos->semaphore > 0); - callbackInfos->semaphore -= 1; - }); + mCallbackInfos.CallErrorCallback(&device, errorType, message); } void Device::HandleLogging(WGPULoggingType loggingType, WGPUStringView message) { - std::optional<WGPULoggingCallbackInfo> callbackInfo; - mCallbackInfos.Use<NotifyType::None>([&](auto callbackInfos) { - callbackInfo = callbackInfos->logging; - if (callbackInfo) { - callbackInfos->semaphore += 1; - } - }); - - // If we don't have a callback info, we can just return. - if (!callbackInfo) { - return; - } - - // Call the callback without holding the lock to prevent any re-entrant issues. - DAWN_ASSERT(callbackInfo->callback != nullptr); - callbackInfo->callback(loggingType, message, callbackInfo->userdata1, callbackInfo->userdata2); - - mCallbackInfos.Use([&](auto callbackInfos) { - DAWN_ASSERT(callbackInfos->semaphore > 0); - callbackInfos->semaphore -= 1; - }); + mCallbackInfos.CallLoggingCallback(loggingType, message); } void Device::HandleDeviceLost(WGPUDeviceLostReason reason, WGPUStringView message) { @@ -429,8 +313,7 @@ void Device::APISetLoggingCallback(const WGPULoggingCallbackInfo& callbackInfo) { if (mIsAlive) { - mCallbackInfos.Use<NotifyType::None>( - [&](auto callbackInfos) { callbackInfos->logging = callbackInfo; }); + mCallbackInfos.SetLoggingCallbackInfo(callbackInfo); } }
diff --git a/src/dawn/wire/client/Device.h b/src/dawn/wire/client/Device.h index 1d3130e..3d2f6ad 100644 --- a/src/dawn/wire/client/Device.h +++ b/src/dawn/wire/client/Device.h
@@ -34,8 +34,8 @@ #include <optional> #include "dawn/common/LinkedList.h" -#include "dawn/common/MutexProtected.h" #include "dawn/common/RefCountedWithExternalCount.h" +#include "dawn/common/WGPUDeviceCallbackInfos.h" #include "dawn/wire/WireCmd_autogen.h" #include "dawn/wire/client/ApiObjects_autogen.h" #include "dawn/wire/client/LimitsAndFeatures.h" @@ -106,22 +106,7 @@ LimitsAndFeatures mLimitsAndFeatures; std::variant<Ref<TrackedEvent>, FutureID> mDeviceLostInfo; - struct CallbackInfos { - CallbackInfos(const WGPUUncapturedErrorCallbackInfo& error, - const WGPULoggingCallbackInfo& logging); - - // The callback infos are optional because once the device is lost, they are set to - // std::nullopt and no longer do anything. - std::optional<WGPUUncapturedErrorCallbackInfo> error = std::nullopt; - std::optional<WGPULoggingCallbackInfo> logging = std::nullopt; - - // Counter that tracks how many places are currently using callback infos. This is used to - // ensure that before we call the device lost callback (which may deallocate the uncaptured - // error and logging callbacks), we have ensured that there are no outstanding references to - // those callbacks. - uint32_t semaphore = 0; - }; - MutexCondVarProtected<CallbackInfos> mCallbackInfos; + WGPUDeviceCallbackInfos mCallbackInfos; Ref<Adapter> mAdapter; Ref<Queue> mQueue;