diff options
| -rw-r--r-- | services/surfaceflinger/Layer.cpp | 56 | ||||
| -rw-r--r-- | services/surfaceflinger/Layer.h | 12 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/LayerHistory.cpp | 7 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/LayerHistory.h | 5 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/LayerInfo.cpp | 29 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/LayerInfo.h | 12 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/Scheduler.h | 10 | ||||
| -rw-r--r-- | services/surfaceflinger/Scheduler/include/scheduler/Features.h | 1 | ||||
| -rw-r--r-- | services/surfaceflinger/SurfaceFlinger.cpp | 12 | ||||
| -rw-r--r-- | services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp | 71 |
10 files changed, 212 insertions, 3 deletions
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 5a010e8af6..68230efd20 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3178,6 +3178,14 @@ bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer, } mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint; + + // If the layer had been updated a TextureView, this would make sure the present time could be + // same to TextureView update when it's a small dirty, and get the correct heuristic rate. + if (mFlinger->mScheduler->supportSmallDirtyDetection()) { + if (mDrawingState.useVsyncIdForRefreshRateSelection) { + mUsedVsyncIdForRefreshRateSelection = true; + } + } return true; } @@ -3200,10 +3208,38 @@ void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerPro mDrawingState.latchedVsyncId); if (prediction.has_value()) { ATRACE_FORMAT_INSTANT("predictedPresentTime"); + mMaxTimeForUseVsyncId = prediction->presentTime + + scheduler::LayerHistory::kMaxPeriodForHistory.count(); return prediction->presentTime; } } + if (!mFlinger->mScheduler->supportSmallDirtyDetection()) { + return static_cast<nsecs_t>(0); + } + + // If the layer is not an application and didn't set an explicit rate or desiredPresentTime, + // return "0" to tell the layer history that it will use the max refresh rate without + // calculating the adaptive rate. + if (mWindowType != WindowInfo::Type::APPLICATION && + mWindowType != WindowInfo::Type::BASE_APPLICATION) { + return static_cast<nsecs_t>(0); + } + + // Return the valid present time only when the layer potentially updated a TextureView so + // LayerHistory could heuristically calculate the rate if the UI is continually updating. + if (mUsedVsyncIdForRefreshRateSelection) { + const auto prediction = + mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken( + mDrawingState.latchedVsyncId); + if (prediction.has_value()) { + if (mMaxTimeForUseVsyncId >= prediction->presentTime) { + return prediction->presentTime; + } + mUsedVsyncIdForRefreshRateSelection = false; + } + } + return static_cast<nsecs_t>(0); }(); @@ -3263,6 +3299,7 @@ bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) { mDrawingState.surfaceDamageRegion = surfaceDamage; mDrawingState.modified = true; setTransactionFlags(eTransactionNeeded); + setIsSmallDirty(); return true; } @@ -4310,6 +4347,25 @@ void Layer::updateLastLatchTime(nsecs_t latchTime) { mLastLatchTime = latchTime; } +void Layer::setIsSmallDirty() { + if (!mFlinger->mScheduler->supportSmallDirtyDetection()) { + return; + } + + if (mWindowType != WindowInfo::Type::APPLICATION && + mWindowType != WindowInfo::Type::BASE_APPLICATION) { + return; + } + Rect bounds = mDrawingState.surfaceDamageRegion.getBounds(); + if (!bounds.isValid()) { + return; + } + + // If the damage region is a small dirty, this could give the hint for the layer history that + // it could suppress the heuristic rate when calculating. + mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(bounds.getWidth() * bounds.getHeight()); +} + // --------------------------------------------------------------------------- std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2fbbbdcb5c..895d25aa03 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -842,6 +842,14 @@ public: mutable bool contentDirty{false}; Region surfaceDamageRegion; + // True when the surfaceDamageRegion is recognized as a small area update. + bool mSmallDirty{false}; + // Used to check if mUsedVsyncIdForRefreshRateSelection should be expired when it stop updating. + nsecs_t mMaxTimeForUseVsyncId = 0; + // True when DrawState.useVsyncIdForRefreshRateSelection previously set to true during updating + // buffer. + bool mUsedVsyncIdForRefreshRateSelection{false}; + // Layer serial number. This gives layers an explicit ordering, so we // have a stable sort order when their layer stack and Z-order are // the same. @@ -904,6 +912,7 @@ public: .transform = getTransform(), .setFrameRateVote = getFrameRateForLayerTree(), .frameRateSelectionPriority = getFrameRateSelectionPriority(), + .isSmallDirty = mSmallDirty, }; }; bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; } @@ -917,6 +926,9 @@ public: // Exposed so SurfaceFlinger can assert that it's held const sp<SurfaceFlinger> mFlinger; + // Check if the damage region is a small dirty. + void setIsSmallDirty(); + protected: // For unit tests friend class TestableSurfaceFlinger; diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index beaf9724a3..a812ab74a6 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -320,4 +320,11 @@ auto LayerHistory::findLayer(int32_t id) -> std::pair<LayerStatus, LayerPair*> { return {LayerStatus::NotFound, nullptr}; } +bool LayerHistory::isSmallDirtyArea(uint32_t dirtyArea) const { + const float ratio = (float)dirtyArea / mDisplayArea; + const bool isSmallDirty = ratio <= kSmallDirtyArea; + ATRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio); + return isSmallDirty; +} + } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h index 69caf9ffd2..06364156bb 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.h +++ b/services/surfaceflinger/Scheduler/LayerHistory.h @@ -43,6 +43,7 @@ struct LayerProps; class LayerHistory { public: using LayerVoteType = RefreshRateSelector::LayerVoteType; + static constexpr std::chrono::nanoseconds kMaxPeriodForHistory = 1s; LayerHistory(); ~LayerHistory(); @@ -87,10 +88,14 @@ public: void attachChoreographer(int32_t layerId, const sp<EventThreadConnection>& choreographerConnection); + bool isSmallDirtyArea(uint32_t dirtyArea) const; + private: friend class LayerHistoryTest; friend class TestableScheduler; + static constexpr float kSmallDirtyArea = 0.07f; + using LayerPair = std::pair<Layer*, std::unique_ptr<LayerInfo>>; // keyed by id as returned from Layer::getSequence() using LayerInfos = std::unordered_map<int32_t, LayerPair>; diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index bae3739501..348e2b9c72 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -63,7 +63,8 @@ void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUp case LayerUpdateType::Buffer: FrameTimeData frameTime = {.presentTime = lastPresentTime, .queueTime = mLastUpdatedTime, - .pendingModeChange = pendingModeChange}; + .pendingModeChange = pendingModeChange, + .isSmallDirty = props.isSmallDirty}; mFrameTimes.push_back(frameTime); if (mFrameTimes.size() > HISTORY_SIZE) { mFrameTimes.pop_front(); @@ -99,11 +100,15 @@ LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const { // classification. bool isFrequent = true; bool isInfrequent = true; + int32_t smallDirtyCount = 0; const auto n = mFrameTimes.size() - 1; for (size_t i = 0; i < kFrequentLayerWindowSize - 1; i++) { if (mFrameTimes[n - i].queueTime - mFrameTimes[n - i - 1].queueTime < kMaxPeriodForFrequentLayerNs.count()) { isInfrequent = false; + if (mFrameTimes[n - i].presentTime == 0 && mFrameTimes[n - i].isSmallDirty) { + smallDirtyCount++; + } } else { isFrequent = false; } @@ -113,7 +118,8 @@ LayerInfo::Frequent LayerInfo::isFrequent(nsecs_t now) const { // If the layer was previously inconclusive, we clear // the history as indeterminate layers changed to frequent, // and we should not look at the stale data. - return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true}; + return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true, + /* isSmallDirty */ smallDirtyCount >= kNumSmallDirtyThreshold}; } // If we can't determine whether the layer is frequent or not, we return @@ -202,6 +208,7 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const { nsecs_t totalDeltas = 0; int numDeltas = 0; + int32_t smallDirtyCount = 0; auto prevFrame = mFrameTimes.begin(); for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) { const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame); @@ -210,6 +217,13 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const { continue; } + // If this is a small area update, we don't want to consider it for calculating the average + // frame time. Instead, we let the bigger frame updates to drive the calculation. + if (it->isSmallDirty && currDelta < kMinPeriodBetweenSmallDirtyFrames) { + smallDirtyCount++; + continue; + } + prevFrame = it; if (currDelta > kMaxPeriodBetweenFrames) { @@ -221,6 +235,10 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const { numDeltas++; } + if (smallDirtyCount > 0) { + ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount); + } + if (numDeltas == 0) { return std::nullopt; } @@ -295,6 +313,13 @@ LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& se clearHistory(now); } + // Return no vote if the latest frames are small dirty. + if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) { + ATRACE_FORMAT_INSTANT("NoVote (small dirty)"); + ALOGV("%s is small dirty", mName.c_str()); + return {LayerHistory::LayerVoteType::NoVote, Fps()}; + } + auto refreshRate = calculateRefreshRateIfPossible(selector, now); if (refreshRate.has_value()) { ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str()); diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index c5a60573f5..6a8580630d 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -57,6 +57,7 @@ class LayerInfo { static constexpr Fps kMinFpsForFrequentLayer = 10_Hz; static constexpr auto kMaxPeriodForFrequentLayerNs = std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms; + static constexpr size_t kNumSmallDirtyThreshold = 2; friend class LayerHistoryTest; friend class LayerInfoTest; @@ -195,6 +196,7 @@ private: nsecs_t presentTime; // desiredPresentTime, if provided nsecs_t queueTime; // buffer queue time bool pendingModeChange; + bool isSmallDirty; }; // Holds information about the calculated and reported refresh rate @@ -259,6 +261,8 @@ private: bool clearHistory; // Represents whether we were able to determine isFrequent conclusively bool isConclusive; + // Represents whether the latest frames are small dirty. + bool isSmallDirty = false; }; Frequent isFrequent(nsecs_t now) const; bool isAnimating(nsecs_t now) const; @@ -277,6 +281,11 @@ private: // this period apart from each other, the interval between them won't be // taken into account when calculating average frame rate. static constexpr nsecs_t kMaxPeriodBetweenFrames = kMinFpsForFrequentLayer.getPeriodNsecs(); + // Used for sanitizing the heuristic data. If frames are small dirty updating and are less + // than this period apart from each other, the interval between them won't be + // taken into account when calculating average frame rate. + static constexpr nsecs_t kMinPeriodBetweenSmallDirtyFrames = (60_Hz).getPeriodNsecs(); + LayerHistory::LayerVoteType mDefaultVote; LayerVote mLayerVote; @@ -291,7 +300,7 @@ private: std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince = std::chrono::steady_clock::now(); static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE; - static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s; + static constexpr std::chrono::nanoseconds HISTORY_DURATION = LayerHistory::kMaxPeriodForHistory; std::unique_ptr<LayerProps> mLayerProps; @@ -309,6 +318,7 @@ struct LayerProps { ui::Transform transform; LayerInfo::FrameRate setFrameRateVote; int32_t frameRateSelectionPriority = -1; + bool isSmallDirty = false; }; } // namespace scheduler diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index b9137003c5..d3c8079c1d 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -305,6 +305,16 @@ public: return mLayerHistory.getLayerFramerate(now, id); } + // Returns true if the small dirty detection is enabled. + bool supportSmallDirtyDetection() const { + return mFeatures.test(Feature::kSmallDirtyContentDetection); + } + + // Returns true if the dirty area is less than threshold. + bool isSmallDirtyArea(uint32_t dirtyArea) const { + return mLayerHistory.isSmallDirtyArea(dirtyArea); + } + private: friend class TestableScheduler; diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h index 200407d1a6..7c72ac6afc 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h @@ -28,6 +28,7 @@ enum class Feature : std::uint8_t { kContentDetection = 1 << 2, kTracePredictedVsync = 1 << 3, kBackpressureGpuComposition = 1 << 4, + kSmallDirtyContentDetection = 1 << 5, }; using FeatureFlags = ftl::Flags<Feature>; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 39ea248ea6..628da0ba12 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3924,6 +3924,9 @@ void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) { if (sysprop::use_content_detection_for_refresh_rate(false)) { features |= Feature::kContentDetection; + if (base::GetBoolProperty("debug.sf.enable_small_dirty_detection"s, false)) { + features |= Feature::kSmallDirtyContentDetection; + } } if (base::GetBoolProperty("debug.sf.show_predicted_vsync"s, false)) { features |= Feature::kTracePredictedVsync; @@ -7959,6 +7962,15 @@ void SurfaceFlinger::sample() { void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) { mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight()); getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize()); + + // Notify layers to update small dirty flag. + if (mScheduler->supportSmallDirtyDetection()) { + mCurrentState.traverse([&](Layer* layer) { + if (layer->getLayerStack() == activeDisplay.getLayerStack()) { + layer->setIsSmallDirty(); + } + }); + } } sp<DisplayDevice> SurfaceFlinger::getActivatableDisplay() const { diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index 85d86a7acc..be4b026b35 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -959,6 +959,77 @@ TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) { recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE); } +TEST_F(LayerHistoryTest, smallDirtyLayer) { + auto layer = createLayer(); + + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + + nsecs_t time = systemTime(); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + EXPECT_EQ(0, frequentLayerCount(time)); + + LayerHistory::Summary summary; + + // layer is active but infrequent. + for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { + auto props = layer->getLayerProps(); + if (i % 3 == 0) { + props.isSmallDirty = false; + } else { + props.isSmallDirty = true; + } + + history().record(layer->getSequence(), props, time, time, + LayerHistory::LayerUpdateType::Buffer); + time += HI_FPS_PERIOD; + summary = summarizeLayerHistory(time); + } + + ASSERT_EQ(1, summary.size()); + ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote); + EXPECT_GE(HI_FPS, summary[0].desiredRefreshRate); +} + +TEST_F(LayerHistoryTest, smallDirtyInMultiLayer) { + auto layer1 = createLayer("UI"); + auto layer2 = createLayer("Video"); + + EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate())); + + EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer2, getFrameRateForLayerTree()) + .WillRepeatedly( + Return(Layer::FrameRate(30_Hz, Layer::FrameRateCompatibility::Default))); + + nsecs_t time = systemTime(); + + EXPECT_EQ(2, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + EXPECT_EQ(0, frequentLayerCount(time)); + + LayerHistory::Summary summary; + + // layer1 is active but infrequent. + for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { + auto props = layer1->getLayerProps(); + props.isSmallDirty = true; + history().record(layer1->getSequence(), props, 0 /*presentTime*/, time, + LayerHistory::LayerUpdateType::Buffer); + history().record(layer2->getSequence(), layer2->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + time += HI_FPS_PERIOD; + summary = summarizeLayerHistory(time); + } + + ASSERT_EQ(1, summary.size()); + ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote); + ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate); +} + class LayerHistoryTestParameterized : public LayerHistoryTest, public testing::WithParamInterface<std::chrono::nanoseconds> { }; |