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) {