| /* |
| * 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" |
| #define ATRACE_TAG ATRACE_TAG_GRAPHICS |
| |
| #include <android-base/properties.h> |
| #include <compositionengine/LayerFECompositionState.h> |
| #include <compositionengine/impl/OutputLayerCompositionState.h> |
| #include <compositionengine/impl/planner/Planner.h> |
| |
| #include <utils/Trace.h> |
| #include <chrono> |
| |
| namespace android::compositionengine::impl::planner { |
| |
| namespace { |
| |
| std::optional<Flattener::Tunables::RenderScheduling> buildRenderSchedulingTunables() { |
| if (!base::GetBoolProperty(std::string("debug.sf.enable_cached_set_render_scheduling"), true)) { |
| return std::nullopt; |
| } |
| |
| const auto renderDuration = std::chrono::nanoseconds( |
| base::GetUintProperty<uint64_t>(std::string("debug.sf.cached_set_render_duration_ns"), |
| Flattener::Tunables::RenderScheduling:: |
| kDefaultCachedSetRenderDuration.count())); |
| |
| const auto maxDeferRenderAttempts = base::GetUintProperty< |
| size_t>(std::string("debug.sf.cached_set_max_defer_render_attmpts"), |
| Flattener::Tunables::RenderScheduling::kDefaultMaxDeferRenderAttempts); |
| |
| return std::make_optional<Flattener::Tunables::RenderScheduling>( |
| Flattener::Tunables::RenderScheduling{ |
| .cachedSetRenderDuration = renderDuration, |
| .maxDeferRenderAttempts = maxDeferRenderAttempts, |
| }); |
| } |
| |
| Flattener::Tunables buildFlattenerTuneables() { |
| const auto activeLayerTimeout = std::chrono::milliseconds( |
| base::GetIntProperty<int32_t>(std::string( |
| "debug.sf.layer_caching_active_layer_timeout_ms"), |
| Flattener::Tunables::kDefaultActiveLayerTimeout.count())); |
| const auto enableHolePunch = |
| base::GetBoolProperty(std::string("debug.sf.enable_hole_punch_pip"), |
| Flattener::Tunables::kDefaultEnableHolePunch); |
| return Flattener::Tunables{ |
| .mActiveLayerTimeout = activeLayerTimeout, |
| .mRenderScheduling = buildRenderSchedulingTunables(), |
| .mEnableHolePunch = enableHolePunch, |
| }; |
| } |
| |
| } // namespace |
| |
| Planner::Planner(renderengine::RenderEngine& renderEngine) |
| : mFlattener(renderEngine, |
| buildFlattenerTuneables()) { |
| mPredictorEnabled = |
| base::GetBoolProperty(std::string("debug.sf.enable_planner_prediction"), false); |
| } |
| |
| void Planner::setDisplaySize(ui::Size size) { |
| mFlattener.setDisplaySize(size); |
| } |
| |
| void Planner::plan( |
| compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) { |
| ATRACE_CALL(); |
| 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; }); |
| |
| std::vector<LayerId> currentLayerIds; |
| 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; |
| ftl::Flags<LayerStateField> differences = state.update(layer); |
| if (differences.get() == 0) { |
| state.incrementFramesSinceBufferUpdate(); |
| } else { |
| 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))); |
| } |
| |
| currentLayerIds.emplace_back(id); |
| |
| if (const auto found = removedLayers.find(id); found != removedLayers.end()) { |
| removedLayers.erase(found); |
| } |
| } |
| |
| mCurrentLayers.clear(); |
| mCurrentLayers.reserve(currentLayerIds.size()); |
| std::transform(currentLayerIds.cbegin(), currentLayerIds.cend(), |
| std::back_inserter(mCurrentLayers), [this](LayerId id) { |
| LayerState* state = &mPreviousLayers.at(id); |
| state->getOutputLayer()->editState().overrideInfo = {}; |
| return state; |
| }); |
| |
| const NonBufferHash hash = getNonBufferHash(mCurrentLayers); |
| mFlattenedHash = |
| mFlattener.flattenLayers(mCurrentLayers, hash, std::chrono::steady_clock::now()); |
| const bool layersWereFlattened = hash != mFlattenedHash; |
| |
| ALOGV("[%s] Initial hash %zx flattened hash %zx", __func__, hash, mFlattenedHash); |
| |
| if (mPredictorEnabled) { |
| mPredictedPlan = |
| mPredictor.getPredictedPlan(layersWereFlattened ? std::vector<const LayerState*>() |
| : mCurrentLayers, |
| mFlattenedHash); |
| if (mPredictedPlan) { |
| ALOGV("[%s] Predicting plan %s", __func__, to_string(mPredictedPlan->plan).c_str()); |
| } else { |
| ALOGV("[%s] No prediction found\n", __func__); |
| } |
| } |
| |
| // Clean up the set of previous layers now that the view of the LayerStates in the flattener are |
| // up-to-date. |
| 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::reportFinalPlan( |
| compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) { |
| ATRACE_CALL(); |
| if (!mPredictorEnabled) { |
| return; |
| } |
| |
| Plan finalPlan; |
| const GraphicBuffer* currentOverrideBuffer = nullptr; |
| bool hasSkippedLayers = false; |
| for (auto layer : layers) { |
| if (!layer->getState().overrideInfo.buffer) { |
| continue; |
| } |
| |
| const GraphicBuffer* overrideBuffer = |
| layer->getState().overrideInfo.buffer->getBuffer().get(); |
| if (overrideBuffer != nullptr && overrideBuffer == currentOverrideBuffer) { |
| // Skip this layer since it is part of a previous cached set |
| hasSkippedLayers = true; |
| continue; |
| } |
| |
| currentOverrideBuffer = overrideBuffer; |
| |
| const bool forcedOrRequestedClient = |
| layer->getState().forceClientComposition || layer->requiresClientComposition(); |
| |
| finalPlan.addLayerType( |
| forcedOrRequestedClient |
| ? aidl::android::hardware::graphics::composer3::Composition::CLIENT |
| : layer->getLayerFE().getCompositionState()->compositionType); |
| } |
| |
| mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers, |
| finalPlan); |
| } |
| |
| void Planner::renderCachedSets(const OutputCompositionState& outputState, |
| std::optional<std::chrono::steady_clock::time_point> renderDeadline, |
| bool deviceHandlesColorTransform) { |
| ATRACE_CALL(); |
| mFlattener.renderCachedSets(outputState, renderDeadline, deviceHandlesColorTransform); |
| } |
| |
| void Planner::dump(const Vector<String16>& args, std::string& result) { |
| if (args.size() > 1) { |
| const String8 command(args[1]); |
| if (command == "--compare" || command == "-c") { |
| if (args.size() < 4) { |
| base::StringAppendF(&result, |
| "Expected two layer stack hashes, e.g. '--planner %s " |
| "<left_hash> <right_hash>'\n", |
| command.c_str()); |
| return; |
| } |
| if (args.size() > 4) { |
| base::StringAppendF(&result, |
| "Too many arguments found, expected '--planner %s <left_hash> " |
| "<right_hash>'\n", |
| command.c_str()); |
| return; |
| } |
| |
| const String8 leftHashString(args[2]); |
| size_t leftHash = 0; |
| int fieldsRead = sscanf(leftHashString.c_str(), "%zx", &leftHash); |
| if (fieldsRead != 1) { |
| base::StringAppendF(&result, "Failed to parse %s as a size_t\n", |
| leftHashString.c_str()); |
| return; |
| } |
| |
| const String8 rightHashString(args[3]); |
| size_t rightHash = 0; |
| fieldsRead = sscanf(rightHashString.c_str(), "%zx", &rightHash); |
| if (fieldsRead != 1) { |
| base::StringAppendF(&result, "Failed to parse %s as a size_t\n", |
| rightHashString.c_str()); |
| return; |
| } |
| |
| if (mPredictorEnabled) { |
| mPredictor.compareLayerStacks(leftHash, rightHash, result); |
| } |
| } else if (command == "--describe" || command == "-d") { |
| if (args.size() < 3) { |
| base::StringAppendF(&result, |
| "Expected a layer stack hash, e.g. '--planner %s <hash>'\n", |
| command.c_str()); |
| return; |
| } |
| if (args.size() > 3) { |
| base::StringAppendF(&result, |
| "Too many arguments found, expected '--planner %s <hash>'\n", |
| command.c_str()); |
| return; |
| } |
| |
| const String8 hashString(args[2]); |
| size_t hash = 0; |
| const int fieldsRead = sscanf(hashString.c_str(), "%zx", &hash); |
| if (fieldsRead != 1) { |
| base::StringAppendF(&result, "Failed to parse %s as a size_t\n", |
| hashString.c_str()); |
| return; |
| } |
| |
| if (mPredictorEnabled) { |
| mPredictor.describeLayerStack(hash, result); |
| } |
| } else if (command == "--help" || command == "-h") { |
| dumpUsage(result); |
| } else if (command == "--similar" || command == "-s") { |
| if (args.size() < 3) { |
| base::StringAppendF(&result, "Expected a plan string, e.g. '--planner %s <plan>'\n", |
| command.c_str()); |
| return; |
| } |
| if (args.size() > 3) { |
| base::StringAppendF(&result, |
| "Too many arguments found, expected '--planner %s <plan>'\n", |
| command.c_str()); |
| return; |
| } |
| |
| const String8 planString(args[2]); |
| std::optional<Plan> plan = Plan::fromString(std::string(planString.c_str())); |
| if (!plan) { |
| base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.c_str()); |
| return; |
| } |
| |
| if (mPredictorEnabled) { |
| mPredictor.listSimilarStacks(*plan, result); |
| } |
| } else if (command == "--layers" || command == "-l") { |
| mFlattener.dumpLayers(result); |
| } else { |
| base::StringAppendF(&result, "Unknown command '%s'\n\n", command.c_str()); |
| dumpUsage(result); |
| } |
| return; |
| } |
| |
| // If there are no specific commands, dump the usual state |
| |
| mFlattener.dump(result); |
| result.append("\n"); |
| |
| if (mPredictorEnabled) { |
| mPredictor.dump(result); |
| } |
| } |
| |
| void Planner::dumpUsage(std::string& result) const { |
| result.append("Planner command line interface usage\n"); |
| result.append(" dumpsys SurfaceFlinger --planner <command> [arguments]\n\n"); |
| |
| result.append("If run without a command, dumps current Planner state\n\n"); |
| |
| result.append("Commands:\n"); |
| |
| result.append("[--compare|-c] <left_hash> <right_hash>\n"); |
| result.append(" Compares the predictions <left_hash> and <right_hash> by showing differences" |
| " in their example layer stacks\n"); |
| |
| result.append("[--describe|-d] <hash>\n"); |
| result.append(" Prints the example layer stack and prediction statistics for <hash>\n"); |
| |
| result.append("[--help|-h]\n"); |
| result.append(" Shows this message\n"); |
| |
| result.append("[--similar|-s] <plan>\n"); |
| result.append(" Prints the example layer names for similar stacks matching <plan>\n"); |
| |
| result.append("[--layers|-l]\n"); |
| result.append(" Prints the current layers\n"); |
| } |
| |
| } // namespace android::compositionengine::impl::planner |