Enable drawing layers' borders in SurfaceFlinger.
Provide an API on SurfaceComposerClient::Transaction for enabling
a border to be drawn for a specified hierarchy. The caller requests a
border to be drawn at a certain Layer and all its children will be
included when computing the visible region. The border will draw around
the union of all the layers' visible regions, starting from the
requested layer.
Test: go/wm-smoke
Test: LayerBorder_test
Bug: 225977175
Change-Id: I7760b51b68cdf01bb9146ec91a270a9d63927995
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index f201751..8039bba 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -32,6 +32,9 @@
using Layers = std::vector<sp<compositionengine::LayerFE>>;
using Outputs = std::vector<std::shared_ptr<compositionengine::Output>>;
+struct BorderRenderInfo {
+ std::vector<int32_t> layerIds;
+};
/**
* A parameter object for refreshing a set of outputs
*/
@@ -90,6 +93,8 @@
// If set, a frame has been scheduled for that time.
std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
+
+ std::vector<BorderRenderInfo> borderInfoList;
};
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 31c51e6..3ad6e52 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -152,6 +152,7 @@
private:
void dirtyEntireOutput();
+ void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&);
compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
void finishPrepareFrame();
ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index c65d467..cd56530 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -33,6 +33,7 @@
#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
#include <compositionengine/ProjectionSpace.h>
+#include <renderengine/BorderRenderInfo.h>
#include <ui/LayerStack.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -164,6 +165,7 @@
bool treat170mAsSrgb = false;
+ std::vector<renderengine::BorderRenderInfo> borderInfoList;
// Debugging
void dump(std::string& result) const;
};
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 4c30f99..430d673 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -733,6 +733,39 @@
forceClientComposition = false;
}
}
+
+ updateCompositionStateForBorder(refreshArgs);
+}
+
+void Output::updateCompositionStateForBorder(
+ const compositionengine::CompositionRefreshArgs& refreshArgs) {
+ std::unordered_map<int32_t, const Region*> layerVisibleRegionMap;
+ // Store a map of layerId to their computed visible region.
+ for (auto* layer : getOutputLayersOrderedByZ()) {
+ int layerId = (layer->getLayerFE()).getSequence();
+ layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion);
+ }
+ OutputCompositionState& outputCompositionState = editState();
+ outputCompositionState.borderInfoList.clear();
+ bool clientComposeTopLayer = false;
+ for (const auto& borderInfo : refreshArgs.borderInfoList) {
+ renderengine::BorderRenderInfo info;
+ for (const auto& id : borderInfo.layerIds) {
+ info.combinedRegion.orSelf(*(layerVisibleRegionMap[id]));
+ }
+ outputCompositionState.borderInfoList.emplace_back(std::move(info));
+ clientComposeTopLayer |= !info.combinedRegion.isEmpty();
+ }
+
+ // In this situation we must client compose the top layer instead of using hwc
+ // because we want to draw the border above all else.
+ // This could potentially cause a bit of a performance regression if the top
+ // layer would have been rendered using hwc originally.
+ // TODO(b/227656283): Measure system's performance before enabling the border feature
+ if (clientComposeTopLayer) {
+ auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1);
+ (topLayer->editState()).forceClientComposition = true;
+ }
}
void Output::planComposition() {
@@ -1183,6 +1216,11 @@
// Compute the global color transform matrix.
clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
+ for (auto& info : outputState.borderInfoList) {
+ renderengine::BorderRenderInfo borderInfo;
+ borderInfo.combinedRegion = info.combinedRegion;
+ clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
+ }
clientCompositionDisplay.deviceHandlesColorTransform =
outputState.usesDeviceComposition || getSkipColorTransform();
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 948c0c9..c512a1e 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -62,14 +62,18 @@
dumpVal(out, "sdrWhitePointNits", sdrWhitePointNits);
dumpVal(out, "clientTargetBrightness", clientTargetBrightness);
dumpVal(out, "displayBrightness", displayBrightness);
-
out.append("\n ");
dumpVal(out, "compositionStrategyPredictionState", ftl::enum_string(strategyPrediction));
+ out.append("\n ");
out.append("\n ");
dumpVal(out, "treate170mAsSrgb", treat170mAsSrgb);
- out += '\n';
+ out.append("\n");
+ for (const auto& borderRenderInfo : borderInfoList) {
+ dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion);
+ }
+ out.append("\n");
}
} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index af39717..d47e423 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -101,7 +101,8 @@
mClientRef(args.client),
mWindowType(static_cast<WindowInfo::Type>(
args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
- mLayerCreationFlags(args.flags) {
+ mLayerCreationFlags(args.flags),
+ mBorderEnabled(false) {
uint32_t layerFlags = 0;
if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden;
if (args.flags & ISurfaceComposerClient::eOpaque) layerFlags |= layer_state_t::eLayerOpaque;
@@ -1156,6 +1157,18 @@
return StretchEffect{};
}
+bool Layer::enableBorder(bool shouldEnable) {
+ if (mBorderEnabled == shouldEnable) {
+ return false;
+ }
+ mBorderEnabled = shouldEnable;
+ return true;
+}
+
+bool Layer::isBorderEnabled() {
+ return mBorderEnabled;
+}
+
bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded) {
// The frame rate for layer tree is this layer's frame rate if present, or the parent frame rate
const auto frameRate = [&] {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index ff18fd0..85187e1 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -895,6 +895,8 @@
bool setStretchEffect(const StretchEffect& effect);
StretchEffect getStretchEffect() const;
+ bool enableBorder(bool shouldEnable);
+ bool isBorderEnabled();
virtual bool setBufferCrop(const Rect& /* bufferCrop */) { return false; }
virtual bool setDestinationFrame(const Rect& /* destinationFrame */) { return false; }
@@ -1143,7 +1145,10 @@
bool mIsAtRoot = false;
uint32_t mLayerCreationFlags;
+
bool findInHierarchy(const sp<Layer>&);
+
+ bool mBorderEnabled = false;
};
std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 040014d..ec87dbe 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -156,6 +156,10 @@
#define NO_THREAD_SAFETY_ANALYSIS \
_Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
+// To enable layer borders in the system, change the below flag to true.
+#undef DOES_CONTAIN_BORDER
+#define DOES_CONTAIN_BORDER false
+
namespace android {
using namespace std::string_literals;
@@ -2169,6 +2173,20 @@
if (auto layerFE = layer->getCompositionEngineLayerFE())
refreshArgs.layers.push_back(layerFE);
});
+
+ if (DOES_CONTAIN_BORDER) {
+ refreshArgs.borderInfoList.clear();
+ mDrawingState.traverse([&refreshArgs](Layer* layer) {
+ if (layer->isBorderEnabled()) {
+ compositionengine::BorderRenderInfo info;
+ layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) {
+ info.layerIds.push_back(ilayer->getSequence());
+ });
+ refreshArgs.borderInfoList.emplace_back(std::move(info));
+ }
+ });
+ }
+
refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
for (auto layer : mLayersWithQueuedFrames) {
if (auto layerFE = layer->getCompositionEngineLayerFE())
@@ -4429,6 +4447,11 @@
if (what & layer_state_t::eBlurRegionsChanged) {
if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded;
}
+ if (what & layer_state_t::eRenderBorderChanged) {
+ if (layer->enableBorder(s.borderEnabled)) {
+ flags |= eTraversalNeeded;
+ }
+ }
if (what & layer_state_t::eLayerStackChanged) {
ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
// We only allow setting layer stacks for top level layers,
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index ceddf27..276431e 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -34,6 +34,7 @@
"DisplayConfigs_test.cpp",
"DisplayEventReceiver_test.cpp",
"EffectLayer_test.cpp",
+ "LayerBorder_test.cpp",
"InvalidHandles_test.cpp",
"LayerCallback_test.cpp",
"LayerRenderTypeTransaction_test.cpp",
diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp
new file mode 100644
index 0000000..4b38214
--- /dev/null
+++ b/services/surfaceflinger/tests/LayerBorder_test.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+// TODO: Amend all tests when screenshots become fully reworked for borders
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
+#include <chrono> // std::chrono::seconds
+#include <thread> // std::this_thread::sleep_for
+#include "LayerTransactionTest.h"
+
+namespace android {
+
+class LayerBorderTest : public LayerTransactionTest {
+protected:
+ virtual void SetUp() {
+ LayerTransactionTest::SetUp();
+ ASSERT_EQ(NO_ERROR, mClient->initCheck());
+
+ toHalf3 = ColorTransformHelper::toHalf3;
+ toHalf4 = ColorTransformHelper::toHalf4;
+
+ const auto display = SurfaceComposerClient::getInternalDisplayToken();
+ ASSERT_FALSE(display == nullptr);
+
+ mParentLayer = createColorLayer("Parent layer", Color::RED);
+
+ mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */,
+ 0 /* height */, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceContainer |
+ ISurfaceComposerClient::eNoColorFill,
+ mParentLayer->getHandle());
+ EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer";
+
+ mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
+ 0 /* height */, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceEffect |
+ ISurfaceComposerClient::eNoColorFill,
+ mContainerLayer->getHandle());
+ EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1";
+
+ mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
+ 0 /* height */, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceEffect |
+ ISurfaceComposerClient::eNoColorFill,
+ mContainerLayer->getHandle());
+
+ EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2";
+
+ asTransaction([&](Transaction& t) {
+ t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
+ t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer);
+ t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+
+ t.setColor(mEffectLayer1, toHalf3(Color::BLUE));
+
+ t.setColor(mEffectLayer2, toHalf3(Color::GREEN));
+ });
+ }
+
+ virtual void TearDown() {
+ // Uncomment the line right below when running any of the tests
+ // std::this_thread::sleep_for (std::chrono::seconds(30));
+ LayerTransactionTest::TearDown();
+ mParentLayer = 0;
+ }
+
+ std::function<half3(Color)> toHalf3;
+ std::function<half4(Color)> toHalf4;
+ sp<SurfaceControl> mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2;
+};
+
+TEST_F(LayerBorderTest, OverlappingVisibleRegions) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+
+ t.enableBorder(mContainerLayer, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+
+ t.enableBorder(mEffectLayer1, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
+ t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
+
+ t.enableBorder(mContainerLayer, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, EmptyVisibleRegion) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600));
+
+ t.enableBorder(mEffectLayer1, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, ZOrderAdjustment) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+ t.setLayer(mParentLayer, 10);
+ t.setLayer(mEffectLayer1, 30);
+ t.setLayer(mEffectLayer2, 20);
+
+ t.enableBorder(mEffectLayer1, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, GrandChildHierarchy) {
+ sp<SurfaceControl> containerLayer2 =
+ mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceContainer |
+ ISurfaceComposerClient::eNoColorFill,
+ mContainerLayer->getHandle());
+ EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2";
+
+ sp<SurfaceControl> effectLayer3 =
+ mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceEffect |
+ ISurfaceComposerClient::eNoColorFill,
+ containerLayer2->getHandle());
+
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+ t.setCrop(effectLayer3, Rect(400, 400, 800, 800));
+ t.setColor(effectLayer3, toHalf3(Color::BLUE));
+
+ t.enableBorder(mContainerLayer, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(effectLayer3);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, TransparentAlpha) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+ t.setAlpha(mEffectLayer1, 0.0f);
+
+ t.enableBorder(mEffectLayer1, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, SemiTransparentAlpha) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+ t.setAlpha(mEffectLayer2, 0.5f);
+
+ t.enableBorder(mEffectLayer2, true);
+ t.show(mEffectLayer1);
+ t.show(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, InvisibleLayers) {
+ asTransaction([&](Transaction& t) {
+ t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
+ t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
+
+ t.enableBorder(mContainerLayer, true);
+ t.hide(mEffectLayer2);
+ t.show(mContainerLayer);
+ });
+}
+
+TEST_F(LayerBorderTest, BufferStateLayer) {
+ asTransaction([&](Transaction& t) {
+ t.hide(mEffectLayer1);
+ t.hide(mEffectLayer2);
+ t.show(mContainerLayer);
+
+ sp<SurfaceControl> bufferStateLayer =
+ mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ mContainerLayer->getHandle());
+
+ sp<GraphicBuffer> buffer =
+ new GraphicBuffer(400, 400, PIXEL_FORMAT_RGBA_8888, 1,
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+ BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE,
+ "test");
+ TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN);
+ TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE);
+
+ t.setBuffer(bufferStateLayer, buffer);
+ t.setPosition(bufferStateLayer, 100, 100);
+ t.show(bufferStateLayer);
+ t.enableBorder(mContainerLayer, true);
+ });
+}
+
+} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 07916b6..38c422a 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -33,6 +33,10 @@
static const Color WHITE;
static const Color BLACK;
static const Color TRANSPARENT;
+
+ half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; }
+
+ half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; }
};
const Color Color::RED{255, 0, 0, 255};
@@ -81,6 +85,14 @@
}
color = ret;
}
+
+ static half3 toHalf3(const Color& color) {
+ return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f};
+ }
+
+ static half4 toHalf4(const Color& color) {
+ return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f};
+ }
};
} // namespace
} // namespace android