// Copyright 2024 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expresso or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "DeviceOpTracker.h" #include #include #include "host-common/GfxstreamFatalError.h" #include "host-common/logging.h" namespace gfxstream { namespace vk { namespace { using emugl::ABORT_REASON_OTHER; using emugl::FatalError; constexpr const size_t kSizeLoggingThreshold = 20; constexpr const auto kTimeThreshold = std::chrono::seconds(5); template inline constexpr bool always_false_v = false; } // namespace DeviceOpTracker::DeviceOpTracker(VkDevice device, VulkanDispatch* deviceDispatch) : mDevice(device), mDeviceDispatch(deviceDispatch) {} void DeviceOpTracker::AddPendingGarbage(DeviceOpWaitable waitable, VkFence fence) { std::lock_guard lock(mPendingGarbageMutex); mPendingGarbage.push_back(PendingGarabage{ .waitable = std::move(waitable), .obj = fence, .timepoint = std::chrono::system_clock::now(), }); if (mPendingGarbage.size() > kSizeLoggingThreshold) { WARN("VkDevice:%p has %d pending garbage objects.", mDevice, mPendingGarbage.size()); } } void DeviceOpTracker::AddPendingGarbage(DeviceOpWaitable waitable, VkSemaphore semaphore) { std::lock_guard lock(mPendingGarbageMutex); mPendingGarbage.push_back(PendingGarabage{ .waitable = std::move(waitable), .obj = semaphore, .timepoint = std::chrono::system_clock::now(), }); if (mPendingGarbage.size() > kSizeLoggingThreshold) { WARN("VkDevice:%p has %d pending garbage objects.", mDevice, mPendingGarbage.size()); } } void DeviceOpTracker::PollAndProcessGarbage() { { std::lock_guard lock(mPollFunctionsMutex); // Assuming that polling functions are added to the queue in the roughly the order // they are used, encountering an unsignaled/pending polling functions likely means // that all polling functions after are also still pending. This might not necessarily // always be the case but it is a simple heuristic to try to minimize the amount of // work performed here as it is expected that this function will be called while // processing other guest vulkan functions. auto firstPendingIt = std::find_if(mPollFunctions.begin(), mPollFunctions.end(), [](const OpPollingFunction& pollingFunc) { DeviceOpStatus status = pollingFunc(); return status == DeviceOpStatus::kPending; }); mPollFunctions.erase(mPollFunctions.begin(), firstPendingIt); if (mPollFunctions.size() > kSizeLoggingThreshold) { WARN("VkDevice:%p has %d pending waitables.", mDevice, mPollFunctions.size()); } } const auto now = std::chrono::system_clock::now(); const auto old = now - kTimeThreshold; { std::lock_guard lock(mPendingGarbageMutex); // Assuming that pending garbage is added to the queue in the roughly the order // they are used, encountering an unsignaled/pending waitable likely means that // all pending garbage after is also still pending. This might not necessarily // always be the case but it is a simple heuristic to try to minimize the amount // of work performed here as it is expected that this function will be called // while processing other guest vulkan functions. auto firstPendingIt = std::find_if(mPendingGarbage.begin(), mPendingGarbage.end(), [&](const PendingGarabage& pendingGarbage) { if (pendingGarbage.timepoint < old) { return /*still pending=*/false; } return !IsDone(pendingGarbage.waitable); }); for (auto it = mPendingGarbage.begin(); it != firstPendingIt; it++) { PendingGarabage& pendingGarbage = *it; if (pendingGarbage.timepoint < old) { const auto difference = std::chrono::duration_cast( pendingGarbage.timepoint - now); WARN("VkDevice:%p had a waitable pending for %d milliseconds. Leaking object.", mDevice, difference.count()); continue; } std::visit( [this](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { mDeviceDispatch->vkDestroyFence(mDevice, arg, nullptr); } else if constexpr (std::is_same_v) { mDeviceDispatch->vkDestroySemaphore(mDevice, arg, nullptr); } else { static_assert(always_false_v, "non-exhaustive visitor!"); } }, pendingGarbage.obj); } mPendingGarbage.erase(mPendingGarbage.begin(), firstPendingIt); if (mPendingGarbage.size() > kSizeLoggingThreshold) { WARN("VkDevice:%p has %d pending garbage objects.", mDevice, mPendingGarbage.size()); } } } void DeviceOpTracker::OnDestroyDevice() { mDeviceDispatch->vkDeviceWaitIdle(mDevice); PollAndProcessGarbage(); { std::lock_guard lock(mPendingGarbageMutex); if (!mPendingGarbage.empty()) { WARN("VkDevice:%p has %d leaking garbage objects on destruction.", mDevice, mPendingGarbage.size()); } } } void DeviceOpTracker::AddPendingDeviceOp(std::function pollFunction) { std::lock_guard lock(mPollFunctionsMutex); mPollFunctions.push_back(std::move(pollFunction)); } DeviceOpBuilder::DeviceOpBuilder(DeviceOpTracker& tracker) : mTracker(tracker) {} DeviceOpBuilder::~DeviceOpBuilder() { if (!mSubmittedFence) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Invalid usage: failed to call OnQueueSubmittedWithFence()."; } } VkFence DeviceOpBuilder::CreateFenceForOp() { const VkFenceCreateInfo fenceCreateInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0, }; VkFence fence = VK_NULL_HANDLE; VkResult result = mTracker.mDeviceDispatch->vkCreateFence(mTracker.mDevice, &fenceCreateInfo, nullptr, &fence); mCreatedFence = fence; if (result != VK_SUCCESS) { ERR("DeviceOpBuilder failed to create VkFence!"); return VK_NULL_HANDLE; } return fence; } DeviceOpWaitable DeviceOpBuilder::OnQueueSubmittedWithFence(VkFence fence) { if (mCreatedFence.has_value() && fence != mCreatedFence) { GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Invalid usage: failed to call OnQueueSubmittedWithFence() with the fence " << "requested from CreateFenceForOp."; } mSubmittedFence = fence; const bool destroyFenceOnCompletion = mCreatedFence.has_value(); std::shared_ptr> promise = std::make_shared>(); DeviceOpWaitable future = promise->get_future().share(); mTracker.AddPendingDeviceOp([device = mTracker.mDevice, deviceDispatch = mTracker.mDeviceDispatch, fence, promise = std::move(promise), destroyFenceOnCompletion] { if (fence == VK_NULL_HANDLE) { return DeviceOpStatus::kDone; } VkResult result = deviceDispatch->vkWaitForFences(device, 1, &fence, /*waitAll=*/VK_TRUE, /*timeout=*/0); if (result == VK_TIMEOUT) { return DeviceOpStatus::kPending; } if (destroyFenceOnCompletion) { deviceDispatch->vkDestroyFence(device, fence, nullptr); } promise->set_value(); return result == VK_SUCCESS ? DeviceOpStatus::kDone : DeviceOpStatus::kFailure; }); return future; } } // namespace vk } // namespace gfxstream