Skip client composition requests

Keep track of client compositions requests in CompositionEngine and
the buffer used to render the request. If the requests do not change
and the buffer matches, reuse the buffer instead of going
to client composition.

Certain layers properties (buffer format, rounded corner or shadows)
will force the device to go into GPU or mixed composition. In mixed
composition scenarios, we can avoid redundant client composition
requests by reusing the RenderSurface buffers. If the device goes
into GPU composition then all the layers will be composited and there
will be no performance benefit.

Bug: b/136561771, b/144690120

Test: dump SurfaceFlinger --timestats -dump
Test: manual testing with mixed and gpu composition scenarios
Test: atest libcompositionengine_test libsurfaceflinger_unittest SurfaceFlinger_test
Change-Id: I466d5dcded0c9fcfa64bc72fd91dfaddd795f315
diff --git a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
new file mode 100644
index 0000000..acaaf4e
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 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 <algorithm>
+
+#include <compositionengine/impl/ClientCompositionRequestCache.h>
+#include <renderengine/DisplaySettings.h>
+#include <renderengine/LayerSettings.h>
+
+namespace android::compositionengine::impl {
+
+namespace {
+LayerFE::LayerSettings getLayerSettingsSnapshot(const LayerFE::LayerSettings& settings) {
+    LayerFE::LayerSettings snapshot = settings;
+    snapshot.source.buffer.buffer = nullptr;
+    snapshot.source.buffer.fence = nullptr;
+    return snapshot;
+}
+
+inline bool equalIgnoringSource(const renderengine::LayerSettings& lhs,
+                                const renderengine::LayerSettings& rhs) {
+    return lhs.geometry == rhs.geometry && lhs.alpha == rhs.alpha &&
+            lhs.sourceDataspace == rhs.sourceDataspace &&
+            lhs.colorTransform == rhs.colorTransform &&
+            lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow;
+}
+
+inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
+    return lhs.textureName == rhs.textureName &&
+            lhs.useTextureFiltering == rhs.useTextureFiltering &&
+            lhs.textureTransform == rhs.textureTransform &&
+            lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
+            lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
+            lhs.maxMasteringLuminance == rhs.maxMasteringLuminance &&
+            lhs.maxContentLuminance == rhs.maxContentLuminance;
+}
+
+inline bool equalIgnoringBuffer(const renderengine::LayerSettings& lhs,
+                                const renderengine::LayerSettings& rhs) {
+    // compare LayerSettings without LayerSettings.PixelSource
+    return equalIgnoringSource(lhs, rhs) &&
+
+            // compare LayerSettings.PixelSource without buffer
+            lhs.source.solidColor == rhs.source.solidColor &&
+
+            // compare LayerSettings.PixelSource.Buffer without buffer & fence
+            equalIgnoringBuffer(lhs.source.buffer, rhs.source.buffer);
+}
+
+bool layerSettingsAreEqual(const LayerFE::LayerSettings& lhs, const LayerFE::LayerSettings& rhs) {
+    return lhs.bufferId == rhs.bufferId && lhs.frameNumber == rhs.frameNumber &&
+            equalIgnoringBuffer(lhs, rhs);
+}
+
+} // namespace
+
+ClientCompositionRequestCache::ClientCompositionRequest::ClientCompositionRequest(
+        const renderengine::DisplaySettings& initDisplay,
+        const std::vector<LayerFE::LayerSettings>& initLayerSettings)
+      : display(initDisplay) {
+    layerSettings.reserve(initLayerSettings.size());
+    for (const LayerFE::LayerSettings& settings : initLayerSettings) {
+        layerSettings.push_back(getLayerSettingsSnapshot(settings));
+    }
+}
+
+bool ClientCompositionRequestCache::ClientCompositionRequest::equals(
+        const renderengine::DisplaySettings& newDisplay,
+        const std::vector<LayerFE::LayerSettings>& newLayerSettings) const {
+    return newDisplay == display &&
+            std::equal(layerSettings.begin(), layerSettings.end(), newLayerSettings.begin(),
+                       newLayerSettings.end(), layerSettingsAreEqual);
+}
+
+bool ClientCompositionRequestCache::exists(
+        uint64_t bufferId, const renderengine::DisplaySettings& display,
+        const std::vector<LayerFE::LayerSettings>& layerSettings) const {
+    for (const auto& [cachedBufferId, cachedRequest] : mCache) {
+        if (cachedBufferId == bufferId) {
+            return cachedRequest.equals(display, layerSettings);
+        }
+    }
+    return false;
+}
+
+void ClientCompositionRequestCache::add(uint64_t bufferId,
+                                        const renderengine::DisplaySettings& display,
+                                        const std::vector<LayerFE::LayerSettings>& layerSettings) {
+    const ClientCompositionRequest request(display, layerSettings);
+    for (auto& [cachedBufferId, cachedRequest] : mCache) {
+        if (cachedBufferId == bufferId) {
+            cachedRequest = std::move(request);
+            return;
+        }
+    }
+
+    if (mCache.size() >= mMaxCacheSize) {
+        mCache.pop_front();
+    }
+
+    mCache.emplace_back(bufferId, std::move(request));
+}
+
+void ClientCompositionRequestCache::remove(uint64_t bufferId) {
+    for (auto it = mCache.begin(); it != mCache.end(); it++) {
+        if (it->first == bufferId) {
+            mCache.erase(it);
+            return;
+        }
+    }
+}
+
+} // namespace android::compositionengine::impl