SF: Updating content FPS tracking
1) Each time SF creates a layer, register it with Scheduler and return handle
2) BufferQueueLayer and BufferStateLayer can now send information about buffers
for given layers via layer handle.
Algorithm for detecting content fps:
1) Keep the refresh rate per layer (explicit timestamp, or 0).
2) Keep information about last 10 present or update timestamps. This will be an
indicator for precedence.
3) Choose the MAX refresh rate among last updated layers.
For more info see go/surface-flinger-scheduler and
go/content-fps-detection-in-scheduler
Test: Updating unit tests. Systrace.
Change-Id: I988a7a79e9a9f0f61674c9b637c5142db3336177
Bug: 127727337
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 776c6e6..e226136 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -190,6 +190,7 @@
desiredPresent - expectedPresent,
systemTime(CLOCK_MONOTONIC),
front->mFrameNumber, maxFrameNumber);
+ ATRACE_NAME("PRESENT_LATER");
return PRESENT_LATER;
}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 52c68df..9080d29 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -147,6 +147,7 @@
"Scheduler/EventThread.cpp",
"Scheduler/IdleTimer.cpp",
"Scheduler/LayerHistory.cpp",
+ "Scheduler/LayerInfo.cpp",
"Scheduler/MessageQueue.cpp",
"Scheduler/Scheduler.cpp",
"Scheduler/SchedulerUtils.cpp",
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 1b2b180..a08ae8c 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -413,6 +413,7 @@
// If the head buffer's acquire fence hasn't signaled yet, return and
// try again later
if (!fenceHasSignaled()) {
+ ATRACE_NAME("!fenceHasSignaled()");
mFlinger->signalLayerUpdate();
return false;
}
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index b623991..ff5f271 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -398,8 +398,8 @@
// Add this buffer from our internal queue tracker
{ // Autolock scope
if (mFlinger->mUseSmart90ForVideo) {
- // Report mApi ID for each layer.
- mFlinger->mScheduler->addNativeWindowApi(item.mApi);
+ const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp;
+ mFlinger->mScheduler->addLayerPresentTime(mSchedulerLayerHandle, presentTime);
}
Mutex::Autolock lock(mQueueItemLock);
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 64dfdc3..2cbb917 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -209,7 +209,8 @@
return true;
}
-bool BufferStateLayer::setBuffer(const sp<GraphicBuffer>& buffer) {
+bool BufferStateLayer::setBuffer(const sp<GraphicBuffer>& buffer, nsecs_t postTime,
+ nsecs_t desiredPresentTime) {
if (mCurrentState.buffer) {
mReleasePreviousBuffer = true;
}
@@ -217,6 +218,15 @@
mCurrentState.buffer = buffer;
mCurrentState.modified = true;
setTransactionFlags(eTransactionNeeded);
+
+ mFlinger->mTimeStats->setPostTime(getSequence(), getFrameNumber(), getName().c_str(), postTime);
+ mDesiredPresentTime = desiredPresentTime;
+
+ if (mFlinger->mUseSmart90ForVideo) {
+ const nsecs_t presentTime = (mDesiredPresentTime == -1) ? 0 : mDesiredPresentTime;
+ mFlinger->mScheduler->addLayerPresentTime(mSchedulerLayerHandle, presentTime);
+ }
+
return true;
}
@@ -348,14 +358,6 @@
return parentBounds;
}
-void BufferStateLayer::setPostTime(nsecs_t postTime) {
- mFlinger->mTimeStats->setPostTime(getSequence(), getFrameNumber(), getName().c_str(), postTime);
-}
-
-void BufferStateLayer::setDesiredPresentTime(nsecs_t desiredPresentTime) {
- mDesiredPresentTime = desiredPresentTime;
-}
-
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 668830a..864a15d 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -63,7 +63,8 @@
bool setTransformToDisplayInverse(bool transformToDisplayInverse) override;
bool setCrop(const Rect& crop) override;
bool setFrame(const Rect& frame) override;
- bool setBuffer(const sp<GraphicBuffer>& buffer) override;
+ bool setBuffer(const sp<GraphicBuffer>& buffer, nsecs_t postTime,
+ nsecs_t desiredPresentTime) override;
bool setAcquireFence(const sp<Fence>& fence) override;
bool setDataspace(ui::Dataspace dataspace) override;
bool setHdrMetadata(const HdrMetadata& hdrMetadata) override;
@@ -90,8 +91,6 @@
Rect getBufferSize(const State& s) const override;
FloatRect computeSourceBounds(const FloatRect& parentBounds) const override;
- void setPostTime(nsecs_t postTime) override;
- void setDesiredPresentTime(nsecs_t desiredPresentTime) override;
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 1142df8..de05f74 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -115,6 +115,8 @@
mFrameEventHistory.initializeCompositorTiming(compositorTiming);
mFrameTracker.setDisplayRefreshPeriod(compositorTiming.interval);
+ mSchedulerLayerHandle = mFlinger->mScheduler->registerLayer(mName.c_str());
+
mFlinger->onLayerCreated();
}
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 8348ce1..66e35b6 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -311,7 +311,10 @@
virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) { return false; };
virtual bool setCrop(const Rect& /*crop*/) { return false; };
virtual bool setFrame(const Rect& /*frame*/) { return false; };
- virtual bool setBuffer(const sp<GraphicBuffer>& /*buffer*/) { return false; };
+ virtual bool setBuffer(const sp<GraphicBuffer>& /*buffer*/, nsecs_t /*postTime*/,
+ nsecs_t /*desiredPresentTime*/) {
+ return false;
+ }
virtual bool setAcquireFence(const sp<Fence>& /*fence*/) { return false; };
virtual bool setDataspace(ui::Dataspace /*dataspace*/) { return false; };
virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) { return false; };
@@ -450,9 +453,6 @@
}
virtual Rect getCrop(const Layer::State& s) const { return s.crop_legacy; }
- virtual void setPostTime(nsecs_t /*postTime*/) {}
- virtual void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/) {}
-
protected:
virtual bool prepareClientLayer(const RenderArea& renderArea, const Region& clip,
bool useIdentityTransform, Region& clearRegion,
@@ -885,6 +885,9 @@
// Can only be accessed with the SF state lock held.
bool mChildrenChanged{false};
+ // This is populated if the layer is registered with Scheduler for tracking purposes.
+ std::unique_ptr<scheduler::LayerHistory::LayerHandle> mSchedulerLayerHandle;
+
private:
/**
* Returns an unsorted vector of all layers that are part of this tree.
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index d5ccbe1..8e36ae9 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -14,14 +14,18 @@
* limitations under the License.
*/
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
#include "LayerHistory.h"
#include <cinttypes>
#include <cstdint>
+#include <limits>
#include <numeric>
#include <string>
#include <unordered_map>
+#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/Timers.h>
#include <utils/Trace.h>
@@ -29,28 +33,114 @@
#include "SchedulerUtils.h"
namespace android {
+namespace scheduler {
-LayerHistory::LayerHistory() {}
+std::atomic<int64_t> LayerHistory::sNextId = 0;
+
+LayerHistory::LayerHistory() {
+ char value[PROPERTY_VALUE_MAX];
+ property_get("debug.sf.layer_history_trace", value, "0");
+ mTraceEnabled = bool(atoi(value));
+}
LayerHistory::~LayerHistory() = default;
-void LayerHistory::insert(const std::string layerName, nsecs_t presentTime) {
- mElements[mCounter].insert(std::make_pair(layerName, presentTime));
+std::unique_ptr<LayerHistory::LayerHandle> LayerHistory::createLayer(const std::string name,
+ float maxRefreshRate) {
+ const int64_t id = sNextId++;
+
+ std::lock_guard lock(mLock);
+ mInactiveLayerInfos.emplace(id, std::make_shared<LayerInfo>(name, maxRefreshRate));
+ return std::make_unique<LayerHistory::LayerHandle>(*this, id);
}
-void LayerHistory::incrementCounter() {
- mCounter++;
- mCounter = mCounter % scheduler::ARRAY_SIZE;
- // Clear all the previous data from the history. This is a ring buffer, so we are
- // reusing memory.
- mElements[mCounter].clear();
+void LayerHistory::destroyLayer(const int64_t id) {
+ std::lock_guard lock(mLock);
+ auto it = mActiveLayerInfos.find(id);
+ if (it != mActiveLayerInfos.end()) {
+ mActiveLayerInfos.erase(it);
+ }
+
+ it = mInactiveLayerInfos.find(id);
+ if (it != mInactiveLayerInfos.end()) {
+ mInactiveLayerInfos.erase(it);
+ }
}
-const std::unordered_map<std::string, nsecs_t>& LayerHistory::get(size_t index) const {
- // For the purposes of the layer history, the index = 0 always needs to start at the
- // current counter, and then decrement to access the layers in correct historical order.
- return mElements.at((scheduler::ARRAY_SIZE + (mCounter - (index % scheduler::ARRAY_SIZE))) %
- scheduler::ARRAY_SIZE);
+void LayerHistory::insert(const std::unique_ptr<LayerHandle>& layerHandle, nsecs_t presentTime) {
+ std::shared_ptr<LayerInfo> layerInfo;
+ {
+ std::lock_guard lock(mLock);
+ auto layerInfoIterator = mInactiveLayerInfos.find(layerHandle->mId);
+ if (layerInfoIterator != mInactiveLayerInfos.end()) {
+ layerInfo = layerInfoIterator->second;
+ mInactiveLayerInfos.erase(layerInfoIterator);
+ mActiveLayerInfos.insert({layerHandle->mId, layerInfo});
+ } else {
+ layerInfoIterator = mActiveLayerInfos.find(layerHandle->mId);
+ if (layerInfoIterator != mActiveLayerInfos.end()) {
+ layerInfo = layerInfoIterator->second;
+ } else {
+ ALOGW("Inserting information about layer that is not registered: %" PRId64,
+ layerHandle->mId);
+ return;
+ }
+ }
+ }
+ layerInfo->setLastPresentTime(presentTime);
}
+float LayerHistory::getDesiredRefreshRate() {
+ float newRefreshRate = 0.f;
+ std::lock_guard lock(mLock);
+
+ removeIrrelevantLayers();
+
+ // Iterate through all layers that have been recently updated, and find the max refresh rate.
+ for (const auto& [layerId, layerInfo] : mActiveLayerInfos) {
+ const float layerRefreshRate = layerInfo->getDesiredRefreshRate();
+ if (mTraceEnabled) {
+ // Store the refresh rate in traces for easy debugging.
+ std::string layerName = "LFPS " + layerInfo->getName();
+ ATRACE_INT(layerName.c_str(), std::round(layerRefreshRate));
+ ALOGD("%s: %f", layerName.c_str(), std::round(layerRefreshRate));
+ }
+ if (layerInfo->isRecentlyActive() && layerRefreshRate > newRefreshRate) {
+ newRefreshRate = layerRefreshRate;
+ }
+ }
+ if (mTraceEnabled) {
+ ALOGD("LayerHistory DesiredRefreshRate: %.2f", newRefreshRate);
+ }
+
+ return newRefreshRate;
+}
+
+void LayerHistory::removeIrrelevantLayers() {
+ const int64_t obsoleteEpsilon = systemTime() - scheduler::OBSOLETE_TIME_EPSILON_NS.count();
+ // Iterator pointing to first element in map
+ auto it = mActiveLayerInfos.begin();
+ while (it != mActiveLayerInfos.end()) {
+ // If last updated was before the obsolete time, remove it.
+ if (it->second->getLastUpdatedTime() < obsoleteEpsilon) {
+ // erase() function returns the iterator of the next
+ // to last deleted element.
+ if (mTraceEnabled) {
+ ALOGD("Layer %s obsolete", it->second->getName().c_str());
+ // Make sure to update systrace to indicate that the layer was erased.
+ std::string layerName = "LFPS " + it->second->getName();
+ ATRACE_INT(layerName.c_str(), 0);
+ }
+ auto id = it->first;
+ auto layerInfo = it->second;
+ layerInfo->clearHistory();
+ mInactiveLayerInfos.insert({id, layerInfo});
+ it = mActiveLayerInfos.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+} // namespace scheduler
} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index c6fab07..39061e7 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -25,40 +25,60 @@
#include <utils/Timers.h>
+#include "LayerInfo.h"
#include "SchedulerUtils.h"
namespace android {
+namespace scheduler {
/*
- * This class represents a circular buffer in which we keep layer history for
- * the past ARRAY_SIZE frames. Each time, a signal for new frame comes, the counter
- * gets incremented and includes all the layers that are requested to draw in that
- * frame.
- *
- * Once the buffer reaches the end of the array, it starts overriding the elements
- * at the beginning of the array.
+ * This class represents information about layers that are considered current. We keep an
+ * unordered map between layer name and LayerInfo.
*/
class LayerHistory {
public:
+ // Handle for each layer we keep track of.
+ class LayerHandle {
+ public:
+ LayerHandle(LayerHistory& lh, int64_t id) : mId(id), mLayerHistory(lh) {}
+ ~LayerHandle() { mLayerHistory.destroyLayer(mId); }
+
+ const int64_t mId;
+
+ private:
+ LayerHistory& mLayerHistory;
+ };
+
LayerHistory();
~LayerHistory();
- // Method for inserting layers and their requested present time into the ring buffer.
- // The elements are going to be inserted into an unordered_map at the position 'now'.
- void insert(const std::string layerName, nsecs_t presentTime);
- // Method for incrementing the current slot in the ring buffer. It also clears the
- // unordered_map, if it was created previously.
- void incrementCounter();
- // Returns unordered_map at the given at index. The index is decremented from 'now'. For
- // example, 0 is now, 1 is previous frame.
- const std::unordered_map<std::string, nsecs_t>& get(size_t index) const;
- // Returns the total size of the ring buffer. The value is always the same regardless
- // of how many slots we filled in.
- static constexpr size_t getSize() { return scheduler::ARRAY_SIZE; }
+ // When the layer is first created, register it.
+ std::unique_ptr<LayerHandle> createLayer(const std::string name, float maxRefreshRate);
+
+ // Method for inserting layers and their requested present time into the unordered map.
+ void insert(const std::unique_ptr<LayerHandle>& layerHandle, nsecs_t presentTime);
+ // Returns the desired refresh rate, which is a max refresh rate of all the current
+ // layers. See go/content-fps-detection-in-scheduler for more information.
+ float getDesiredRefreshRate();
+
+ // Removes the handle and the object from the map.
+ void destroyLayer(const int64_t id);
private:
- size_t mCounter = 0;
- std::array<std::unordered_map<std::string, nsecs_t>, scheduler::ARRAY_SIZE> mElements;
+ // Removes the layers that have been idle for a given amount of time from mLayerInfos.
+ void removeIrrelevantLayers() REQUIRES(mLock);
+
+ // Information about currently active layers.
+ std::mutex mLock;
+ std::unordered_map<int64_t, std::shared_ptr<LayerInfo>> mActiveLayerInfos GUARDED_BY(mLock);
+ std::unordered_map<int64_t, std::shared_ptr<LayerInfo>> mInactiveLayerInfos GUARDED_BY(mLock);
+
+ // Each layer has it's own ID. This variable keeps track of the count.
+ static std::atomic<int64_t> sNextId;
+
+ // Flag whether to log layer FPS in systrace
+ bool mTraceEnabled = false;
};
+} // namespace scheduler
} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
new file mode 100644
index 0000000..95d7d31
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 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 "LayerInfo.h"
+
+#include <cinttypes>
+#include <cstdint>
+#include <numeric>
+#include <string>
+
+namespace android {
+namespace scheduler {
+
+LayerInfo::LayerInfo(const std::string name, float maxRefreshRate)
+ : mName(name),
+ mMinRefreshDuration(1e9f / maxRefreshRate),
+ mRefreshRateHistory(mMinRefreshDuration) {}
+
+LayerInfo::~LayerInfo() = default;
+
+void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime) {
+ std::lock_guard lock(mLock);
+
+ // Buffers can come with a present time far in the future. That keeps them relevant.
+ mLastUpdatedTime = std::max(lastPresentTime, systemTime());
+ mPresentTimeHistory.insertPresentTime(mLastUpdatedTime);
+
+ const nsecs_t timeDiff = lastPresentTime - mLastPresentTime;
+ mLastPresentTime = lastPresentTime;
+ // Ignore time diff that are too high - those are stale values
+ if (timeDiff > TIME_EPSILON_NS.count()) return;
+ const nsecs_t refreshDuration = (timeDiff > 0) ? timeDiff : mMinRefreshDuration;
+ mRefreshRateHistory.insertRefreshRate(refreshDuration);
+}
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
new file mode 100644
index 0000000..1970a47
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#pragma once
+
+#include <cinttypes>
+#include <cstdint>
+#include <deque>
+#include <mutex>
+#include <numeric>
+#include <string>
+
+#include <log/log.h>
+
+#include <utils/Mutex.h>
+#include <utils/Timers.h>
+
+#include "SchedulerUtils.h"
+
+namespace android {
+namespace scheduler {
+
+/*
+ * This class represents information about individial layers.
+ */
+class LayerInfo {
+ /**
+ * Struct that keeps the information about the refresh rate for last
+ * HISTORY_SIZE frames. This is used to better determine the refresh rate
+ * for individual layers.
+ */
+ class RefreshRateHistory {
+ public:
+ explicit RefreshRateHistory(nsecs_t minRefreshDuration)
+ : mMinRefreshDuration(minRefreshDuration) {}
+ void insertRefreshRate(nsecs_t refreshRate) {
+ mElements.push_back(refreshRate);
+ if (mElements.size() > HISTORY_SIZE) {
+ mElements.pop_front();
+ }
+ }
+
+ float getRefreshRateAvg() const {
+ nsecs_t refreshDuration = mMinRefreshDuration;
+ if (mElements.size() == HISTORY_SIZE) {
+ refreshDuration = scheduler::calculate_mean(mElements);
+ }
+
+ return 1e9f / refreshDuration;
+ }
+ void clearHistory() { mElements.clear(); }
+
+ private:
+ std::deque<nsecs_t> mElements;
+ static constexpr size_t HISTORY_SIZE = 30;
+ const nsecs_t mMinRefreshDuration;
+ };
+
+ /**
+ * Struct that keeps the information about the present time for last
+ * HISTORY_SIZE frames. This is used to better determine whether the given layer
+ * is still relevant and it's refresh rate should be considered.
+ */
+ class PresentTimeHistory {
+ public:
+ void insertPresentTime(nsecs_t presentTime) {
+ mElements.push_back(presentTime);
+ if (mElements.size() > HISTORY_SIZE) {
+ mElements.pop_front();
+ }
+ }
+
+ // Checks whether the present time that was inserted HISTORY_SIZE ago is within a
+ // certain threshold: TIME_EPSILON_NS.
+ bool isRelevant() const {
+ const int64_t obsoleteEpsilon = systemTime() - scheduler::TIME_EPSILON_NS.count();
+ // The layer had to publish at least HISTORY_SIZE of updates, and the first
+ // update should not be older than TIME_EPSILON_NS nanoseconds.
+ if (mElements.size() == HISTORY_SIZE &&
+ mElements.at(HISTORY_SIZE - 1) > obsoleteEpsilon) {
+ return true;
+ }
+ return false;
+ }
+
+ void clearHistory() { mElements.clear(); }
+
+ private:
+ std::deque<nsecs_t> mElements;
+ static constexpr size_t HISTORY_SIZE = 10;
+ };
+
+public:
+ LayerInfo(const std::string name, float maxRefreshRate);
+ ~LayerInfo();
+
+ LayerInfo(const LayerInfo&) = delete;
+ LayerInfo& operator=(const LayerInfo&) = delete;
+
+ // Records the last requested oresent time. It also stores information about when
+ // the layer was last updated. If the present time is farther in the future than the
+ // updated time, the updated time is the present time.
+ void setLastPresentTime(nsecs_t lastPresentTime);
+
+ // Checks the present time history to see whether the layer is relevant.
+ bool isRecentlyActive() const {
+ std::lock_guard lock(mLock);
+ return mPresentTimeHistory.isRelevant();
+ }
+
+ // Calculate the average refresh rate.
+ float getDesiredRefreshRate() const {
+ std::lock_guard lock(mLock);
+ return mRefreshRateHistory.getRefreshRateAvg();
+ }
+
+ // Return the last updated time. If the present time is farther in the future than the
+ // updated time, the updated time is the present time.
+ nsecs_t getLastUpdatedTime() {
+ std::lock_guard lock(mLock);
+ return mLastUpdatedTime;
+ }
+
+ std::string getName() const { return mName; }
+
+ void clearHistory() {
+ std::lock_guard lock(mLock);
+ mRefreshRateHistory.clearHistory();
+ mPresentTimeHistory.clearHistory();
+ }
+
+private:
+ const std::string mName;
+ const nsecs_t mMinRefreshDuration;
+ mutable std::mutex mLock;
+ nsecs_t mLastUpdatedTime GUARDED_BY(mLock) = 0;
+ nsecs_t mLastPresentTime GUARDED_BY(mLock) = 0;
+ RefreshRateHistory mRefreshRateHistory GUARDED_BY(mLock);
+ PresentTimeHistory mPresentTimeHistory GUARDED_BY(mLock);
+};
+
+} // namespace scheduler
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 5874066..eb9ae35 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -54,7 +54,7 @@
const std::map<RefreshRateType, std::shared_ptr<RefreshRate>>& getRefreshRates() const {
return mRefreshRates;
}
- std::shared_ptr<RefreshRate> getRefreshRate(RefreshRateType type) {
+ std::shared_ptr<RefreshRate> getRefreshRate(RefreshRateType type) const {
const auto& refreshRate = mRefreshRates.find(type);
if (refreshRate != mRefreshRates.end()) {
return refreshRate->second;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 0063c8a..9f92d92 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -39,6 +39,7 @@
#include "EventThread.h"
#include "IdleTimer.h"
#include "InjectVSyncSource.h"
+#include "LayerInfo.h"
#include "SchedulerUtils.h"
#include "SurfaceFlingerProperties.h"
@@ -55,11 +56,13 @@
std::atomic<int64_t> Scheduler::sNextId = 0;
-Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function)
+Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
+ const scheduler::RefreshRateConfigs& refreshRateConfig)
: mHasSyncFramework(running_without_sync_framework(true)),
mDispSyncPresentTimeOffset(present_time_offset_from_vsync_ns(0)),
mPrimaryHWVsyncEnabled(false),
- mHWVsyncAvailable(false) {
+ mHWVsyncAvailable(false),
+ mRefreshRateConfigs(refreshRateConfig) {
// Note: We create a local temporary with the real DispSync implementation
// type temporarily so we can initialize it with the configured values,
// before storing it for more generic use using the interface type.
@@ -297,32 +300,32 @@
mPrimaryDispSync->dump(result);
}
-void Scheduler::addNativeWindowApi(int apiId) {
- std::lock_guard<std::mutex> lock(mWindowApiHistoryLock);
- mWindowApiHistory[mApiHistoryCounter] = apiId;
- mApiHistoryCounter++;
- mApiHistoryCounter = mApiHistoryCounter % scheduler::ARRAY_SIZE;
+std::unique_ptr<scheduler::LayerHistory::LayerHandle> Scheduler::registerLayer(
+ const std::string name) {
+ RefreshRateType refreshRateType = RefreshRateType::PERFORMANCE;
+
+ const auto refreshRate = mRefreshRateConfigs.getRefreshRate(refreshRateType);
+ const uint32_t fps = (refreshRate) ? refreshRate->fps : 0;
+ return mLayerHistory.createLayer(name, fps);
+}
+
+void Scheduler::addLayerPresentTime(
+ const std::unique_ptr<scheduler::LayerHistory::LayerHandle>& layerHandle,
+ nsecs_t presentTime) {
+ mLayerHistory.insert(layerHandle, presentTime);
}
void Scheduler::withPrimaryDispSync(std::function<void(DispSync&)> const& fn) {
fn(*mPrimaryDispSync);
}
-void Scheduler::updateFpsBasedOnNativeWindowApi() {
- int mode;
- {
- std::lock_guard<std::mutex> lock(mWindowApiHistoryLock);
- mode = scheduler::calculate_mode(mWindowApiHistory);
- }
- ATRACE_INT("NativeWindowApiMode", mode);
-
- if (mode == NATIVE_WINDOW_API_MEDIA) {
- // TODO(b/127365162): These callback names are not accurate anymore. Update.
- mediaChangeRefreshRate(MediaFeatureState::MEDIA_PLAYING);
- ATRACE_INT("DetectedVideo", 1);
+void Scheduler::updateFpsBasedOnContent() {
+ uint32_t refreshRate = std::round(mLayerHistory.getDesiredRefreshRate());
+ ATRACE_INT("ContentFPS", refreshRate);
+ if (refreshRate > 0) {
+ contentChangeRefreshRate(ContentFeatureState::CONTENT_DETECTION_ON, refreshRate);
} else {
- mediaChangeRefreshRate(MediaFeatureState::MEDIA_OFF);
- ATRACE_INT("DetectedVideo", 0);
+ contentChangeRefreshRate(ContentFeatureState::CONTENT_DETECTION_OFF, 0);
}
}
@@ -365,39 +368,59 @@
return stream.str();
}
-void Scheduler::mediaChangeRefreshRate(MediaFeatureState mediaFeatureState) {
- // Default playback for media is DEFAULT when media is on.
- RefreshRateType refreshRateType = RefreshRateType::DEFAULT;
- ConfigEvent configEvent = ConfigEvent::None;
-
+void Scheduler::contentChangeRefreshRate(ContentFeatureState contentFeatureState,
+ uint32_t refreshRate) {
+ RefreshRateType newRefreshRateType;
{
std::lock_guard<std::mutex> lock(mFeatureStateLock);
- mCurrentMediaFeatureState = mediaFeatureState;
- // Only switch to PERFORMANCE if idle timer was reset, when turning
- // media off. If the timer is IDLE, stay at DEFAULT.
- if (mediaFeatureState == MediaFeatureState::MEDIA_OFF &&
- mCurrentIdleTimerState == IdleTimerState::RESET) {
- refreshRateType = RefreshRateType::PERFORMANCE;
- }
+ mCurrentContentFeatureState = contentFeatureState;
+ mContentRefreshRate = refreshRate;
+ newRefreshRateType = calculateRefreshRateType();
}
- changeRefreshRate(refreshRateType, configEvent);
+ changeRefreshRate(newRefreshRateType, ConfigEvent::Changed);
}
void Scheduler::timerChangeRefreshRate(IdleTimerState idleTimerState) {
- RefreshRateType refreshRateType = RefreshRateType::DEFAULT;
- ConfigEvent configEvent = ConfigEvent::None;
-
+ RefreshRateType newRefreshRateType;
{
std::lock_guard<std::mutex> lock(mFeatureStateLock);
mCurrentIdleTimerState = idleTimerState;
- // Only switch to PERFOMANCE if the idle timer was reset, and media is
- // not playing. Otherwise, stay at DEFAULT.
- if (idleTimerState == IdleTimerState::RESET &&
- mCurrentMediaFeatureState == MediaFeatureState::MEDIA_OFF) {
- refreshRateType = RefreshRateType::PERFORMANCE;
- }
+ newRefreshRateType = calculateRefreshRateType();
}
- changeRefreshRate(refreshRateType, configEvent);
+ changeRefreshRate(newRefreshRateType, ConfigEvent::None);
+}
+
+Scheduler::RefreshRateType Scheduler::calculateRefreshRateType() {
+ // First check if timer has expired as it means there is no new content on the screen
+ if (mCurrentIdleTimerState == IdleTimerState::EXPIRED) {
+ return RefreshRateType::DEFAULT;
+ }
+
+ // If content detection is off we choose performance as we don't know the content fps
+ if (mCurrentContentFeatureState == ContentFeatureState::CONTENT_DETECTION_OFF) {
+ return RefreshRateType::PERFORMANCE;
+ }
+
+ // Content detection is on, find the appropriate refresh rate
+ // Start with the smallest refresh rate which is greater than the content
+ auto iter = mRefreshRateConfigs.getRefreshRates().cbegin();
+ RefreshRateType currRefreshRateType = iter->first;
+ while (iter != mRefreshRateConfigs.getRefreshRates().cend()) {
+ if (iter->second->fps >= mContentRefreshRate) {
+ currRefreshRateType = iter->first;
+ break;
+ }
+ ++iter;
+ }
+
+ if (iter == mRefreshRateConfigs.getRefreshRates().cend()) {
+ return RefreshRateType::PERFORMANCE;
+ }
+
+ // TODO(b/129874336): This logic is sub-optimal for content refresh rate that aligns better
+ // with a higher refresh rate. For example for 45fps we should choose 90Hz config.
+
+ return currRefreshRateType;
}
void Scheduler::changeRefreshRate(RefreshRateType refreshRateType, ConfigEvent configEvent) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 73896d5..1d53252 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -90,7 +90,8 @@
std::atomic<nsecs_t> lastResyncTime = 0;
};
- explicit Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function);
+ explicit Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
+ const scheduler::RefreshRateConfigs& refreshRateConfig);
virtual ~Scheduler();
@@ -145,16 +146,20 @@
void addPresentFence(const std::shared_ptr<FenceTime>& fenceTime);
void setIgnorePresentFences(bool ignore);
nsecs_t expectedPresentTime();
- // apiId indicates the API (NATIVE_WINDOW_API_xxx) that queues the buffer.
- // TODO(b/123956502): Remove this call with V1 go/content-fps-detection-in-scheduler.
- void addNativeWindowApi(int apiId);
- // Updates FPS based on the most occured request for Native Window API.
- void updateFpsBasedOnNativeWindowApi();
+ // Registers the layer in the scheduler, and returns the handle for future references.
+ std::unique_ptr<scheduler::LayerHistory::LayerHandle> registerLayer(const std::string name);
+ // Stores present time for a layer.
+ void addLayerPresentTime(
+ const std::unique_ptr<scheduler::LayerHistory::LayerHandle>& layerHandle,
+ nsecs_t presentTime);
+ // Updates FPS based on the most content presented.
+ void updateFpsBasedOnContent();
// Callback that gets invoked when Scheduler wants to change the refresh rate.
void setChangeRefreshRateCallback(const ChangeRefreshRateCallback& changeRefreshRateCallback);
// Returns whether idle timer is enabled or not
bool isIdleTimerEnabled() { return mSetIdleTimerMs > 0; }
+
// Returns relevant information about Scheduler for dumpsys purposes.
std::string doDump();
@@ -171,7 +176,7 @@
// In order to make sure that the features don't override themselves, we need a state machine
// to keep track which feature requested the config change.
- enum class MediaFeatureState { MEDIA_PLAYING, MEDIA_OFF };
+ enum class ContentFeatureState { CONTENT_DETECTION_ON, CONTENT_DETECTION_OFF };
enum class IdleTimerState { EXPIRED, RESET };
// Creates a connection on the given EventThread and forwards the given callbacks.
@@ -188,12 +193,17 @@
// Sets vsync period.
void setVsyncPeriod(const nsecs_t period);
// Media feature's function to change the refresh rate.
- void mediaChangeRefreshRate(MediaFeatureState mediaFeatureState);
+ void contentChangeRefreshRate(ContentFeatureState contentFeatureState, uint32_t refreshRate);
// Idle timer feature's function to change the refresh rate.
void timerChangeRefreshRate(IdleTimerState idleTimerState);
+ // Calculate the new refresh rate type
+ RefreshRateType calculateRefreshRateType() REQUIRES(mFeatureStateLock);
// Acquires a lock and calls the ChangeRefreshRateCallback() with given parameters.
void changeRefreshRate(RefreshRateType refreshRateType, ConfigEvent configEvent);
+ // Helper function to calculate error frames
+ float getErrorFrames(float contentFps, float configFps);
+
// If fences from sync Framework are supported.
const bool mHasSyncFramework;
@@ -226,13 +236,8 @@
std::array<int64_t, scheduler::ARRAY_SIZE> mTimeDifferences{};
size_t mCounter = 0;
- // The following few fields follow native window api bits that come with buffers. If there are
- // more buffers with NATIVE_WINDOW_API_MEDIA we render at 60Hz, otherwise we render at 90Hz.
- // There is not dependency on timestamp for V0.
- // TODO(b/123956502): Remove this when more robust logic for content fps detection is developed.
- std::mutex mWindowApiHistoryLock;
- std::array<int, scheduler::ARRAY_SIZE> mWindowApiHistory GUARDED_BY(mWindowApiHistoryLock);
- int64_t mApiHistoryCounter = 0;
+ // Historical information about individual layers. Used for predicting the refresh rate.
+ scheduler::LayerHistory mLayerHistory;
// Timer that records time between requests for next vsync. If the time is higher than a given
// interval, a callback is fired. Set this variable to >0 to use this feature.
@@ -245,9 +250,12 @@
// In order to make sure that the features don't override themselves, we need a state machine
// to keep track which feature requested the config change.
std::mutex mFeatureStateLock;
- MediaFeatureState mCurrentMediaFeatureState GUARDED_BY(mFeatureStateLock) =
- MediaFeatureState::MEDIA_OFF;
+ ContentFeatureState mCurrentContentFeatureState GUARDED_BY(mFeatureStateLock) =
+ ContentFeatureState::CONTENT_DETECTION_OFF;
IdleTimerState mCurrentIdleTimerState GUARDED_BY(mFeatureStateLock) = IdleTimerState::RESET;
+ uint32_t mContentRefreshRate;
+
+ const scheduler::RefreshRateConfigs& mRefreshRateConfigs;
};
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.h b/services/surfaceflinger/Scheduler/SchedulerUtils.h
index 9e6e8c7..a935cac 100644
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.h
+++ b/services/surfaceflinger/Scheduler/SchedulerUtils.h
@@ -16,6 +16,7 @@
#pragma once
+#include <chrono>
#include <cinttypes>
#include <numeric>
#include <unordered_map>
@@ -23,6 +24,8 @@
namespace android {
namespace scheduler {
+using namespace std::chrono_literals;
+
// This number is used to set the size of the arrays in scheduler that hold information
// about layers.
static constexpr size_t ARRAY_SIZE = 30;
@@ -32,12 +35,20 @@
// still like to keep track of time when the device is in this config.
static constexpr int SCREEN_OFF_CONFIG_ID = -1;
+// This number is used when we try to determine how long does a given layer stay relevant.
+// Currently it is set to 100ms, because that would indicate 10Hz rendering.
+static constexpr std::chrono::nanoseconds TIME_EPSILON_NS = 100ms;
+
+// This number is used when we try to determine how long do we keep layer information around
+// before we remove it. Currently it is set to 100ms.
+static constexpr std::chrono::nanoseconds OBSOLETE_TIME_EPSILON_NS = 100ms;
+
// Calculates the statistical mean (average) in the data structure (array, vector). The
// function does not modify the contents of the array.
template <typename T>
auto calculate_mean(const T& v) {
using V = typename T::value_type;
- V sum = std::accumulate(v.begin(), v.end(), 0);
+ V sum = std::accumulate(v.begin(), v.end(), static_cast<V>(0));
return sum / static_cast<V>(v.size());
}
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9c2cef8..7c88cd8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -605,7 +605,8 @@
Mutex::Autolock _l(mStateLock);
// start the EventThread
mScheduler =
- getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); });
+ getFactory().createScheduler([this](bool enabled) { setPrimaryVsyncEnabled(enabled); },
+ mRefreshRateConfigs);
auto resyncCallback =
mScheduler->makeResyncCallback(std::bind(&SurfaceFlinger::getVsyncPeriod, this));
@@ -702,6 +703,7 @@
setRefreshRateTo(type, event);
});
}
+
mRefreshRateConfigs.populate(getHwComposer().getConfigs(*display->getId()));
mRefreshRateStats.setConfigMode(getHwComposer().getActiveConfigIndex(*display->getId()));
@@ -1607,7 +1609,7 @@
if (mUseSmart90ForVideo) {
// This call is made each time SF wakes up and creates a new frame. It is part
// of video detection feature.
- mScheduler->updateFpsBasedOnNativeWindowApi();
+ mScheduler->updateFpsBasedOnContent();
}
if (performSetActiveConfig()) {
@@ -3115,6 +3117,7 @@
bool SurfaceFlinger::handlePageFlip()
{
+ ATRACE_CALL();
ALOGV("handlePageFlip");
nsecs_t latchTime = systemTime();
@@ -3140,6 +3143,7 @@
if (layer->shouldPresentNow(expectedPresentTime)) {
mLayersWithQueuedFrames.push_back(layer);
} else {
+ ATRACE_NAME("!layer->shouldPresentNow()");
layer->useEmptyDamage();
}
} else {
@@ -3979,10 +3983,8 @@
buffer = s.buffer;
}
if (buffer) {
- if (layer->setBuffer(buffer)) {
+ if (layer->setBuffer(buffer, postTime, desiredPresentTime)) {
flags |= eTraversalNeeded;
- layer->setPostTime(postTime);
- layer->setDesiredPresentTime(desiredPresentTime);
}
}
if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.cpp b/services/surfaceflinger/SurfaceFlingerFactory.cpp
index 26d2c21..e425b2a 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerFactory.cpp
@@ -73,8 +73,10 @@
return std::make_unique<scheduler::impl::PhaseOffsets>();
}
- std::unique_ptr<Scheduler> createScheduler(std::function<void(bool)> callback) override {
- return std::make_unique<Scheduler>(callback);
+ std::unique_ptr<Scheduler> createScheduler(
+ std::function<void(bool)> callback,
+ const scheduler::RefreshRateConfigs& refreshRateConfig) override {
+ return std::make_unique<Scheduler>(callback, refreshRateConfig);
}
std::unique_ptr<SurfaceInterceptor> createSurfaceInterceptor(
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index fc1d0f8..c2bc808 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -71,7 +71,9 @@
virtual std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) = 0;
virtual std::unique_ptr<MessageQueue> createMessageQueue() = 0;
virtual std::unique_ptr<scheduler::PhaseOffsets> createPhaseOffsets() = 0;
- virtual std::unique_ptr<Scheduler> createScheduler(std::function<void(bool)> callback) = 0;
+ virtual std::unique_ptr<Scheduler> createScheduler(
+ std::function<void(bool)> callback,
+ const scheduler::RefreshRateConfigs& refreshRateConfig) = 0;
virtual std::unique_ptr<SurfaceInterceptor> createSurfaceInterceptor(SurfaceFlinger*) = 0;
virtual sp<StartPropertySetThread> createStartPropertySetThread(
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index ef3dfef..bebfa6c 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -125,7 +125,7 @@
}
void setupScheduler() {
- mScheduler = new TestableScheduler();
+ mScheduler = new TestableScheduler(mFlinger.mutableRefreshRateConfigs());
mScheduler->mutableEventControlThread().reset(mEventControlThread);
mScheduler->mutablePrimaryDispSync().reset(mPrimaryDispSync);
EXPECT_CALL(*mEventThread.get(), registerDisplayEventConnection(_));
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 9bf29a2..d83f1bd 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -179,7 +179,7 @@
}
void DisplayTransactionTest::setupScheduler() {
- mScheduler = new TestableScheduler();
+ mScheduler = new TestableScheduler(mFlinger.mutableRefreshRateConfigs());
mScheduler->mutableEventControlThread().reset(mEventControlThread);
mScheduler->mutablePrimaryDispSync().reset(mPrimaryDispSync);
EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 3fb1a6e..e51121f 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -7,6 +7,7 @@
#include <log/log.h>
#include <mutex>
+#include <thread>
#include "Scheduler/LayerHistory.h"
@@ -14,6 +15,7 @@
using testing::Return;
namespace android {
+namespace scheduler {
class LayerHistoryTest : public testing::Test {
public:
@@ -22,6 +24,8 @@
protected:
std::unique_ptr<LayerHistory> mLayerHistory;
+
+ static constexpr float MAX_REFRESH_RATE = 90.f;
};
LayerHistoryTest::LayerHistoryTest() {
@@ -30,145 +34,79 @@
LayerHistoryTest::~LayerHistoryTest() {}
namespace {
-TEST_F(LayerHistoryTest, simpleInsertAndGet) {
- mLayerHistory->insert("TestLayer", 0);
+TEST_F(LayerHistoryTest, oneLayer) {
+ std::unique_ptr<LayerHistory::LayerHandle> testLayer =
+ mLayerHistory->createLayer("TestLayer", MAX_REFRESH_RATE);
- const std::unordered_map<std::string, nsecs_t>& testMap = mLayerHistory->get(0);
- EXPECT_EQ(1, testMap.size());
- auto element = testMap.find("TestLayer");
- EXPECT_EQ("TestLayer", element->first);
- EXPECT_EQ(0, element->second);
+ mLayerHistory->insert(testLayer, 0);
+ EXPECT_FLOAT_EQ(0.f, mLayerHistory->getDesiredRefreshRate());
- // Testing accessing object at an empty container will return an empty map.
- const std::unordered_map<std::string, nsecs_t>& emptyMap = mLayerHistory->get(1);
- EXPECT_EQ(0, emptyMap.size());
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ // This is still 0, because the layer is not considered recently active if it
+ // has been present in less than 10 frames.
+ EXPECT_FLOAT_EQ(0.f, mLayerHistory->getDesiredRefreshRate());
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ mLayerHistory->insert(testLayer, 0);
+ // This should be MAX_REFRESH_RATE as we have more than 10 samples
+ EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRate());
}
-TEST_F(LayerHistoryTest, multipleInserts) {
- mLayerHistory->insert("TestLayer0", 0);
- mLayerHistory->insert("TestLayer1", 1);
- mLayerHistory->insert("TestLayer2", 2);
- mLayerHistory->insert("TestLayer3", 3);
+TEST_F(LayerHistoryTest, explicitTimestamp) {
+ std::unique_ptr<LayerHistory::LayerHandle> test30FpsLayer =
+ mLayerHistory->createLayer("30FpsLayer", MAX_REFRESH_RATE);
- const std::unordered_map<std::string, nsecs_t>& testMap = mLayerHistory->get(0);
- // Because the counter was not incremented, all elements were inserted into the first
- // container.
- EXPECT_EQ(4, testMap.size());
- auto element = testMap.find("TestLayer0");
- EXPECT_EQ("TestLayer0", element->first);
- EXPECT_EQ(0, element->second);
-
- element = testMap.find("TestLayer1");
- EXPECT_EQ("TestLayer1", element->first);
- EXPECT_EQ(1, element->second);
-
- element = testMap.find("TestLayer2");
- EXPECT_EQ("TestLayer2", element->first);
- EXPECT_EQ(2, element->second);
-
- element = testMap.find("TestLayer3");
- EXPECT_EQ("TestLayer3", element->first);
- EXPECT_EQ(3, element->second);
-
- // Testing accessing object at an empty container will return an empty map.
- const std::unordered_map<std::string, nsecs_t>& emptyMap = mLayerHistory->get(1);
- EXPECT_EQ(0, emptyMap.size());
-}
-
-TEST_F(LayerHistoryTest, incrementingCounter) {
- mLayerHistory->insert("TestLayer0", 0);
- mLayerHistory->incrementCounter();
- mLayerHistory->insert("TestLayer1", 1);
- mLayerHistory->insert("TestLayer2", 2);
- mLayerHistory->incrementCounter();
- mLayerHistory->insert("TestLayer3", 3);
-
- // Because the counter was incremented, the elements were inserted into different
- // containers.
- // We expect the get method to access the slot at the current counter of the index
- // is 0.
- const std::unordered_map<std::string, nsecs_t>& testMap1 = mLayerHistory->get(0);
- EXPECT_EQ(1, testMap1.size());
- auto element = testMap1.find("TestLayer3");
- EXPECT_EQ("TestLayer3", element->first);
- EXPECT_EQ(3, element->second);
-
- const std::unordered_map<std::string, nsecs_t>& testMap2 = mLayerHistory->get(1);
- EXPECT_EQ(2, testMap2.size());
- element = testMap2.find("TestLayer1");
- EXPECT_EQ("TestLayer1", element->first);
- EXPECT_EQ(1, element->second);
- element = testMap2.find("TestLayer2");
- EXPECT_EQ("TestLayer2", element->first);
- EXPECT_EQ(2, element->second);
-
- const std::unordered_map<std::string, nsecs_t>& testMap3 = mLayerHistory->get(2);
- EXPECT_EQ(1, testMap3.size());
- element = testMap3.find("TestLayer0");
- EXPECT_EQ("TestLayer0", element->first);
- EXPECT_EQ(0, element->second);
-
- // Testing accessing object at an empty container will return an empty map.
- const std::unordered_map<std::string, nsecs_t>& emptyMap = mLayerHistory->get(3);
- EXPECT_EQ(0, emptyMap.size());
-}
-
-TEST_F(LayerHistoryTest, clearTheMap) {
- mLayerHistory->insert("TestLayer0", 0);
-
- const std::unordered_map<std::string, nsecs_t>& testMap1 = mLayerHistory->get(0);
- EXPECT_EQ(1, testMap1.size());
- auto element = testMap1.find("TestLayer0");
- EXPECT_EQ("TestLayer0", element->first);
- EXPECT_EQ(0, element->second);
-
- mLayerHistory->incrementCounter();
- // The array currently contains 30 elements.
- for (int i = 1; i < 30; ++i) {
- mLayerHistory->insert("TestLayer0", i);
- mLayerHistory->incrementCounter();
- }
- // Expect the map to be cleared.
- const std::unordered_map<std::string, nsecs_t>& testMap2 = mLayerHistory->get(0);
- EXPECT_EQ(0, testMap2.size());
-
- mLayerHistory->insert("TestLayer30", 30);
- const std::unordered_map<std::string, nsecs_t>& testMap3 = mLayerHistory->get(0);
- element = testMap3.find("TestLayer30");
- EXPECT_EQ("TestLayer30", element->first);
- EXPECT_EQ(30, element->second);
- // The original element in this location does not exist anymore.
- element = testMap3.find("TestLayer0");
- EXPECT_EQ(testMap3.end(), element);
-}
-
-TEST_F(LayerHistoryTest, testingGet) {
- // The array currently contains 30 elements.
- for (int i = 0; i < 30; ++i) {
- const auto name = "TestLayer" + std::to_string(i);
- mLayerHistory->insert(name, i);
- mLayerHistory->incrementCounter();
+ nsecs_t startTime = systemTime();
+ for (int i = 0; i < 31; i++) {
+ mLayerHistory->insert(test30FpsLayer, startTime + (i * 33333333));
}
- // The counter should be set to 0, and the map at 0 should be cleared.
- const std::unordered_map<std::string, nsecs_t>& testMap1 = mLayerHistory->get(0);
- EXPECT_EQ(0, testMap1.size());
+ EXPECT_FLOAT_EQ(30.f, mLayerHistory->getDesiredRefreshRate());
+}
- // This should return (ARRAY_SIZE + (counter - 3)) % ARRAY_SIZE
- const std::unordered_map<std::string, nsecs_t>& testMap2 = mLayerHistory->get(3);
- EXPECT_EQ(1, testMap2.size());
- auto element = testMap2.find("TestLayer27");
- EXPECT_EQ("TestLayer27", element->first);
- EXPECT_EQ(27, element->second);
+TEST_F(LayerHistoryTest, multipleLayers) {
+ std::unique_ptr<LayerHistory::LayerHandle> testLayer =
+ mLayerHistory->createLayer("TestLayer", MAX_REFRESH_RATE);
+ std::unique_ptr<LayerHistory::LayerHandle> test30FpsLayer =
+ mLayerHistory->createLayer("30FpsLayer", MAX_REFRESH_RATE);
+ std::unique_ptr<LayerHistory::LayerHandle> testLayer2 =
+ mLayerHistory->createLayer("TestLayer2", MAX_REFRESH_RATE);
- // If the user gives an out of bound index, we should mod it with ARRAY_SIZE first,
- // so requesting element 40 would be the same as requesting element 10.
- const std::unordered_map<std::string, nsecs_t>& testMap3 = mLayerHistory->get(40);
- EXPECT_EQ(1, testMap3.size());
- element = testMap3.find("TestLayer20");
- EXPECT_EQ("TestLayer20", element->first);
- EXPECT_EQ(20, element->second);
+ nsecs_t startTime = systemTime();
+ for (int i = 0; i < 10; i++) {
+ mLayerHistory->insert(testLayer, 0);
+ }
+ EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRate());
+
+ startTime = systemTime();
+ for (int i = 0; i < 10; i++) {
+ mLayerHistory->insert(test30FpsLayer, startTime + (i * 33333333));
+ }
+ EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRate());
+
+ for (int i = 10; i < 30; i++) {
+ mLayerHistory->insert(test30FpsLayer, startTime + (i * 33333333));
+ }
+ EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRate());
+
+ // This frame is only around for 9 occurrences, so it doesn't throw
+ // anything off.
+ for (int i = 0; i < 9; i++) {
+ mLayerHistory->insert(testLayer2, 0);
+ }
+ EXPECT_FLOAT_EQ(MAX_REFRESH_RATE, mLayerHistory->getDesiredRefreshRate());
+ // After 100 ms frames become obsolete.
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ // Insert the 31st frame.
+ mLayerHistory->insert(test30FpsLayer, startTime + (30 * 33333333));
+ EXPECT_FLOAT_EQ(30.f, mLayerHistory->getDesiredRefreshRate());
}
} // namespace
+} // namespace scheduler
} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index ec76538..b960bdf 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -33,14 +33,17 @@
MOCK_METHOD0(requestNextVsync, void());
};
+ scheduler::RefreshRateConfigs mRefreshRateConfigs;
+
/**
* This mock Scheduler class uses implementation of mock::EventThread but keeps everything else
* the same.
*/
class MockScheduler : public android::Scheduler {
public:
- MockScheduler(std::unique_ptr<EventThread> eventThread)
- : Scheduler([](bool) {}), mEventThread(std::move(eventThread)) {}
+ MockScheduler(const scheduler::RefreshRateConfigs& refreshRateConfigs,
+ std::unique_ptr<EventThread> eventThread)
+ : Scheduler([](bool) {}, refreshRateConfigs), mEventThread(std::move(eventThread)) {}
std::unique_ptr<EventThread> makeEventThread(
const char* /* connectionName */, DispSync* /* dispSync */,
@@ -71,7 +74,7 @@
std::unique_ptr<mock::EventThread> eventThread = std::make_unique<mock::EventThread>();
mEventThread = eventThread.get();
- mScheduler = std::make_unique<MockScheduler>(std::move(eventThread));
+ mScheduler = std::make_unique<MockScheduler>(mRefreshRateConfigs, std::move(eventThread));
EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
mEventThreadConnection = new MockEventThreadConnection(mEventThread);
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index dcbf973..1c397d8 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -19,13 +19,15 @@
#include <gmock/gmock.h>
#include "Scheduler/EventThread.h"
+#include "Scheduler/RefreshRateConfigs.h"
#include "Scheduler/Scheduler.h"
namespace android {
class TestableScheduler : public Scheduler {
public:
- TestableScheduler() : Scheduler([](bool) {}) {}
+ TestableScheduler(const scheduler::RefreshRateConfigs& refreshRateConfig)
+ : Scheduler([](bool) {}, refreshRateConfig) {}
// Creates EventThreadConnection with the given eventThread. Creates Scheduler::Connection
// and adds it to the list of connectins. Returns the ConnectionHandle for the
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 81235ba..74a380a 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -86,7 +86,8 @@
return std::make_unique<scheduler::FakePhaseOffsets>();
}
- std::unique_ptr<Scheduler> createScheduler(std::function<void(bool)>) override {
+ std::unique_ptr<Scheduler> createScheduler(std::function<void(bool)>,
+ const scheduler::RefreshRateConfigs&) override {
// TODO: Use test-fixture controlled factory
return nullptr;
}
@@ -339,6 +340,7 @@
auto& mutableScheduler() { return mFlinger->mScheduler; }
auto& mutableAppConnectionHandle() { return mFlinger->mAppConnectionHandle; }
auto& mutableSfConnectionHandle() { return mFlinger->mSfConnectionHandle; }
+ auto& mutableRefreshRateConfigs() { return mFlinger->mRefreshRateConfigs; }
~TestableSurfaceFlinger() {
// All these pointer and container clears help ensure that GMock does