/* * 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 #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, bool deviceHandlesColorTransform) { ATRACE_CALL(); if (outputState.powerCallback) { outputState.powerCallback->notifyCpuLoadUp(); } const Rect& viewport = outputState.layerStackSpace.getContent(); const ui::Dataspace& outputDataspace = outputState.dataspace; const ui::Transform::RotationFlags orientation = ui::Transform::toRotationFlags(outputState.framebufferSpace.getOrientation()); renderengine::DisplaySettings displaySettings{ .physicalDisplay = outputState.framebufferSpace.getContent(), .clip = viewport, .outputDataspace = outputDataspace, .colorTransform = outputState.colorTransformMatrix, .deviceHandlesColorTransform = deviceHandlesColorTransform, .orientation = orientation, .targetLuminanceNits = outputState.displayBrightnessNits, }; LayerFE::ClientCompositionTargetSettings targetSettings{.clip = Region(viewport), .needsFiltering = false, .isSecure = outputState.isSecure, .isProtected = false, .viewport = viewport, .dataspace = outputDataspace, .realContentIsVisible = true, .clearContent = false, .blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled, .whitePointNits = outputState.displayBrightnessNits, .treat170mAsSrgb = outputState.treat170mAsSrgb}; std::vector layerSettings; renderengine::LayerSettings highlight; for (const auto& layer : mLayers) { if (auto clientCompositionSettings = layer.getState()->getOutputLayer()->getLayerFE().prepareClientComposition( targetSettings)) { layerSettings.push_back(std::move(*clientCompositionSettings)); } } renderengine::LayerSettings blurLayerSettings; if (mBlurLayer) { auto blurSettings = targetSettings; blurSettings.blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly; auto blurLayerSettings = mBlurLayer->getOutputLayer()->getLayerFE().prepareClientComposition(blurSettings); // This mimics Layer::prepareClearClientComposition blurLayerSettings->skipContentDraw = true; blurLayerSettings->name = std::string("blur layer"); // Clear out the shadow settings blurLayerSettings->shadow = {}; layerSettings.push_back(std::move(*blurLayerSettings)); } if (mHolePunchLayer) { auto& layerFE = mHolePunchLayer->getOutputLayer()->getLayerFE(); auto holePunchSettings = layerFE.prepareClientComposition(targetSettings); // 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 = android::base::StringPrintf("hole punch layer for %s", layerFE.getDebugName()); // Add a solid background as the first layer in case there is no opaque // buffer behind the punch hole renderengine::LayerSettings holePunchBackgroundSettings; holePunchBackgroundSettings.alpha = 1.0f; holePunchBackgroundSettings.name = std::string("holePunchBackground"); holePunchBackgroundSettings.geometry.boundaries = holePunchSettings->geometry.boundaries; holePunchBackgroundSettings.geometry.positionTransform = holePunchSettings->geometry.positionTransform; layerSettings.emplace(layerSettings.begin(), std::move(holePunchBackgroundSettings)); layerSettings.push_back(std::move(*holePunchSettings)); } 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), }; layerSettings.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()); } auto fenceResult = renderEngine .drawLayers(displaySettings, layerSettings, texture->get(), std::move(bufferFence)) .get(); if (fenceStatus(fenceResult) == NO_ERROR) { mDrawFence = std::move(fenceResult).value_or(Fence::NO_FENCE); mOutputSpace = outputState.framebufferSpace; mTexture = texture; mTexture->setReadyFence(mDrawFence); mOutputSpace.setOrientation(outputState.framebufferSpace.getOrientation()); 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 (hasKnownColorShift()) { return false; } const auto& layerFE = mLayers[0].getState()->getOutputLayer()->getLayerFE(); const auto* compositionState = layerFE.getCompositionState(); if (compositionState->forceClientComposition) { return false; } if (compositionState->blendMode != hal::BlendMode::NONE) { 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::hasKnownColorShift() const { return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { auto dataspace = layer.getState()->getDataspace(); // Layers are never dimmed when rendering a cached set, meaning that we may ask HWC to // dim a cached set. But this means that we can never cache any HDR layers so that we // don't accidentally dim those layers. const auto hdrType = getHdrRenderType(dataspace, layer.getState()->getPixelFormat(), layer.getState()->getHdrSdrRatio()); if (hdrType != HdrRenderType::SDR) { return true; } // Layers that have dimming disabled pretend that they're HDR. if (!layer.getState()->isDimmingEnabled()) { 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(); }); } bool CachedSet::cachingHintExcludesLayers() const { const bool shouldExcludeLayers = std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) { return layer.getState()->getCachingHint() == gui::CachingHint::Disabled; }); LOG_ALWAYS_FATAL_IF(shouldExcludeLayers && getLayerCount() > 1, "CachedSet is invalid: should be excluded but contains %zu layers", getLayerCount()); return shouldExcludeLayers; } 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\t%s\n", mHolePunchLayer, mHolePunchLayer ? mHolePunchLayer->getOutputLayer()->getLayerFE().getDebugName() : ""); if (mLayers.size() == 1) { base::StringAppendF(&result, " Layer [%s]\n", mLayers[0].getName().c_str()); if (const sp buffer = mLayers[0].getState()->getBuffer().promote()) { base::StringAppendF(&result, " Buffer %p", buffer.get()); base::StringAppendF(&result, " Format %s", decodePixelFormat(buffer->getPixelFormat()).c_str()); } base::StringAppendF(&result, " Protected [%s]\n", mLayers[0].getState()->isProtected() ? "true" : "false"); } else { result.append(" Cached set of:\n"); for (const Layer& layer : mLayers) { base::StringAppendF(&result, " Layer [%s]\n", layer.getName().c_str()); if (const sp buffer = layer.getState()->getBuffer().promote()) { base::StringAppendF(&result, " Buffer %p", buffer.get()); base::StringAppendF(&result, " Format[%s]", decodePixelFormat(buffer->getPixelFormat()).c_str()); } base::StringAppendF(&result, " Protected [%s]\n", layer.getState()->isProtected() ? "true" : "false"); } } base::StringAppendF(&result, " Creation cost: %zd\n", getCreationCost()); base::StringAppendF(&result, " Display cost: %zd\n", getDisplayCost()); } } // namespace android::compositionengine::impl::planner