SF: Add initial Planner infrastructure

Adds infrastructure for the SF Planner, which will support layer
caching/flattening and composition strategy prediction.

Bug: 158790260
Test: atest libcompositionengine_test libsurfaceflinger_unittest
Change-Id: I0d3027cea073fe25f269f3d5e83fe621dfbe7b2b
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3907ac5..709d7c9 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -27,6 +27,9 @@
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <compositionengine/impl/planner/Planner.h>
+
+#include <SurfaceFlingerProperties.sysprop.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -38,6 +41,7 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
+#include <android-base/properties.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
 #include <utils/Trace.h>
@@ -50,6 +54,18 @@
 
 namespace impl {
 
+Output::Output() {
+    const bool enableLayerCaching = [] {
+        const bool enable =
+                android::sysprop::SurfaceFlingerProperties::enable_layer_caching().value_or(false);
+        return base::GetBoolProperty(std::string("debug.sf.enable_layer_caching"), enable);
+    }();
+
+    if (enableLayerCaching) {
+        mPlanner = std::make_unique<planner::Planner>();
+    }
+}
+
 namespace {
 
 template <typename T>
@@ -269,6 +285,15 @@
     }
 }
 
+void Output::dumpPlannerInfo(const Vector<String16>& args, std::string& out) const {
+    if (!mPlanner) {
+        base::StringAppendF(&out, "Planner is disabled\n");
+        return;
+    }
+    base::StringAppendF(&out, "Planner info for display [%s]\n", mName.c_str());
+    mPlanner->dump(args, out);
+}
+
 compositionengine::DisplayColorProfile* Output::getDisplayColorProfile() const {
     return mDisplayColorProfile.get();
 }
@@ -368,7 +393,9 @@
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
-    updateAndWriteCompositionState(refreshArgs);
+    updateCompositionState(refreshArgs);
+    planComposition();
+    writeCompositionState(refreshArgs);
     setColorTransform(refreshArgs);
     beginFrame();
     prepareFrame();
@@ -632,8 +659,7 @@
     }
 }
 
-void Output::updateAndWriteCompositionState(
-        const compositionengine::CompositionRefreshArgs& refreshArgs) {
+void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
 
@@ -653,8 +679,29 @@
         if (mLayerRequestingBackgroundBlur == layer) {
             forceClientComposition = false;
         }
+    }
+}
 
