SF - plumbing game mode for metrics (Part 1)
This change adds a game mode in the Layer metadata that gets updated every
time a game app comes to the foreground. The gameMode is then set on the
Layer based on the metadata and reparenting.
The game mode will then be
updated in TimeStats as a dimension (Part 2, in a separate CL later).
Bug: 186025682
Test: libsurfaceflinger_unittest:GameModeTest
Change-Id: I09deffc01d1b318cc08d0475611289dec055c190
diff --git a/aidl/gui/android/view/LayerMetadataKey.aidl b/aidl/gui/android/view/LayerMetadataKey.aidl
index a1d8ce5..d6ca3db 100644
--- a/aidl/gui/android/view/LayerMetadataKey.aidl
+++ b/aidl/gui/android/view/LayerMetadataKey.aidl
@@ -26,4 +26,5 @@
METADATA_ACCESSIBILITY_ID = 5,
METADATA_OWNER_PID = 6,
METADATA_DEQUEUE_TIME = 7,
+ METADATA_GAME_MODE = 8,
}
diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp
index 634d8b7..189d51a 100644
--- a/libs/gui/LayerMetadata.cpp
+++ b/libs/gui/LayerMetadata.cpp
@@ -136,6 +136,8 @@
return StringPrintf("ownerPID%s%d", separator, getInt32(key, 0));
case view::LayerMetadataKey::METADATA_DEQUEUE_TIME:
return StringPrintf("dequeueTime%s%" PRId64, separator, *getInt64(key));
+ case view::LayerMetadataKey::METADATA_GAME_MODE:
+ return StringPrintf("gameMode%s%d", separator, getInt32(key, 0));
default:
return StringPrintf("%d%s%dbytes", key, separator,
static_cast<int>(mMap.at(key).size()));
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index 41982c2..de14b3d 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -29,7 +29,8 @@
METADATA_MOUSE_CURSOR = 4,
METADATA_ACCESSIBILITY_ID = 5,
METADATA_OWNER_PID = 6,
- METADATA_DEQUEUE_TIME = 7
+ METADATA_DEQUEUE_TIME = 7,
+ METADATA_GAME_MODE = 8
};
struct LayerMetadata : public Parcelable {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index a7c8704..d78a822 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1651,12 +1651,25 @@
return count;
}
+void Layer::setGameModeForTree(int parentGameMode) {
+ int gameMode = parentGameMode;
+ auto& currentState = getCurrentState();
+ if (currentState.metadata.has(METADATA_GAME_MODE)) {
+ gameMode = currentState.metadata.getInt32(METADATA_GAME_MODE, 0);
+ }
+ setGameMode(gameMode);
+ for (const sp<Layer>& child : mCurrentChildren) {
+ child->setGameModeForTree(gameMode);
+ }
+}
+
void Layer::addChild(const sp<Layer>& layer) {
mChildrenChanged = true;
setTransactionFlags(eTransactionNeeded);
mCurrentChildren.add(layer);
layer->setParent(this);
+ layer->setGameModeForTree(mGameMode);
updateTreeHasFrameRateVote();
}
@@ -1668,6 +1681,7 @@
const auto removeResult = mCurrentChildren.remove(layer);
updateTreeHasFrameRateVote();
+ layer->setGameModeForTree(0);
layer->updateTreeHasFrameRateVote();
return removeResult;
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 66d7018..af26045 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -847,6 +847,13 @@
*/
bool hasInputInfo() const;
+ // Sets the parent's gameMode for this layer and all its children. Parent's gameMode is applied
+ // only to layers that do not have the GAME_MODE_METADATA set by WMShell. Any layer(along with
+ // its children) that has the metadata set will use the gameMode from the metadata.
+ void setGameModeForTree(int parentGameMode);
+ void setGameMode(int gameMode) { mGameMode = gameMode; };
+ int getGameMode() const { return mGameMode; }
+
virtual uid_t getOwnerUid() const { return mOwnerUid; }
pid_t getOwnerPid() { return mOwnerPid; }
@@ -1089,6 +1096,10 @@
// shadow radius is the set shadow radius, otherwise its the parent's shadow radius.
float mEffectiveShadowRadius = 0.f;
+ // Game mode for the layer. Set by WindowManagerShell, game mode is used in
+ // metrics(SurfaceFlingerStats).
+ int mGameMode = 0;
+
// A list of regions on this layer that should have blurs.
const std::vector<BlurRegion> getBlurRegions() const;
};
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 881ee5b..3f75af2 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -4018,6 +4018,13 @@
std::optional<nsecs_t> dequeueBufferTimestamp;
if (what & layer_state_t::eMetadataChanged) {
dequeueBufferTimestamp = s.metadata.getInt64(METADATA_DEQUEUE_TIME);
+ auto gameMode = s.metadata.getInt32(METADATA_GAME_MODE, -1);
+ if (gameMode != -1) {
+ // The transaction will be received on the Task layer and needs to be applied to all
+ // child layers. Child layers that are added at a later point will obtain the game mode
+ // info through addChild().
+ layer->setGameModeForTree(gameMode);
+ }
if (layer->setMetadata(s.metadata)) flags |= eTraversalNeeded;
}
if (what & layer_state_t::eColorSpaceAgnosticChanged) {
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 1b25a36..736ef30 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -57,6 +57,7 @@
"FpsTest.cpp",
"FramebufferSurfaceTest.cpp",
"FrameTimelineTest.cpp",
+ "GameModeTest.cpp",
"HWComposerTest.cpp",
"OneShotTimerTest.cpp",
"LayerHistoryTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
new file mode 100644
index 0000000..3fa1a2c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+#include <gui/SurfaceComposerClient.h>
+#include <log/log.h>
+
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/MockEventThread.h"
+#include "mock/MockVsyncController.h"
+
+namespace android {
+
+using testing::_;
+using testing::Mock;
+using testing::Return;
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+class GameModeTest : public testing::Test {
+public:
+ GameModeTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+ setupScheduler();
+ setupComposer();
+ }
+
+ ~GameModeTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+
+ sp<BufferStateLayer> createBufferStateLayer() {
+ sp<Client> client;
+ LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 100, 100, 0,
+ LayerMetadata());
+ return new BufferStateLayer(args);
+ }
+
+ void setupScheduler() {
+ auto eventThread = std::make_unique<mock::EventThread>();
+ auto sfEventThread = std::make_unique<mock::EventThread>();
+
+ EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+ EXPECT_CALL(*eventThread, createEventConnection(_, _))
+ .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+ ResyncCallback())));
+
+ EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+ EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+ .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+ ResyncCallback())));
+
+ auto vsyncController = std::make_unique<mock::VsyncController>();
+ auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+ EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+ EXPECT_CALL(*vsyncTracker, currentPeriod())
+ .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+ EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+ mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+ std::move(eventThread), std::move(sfEventThread));
+ }
+
+ void setupComposer() {
+ mComposer = new Hwc2::mock::Composer();
+ mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+
+ Mock::VerifyAndClear(mComposer);
+ }
+
+ // Mocks the behavior of applying a transaction from WMShell
+ void setGameModeMetadata(sp<Layer> layer, int gameMode) {
+ mLayerMetadata.setInt32(METADATA_GAME_MODE, gameMode);
+ layer->setMetadata(mLayerMetadata);
+ layer->setGameModeForTree(gameMode);
+ }
+
+ TestableSurfaceFlinger mFlinger;
+ Hwc2::mock::Composer* mComposer = nullptr;
+ client_cache_t mClientCache;
+ LayerMetadata mLayerMetadata;
+};
+
+TEST_F(GameModeTest, SetGameModeSetsForAllCurrentChildren) {
+ sp<BufferStateLayer> rootLayer = createBufferStateLayer();
+ sp<BufferStateLayer> childLayer1 = createBufferStateLayer();
+ sp<BufferStateLayer> childLayer2 = createBufferStateLayer();
+ rootLayer->addChild(childLayer1);
+ rootLayer->addChild(childLayer2);
+ rootLayer->setGameModeForTree(/*gameMode*/ 2);
+
+ EXPECT_EQ(rootLayer->getGameMode(), 2);
+ EXPECT_EQ(childLayer1->getGameMode(), 2);
+ EXPECT_EQ(childLayer2->getGameMode(), 2);
+}
+
+TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) {
+ sp<BufferStateLayer> rootLayer = createBufferStateLayer();
+ sp<BufferStateLayer> childLayer = createBufferStateLayer();
+ rootLayer->setGameModeForTree(/*gameMode*/ 2);
+ rootLayer->addChild(childLayer);
+
+ EXPECT_EQ(rootLayer->getGameMode(), 2);
+ EXPECT_EQ(childLayer->getGameMode(), 2);
+}
+
+TEST_F(GameModeTest, RemoveChildResetsGameMode) {
+ sp<BufferStateLayer> rootLayer = createBufferStateLayer();
+ sp<BufferStateLayer> childLayer = createBufferStateLayer();
+ rootLayer->setGameModeForTree(/*gameMode*/ 2);
+ rootLayer->addChild(childLayer);
+
+ EXPECT_EQ(rootLayer->getGameMode(), 2);
+ EXPECT_EQ(childLayer->getGameMode(), 2);
+
+ rootLayer->removeChild(childLayer);
+ EXPECT_EQ(childLayer->getGameMode(), 0);
+}
+
+TEST_F(GameModeTest, ReparentingDoesNotOverrideMetadata) {
+ sp<BufferStateLayer> rootLayer = createBufferStateLayer();
+ sp<BufferStateLayer> childLayer1 = createBufferStateLayer();
+ sp<BufferStateLayer> childLayer2 = createBufferStateLayer();
+ rootLayer->setGameModeForTree(/*gameMode*/ 1);
+ rootLayer->addChild(childLayer1);
+
+ setGameModeMetadata(childLayer2, /*gameMode*/ 2);
+ rootLayer->addChild(childLayer2);
+
+ EXPECT_EQ(rootLayer->getGameMode(), 1);
+ EXPECT_EQ(childLayer1->getGameMode(), 1);
+ EXPECT_EQ(childLayer2->getGameMode(), 2);
+
+ rootLayer->removeChild(childLayer2);
+ EXPECT_EQ(childLayer2->getGameMode(), 2);
+}
+} // namespace android
\ No newline at end of file