/* * 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 #include #include #include #include #include namespace android::compositionengine::impl::planner { namespace { std::optional 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(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{ .cachedSetRenderDuration = renderDuration, .maxDeferRenderAttempts = maxDeferRenderAttempts, }); } Flattener::Tunables buildFlattenerTuneables() { const auto activeLayerTimeout = std::chrono::milliseconds( base::GetIntProperty(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&& layers) { ATRACE_CALL(); std::unordered_set removedLayers; removedLayers.reserve(mPreviousLayers.size()); std::transform(mPreviousLayers.begin(), mPreviousLayers.end(), std::inserter(removedLayers, removedLayers.begin()), [](const auto& layer) { return layer.first; }); std::vector 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; Flags 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() : 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&& 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 ? hardware::graphics::composer::hal::Composition::CLIENT : layer->getLayerFE().getCompositionState()->compositionType); } mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers, finalPlan); } void Planner::renderCachedSets( const OutputCompositionState& outputState, std::optional renderDeadline) { ATRACE_CALL(); mFlattener.renderCachedSets(outputState, renderDeadline); } void Planner::dump(const Vector& 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 " " '\n", command.string()); return; } if (args.size() > 4) { base::StringAppendF(&result, "Too many arguments found, expected '--planner %s " "'\n", command.string()); return; } const String8 leftHashString(args[2]); size_t leftHash = 0; int fieldsRead = sscanf(leftHashString.string(), "%zx", &leftHash); if (fieldsRead != 1) { base::StringAppendF(&result, "Failed to parse %s as a size_t\n", leftHashString.string()); return; } const String8 rightHashString(args[3]); size_t rightHash = 0; fieldsRead = sscanf(rightHashString.string(), "%zx", &rightHash); if (fieldsRead != 1) { base::StringAppendF(&result, "Failed to parse %s as a size_t\n", rightHashString.string()); 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 '\n", command.string()); return; } if (args.size() > 3) { base::StringAppendF(&result, "Too many arguments found, expected '--planner %s '\n", command.string()); return; } const String8 hashString(args[2]); size_t hash = 0; const int fieldsRead = sscanf(hashString.string(), "%zx", &hash); if (fieldsRead != 1) { base::StringAppendF(&result, "Failed to parse %s as a size_t\n", hashString.string()); 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 '\n", command.string()); return; } if (args.size() > 3) { base::StringAppendF(&result, "Too many arguments found, expected '--planner %s '\n", command.string()); return; } const String8 planString(args[2]); std::optional plan = Plan::fromString(std::string(planString.string())); if (!plan) { base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.string()); 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.string()); 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 [arguments]\n\n"); result.append("If run without a command, dumps current Planner state\n\n"); result.append("Commands:\n"); result.append("[--compare|-c] \n"); result.append(" Compares the predictions and by showing differences" " in their example layer stacks\n"); result.append("[--describe|-d] \n"); result.append(" Prints the example layer stack and prediction statistics for \n"); result.append("[--help|-h]\n"); result.append(" Shows this message\n"); result.append("[--similar|-s] \n"); result.append(" Prints the example layer names for similar stacks matching \n"); result.append("[--layers|-l]\n"); result.append(" Prints the current layers\n"); } } // namespace android::compositionengine::impl::planner