-        // Send the updated state to the HWC, if appropriate.
+void Output::planComposition() {
+    if (!mPlanner || !getState().isEnabled) {
+        return;
+    }
+
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    mPlanner->plan(getOutputLayersOrderedByZ());
+}
+
+void Output::writeCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    if (!getState().isEnabled) {
+        return;
+    }
+
+    for (auto* layer : getOutputLayersOrderedByZ()) {
         layer->writeStateToHWC(refreshArgs.updatingGeometryThisFrame);
     }
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
new file mode 100644
index 0000000..7cf4819
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+#include <compositionengine/impl/planner/LayerState.h>
+
+namespace android::compositionengine::impl::planner {
+
+LayerState::LayerState(compositionengine::OutputLayer* layer) : mOutputLayer(layer) {
+    update(layer);
+}
+
+Flags<LayerStateField> LayerState::update(compositionengine::OutputLayer* layer) {
+    ALOGE_IF(layer != mOutputLayer, "[%s] Expected mOutputLayer to never change", __func__);
+
+    Flags<LayerStateField> differences;
+
+    // Update the unique fields as well, since we have to set them at least
+    // once from the OutputLayer
+    differences |= mId.update(layer);
+    differences |= mName.update(layer);
+
+    for (StateInterface* field : getNonUniqueFields()) {
+        differences |= field->update(layer);
+    }
+
+    return differences;
+}
+
+size_t LayerState::getHash(
+        Flags<LayerStateField> skipFields = static_cast<LayerStateField>(0)) const {
+    size_t hash = 0;
+    for (const StateInterface* field : getNonUniqueFields()) {
+        android::hashCombineSingleHashed(hash, field->getHash(skipFields));
+    }
+
+    return hash;
+}
+
+Flags<LayerStateField> LayerState::getDifferingFields(
+        const LayerState& other,
+        Flags<LayerStateField> skipFields = static_cast<LayerStateField>(0)) const {
+    Flags<LayerStateField> differences;
+    auto myFields = getNonUniqueFields();
+    auto otherFields = other.getNonUniqueFields();
+    for (size_t i = 0; i < myFields.size(); ++i) {
+        if (skipFields.test(myFields[i]->getField())) {
+            continue;
+        }
+
+        differences |= myFields[i]->getFieldIfDifferent(otherFields[i]);
+    }
+
+    return differences;
+}
+
+void LayerState::dump(std::string& result) const {
+    for (const StateInterface* field : getNonUniqueFields()) {
+        if (auto viewOpt = flag_name(field->getField()); viewOpt) {
+            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
+        } else {
+            result.append("<UNKNOWN FIELD>:\n");
+        }
+
+        bool first = true;
+        for (const std::string& line : field->toStrings()) {
+            base::StringAppendF(&result, "%s%s\n", first ? "" : "                    ",
+                                line.c_str());
+            first = false;
+        }
+    }
+    result.append("\n");
+}
+
+std::optional<std::string> LayerState::compare(const LayerState& other) const {
+    std::string result;
+
+    const auto& thisFields = getNonUniqueFields();
+    const auto& otherFields = other.getNonUniqueFields();
+    for (size_t f = 0; f < thisFields.size(); ++f) {
+        const auto& thisField = thisFields[f];
+        const auto& otherField = otherFields[f];
+        // Skip comparing buffers
+        if (thisField->getField() == LayerStateField::Buffer) {
+            continue;
+        }
+
+        if (thisField->equals(otherField)) {
+            continue;
+        }
+
+        if (auto viewOpt = flag_name(thisField->getField()); viewOpt) {
+            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
+        } else {
+            result.append("<UNKNOWN FIELD>:\n");
+        }
+
+        const auto& thisStrings = thisField->toStrings();
+        const auto& otherStrings = otherField->toStrings();
+        bool first = true;
+        for (size_t line = 0; line < std::max(thisStrings.size(), otherStrings.size()); ++line) {
+            if (!first) {
+                result.append("                    ");
+            }
+            first = false;
+
+            if (line < thisStrings.size()) {
+                base::StringAppendF(&result, "%-48.48s", thisStrings[line].c_str());
+            } else {
+                result.append("                                                ");
+            }
+
+            if (line < otherStrings.size()) {
+                base::StringAppendF(&result, "%-48.48s", otherStrings[line].c_str());
+            } else {
+                result.append("                                                ");
+            }
+            result.append("\n");
+        }
+    }
+
+    return result.empty() ? std::nullopt : std::make_optional(result);
+}
+
+bool operator==(const LayerState& lhs, const LayerState& rhs) {
+    return lhs.mId == rhs.mId && lhs.mName == rhs.mName && lhs.mDisplayFrame == rhs.mDisplayFrame &&
+            lhs.mSourceCrop == rhs.mSourceCrop && lhs.mZOrder == rhs.mZOrder &&
+            lhs.mBufferTransform == rhs.mBufferTransform && lhs.mBlendMode == rhs.mBlendMode &&
+            lhs.mAlpha == rhs.mAlpha && lhs.mVisibleRegion == rhs.mVisibleRegion &&
+            lhs.mDataspace == rhs.mDataspace && lhs.mColorTransform == rhs.mColorTransform &&
+            lhs.mCompositionType == rhs.mCompositionType &&
+            lhs.mSidebandStream == rhs.mSidebandStream && lhs.mBuffer == rhs.mBuffer &&
+            (lhs.mCompositionType.get() != hal::Composition::SOLID_COLOR ||
+             lhs.mSolidColor == rhs.mSolidColor);
+}
+
+NonBufferHash getNonBufferHash(const std::vector<const LayerState*>& layers) {
+    size_t hash = 0;
+    for (const auto layer : layers) {
+        android::hashCombineSingleHashed(hash, layer->getHash(LayerStateField::Buffer));
+    }
+
+    return hash;
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
new file mode 100644
index 0000000..30f4892
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+
+#include <compositionengine/impl/planner/Planner.h>
+
+namespace android::compositionengine::impl::planner {
+
+void Planner::plan(
+        compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    std::unordered_set<LayerId> removedLayers;
+    removedLayers.reserve(mPreviousLayers.size());
+
+    std::transform(mPreviousLayers.begin(), mPreviousLayers.end(),
+                   std::inserter(removedLayers, removedLayers.begin()),
+                   [](const auto& layer) { return layer.first; });
+
+    for (auto layer : layers) {
+        LayerId id = layer->getLayerFE().getSequence();
+        if (const auto layerEntry = mPreviousLayers.find(id); layerEntry != mPreviousLayers.end()) {
+            // Track changes from previous info
+            LayerState& state = layerEntry->second;
+            Flags<LayerStateField> differences = state.update(layer);
+            if (differences.get() != 0) {
+                ALOGV("Layer %s changed: %s", state.getName().c_str(),
+                      differences.string().c_str());
+
+                if (differences.test(LayerStateField::Buffer)) {
+                    state.resetFramesSinceBufferUpdate();
+                } else {
+                    state.incrementFramesSinceBufferUpdate();
+                }
+            }
+        } else {
+            LayerState state(layer);
+            ALOGV("Added layer %s", state.getName().c_str());
+            mPreviousLayers.emplace(std::make_pair(id, std::move(state)));
+        }
+
+        if (const auto found = removedLayers.find(id); found != removedLayers.end()) {
+            removedLayers.erase(found);
+        }
+    }
+
+    for (LayerId removedLayer : removedLayers) {
+        if (const auto layerEntry = mPreviousLayers.find(removedLayer);
+            layerEntry != mPreviousLayers.end()) {
+            const auto& [id, state] = *layerEntry;
+            ALOGV("Removed layer %s", state.getName().c_str());
+            mPreviousLayers.erase(removedLayer);
+        }
+    }
+}
+
+void Planner::dump(const Vector<String16>& /* args */, std::string& result) {
+    result.append("Dumping Planner state");
+}
+
+} // namespace android::compositionengine::impl::planner