SF: Report missed frames per display

Create a FrameTargeter per display, each with its own present fences and
metrics about missed frames. Label the traces for missed frames with the
DisplayId. Track CompositionCoverage per display.

Fixes: 262269033
Bug: 241285475
Test: Perfetto
Test: dumpsys SurfaceFlinger --scheduler
Change-Id: I0e599c602b9fd9ae4446dd076dea4b8a75652bd4
Merged-In: I0e599c602b9fd9ae4446dd076dea4b8a75652bd4
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 41639b6..0dca21e 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -25,6 +25,7 @@
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
+#include <ftl/concat.h>
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <ftl/small_map.h>
@@ -130,8 +131,8 @@
     auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) {
         std::scoped_lock lock(mDisplayLock);
         const bool isNew = mDisplays
-                                   .emplace_or_replace(displayId, std::move(selectorPtr),
-                                                       std::move(schedulePtr))
+                                   .emplace_or_replace(displayId, displayId, std::move(selectorPtr),
+                                                       std::move(schedulePtr), mFeatures)
                                    .second;
 
         return std::make_pair(promotePacesetterDisplayLocked(), isNew);
@@ -171,21 +172,43 @@
 
 void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId,
                               TimePoint expectedVsyncTime) {
-    mPacesetterFrameTargeter.beginFrame({.frameBeginTime = SchedulerClock::now(),
-                                         .vsyncId = vsyncId,
-                                         .expectedVsyncTime = expectedVsyncTime,
-                                         .sfWorkDuration =
-                                                 mVsyncModulator->getVsyncConfig().sfWorkDuration},
-                                        *getVsyncSchedule());
+    const FrameTargeter::BeginFrameArgs beginFrameArgs =
+            {.frameBeginTime = SchedulerClock::now(),
+             .vsyncId = vsyncId,
+             // TODO(b/255601557): Calculate per display.
+             .expectedVsyncTime = expectedVsyncTime,
+             .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration};
 
-    if (!compositor.commit(mPacesetterFrameTargeter.target())) {
-        return;
+    LOG_ALWAYS_FATAL_IF(!mPacesetterDisplayId);
+    const auto pacesetterId = *mPacesetterDisplayId;
+    const auto pacesetterOpt = mDisplays.get(pacesetterId);
+
+    FrameTargeter& pacesetterTargeter = *pacesetterOpt->get().targeterPtr;
+    pacesetterTargeter.beginFrame(beginFrameArgs, *pacesetterOpt->get().schedulePtr);
+
+    if (!compositor.commit(pacesetterTargeter.target())) return;
+
+    // TODO(b/256196556): Choose the frontrunner display.
+    FrameTargeters targeters;
+    targeters.try_emplace(pacesetterId, &pacesetterTargeter);
+
+    for (auto& [id, display] : mDisplays) {
+        if (id == pacesetterId) continue;
+
+        FrameTargeter& targeter = *display.targeterPtr;
+        targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
+
+        targeters.try_emplace(id, &targeter);
     }
 
-    const auto compositeResult = compositor.composite(mPacesetterFrameTargeter);
+    const auto resultsPerDisplay = compositor.composite(pacesetterId, targeters);
     compositor.sample();
 
-    mPacesetterFrameTargeter.endFrame(compositeResult);
+    for (const auto& [id, targeter] : targeters) {
+        const auto resultOpt = resultsPerDisplay.get(id);
+        LOG_ALWAYS_FATAL_IF(!resultOpt);
+        targeter->endFrame(*resultOpt);
+    }
 }
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
@@ -534,8 +557,16 @@
 }
 
 void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
-    auto schedule = getVsyncSchedule(id);
-    LOG_ALWAYS_FATAL_IF(!schedule);
+    const auto scheduleOpt =
+            (ftl::FakeGuard(mDisplayLock), mDisplays.get(id)).and_then([](const Display& display) {
+                return display.powerMode == hal::PowerMode::OFF
+                        ? std::nullopt
+                        : std::make_optional(display.schedulePtr);
+            });
+
+    if (!scheduleOpt) return;
+    const auto& schedule = scheduleOpt->get();
+
     if (const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence))) {
         schedule->enableHardwareVsync();
     } else {
@@ -724,7 +755,23 @@
     mFrameRateOverrideMappings.dump(dumper);
     dumper.eol();
 
-    mPacesetterFrameTargeter.dump(dumper);
+    {
+        utils::Dumper::Section section(dumper, "Frame Targeting"sv);
+
+        std::scoped_lock lock(mDisplayLock);
+        ftl::FakeGuard guard(kMainThreadContext);
+
+        for (const auto& [id, display] : mDisplays) {
+            utils::Dumper::Section
+                    section(dumper,
+                            id == mPacesetterDisplayId
+                                    ? ftl::Concat("Pacesetter Display ", id.value).c_str()
+                                    : ftl::Concat("Follower Display ", id.value).c_str());
+
+            display.targeterPtr->dump(dumper);
+            dumper.eol();
+        }
+    }
 }
 
 void Scheduler::dumpVsync(std::string& out) const {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 17e9cea..b913700 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -220,7 +220,7 @@
     // otherwise.
     bool addResyncSample(PhysicalDisplayId, nsecs_t timestamp,
                          std::optional<nsecs_t> hwcVsyncPeriod);
-    void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>) EXCLUDES(mDisplayLock)
+    void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>)
             REQUIRES(kMainThreadContext);
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
@@ -250,7 +250,14 @@
         return std::const_pointer_cast<VsyncSchedule>(std::as_const(*this).getVsyncSchedule(idOpt));
     }
 
