/* * 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 "Planner" // #define LOG_NDEBUG 0 #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include #include #include #include #include #include #include #include namespace android::compositionengine::impl::planner { const bool CachedSet::sDebugHighlighLayers = base::GetBoolProperty(std::string("debug.sf.layer_caching_highlight"), false); std::string durationString(std::chrono::milliseconds duration) { using namespace std::chrono_literals; std::string result; if (duration >= 1h) { const auto hours = std::chrono::duration_cast(duration); base::StringAppendF(&result, "%d hr ", static_cast(hours.count())); duration -= hours; } if (duration >= 1min) { const auto minutes = std::chrono::duration_cast(duration); base::StringAppendF(&result, "%d min ", static_cast(minutes.count())); duration -= minutes; } base::StringAppendF(&result, "%.3f sec ", duration.count() / 1000.0f); return result; } CachedSet::Layer::Layer(const LayerState* state, std::chrono::steady_clock::time_point lastUpdate) : mState(state), mHash(state->getHash()), mLastUpdate(lastUpdate) {} CachedSet::CachedSet(const LayerState* layer, std::chrono::steady_clock::time_point lastUpdate) : mFingerprint(layer->getHash()), mLastUpdate(lastUpdate) { addLayer(layer, lastUpdate); } CachedSet::CachedSet(Layer layer) : mFingerprint(layer.getHash()), mLastUpdate(layer.getLastUpdate()), mBounds(layer.getDisplayFrame()), mVisibleRegion(layer.getVisibleRegion()) { mLayers.emplace_back(std::move(layer)); } void CachedSet::addLayer(const LayerState* layer, std::chrono::steady_clock::time_point lastUpdate) { mLayers.emplace_back(layer, lastUpdate); Region boundingRegion; boundingRegion.orSelf(mBounds); boundingRegion.orSelf(layer->getDisplayFrame()); mBounds = boundingRegion.getBounds(); mVisibleRegion.orSelf(layer->getVisibleRegion()); } NonBufferHash CachedSet::getNonBufferHash() const { if (mLayers.size() == 1) { return mFingerprint; } // TODO(b/182614524): We sometimes match this with LayerState hashes. Determine if that is // necessary (and therefore we need to match implementations). size_t hash = 0; android::hashCombineSingle(hash, mBounds); android::hashCombineSingle(hash, mOutputDataspace); android::hashCombineSingle(hash, mOrientation); return hash; } size_t CachedSet::getComponentDisplayCost() const { size_t displayCost = 0; for (const Layer& layer : mLayers) { displayCost += static_cast(layer.getDisplayFrame().width() * layer.getDisplayFrame().height()); } return displayCost; } size_t CachedSet::getCreationCost() const { if (mLayers.size() == 1) { return 0; } // Reads size_t creationCost = getComponentDisplayCost(); // Write - assumes that the output buffer only gets written once per pixel creationCost += static_cast(mBounds.width() * mBounds.height()); return creationCost; } size_t CachedSet::getDisplayCost() const { return static_cast(mBounds.width() * mBounds.height()); } bool CachedSet::hasBufferUpdate() const { for (const Layer& layer : mLayers) { if (layer.getFramesSinceBufferUpdate() == 0) { return true; } } return false; } bool CachedSet::hasReadyBuffer() const { return mTexture && mDrawFence->getStatus() == Fence::Status::Signaled; } std::vector CachedSet::decompose() const { std::vector layers; std::transform(mLayers.begin(), mLayers.end(), std::back_inserter(layers), [](Layer layer) { return CachedSet(std::move(layer)); }); return layers; } void CachedSet::updateAge(std::chrono::steady_clock::time_point now) { LOG_ALWAYS_FATAL_IF(mLayers.size() > 1, "[%s] This should only be called on single-layer sets", __func__); if (mLayers[0].getFramesSinceBufferUpdate() == 0) { mLastUpdate = now; mAge = 0; } } void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool, const OutputCompositionState& outputState) { ATRACE_CALL(); const Rect& viewport = outputState.layerStackSpace.content; const ui::Dataspace& outputDataspace = outputState.dataspace; const ui::Transform::RotationFlags orientation = ui::Transform::toRotationFlags(outputState.framebufferSpace.orientation); renderengine::DisplaySettings displaySettings{ .physicalDisplay = outputState.framebufferSpace.content, .clip = viewport, .outputDataspace = outputDataspace, .orientation = orientation, }; Region clearRegion = Region::INVALID_REGION; LayerFE::ClientCompositionTargetSettings targetSettings{ .clip = Region(viewport), .needsFiltering = false, .isSecure = outputState.isSecure, .supportsProtectedContent = false, .clearRegion = clearRegion, .viewport = viewport, .dataspace = outputDataspace, .realContentIsVisible = true, .clearContent = false, .blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, }; std::vector layerSettings; renderengine::LayerSettings highlight; for (const auto& layer : mLayers) { const auto clientCompositionList = layer.getState()->getOutputLayer()->getLayerFE().prepareClientCompositionList( targetSettings); layerSettings.insert(layerSettings.end(), clientCompositionList.cbegin(), clientCompositionList.cend()); } std::vector layerSettingsPointers; std::transform(layerSettings.cbegin(), layerSettings.cend(), std::back_inserter(layerSettingsPointers), [](const renderengine::LayerSettings& settings) { return &settings; }); renderengine::LayerSettings blurLayerSettings; if (mBlurLayer) { auto blurSettings = targetSettings; blurSettings.blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly; auto clientCompositionList = mBlurLayer->getOutputLayer()->getLayerFE().prepareClientCompositionList( blurSettings); blurLayerSettings = clientCompositionList.back(); // This mimics Layer::prepareClearClientComposition blurLayerSettings.skipContentDraw = true; blurLayerSettings.name = std::string("blur layer"); // Clear out the shadow settings blurLayerSettings.shadow = {}; layerSettingsPointers.push_back(&blurLayerSettings); } renderengine::LayerSettings holePunchSettings; renderengine::LayerSettings holePunchBackgroundSettings; if (mHolePunchLayer) { auto clientCompositionList = mHolePunchLayer->getOutputLayer()->getLayerFE().prepareClientCompositionList( targetSettings); // Assume that the final layer contains the buffer that we want to // replace with a hole punch. holePunchSettings = clientCompositionList.back(); // This mimics Layer::prepareClearClientComposition holePunchSettings.source.buffer.buffer = nullptr; holePunchSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f); holePunchSettings.disableBlending = true; holePunchSettings.alpha = 0.0f; holePunchSettings.name = std::string("hole punch layer"); layerSettingsPointers.push_back(&holePunchSettings); // Add a solid background as the first layer in case there is no opaque // buffer behind the punch hole holePunchBackgroundSettings.alpha = 1.0f; holePunchBackgroundSettings.name = std::string("holePunchBackground"); holePunchBackgroundSettings.geometry.boundaries = holePunchSettings.geometry.boundaries; holePunchBackgroundSettings.geometry.positionTransform = holePunchSettings.geometry.positionTransform; layerSettingsPointers.insert(layerSettingsPointers.begin(), &holePunchBackgroundSettings); } if (sDebugHighlighLayers) { highlight = { .geometry = renderengine::Geometry{ .boundaries = FloatRect(0.0f, 0.0f, static_cast(mBounds.getWidth()), static_cast(mBounds.getHeight())), }, .source = renderengine::PixelSource{ .solidColor = half3(0.25f, 0.0f, 0.5f), }, .alpha = half(0.05f), }; layerSettingsPointers.emplace_back(&highlight); } auto texture = texturePool.borrowTexture(); LOG_ALWAYS_FATAL_IF(texture->get()->getBuffer()->initCheck() != OK); base::unique_fd bufferFence; if (texture->getReadyFence()) { // Bail out if the buffer is not ready, because there is some pending GPU work left. if (texture->getReadyFence()->getStatus() != Fence::Status::Signaled) { return; } bufferFence.reset(texture->getReadyFence()->dup()); } base::unique_fd drawFence; status_t result = renderEngine.drawLayers(displaySettings, layerSettingsPointers, texture->get(), false, std::move(bufferFence), &drawFence); if (result == NO_ERROR) { mDrawFence = new Fence(drawFence.release()); mOutputSpace = outputState.framebufferSpace; mTexture = texture; mTexture->setReadyFence(mDrawFence); mOutputSpace.orientation = outputState.framebufferSpace.orientation; mOutputDataspace = outputDataspace; mOrientation = orientation; mSkipCount = 0; } else { mTexture.reset(); } } bool CachedSet::requiresHolePunch() const { // In order for the hole punch to be beneficial, the layer must be updating // regularly, meaning it should not have been merged with other layers. if (getLayerCount() != 1) { return false; } // There is no benefit to a hole punch unless the layer has a buffer. if (!mLayers[0].getBuffer()) { return false; } if (hasUnsupportedDataspace()) { return false; } const auto& layerFE = mLayers[0].getState()->getOutputLayer()->getLayerFE(); if (layerFE.getCompositionState()->forceClientComposition) { return false; } return layerFE.hasRoundedCorners(); } bool CachedSet::hasBlurBehind() const { return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { return layer.getState()->hasBlurBehind(); }); } namespace { bool contains(const Rect& outer, const Rect& inner) { return outer.left <= inner.left && outer.right >= inner.right && outer.top <= inner.top && outer.bottom >= inner.bottom; } }; // namespace void CachedSet::addHolePunchLayerIfFeasible(const CachedSet& holePunchLayer, bool isFirstLayer) { // Verify that this CachedSet is opaque where the hole punch layer // will draw. const Rect& holePunchBounds = holePunchLayer.getBounds(); for (const auto& layer : mLayers) { // The first layer is considered opaque because nothing is behind it. // Note that isOpaque is always false for a layer with rounded // corners, even if the interior is opaque. In theory, such a layer // could be used for a hole punch, but this is unlikely to happen in // practice. const auto* outputLayer = layer.getState()->getOutputLayer(); if (contains(outputLayer->getState().displayFrame, holePunchBounds) && (isFirstLayer || outputLayer->getLayerFE().getCompositionState()->isOpaque)) { mHolePunchLayer = holePunchLayer.getFirstLayer().getState(); return; } } } void CachedSet::addBackgroundBlurLayer(const CachedSet& blurLayer) { mBlurLayer = blurLayer.getFirstLayer().getState(); } compositionengine::OutputLayer* CachedSet::getHolePunchLayer() const { return mHolePunchLayer ? mHolePunchLayer->getOutputLayer() : nullptr; } compositionengine::OutputLayer* CachedSet::getBlurLayer() const { return mBlurLayer ? mBlurLayer->getOutputLayer() : nullptr; } bool CachedSet::hasUnsupportedDataspace() const { return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { auto dataspace = layer.getState()->getDataspace(); const auto transfer = static_cast(dataspace & ui::Dataspace::TRANSFER_MASK); if (transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG) { // Skip HDR. return true; } if ((dataspace & HAL_DATASPACE_STANDARD_MASK) == HAL_DATASPACE_STANDARD_BT601_625) { // RenderEngine does not match some DPUs, so skip // to avoid flickering/color differences. return true; } return false; }); } bool CachedSet::hasProtectedLayers() const { return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { return layer.getState()->isProtected(); }); } void CachedSet::dump(std::string& result) const { const auto now = std::chrono::steady_clock::now(); const auto lastUpdate = std::chrono::duration_cast(now - mLastUpdate); base::StringAppendF(&result, " + Fingerprint %016zx, last update %sago, age %zd\n", mFingerprint, durationString(lastUpdate).c_str(), mAge); { const auto b = mTexture ? mTexture->get()->getBuffer().get() : nullptr; base::StringAppendF(&result, " Override buffer: %p\n", b); } base::StringAppendF(&result, " HolePunchLayer: %p\n", mHolePunchLayer); if (mLayers.size() == 1) { base::StringAppendF(&result, " Layer [%s]\n", mLayers[0].getName().c_str()); base::StringAppendF(&result, " Buffer %p", mLayers[0].getBuffer().get()); base::StringAppendF(&result, " Protected [%s]", mLayers[0].getState()->isProtected() ? "true" : "false"); } else { result.append(" Cached set of:"); for (const Layer& layer : mLayers) { base::StringAppendF(&result, "\n Layer [%s]", layer.getName().c_str()); base::StringAppendF(&result, "\n Protected [%s]", layer.getState()->isProtected() ? "true" : "false"); } } base::StringAppendF(&result, "\n Creation cost: %zd", getCreationCost()); base::StringAppendF(&result, "\n Display cost: %zd\n", getDisplayCost()); } } // namespace android::compositionengine::impl::planner