| // Copyright 2021 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 express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| #include "VirtioGpuTimelines.h" |
| |
| #include <cinttypes> |
| #include <cstdio> |
| |
| #include "gfxstream/host/Tracing.h" |
| #include "host-common/GfxstreamFatalError.h" |
| |
| using TaskId = VirtioGpuTimelines::TaskId; |
| using Ring = VirtioGpuTimelines::Ring; |
| using FenceId = VirtioGpuTimelines::FenceId; |
| using emugl::ABORT_REASON_OTHER; |
| using emugl::FatalError; |
| |
| std::unique_ptr<VirtioGpuTimelines> VirtioGpuTimelines::create(FenceCompletionCallback callback) { |
| return std::unique_ptr<VirtioGpuTimelines>(new VirtioGpuTimelines(std::move(callback))); |
| } |
| |
| VirtioGpuTimelines::VirtioGpuTimelines(FenceCompletionCallback callback) |
| : mFenceCompletionCallback(std::move(callback)), mNextId(0) { |
| gfxstream::host::InitializeTracing(); |
| } |
| |
| TaskId VirtioGpuTimelines::enqueueTask(const Ring& ring) { |
| std::lock_guard<std::mutex> lock(mTimelinesMutex); |
| |
| TaskId id = mNextId++; |
| |
| const uint64_t traceId = gfxstream::host::GetUniqueTracingId(); |
| GFXSTREAM_TRACE_EVENT_INSTANT(GFXSTREAM_TRACE_VIRTIO_GPU_TIMELINE_CATEGORY, |
| "Queue timeline task", "Task ID", id, |
| GFXSTREAM_TRACE_FLOW(traceId)); |
| |
| std::shared_ptr<Task> task(new Task(id, ring, traceId), [this](Task* task) { |
| mTaskIdToTask.erase(task->mId); |
| delete task; |
| }); |
| mTaskIdToTask[id] = task; |
| |
| Timeline& timeline = GetOrCreateTimelineLocked(ring); |
| timeline.mQueue.emplace_back(std::move(task)); |
| return id; |
| } |
| |
| void VirtioGpuTimelines::enqueueFence(const Ring& ring, FenceId fenceId) { |
| std::lock_guard<std::mutex> lock(mTimelinesMutex); |
| |
| Timeline& timeline = GetOrCreateTimelineLocked(ring); |
| timeline.mQueue.emplace_back(fenceId); |
| |
| poll_locked(ring); |
| } |
| |
| void VirtioGpuTimelines::notifyTaskCompletion(TaskId taskId) { |
| std::lock_guard<std::mutex> lock(mTimelinesMutex); |
| auto iTask = mTaskIdToTask.find(taskId); |
| if (iTask == mTaskIdToTask.end()) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) |
| << "Task(id = " << static_cast<uint64_t>(taskId) << ") can't be found"; |
| } |
| std::shared_ptr<Task> task = iTask->second.lock(); |
| if (task == nullptr) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) |
| << "Task(id = " << static_cast<uint64_t>(taskId) << ") has been destroyed"; |
| } |
| if (task->mId != taskId) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) |
| << "Task id mismatch. Expected " << static_cast<uint64_t>(taskId) << " Actual " |
| << static_cast<uint64_t>(task->mId); |
| } |
| if (task->mHasCompleted) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) |
| << "Task(id = " << static_cast<uint64_t>(taskId) << ") has been set to completed."; |
| } |
| |
| GFXSTREAM_TRACE_EVENT_INSTANT(GFXSTREAM_TRACE_VIRTIO_GPU_TIMELINE_CATEGORY, |
| "Notify timeline task completed", |
| GFXSTREAM_TRACE_FLOW(task->mTraceId), "Task ID", task->mId); |
| |
| task->mHasCompleted = true; |
| |
| poll_locked(task->mRing); |
| } |
| |
| VirtioGpuTimelines::Timeline& VirtioGpuTimelines::GetOrCreateTimelineLocked(const Ring& ring) { |
| auto [it, inserted] = |
| mTimelineQueues.emplace(std::piecewise_construct, std::make_tuple(ring), std::make_tuple()); |
| Timeline& timeline = it->second; |
| if (inserted) { |
| timeline.mTraceTrackId = gfxstream::host::GetUniqueTracingId(); |
| |
| const std::string timelineName = "Virtio Gpu Timeline " + to_string(ring); |
| GFXSTREAM_TRACE_NAME_TRACK(GFXSTREAM_TRACE_TRACK(timeline.mTraceTrackId), timelineName); |
| |
| GFXSTREAM_TRACE_EVENT_INSTANT(GFXSTREAM_TRACE_VIRTIO_GPU_TIMELINE_CATEGORY, |
| "Create Timeline", |
| GFXSTREAM_TRACE_TRACK(timeline.mTraceTrackId)); |
| } |
| |
| return timeline; |
| } |
| |
| void VirtioGpuTimelines::poll() { |
| std::lock_guard<std::mutex> lock(mTimelinesMutex); |
| for (const auto& [ring, timeline] : mTimelineQueues) { |
| poll_locked(ring); |
| } |
| } |
| void VirtioGpuTimelines::poll_locked(const Ring& ring) { |
| auto timelineIt = mTimelineQueues.find(ring); |
| if (timelineIt == mTimelineQueues.end()) { |
| GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) |
| << "Ring(" << to_string(ring) << ") doesn't exist."; |
| } |
| Timeline& timeline = timelineIt->second; |
| |
| auto& timelineQueue = timeline.mQueue; |
| auto i = timelineQueue.begin(); |
| for (; i != timelineQueue.end(); i++) { |
| bool shouldStop = std::visit( |
| [&](auto& arg) { |
| using T = std::decay_t<decltype(arg)>; |
| if constexpr (std::is_same_v<T, FenceId>) { |
| auto& fenceId = arg; |
| |
| GFXSTREAM_TRACE_EVENT_INSTANT( |
| GFXSTREAM_TRACE_VIRTIO_GPU_TIMELINE_CATEGORY, "Signal Virtio Gpu Fence", |
| GFXSTREAM_TRACE_TRACK(timeline.mTraceTrackId), "Fence", fenceId); |
| |
| mFenceCompletionCallback(ring, fenceId); |
| |
| return false; |
| } else if constexpr (std::is_same_v<T, std::shared_ptr<Task>>) { |
| auto& task = arg; |
| |
| const bool completed = task->mHasCompleted; |
| if (completed) { |
| GFXSTREAM_TRACE_EVENT_INSTANT( |
| GFXSTREAM_TRACE_VIRTIO_GPU_TIMELINE_CATEGORY, "Process Task Complete", |
| GFXSTREAM_TRACE_TRACK(timeline.mTraceTrackId), |
| GFXSTREAM_TRACE_FLOW(task->mTraceId), "Task", task->mId); |
| } |
| return !completed; |
| } |
| }, |
| *i); |
| |
| if (shouldStop) { |
| break; |
| } |
| } |
| timelineQueue.erase(timelineQueue.begin(), i); |
| } |
| |
| #ifdef GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |
| |
| std::optional<gfxstream::host::snapshot::VirtioGpuRing> SnapshotRing(const VirtioGpuRing& ring) { |
| gfxstream::host::snapshot::VirtioGpuRing snapshot; |
| |
| if (std::holds_alternative<VirtioGpuRingGlobal>(ring)) { |
| snapshot.mutable_global(); |
| } else if (std::holds_alternative<VirtioGpuRingContextSpecific>(ring)) { |
| auto& specific = std::get<VirtioGpuRingContextSpecific>(ring); |
| |
| auto* specificSnapshot = snapshot.mutable_context_specific(); |
| specificSnapshot->set_context_id(specific.mCtxId); |
| specificSnapshot->set_ring_id(specific.mRingIdx); |
| } else { |
| return std::nullopt; |
| } |
| |
| return snapshot; |
| } |
| |
| std::optional<VirtioGpuRing> RestoreRing(const gfxstream::host::snapshot::VirtioGpuRing& snapshot) { |
| if (snapshot.has_global()) { |
| return VirtioGpuRingGlobal{}; |
| } else if (snapshot.has_context_specific()) { |
| const auto& specific = snapshot.context_specific(); |
| return VirtioGpuRingContextSpecific{ |
| .mCtxId = specific.context_id(), |
| .mRingIdx = static_cast<VirtioGpuRingIdx>(specific.ring_id()), |
| }; |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| /*static*/ |
| std::optional<gfxstream::host::snapshot::VirtioGpuTimelineItem> |
| VirtioGpuTimelines::SnapshotTimelineItem(const TimelineItem& timelineItem) { |
| gfxstream::host::snapshot::VirtioGpuTimelineItem snapshot; |
| |
| if (std::holds_alternative<FenceId>(timelineItem)) { |
| auto fenceId = std::get<FenceId>(timelineItem); |
| |
| auto* fenceSnapshot = snapshot.mutable_fence(); |
| fenceSnapshot->set_id(fenceId); |
| } else if (std::holds_alternative<std::shared_ptr<Task>>(timelineItem)) { |
| const auto& task = std::get<std::shared_ptr<Task>>(timelineItem); |
| |
| auto ringOpt = SnapshotRing(task->mRing); |
| if (!ringOpt) { |
| stream_renderer_error("Failed to snapshot timeline item: failed to snapshot ring."); |
| return std::nullopt; |
| } |
| |
| auto* taskSnapshot = snapshot.mutable_task(); |
| taskSnapshot->set_id(task->mId); |
| taskSnapshot->mutable_ring()->Swap(&*ringOpt); |
| taskSnapshot->set_trace_id(task->mTraceId); |
| taskSnapshot->set_completed(task->mHasCompleted); |
| } else { |
| stream_renderer_error("Failed to snapshot timeline item: unhandled."); |
| return std::nullopt; |
| } |
| |
| return snapshot; |
| } |
| |
| /*static*/ |
| std::optional<VirtioGpuTimelines::TimelineItem> VirtioGpuTimelines::RestoreTimelineItem( |
| const gfxstream::host::snapshot::VirtioGpuTimelineItem& snapshot) { |
| if (snapshot.has_fence()) { |
| return snapshot.fence().id(); |
| } else if (snapshot.has_task()) { |
| const auto& taskSnapshot = snapshot.task(); |
| |
| auto ringOpt = RestoreRing(taskSnapshot.ring()); |
| if (!ringOpt) { |
| stream_renderer_error("Failed to restore timeline item: failed to restore ring."); |
| return std::nullopt; |
| } |
| |
| auto task = std::make_shared<Task>(taskSnapshot.id(), *ringOpt, taskSnapshot.trace_id()); |
| task->mHasCompleted = taskSnapshot.completed(); |
| return task; |
| } |
| |
| stream_renderer_error("Failed to restore timeline item: unhandled."); |
| return std::nullopt; |
| } |
| |
| std::optional<gfxstream::host::snapshot::VirtioGpuTimeline> VirtioGpuTimelines::Timeline::Snapshot() |
| const { |
| gfxstream::host::snapshot::VirtioGpuTimeline timeline; |
| timeline.set_trace_id(mTraceTrackId); |
| for (const auto& timelineItem : mQueue) { |
| auto timelineItemSnapshotOpt = SnapshotTimelineItem(timelineItem); |
| if (!timelineItemSnapshotOpt) { |
| stream_renderer_error("Failed to snapshot timeline item."); |
| return std::nullopt; |
| } |
| timeline.mutable_items()->Add(std::move(*timelineItemSnapshotOpt)); |
| } |
| return timeline; |
| } |
| |
| /*static*/ |
| std::optional<VirtioGpuTimelines::Timeline> VirtioGpuTimelines::Timeline::Restore( |
| const gfxstream::host::snapshot::VirtioGpuTimeline& snapshot) { |
| VirtioGpuTimelines::Timeline timeline; |
| timeline.mTraceTrackId = snapshot.trace_id(); |
| for (const auto& timelineItemSnapshot : snapshot.items()) { |
| auto timelineItemOpt = RestoreTimelineItem(timelineItemSnapshot); |
| if (!timelineItemOpt) { |
| stream_renderer_error("Failed to snapshot timeline item."); |
| return std::nullopt; |
| } |
| timeline.mQueue.emplace_back(std::move(*timelineItemOpt)); |
| } |
| return timeline; |
| } |
| |
| std::optional<gfxstream::host::snapshot::VirtioGpuTimelinesSnapshot> VirtioGpuTimelines::Snapshot() |
| const { |
| std::lock_guard<std::mutex> lock(mTimelinesMutex); |
| |
| gfxstream::host::snapshot::VirtioGpuTimelinesSnapshot snapshot; |
| snapshot.set_next_id(mNextId); |
| |
| for (const auto& [ring, timeline] : mTimelineQueues) { |
| auto ringSnapshotOpt = SnapshotRing(ring); |
| if (!ringSnapshotOpt) { |
| stream_renderer_error("Failed to snapshot timelines: failed to snapshot ring."); |
| return std::nullopt; |
| } |
| |
| auto timelineSnapshotOpt = timeline.Snapshot(); |
| if (!timelineSnapshotOpt) { |
| stream_renderer_error("Failed to snapshot timelines: failed to snapshot timeline."); |
| return std::nullopt; |
| } |
| |
| auto* kv = snapshot.add_timelines(); |
| kv->mutable_ring()->Swap(&*ringSnapshotOpt); |
| kv->mutable_timeline()->Swap(&*timelineSnapshotOpt); |
| } |
| |
| return snapshot; |
| } |
| |
| /*static*/ |
| std::unique_ptr<VirtioGpuTimelines> VirtioGpuTimelines::Restore( |
| FenceCompletionCallback callback, |
| const gfxstream::host::snapshot::VirtioGpuTimelinesSnapshot& snapshot) { |
| std::unique_ptr<VirtioGpuTimelines> timelines(new VirtioGpuTimelines(std::move(callback))); |
| timelines->mNextId.store(snapshot.next_id()); |
| |
| for (const auto& timelineSnapshot : snapshot.timelines()) { |
| if (!timelineSnapshot.has_ring()) { |
| stream_renderer_error("Failed to restore timelines: missing ring."); |
| return nullptr; |
| } |
| auto ringOpt = RestoreRing(timelineSnapshot.ring()); |
| if (!ringOpt) { |
| stream_renderer_error("Failed to restore timelines: failed to restore ring."); |
| return nullptr; |
| } |
| |
| if (!timelineSnapshot.has_timeline()) { |
| stream_renderer_error("Failed to restore timelines: missing timeline."); |
| return nullptr; |
| } |
| auto timelineOpt = Timeline::Restore(timelineSnapshot.timeline()); |
| if (!timelineOpt) { |
| stream_renderer_error("Failed to restore timelines: failed to restore timeline."); |
| return nullptr; |
| } |
| |
| timelines->mTimelineQueues[std::move(*ringOpt)] = std::move(*timelineOpt); |
| } |
| |
| // Rebuild task index: |
| for (const auto& [_, timeline] : timelines->mTimelineQueues) { |
| for (const auto& timelineItem : timeline.mQueue) { |
| if (std::holds_alternative<std::shared_ptr<Task>>(timelineItem)) { |
| auto& task = std::get<std::shared_ptr<Task>>(timelineItem); |
| timelines->mTaskIdToTask[task->mId] = task; |
| } |
| } |
| } |
| |
| return timelines; |
| } |
| |
| #endif // GFXSTREAM_BUILD_WITH_SNAPSHOT_FRONTEND_SUPPORT |