-    const FrameTarget& pacesetterFrameTarget() { return mPacesetterFrameTargeter.target(); }
+    TimePoint expectedPresentTimeForPacesetter() const EXCLUDES(mDisplayLock) {
+        std::scoped_lock lock(mDisplayLock);
+        return pacesetterDisplayLocked()
+                .transform([](const Display& display) {
+                    return display.targeterPtr->target().expectedPresentTime();
+                })
+                .value_or(TimePoint());
+    }
 
     // Returns true if a given vsync timestamp is considered valid vsync
     // for a given uid
@@ -306,7 +313,8 @@
     enum class TouchState { Inactive, Active };
 
     // impl::MessageQueue overrides:
-    void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) override;
+    void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) override
+            REQUIRES(kMainThreadContext, mDisplayLock);
 
     // Create a connection on the given EventThread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread>);
@@ -429,13 +437,24 @@
     // must lock for writes but not reads. See also mPolicyLock for locking order.
     mutable std::mutex mDisplayLock;
 
+    using FrameTargeterPtr = std::unique_ptr<FrameTargeter>;
+
     struct Display {
-        Display(RefreshRateSelectorPtr selectorPtr, VsyncSchedulePtr schedulePtr)
-              : selectorPtr(std::move(selectorPtr)), schedulePtr(std::move(schedulePtr)) {}
+        Display(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+                VsyncSchedulePtr schedulePtr, FeatureFlags features)
+              : displayId(displayId),
+                selectorPtr(std::move(selectorPtr)),
+                schedulePtr(std::move(schedulePtr)),
+                targeterPtr(std::make_unique<
+                            FrameTargeter>(displayId,
+                                           features.test(Feature::kBackpressureGpuComposition))) {}
+
+        const PhysicalDisplayId displayId;
 
         // Effectively const except in move constructor.
         RefreshRateSelectorPtr selectorPtr;
         VsyncSchedulePtr schedulePtr;
+        FrameTargeterPtr targeterPtr;
 
         hal::PowerMode powerMode = hal::PowerMode::OFF;
     };
@@ -449,8 +468,6 @@
     ftl::Optional<PhysicalDisplayId> mPacesetterDisplayId GUARDED_BY(mDisplayLock)
             GUARDED_BY(kMainThreadContext);
 
