Support skipping CachedSet rendering based on invalidate() proximity
Add tunable sysprops which allow for skipping rendering a new
CachedSet if we're too close to an invalidate(), so that rendering the
CachedSet does not cause jank.
The sysprops control:
1. Whether we support this type of deferred rendering
2. How close to a rendering deadline we're allowed to be
3. Max number of times we can skip before we give up and render anyway.
Bug: 188678587
Test: builds, boots
Test: perfetto
Change-Id: I9801c25da1d9178faa8fa28b9bd8f9d180db7b11
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 289cb11..29937fb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -82,6 +82,9 @@
// The earliest time to send the present command to the HAL
std::chrono::steady_clock::time_point earliestPresentTime;
+
+ // The predicted next invalidation time
+ std::optional<std::chrono::steady_clock::time_point> nextInvalidateTime;
};
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 257974f..1416b1e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -286,7 +286,7 @@
virtual std::optional<base::unique_fd> composeSurfaces(
const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) = 0;
virtual void postFramebuffer() = 0;
- virtual void renderCachedSets() = 0;
+ virtual void renderCachedSets(const CompositionRefreshArgs&) = 0;
virtual void chooseCompositionStrategy() = 0;
virtual bool getSkipColorTransform() const = 0;
virtual FrameFences presentAndGetFrameFences() = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index f10ff25..f832084 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -93,7 +93,7 @@
std::optional<base::unique_fd> composeSurfaces(
const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) override;
void postFramebuffer() override;
- void renderCachedSets() override;
+ void renderCachedSets(const CompositionRefreshArgs&) override;
void cacheClientCompositionRequests(uint32_t) override;
// Testing
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index a4356c5..7cb0f6b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -98,6 +98,7 @@
mDrawFence = nullptr;
mBlurLayer = nullptr;
mHolePunchLayer = nullptr;
+ mSkipCount = 0;
mLayers.insert(mLayers.end(), other.mLayers.cbegin(), other.mLayers.cend());
Region boundingRegion;
@@ -107,6 +108,8 @@
mVisibleRegion.orSelf(other.mVisibleRegion);
}
void incrementAge() { ++mAge; }
+ void incrementSkipCount() { mSkipCount++; }
+ size_t getSkipCount() { return mSkipCount; }
// Renders the cached set with the supplied output composition state.
void render(renderengine::RenderEngine& re, TexturePool& texturePool,
@@ -155,6 +158,7 @@
Rect mBounds = Rect::EMPTY_RECT;
Region mVisibleRegion;
size_t mAge = 0;
+ size_t mSkipCount = 0;
// TODO(b/190411067): This is a shared pointer only because CachedSets are copied into different
// containers in the Flattener. Logically this should have unique ownership otherwise.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 94a169e..7534548 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -20,6 +20,7 @@
#include <compositionengine/impl/planner/CachedSet.h>
#include <compositionengine/impl/planner/LayerState.h>
+#include <chrono>
#include <numeric>
#include <vector>
@@ -37,7 +38,35 @@
class Flattener {
public:
- Flattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch = false);
+ struct CachedSetRenderSchedulingTunables {
+ // This default assumes that rendering a cached set takes about 3ms. That time is then cut
+ // in half - the next frame using the cached set would have the same workload, meaning that
+ // composition cost is the same. This is best illustrated with the following example:
+ //
+ // Suppose we're at a 120hz cadence so SurfaceFlinger is budgeted 8.3ms per-frame. If
+ // renderCachedSets costs 3ms, then two consecutive frames have timings:
+ //
+ // First frame: Start at 0ms, end at 6.8ms.
+ // renderCachedSets: Start at 6.8ms, end at 9.8ms.
+ // Second frame: Start at 9.8ms, end at 16.6ms.
+ //
+ // Now the second frame won't render a cached set afterwards, but the first frame didn't
+ // really steal time from the second frame.
+ static const constexpr std::chrono::nanoseconds kDefaultCachedSetRenderDuration = 1500us;
+
+ static const constexpr size_t kDefaultMaxDeferRenderAttempts = 240;
+
+ // Duration allocated for rendering a cached set. If we don't have enough time for rendering
+ // a cached set, then rendering is deferred to another frame.
+ const std::chrono::nanoseconds cachedSetRenderDuration;
+ // Maximum of times that we defer rendering a cached set. If we defer rendering a cached set
+ // too many times, then render it anyways so that future frames would benefit from the
+ // flattened cached set.
+ const size_t maxDeferRenderAttempts;
+ };
+ Flattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch = false,
+ std::optional<CachedSetRenderSchedulingTunables> cachedSetRenderSchedulingTunables =
+ std::nullopt);
void setDisplaySize(ui::Size size) {
mDisplaySize = size;
@@ -48,16 +77,14 @@
std::chrono::steady_clock::time_point now);
// Renders the newest cached sets with the supplied output composition state
- void renderCachedSets(const OutputCompositionState& outputState);
+ void renderCachedSets(const OutputCompositionState& outputState,
+ std::optional<std::chrono::steady_clock::time_point> renderDeadline);
void dump(std::string& result) const;
void dumpLayers(std::string& result) const;
const std::optional<CachedSet>& getNewCachedSetForTesting() const { return mNewCachedSet; }
-protected:
- std::optional<CachedSet> mNewCachedSet;
-
private:
size_t calculateDisplayCost(const std::vector<const LayerState*>& layers) const;
@@ -149,9 +176,15 @@
renderengine::RenderEngine& mRenderEngine;
const bool mEnableHolePunch;
+ const std::optional<CachedSetRenderSchedulingTunables> mCachedSetRenderSchedulingTunables;
TexturePool mTexturePool;
+protected:
+ // mNewCachedSet must be destroyed before mTexturePool is.
+ std::optional<CachedSet> mNewCachedSet;
+
+private:
ui::Size mDisplaySize;
NonBufferHash mCurrentGeometry;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
index fd1ddfc..be34153 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -58,8 +58,11 @@
void reportFinalPlan(
compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
- // The planner will call to the Flattener to render any pending cached set
- void renderCachedSets(const OutputCompositionState& outputState);
+ // The planner will call to the Flattener to render any pending cached set.
+ // Rendering a pending cached set is optional: if the renderDeadline is not far enough in the
+ // future then the planner may opt to skip rendering the cached set.
+ void renderCachedSets(const OutputCompositionState& outputState,
+ std::optional<std::chrono::steady_clock::time_point> renderDeadline);
void dump(const Vector<String16>& args, std::string&);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 4b4d375..8e777e3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -109,7 +109,7 @@
MOCK_CONST_METHOD0(getSkipColorTransform, bool());
MOCK_METHOD0(postFramebuffer, void());
- MOCK_METHOD0(renderCachedSets, void());
+ MOCK_METHOD1(renderCachedSets, void(const CompositionRefreshArgs&));
MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
MOCK_METHOD3(generateClientCompositionRequests,
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index cd2f742..67bb149 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -434,7 +434,7 @@
devOptRepaintFlash(refreshArgs);
finishFrame(refreshArgs);
postFramebuffer();
- renderCachedSets();
+ renderCachedSets(refreshArgs);
}
void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
@@ -1312,9 +1312,9 @@
mReleasedLayers.clear();
}
-void Output::renderCachedSets() {
+void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) {
if (mPlanner) {
- mPlanner->renderCachedSets(getState());
+ mPlanner->renderCachedSets(getState(), refreshArgs.nextInvalidateTime);
}
}
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 69e8c7d..c1d525b 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -277,6 +277,7 @@
mOutputSpace.orientation = outputState.framebufferSpace.orientation;
mOutputDataspace = outputDataspace;
mOrientation = orientation;
+ mSkipCount = 0;
} else {
mTexture.reset();
}
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 192c411..2bcaf60 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -23,7 +23,7 @@
#include <compositionengine/impl/planner/Flattener.h>
#include <compositionengine/impl/planner/LayerState.h>
-#include <utils/Trace.h>
+#include <gui/TraceUtils.h>
using time_point = std::chrono::steady_clock::time_point;
using namespace std::chrono_literals;
@@ -60,9 +60,12 @@
} // namespace
-Flattener::Flattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch)
+Flattener::Flattener(
+ renderengine::RenderEngine& renderEngine, bool enableHolePunch,
+ std::optional<CachedSetRenderSchedulingTunables> cachedSetRenderSchedulingTunables)
: mRenderEngine(renderEngine),
mEnableHolePunch(enableHolePunch),
+ mCachedSetRenderSchedulingTunables(cachedSetRenderSchedulingTunables),
mTexturePool(mRenderEngine) {
const int timeoutInMs =
base::GetIntProperty(std::string("debug.sf.layer_caching_active_layer_timeout_ms"), 0);
@@ -105,12 +108,45 @@
return hash;
}
-void Flattener::renderCachedSets(const OutputCompositionState& outputState) {
+void Flattener::renderCachedSets(
+ const OutputCompositionState& outputState,
+ std::optional<std::chrono::steady_clock::time_point> renderDeadline) {
ATRACE_CALL();
- if (!mNewCachedSet || mNewCachedSet->hasRenderedBuffer()) {
+
+ if (!mNewCachedSet) {
return;
}
+ // Ensure that a cached set has a valid buffer first
+ if (mNewCachedSet->hasRenderedBuffer()) {
+ ATRACE_NAME("mNewCachedSet->hasRenderedBuffer()");
+ return;
+ }
+
+ const auto now = std::chrono::steady_clock::now();
+
+ // If we have a render deadline, and the flattener is configured to skip rendering if we don't
+ // have enough time, then we skip rendering the cached set if we think that we'll steal too much
+ // time from the next frame.
+ if (renderDeadline && mCachedSetRenderSchedulingTunables) {
+ if (const auto estimatedRenderFinish =
+ now + mCachedSetRenderSchedulingTunables->cachedSetRenderDuration;
+ estimatedRenderFinish > *renderDeadline) {
+ mNewCachedSet->incrementSkipCount();
+
+ if (mNewCachedSet->getSkipCount() <=
+ mCachedSetRenderSchedulingTunables->maxDeferRenderAttempts) {
+ ATRACE_FORMAT("DeadlinePassed: exceeded deadline by: %d us",
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ estimatedRenderFinish - *renderDeadline)
+ .count());
+ return;
+ } else {
+ ATRACE_NAME("DeadlinePassed: exceeded max skips");
+ }
+ }
+ }
+
mNewCachedSet->render(mRenderEngine, mTexturePool, outputState);
}
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 711a634..be2510f 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -26,16 +26,43 @@
#include <compositionengine/impl/planner/Planner.h>
#include <utils/Trace.h>
+#include <chrono>
namespace android::compositionengine::impl::planner {
+namespace {
+
+std::optional<Flattener::CachedSetRenderSchedulingTunables> buildFlattenerTuneables() {
+ if (!base::GetBoolProperty(std::string("debug.sf.enable_cached_set_render_scheduling"), true)) {
+ return std::nullopt;
+ }
+
+ auto renderDuration = std::chrono::nanoseconds(
+ base::GetUintProperty<uint64_t>(std::string("debug.sf.cached_set_render_duration_ns"),
+ Flattener::CachedSetRenderSchedulingTunables::
+ kDefaultCachedSetRenderDuration.count()));
+
+ auto maxDeferRenderAttempts = base::GetUintProperty<
+ size_t>(std::string("debug.sf.cached_set_max_defer_render_attmpts"),
+ Flattener::CachedSetRenderSchedulingTunables::kDefaultMaxDeferRenderAttempts);
+
+ return std::make_optional<Flattener::CachedSetRenderSchedulingTunables>(
+ Flattener::CachedSetRenderSchedulingTunables{
+ .cachedSetRenderDuration = renderDuration,
+ .maxDeferRenderAttempts = maxDeferRenderAttempts,
+ });
+}
+
+} // namespace
+
Planner::Planner(renderengine::RenderEngine& renderEngine)
// Implicitly, layer caching must also be enabled for the hole punch or
// predictor to have any effect.
// E.g., setprop debug.sf.enable_layer_caching 1, or
// adb shell service call SurfaceFlinger 1040 i32 1 [i64 <display ID>]
: mFlattener(renderEngine,
- base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), true)) {
+ base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), true),
+ buildFlattenerTuneables()) {
mPredictorEnabled =
base::GetBoolProperty(std::string("debug.sf.enable_planner_prediction"), false);
}
@@ -161,9 +188,11 @@
finalPlan);
}
-void Planner::renderCachedSets(const OutputCompositionState& outputState) {
+void Planner::renderCachedSets(
+ const OutputCompositionState& outputState,
+ std::optional<std::chrono::steady_clock::time_point> renderDeadline) {
ATRACE_CALL();
- mFlattener.renderCachedSets(outputState);
+ mFlattener.renderCachedSets(outputState, renderDeadline);
}
void Planner::dump(const Vector<String16>& args, std::string& result) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index c381081..742b155 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -1767,7 +1767,7 @@
MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
MOCK_METHOD1(finishFrame, void(const compositionengine::CompositionRefreshArgs&));
MOCK_METHOD0(postFramebuffer, void());
- MOCK_METHOD0(renderCachedSets, void());
+ MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
};
StrictMock<OutputPartialMock> mOutput;
@@ -1787,7 +1787,7 @@
EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
EXPECT_CALL(mOutput, finishFrame(Ref(args)));
EXPECT_CALL(mOutput, postFramebuffer());
- EXPECT_CALL(mOutput, renderCachedSets());
+ EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
mOutput.present(args);
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index b15e4f3..449ae21 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -223,6 +223,16 @@
EXPECT_EQ(2u, cachedSet.getAge());
}
+TEST_F(CachedSetTest, incrementSkipCount) {
+ CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
+ CachedSet cachedSet(layer);
+ EXPECT_EQ(0u, cachedSet.getSkipCount());
+ cachedSet.incrementSkipCount();
+ EXPECT_EQ(1u, cachedSet.getSkipCount());
+ cachedSet.incrementSkipCount();
+ EXPECT_EQ(2u, cachedSet.getSkipCount());
+}
+
TEST_F(CachedSetTest, hasBufferUpdate_NoUpdate) {
CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
@@ -257,6 +267,8 @@
CachedSet cachedSet1(layer1);
CachedSet cachedSet2(layer2);
cachedSet1.addLayer(layer3.getState(), kStartTime + 10ms);
+ cachedSet1.incrementSkipCount();
+ EXPECT_EQ(1u, cachedSet1.getSkipCount());
cachedSet1.append(cachedSet2);
EXPECT_EQ(kStartTime, cachedSet1.getLastUpdate());
@@ -268,6 +280,8 @@
EXPECT_TRUE(cachedSet1.getVisibleRegion().hasSameRects(expectedRegion));
EXPECT_EQ(3u, cachedSet1.getLayerCount());
EXPECT_EQ(0u, cachedSet1.getAge());
+ EXPECT_EQ(0u, cachedSet1.getSkipCount());
+
expectNoBuffer(cachedSet1);
// TODO(b/181192080): check that getNonBufferHash returns the correct hash value
// EXPECT_EQ(android::hashCombine(layer1.getHash(), layer2.getHash()),
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index e176c98..334b855 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -24,6 +24,7 @@
#include <renderengine/ExternalTexture.h>
#include <renderengine/LayerSettings.h>
#include <renderengine/mock/RenderEngine.h>
+#include <chrono>
namespace android::compositionengine {
using namespace std::chrono_literals;
@@ -46,22 +47,28 @@
class TestableFlattener : public Flattener {
public:
- TestableFlattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch)
- : Flattener(renderEngine, enableHolePunch) {}
+ TestableFlattener(renderengine::RenderEngine& renderEngine, bool enableHolePunch,
+ std::optional<Flattener::CachedSetRenderSchedulingTunables>
+ cachedSetRenderSchedulingTunables = std::nullopt)
+ : Flattener(renderEngine, enableHolePunch, cachedSetRenderSchedulingTunables) {}
const std::optional<CachedSet>& getNewCachedSetForTesting() const { return mNewCachedSet; }
};
class FlattenerTest : public testing::Test {
public:
- FlattenerTest() : mFlattener(std::make_unique<TestableFlattener>(mRenderEngine, true)) {}
+ FlattenerTest() : FlattenerTest(std::nullopt) {}
void SetUp() override;
protected:
+ FlattenerTest(std::optional<Flattener::CachedSetRenderSchedulingTunables>
+ cachedSetRenderSchedulingTunables)
+ : mFlattener(std::make_unique<TestableFlattener>(mRenderEngine, true,
+ cachedSetRenderSchedulingTunables)) {}
void initializeOverrideBuffer(const std::vector<const LayerState*>& layers);
void initializeFlattener(const std::vector<const LayerState*>& layers);
void expectAllLayersFlattened(const std::vector<const LayerState*>& layers);
- // mRenderEngine is held as a reference in mFlattener, so mFlattener must be destroyed first.
+ // mRenderEngine is held as a reference in mFlattener, so explicitly destroy mFlattener first.
renderengine::mock::RenderEngine mRenderEngine;
std::unique_ptr<TestableFlattener> mFlattener;
@@ -148,13 +155,13 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
// same geometry, update the internal layer stack
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
}
void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
@@ -164,7 +171,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
for (const auto layer : layers) {
EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -174,7 +181,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
const auto buffer = layers[0]->getOutputLayer()->getState().overrideInfo.buffer;
EXPECT_NE(nullptr, buffer);
@@ -209,7 +216,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
}
TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
@@ -255,7 +262,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -360,7 +367,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_EQ(nullptr, overrideBuffer2);
@@ -397,7 +404,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_EQ(nullptr, overrideBuffer2);
@@ -406,7 +413,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_NE(nullptr, overrideBuffer2);
@@ -419,7 +426,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_NE(nullptr, overrideBuffer2);
@@ -428,7 +435,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -470,7 +477,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_EQ(nullptr, overrideBuffer2);
@@ -484,7 +491,7 @@
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
mOutputState.framebufferSpace.orientation = ui::ROTATION_90;
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -497,7 +504,7 @@
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
mOutputState.framebufferSpace.orientation = ui::ROTATION_180;
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -512,7 +519,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -524,7 +531,7 @@
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
mOutputState.framebufferSpace.orientation = ui::ROTATION_270;
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -563,7 +570,7 @@
// This will render a CachedSet.
EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
// We've rendered a CachedSet, but we haven't merged it in.
EXPECT_EQ(nullptr, overrideBuffer1);
@@ -576,7 +583,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -625,7 +632,7 @@
// This will render a CachedSet.
EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
// We've rendered a CachedSet, but we haven't merged it in.
EXPECT_EQ(nullptr, overrideBuffer1);
@@ -638,7 +645,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
@@ -682,7 +689,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
for (const auto layer : layers) {
EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -692,7 +699,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer1, overrideBuffer2);
EXPECT_EQ(nullptr, overrideBuffer3);
@@ -726,7 +733,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
for (const auto layer : layers) {
EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -737,7 +744,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
for (const auto layer : layers) {
EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
}
@@ -778,7 +785,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
for (const auto layer : layers) {
EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
@@ -788,7 +795,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_EQ(nullptr, blurOverrideBuffer);
EXPECT_NE(nullptr, overrideBuffer3);
@@ -825,7 +832,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
const auto& cachedSet = mFlattener->getNewCachedSetForTesting();
ASSERT_NE(std::nullopt, cachedSet);
@@ -839,7 +846,7 @@
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer2, overrideBuffer1);
EXPECT_EQ(nullptr, blurOverrideBuffer);
@@ -866,7 +873,7 @@
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_EQ(nullptr, overrideBuffer1);
EXPECT_EQ(nullptr, overrideBuffer2);
@@ -874,16 +881,61 @@
// Simulate attempting to render prior to merging the new cached set with the layer stack.
// Here we should not try to re-render.
EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
// We provide the override buffer now that it's rendered
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
- mFlattener->renderCachedSets(mOutputState);
+ mFlattener->renderCachedSets(mOutputState, std::nullopt);
EXPECT_NE(nullptr, overrideBuffer1);
EXPECT_EQ(overrideBuffer2, overrideBuffer1);
}
+const constexpr std::chrono::nanoseconds kCachedSetRenderDuration = 0ms;
+const constexpr size_t kMaxDeferRenderAttempts = 2;
+
+class FlattenerRenderSchedulingTest : public FlattenerTest {
+public:
+ FlattenerRenderSchedulingTest()
+ : FlattenerTest(
+ Flattener::CachedSetRenderSchedulingTunables{.cachedSetRenderDuration =
+ kCachedSetRenderDuration,
+ .maxDeferRenderAttempts =
+ kMaxDeferRenderAttempts}) {
+ }
+};
+
+TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToMaxAttempts) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+ auto& layerState2 = mTestLayers[1]->layerState;
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerState2.get(),
+ };
+
+ initializeFlattener(layers);
+
+ // Mark the layers inactive
+ mTime += 200ms;
+
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+ for (size_t i = 0; i < kMaxDeferRenderAttempts; i++) {
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+ mFlattener->renderCachedSets(mOutputState,
+ std::chrono::steady_clock::now() -
+ (kCachedSetRenderDuration + 10ms));
+ }
+
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+ mFlattener->renderCachedSets(mOutputState,
+ std::chrono::steady_clock::now() -
+ (kCachedSetRenderDuration + 10ms));
+}
+
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a4b6fef..a109f3b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2040,6 +2040,7 @@
}
refreshArgs.earliestPresentTime = mScheduler->getPreviousVsyncFrom(mExpectedPresentTime);
+ refreshArgs.nextInvalidateTime = mEventQueue->nextExpectedInvalidate();
mGeometryInvalid = false;