-    FrameTargeter mPacesetterFrameTargeter{mFeatures.test(Feature::kBackpressureGpuComposition)};
-
     ftl::Optional<DisplayRef> pacesetterDisplayLocked() REQUIRES(mDisplayLock) {
         return static_cast<const Scheduler*>(this)->pacesetterDisplayLocked().transform(
                 [](const Display& display) { return std::ref(const_cast<Display&>(display)); });
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index 85f2e64..ae74205 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -20,6 +20,7 @@
 #include <atomic>
 #include <memory>
 
+#include <ui/DisplayId.h>
 #include <ui/Fence.h>
 #include <ui/FenceTime.h>
 
@@ -75,16 +76,17 @@
     bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
 
 protected:
+    explicit FrameTarget(const std::string& displayLabel);
     ~FrameTarget() = default;
 
     VsyncId mVsyncId;
     TimePoint mFrameBeginTime;
     TimePoint mExpectedPresentTime;
 
-    TracedOrdinal<bool> mFramePending{"PrevFramePending", false};
-    TracedOrdinal<bool> mFrameMissed{"PrevFrameMissed", false};
-    TracedOrdinal<bool> mHwcFrameMissed{"PrevHwcFrameMissed", false};
-    TracedOrdinal<bool> mGpuFrameMissed{"PrevGpuFrameMissed", false};
+    TracedOrdinal<bool> mFramePending;
+    TracedOrdinal<bool> mFrameMissed;
+    TracedOrdinal<bool> mHwcFrameMissed;
+    TracedOrdinal<bool> mGpuFrameMissed;
 
     struct FenceWithFenceTime {
         sp<Fence> fence = Fence::NO_FENCE;
@@ -103,8 +105,9 @@
 // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
 class FrameTargeter final : private FrameTarget {
 public:
-    explicit FrameTargeter(bool backpressureGpuComposition)
-          : mBackpressureGpuComposition(backpressureGpuComposition) {}
+    FrameTargeter(PhysicalDisplayId displayId, bool backpressureGpuComposition)
+          : FrameTarget(to_string(displayId)),
+            mBackpressureGpuComposition(backpressureGpuComposition) {}
 
     const FrameTarget& target() const { return *this; }
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
index f795f1f..87c704e 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
@@ -16,6 +16,9 @@
 
 #pragma once
 
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
 #include <scheduler/interface/CompositionCoverage.h>
 
 namespace android {
@@ -24,4 +27,6 @@
     CompositionCoverageFlags compositionCoverage;
 };
 
+using CompositeResultsPerDisplay = ui::PhysicalDisplayMap<PhysicalDisplayId, CompositeResult>;
+
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
index 3d0f1a9..767462d 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
@@ -19,6 +19,8 @@
 #include <cstdint>
 
 #include <ftl/flags.h>
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
 
 namespace android {
 
@@ -34,4 +36,14 @@
 
 using CompositionCoverageFlags = ftl::Flags<CompositionCoverage>;
 
+using CompositionCoveragePerDisplay = ui::DisplayMap<DisplayId, CompositionCoverageFlags>;
+
+inline CompositionCoverageFlags multiDisplayUnion(const CompositionCoveragePerDisplay& displays) {
+    CompositionCoverageFlags coverage;
+    for (const auto& [id, flags] : displays) {
+        coverage |= flags;
+    }
+    return coverage;
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
index 2696076..6fe813a 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -16,6 +16,9 @@
 
 #pragma once
 
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
 #include <scheduler/Time.h>
 #include <scheduler/VsyncId.h>
 #include <scheduler/interface/CompositeResult.h>
@@ -26,6 +29,8 @@
 class FrameTarget;
 class FrameTargeter;
 
+using FrameTargeters = ui::PhysicalDisplayMap<PhysicalDisplayId, scheduler::FrameTargeter*>;
+
 } // namespace scheduler
 
 struct ICompositor {
@@ -38,7 +43,8 @@
 
     // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
     // via RenderEngine and the Composer HAL, respectively.
-    virtual CompositeResult composite(scheduler::FrameTargeter&) = 0;
+    virtual CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+                                                 const scheduler::FrameTargeters&) = 0;
 
     // Samples the composited frame via RegionSamplingThread.
     virtual void sample() = 0;
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 7138afd..7a18654 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -21,6 +21,12 @@
 
 namespace android::scheduler {
 
+FrameTarget::FrameTarget(const std::string& displayLabel)
+      : mFramePending("PrevFramePending " + displayLabel, false),
+        mFrameMissed("PrevFrameMissed " + displayLabel, false),
+        mHwcFrameMissed("PrevHwcFrameMissed " + displayLabel, false),
+        mGpuFrameMissed("PrevGpuFrameMissed " + displayLabel, false) {}
+
 TimePoint FrameTarget::pastVsyncTime(Period vsyncPeriod) const {
     // TODO(b/267315508): Generalize to N VSYNCs.
     const int shift = static_cast<int>(targetsVsyncsAhead<2>(vsyncPeriod));
@@ -130,10 +136,6 @@
 }
 
 void FrameTargeter::dump(utils::Dumper& dumper) const {
-    using namespace std::string_view_literals;
-
-    utils::Dumper::Section section(dumper, "Frame Targeting"sv);
-
     // There are scripts and tests that expect this (rather than "name=value") format.
     dumper.dump({}, "Total missed frame count: " + std::to_string(mFrameMissedCount));
     dumper.dump({}, "HWC missed frame count: " + std::to_string(mHwcFrameMissedCount));
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index 908f214..1e038d1 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -96,7 +96,7 @@
     FenceToFenceTimeMap mFenceMap;
 
     static constexpr bool kBackpressureGpuComposition = true;
-    FrameTargeter mTargeter{kBackpressureGpuComposition};
+    FrameTargeter mTargeter{PhysicalDisplayId::fromPort(13), kBackpressureGpuComposition};
 };
 
 TEST_F(FrameTargeterTest, targetsFrames) {