diff options
author | 2025-03-24 11:12:01 -0700 | |
---|---|---|
committer | 2025-03-24 11:12:01 -0700 | |
commit | 631b37078a3d09e2ee5f574fc27543e86991d079 (patch) | |
tree | 96a44c8b90d71db13ea0e753ba713d260949759c | |
parent | e511fd4008548784561ef38304885293b50a8141 (diff) | |
parent | 65fb1c6daa8234ddc9f2d06972efcb40a425522e (diff) |
Merge "Add border API to surface control" into main
31 files changed, 441 insertions, 13 deletions
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 158c54886d..2117c987e4 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -93,6 +93,7 @@ filegroup { "android/gui/StalledTransactionInfo.aidl", "android/**/TouchOcclusionMode.aidl", "android/gui/TrustedOverlay.aidl", + "android/gui/BorderSettings.aidl", ], } diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index ad95d1a85a..86bc97e9d3 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -180,6 +180,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.writeParcelableVector, listener.callbackIds); } SAFE_PARCEL(output.writeFloat, shadowRadius); + SAFE_PARCEL(output.writeParcelable, borderSettings); SAFE_PARCEL(output.writeInt32, frameRateSelectionPriority); SAFE_PARCEL(output.writeFloat, frameRate); SAFE_PARCEL(output.writeByte, frameRateCompatibility); @@ -328,6 +329,8 @@ status_t layer_state_t::read(const Parcel& input) listeners.emplace_back(listener, callbackIds); } SAFE_PARCEL(input.readFloat, &shadowRadius); + SAFE_PARCEL(input.readParcelable, &borderSettings); + SAFE_PARCEL(input.readInt32, &frameRateSelectionPriority); SAFE_PARCEL(input.readFloat, &frameRate); SAFE_PARCEL(input.readByte, &frameRateCompatibility); @@ -727,6 +730,10 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eShadowRadiusChanged; shadowRadius = other.shadowRadius; } + if (other.what & eBorderSettingsChanged) { + what |= eBorderSettingsChanged; + borderSettings = other.borderSettings; + } if (other.what & eLutsChanged) { what |= eLutsChanged; luts = other.luts; @@ -881,6 +888,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace); if (other.what & eMetadataChanged) diff |= eMetadataChanged; CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius); + CHECK_DIFF(diff, eBorderSettingsChanged, other, borderSettings); CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility); CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority); CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility, diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 69ba1d731d..786bc06f64 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2025,6 +2025,19 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setShado return *this; } +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBorderSettings( + const sp<SurfaceControl>& sc, gui::BorderSettings settings) { + layer_state_t* s = getLayerState(sc); + if (!s) { + mStatus = BAD_INDEX; + return *this; + } + + s->what |= layer_state_t::eBorderSettingsChanged; + s->borderSettings = settings; + return *this; +} + SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRate( const sp<SurfaceControl>& sc, float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy) { diff --git a/libs/gui/android/gui/BorderSettings.aidl b/libs/gui/android/gui/BorderSettings.aidl new file mode 100644 index 0000000000..547f57fe76 --- /dev/null +++ b/libs/gui/android/gui/BorderSettings.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2025, 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. + */ + +package android.gui; + +/** @hide */ +parcelable BorderSettings { + float strokeWidth; + // Space is sRGB, not premultiplied, bit pattern is 0xAARRGGBB. + int color; +} diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 369d3d136a..e2d27ac464 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -21,6 +21,7 @@ #include <sys/types.h> #include <span> +#include <android/gui/BorderSettings.h> #include <android/gui/DisplayCaptureArgs.h> #include <android/gui/IWindowInfosReportedListener.h> #include <android/gui/LayerCaptureArgs.h> @@ -250,6 +251,7 @@ struct layer_state_t { ePictureProfileHandleChanged = 0x80000'00000000, eAppContentPriorityChanged = 0x100000'00000000, eClientDrawnCornerRadiusChanged = 0x200000'00000000, + eBorderSettingsChanged = 0x400000'00000000, }; layer_state_t(); @@ -293,8 +295,8 @@ struct layer_state_t { layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged | layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged | - layer_state_t::eStretchChanged | - layer_state_t::ePictureProfileHandleChanged | layer_state_t::eAppContentPriorityChanged; + layer_state_t::eStretchChanged | layer_state_t::ePictureProfileHandleChanged | + layer_state_t::eAppContentPriorityChanged | layer_state_t::eBorderSettingsChanged; // Changes which invalidates the layer's visible region in CE. static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES | @@ -322,7 +324,8 @@ struct layer_state_t { // Changes that force GPU composition. static constexpr uint64_t COMPOSITION_EFFECTS = layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged | layer_state_t::eCornerRadiusChanged | - layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged; + layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged | + layer_state_t::eBorderSettingsChanged; bool hasValidBuffer() const; void sanitize(int32_t permissions); @@ -411,6 +414,9 @@ struct layer_state_t { // Draws a shadow around the surface. float shadowRadius; + // Draws an outline around the layer. + gui::BorderSettings borderSettings; + // Priority of the layer assigned by Window Manager. int32_t frameRateSelectionPriority; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 668bd6fbb8..5c348cb9c7 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -679,6 +679,8 @@ public: const Rect& source, const Rect& dst, int transform); Transaction& setShadowRadius(const sp<SurfaceControl>& sc, float cornerRadius); + Transaction& setBorderSettings(const sp<SurfaceControl>& sc, gui::BorderSettings settings); + Transaction& setFrameRate(const sp<SurfaceControl>& sc, float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy); diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index f9b84fa948..39182aa389 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -52,6 +52,7 @@ cc_defaults { "libtonemap", "libsurfaceflinger_common", "libsurfaceflingerflags", + "libgui_window_info_static", ], local_include_dirs: ["include"], export_include_dirs: ["include"], @@ -122,7 +123,13 @@ cc_defaults { "skia_renderengine_deps", "libsurfaceflinger_common_deps", ], - static_libs: ["libskia_renderengine"], + static_libs: [ + "libgui_window_info_static", + "libskia_renderengine", + ], + shared_libs: [ + "libbinder", + ], } // Note: if compilation fails when adding librenderengine as a dependency, try adding diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h index ecb16b2e17..3523497519 100644 --- a/libs/renderengine/include/renderengine/LayerSettings.h +++ b/libs/renderengine/include/renderengine/LayerSettings.h @@ -16,6 +16,7 @@ #pragma once +#include <android/gui/BorderSettings.h> #include <gui/DisplayLuts.h> #include <math/mat4.h> #include <math/vec3.h> @@ -71,6 +72,10 @@ struct Geometry { // Boundaries of the layer. FloatRect boundaries = FloatRect(); + // Boundaries of the layer before transparent region hint is subtracted. + // Effects like shadows and outline ignore the transparent region hint. + FloatRect originalBounds = FloatRect(); + // Transform matrix to apply to mesh coordinates. mat4 positionTransform = mat4(); @@ -127,6 +132,8 @@ struct LayerSettings { ShadowSettings shadow; + gui::BorderSettings borderSettings; + int backgroundBlurRadius = 0; std::vector<BlurRegion> blurRegions; diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 9e1c226371..5b6edb4e30 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -986,6 +986,30 @@ void SkiaRenderEngine::drawLayersInternal( drawShadow(canvas, rrect, layer.shadow); } + // Similar to shadows, do the rendering before the clip is applied because even when the + // layer is occluded it should have an outline. + if (layer.borderSettings.strokeWidth > 0) { + // TODO(b/367464660): Move this code to the parent scope and + // update shadow rendering above to use these bounds since they should be + // identical. + SkRRect originalBounds, originalClip; + std::tie(originalBounds, originalClip) = + getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop, + layer.geometry.roundedCornersRadius); + const SkRRect& preferredOriginalBounds = + originalBounds.isRect() && !originalClip.isEmpty() ? originalClip + : originalBounds; + + SkRRect outlineRect = preferredOriginalBounds; + outlineRect.outset(layer.borderSettings.strokeWidth, layer.borderSettings.strokeWidth); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(layer.borderSettings.color); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawDRRect(outlineRect, preferredOriginalBounds, paint); + } + const float layerDimmingRatio = layer.whitePointNits <= 0.f ? displayDimmingRatio : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h index fb8fed0743..34b0bb5653 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h @@ -18,6 +18,7 @@ #include <cstdint> +#include <android/gui/BorderSettings.h> #include <android/gui/CachingHint.h> #include <gui/DisplayLuts.h> #include <gui/HdrMetadata.h> @@ -141,6 +142,9 @@ struct LayerFECompositionState { ShadowSettings shadowSettings; + // The settings to configure the outline of a layer. + gui::BorderSettings borderSettings; + // List of regions that require blur std::vector<BlurRegion> blurRegions; diff --git a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp index 348111d06e..294b167d4a 100644 --- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp @@ -70,6 +70,9 @@ void LayerFECompositionState::dump(std::string& out) const { out.append(" "); dumpVal(out, "shadowLength", shadowSettings.length); + out.append(" "); + dumpVal(out, "borderSettings", borderSettings.toString()); + out.append("\n "); dumpVal(out, "blend", toString(blendMode), blendMode); dumpVal(out, "alpha", alpha); diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp index ea360115c1..e4793a4cf2 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp @@ -237,6 +237,16 @@ Rect OutputLayer::calculateOutputDisplayFrame() const { geomLayerBounds.bottom += outset; } + // Similar to above + if (layerState.forceClientComposition && layerState.borderSettings.strokeWidth > 0.0f) { + // Antialiasing should never add more than 2 pixels. + const auto outset = layerState.borderSettings.strokeWidth + 2; + geomLayerBounds.left -= outset; + geomLayerBounds.top -= outset; + geomLayerBounds.right += outset; + geomLayerBounds.bottom += outset; + } + geomLayerBounds = layerTransform.transform(geomLayerBounds); FloatRect frame = reduce(geomLayerBounds, activeTransparentRegion); frame = frame.intersect(outputState.layerStackSpace.getContent().toFloatRect()); diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp index ca262ee16a..2f531f11fe 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp @@ -355,6 +355,26 @@ TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame_onlyIfForcingClien EXPECT_THAT(calculateOutputDisplayFrame(), expected); } +TEST_F(OutputLayerDisplayFrameTest, outlineExpandsDisplayFrame) { + const int kStrokeWidth = 3; + mLayerFEState.borderSettings.strokeWidth = kStrokeWidth; + mLayerFEState.forceClientComposition = true; + + mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f}; + Rect expected{mLayerFEState.geomLayerBounds}; + expected.inset(-kStrokeWidth - 2, -kStrokeWidth - 2, -kStrokeWidth - 2, -kStrokeWidth - 2); + EXPECT_THAT(calculateOutputDisplayFrame(), expected); +} +TEST_F(OutputLayerDisplayFrameTest, outlineExpandsDisplayFrame_onlyIfForcingClientComposition) { + const int kStrokeWidth = 3; + mLayerFEState.borderSettings.strokeWidth = kStrokeWidth; + mLayerFEState.forceClientComposition = false; + + mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f}; + Rect expected{mLayerFEState.geomLayerBounds}; + EXPECT_THAT(calculateOutputDisplayFrame(), expected); +} + /* * OutputLayer::calculateOutputRelativeBufferTransform() */ diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp index 964a9703b3..3aa2e98e8f 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp @@ -179,8 +179,12 @@ bool LayerSnapshot::hasBlur() const { return backgroundBlurRadius > 0 || blurRegions.size() > 0; } +bool LayerSnapshot::hasOutline() const { + return borderSettings.strokeWidth > 0; +} + bool LayerSnapshot::hasEffect() const { - return fillsColor() || drawShadows() || hasBlur(); + return fillsColor() || drawShadows() || hasBlur() || hasOutline(); } bool LayerSnapshot::hasSomethingToDraw() const { @@ -253,6 +257,7 @@ std::string LayerSnapshot::getIsVisibleReason() const { reason << " buffer=" << externalTexture->getId() << " frame=" << frameNumber; if (fillsColor() || color.a > 0.0f) reason << " color{" << color << "}"; if (drawShadows()) reason << " shadowSettings.length=" << shadowSettings.length; + if (hasOutline()) reason << "borderSettings=" << borderSettings.toString(); if (backgroundBlurRadius > 0) reason << " backgroundBlurRadius=" << backgroundBlurRadius; if (blurRegions.size() > 0) reason << " blurRegions.size()=" << blurRegions.size(); if (contentDirty) reason << " contentDirty"; @@ -410,7 +415,9 @@ void LayerSnapshot::merge(const RequestedLayerState& requested, bool forceUpdate if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) { shadowSettings.length = requested.shadowRadius; } - + if (forceUpdate || requested.what & layer_state_t::eBorderSettingsChanged) { + borderSettings = requested.borderSettings; + } if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) { frameRateSelectionPriority = requested.frameRateSelectionPriority; } @@ -508,9 +515,9 @@ void LayerSnapshot::merge(const RequestedLayerState& requested, bool forceUpdate (layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged | layer_state_t::eApiChanged | layer_state_t::eShadowRadiusChanged | layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged | - layer_state_t::eEdgeExtensionChanged)) { + layer_state_t::eEdgeExtensionChanged | layer_state_t::eBorderSettingsChanged)) { forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect() || - edgeExtensionEffect.hasEffect(); + edgeExtensionEffect.hasEffect() || borderSettings.strokeWidth > 0; } if (forceUpdate || diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index 69120bdcff..eca9718c0c 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -149,6 +149,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { bool hasBlur() const; bool hasBufferOrSidebandStream() const; bool hasEffect() const; + bool hasOutline() const; bool hasSomethingToDraw() const; bool isContentOpaque() const; bool isHiddenByPolicy() const; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 50ed72de9e..e3526d9de0 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -936,6 +936,18 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } if (forceUpdate || + snapshot.clientChanges & + (layer_state_t::eBorderSettingsChanged | layer_state_t::eAlphaChanged)) { + snapshot.borderSettings = requested.borderSettings; + + // Multiply outline alpha by snapshot alpha. + uint32_t c = static_cast<uint32_t>(snapshot.borderSettings.color); + float alpha = snapshot.alpha * (c >> 24) / 255.0f; + uint32_t a = static_cast<uint32_t>(alpha * 255 + 0.5f); + snapshot.borderSettings.color = static_cast<int32_t>((c & ~0xff000000) | (a << 24)); + } + + if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Geometry | RequestedLayerState::Changes::Input)) { updateInput(snapshot, requested, parentSnapshot, path, args); @@ -943,7 +955,9 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a // computed snapshot properties snapshot.forceClientComposition = snapshot.shadowSettings.length > 0 || - snapshot.stretchEffect.hasEffect() || snapshot.edgeExtensionEffect.hasEffect(); + snapshot.stretchEffect.hasEffect() || snapshot.edgeExtensionEffect.hasEffect() || + snapshot.borderSettings.strokeWidth > 0; + snapshot.contentOpaque = snapshot.isContentOpaque(); snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() && snapshot.color.a == 1.f; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index 5e076bdae4..3cd432cd2b 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -113,6 +113,8 @@ std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientC // set the shadow for the layer if needed prepareShadowClientComposition(*layerSettings, targetSettings.viewport); + layerSettings->borderSettings = mSnapshot->borderSettings; + return layerSettings; } @@ -120,6 +122,7 @@ std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientC compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const { SFTRACE_CALL(); compositionengine::LayerFE::LayerSettings layerSettings; + layerSettings.geometry.originalBounds = mSnapshot->geomLayerBounds; layerSettings.geometry.boundaries = reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint); layerSettings.geometry.positionTransform = mSnapshot->geomLayerTransform.asMatrix4(); @@ -205,7 +208,7 @@ void LayerFE::prepareEffectsClientComposition( if (targetSettings.realContentIsVisible && fillsColor()) { // Set color for color fill settings. layerSettings.source.solidColor = mSnapshot->color.rgb; - } else if (hasBlur() || drawShadows()) { + } else if (hasBlur() || drawShadows() || hasOutline()) { layerSettings.skipContentDraw = true; } } @@ -392,6 +395,10 @@ bool LayerFE::hasBlur() const { return mSnapshot->backgroundBlurRadius > 0 || mSnapshot->blurRegions.size() > 0; } +bool LayerFE::hasOutline() const { + return mSnapshot->borderSettings.strokeWidth > 0; +} + bool LayerFE::drawShadows() const { return mSnapshot->shadowSettings.length > 0.f && (mSnapshot->shadowSettings.ambientColor.a > 0 || diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index b89b6b4b92..b897a90687 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -83,12 +83,13 @@ private: compositionengine::LayerFE::LayerSettings&, compositionengine::LayerFE::ClientCompositionTargetSettings&) const; - bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); } + bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur() || hasOutline(); } bool hasBufferOrSidebandStream() const; bool fillsColor() const; bool hasBlur() const; bool drawShadows() const; + bool hasOutline() const; const sp<GraphicBuffer> getBuffer() const; diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp index 13f65770cd..c68513e0f0 100644 --- a/services/surfaceflinger/common/Android.bp +++ b/services/surfaceflinger/common/Android.bp @@ -22,6 +22,7 @@ cc_defaults { ], static_libs: [ "librenderengine_includes", + "libgui_window_info_static", ], srcs: [ "FlagManager.cpp", diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index b5f7a74347..37f3aa7e98 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -63,7 +63,10 @@ cc_test { "VirtualDisplay_test.cpp", "WindowInfosListener_test.cpp", ], - data: ["SurfaceFlinger_test.filter"], + data: [ + "SurfaceFlinger_test.filter", + "testdata/*", + ], static_libs: [ "android.hardware.graphics.composer@2.1", "libsurfaceflinger_common", @@ -76,6 +79,7 @@ cc_test { "libcutils", "libEGL", "libGLESv2", + "libjnigraphics", "libgui", "liblog", "libnativewindow", @@ -83,6 +87,7 @@ cc_test { "libui", "libutils", "server_configurable_flags", + "libc++", ], header_libs: [ "libnativewindow_headers", diff --git a/services/surfaceflinger/tests/AndroidTest.xml b/services/surfaceflinger/tests/AndroidTest.xml index ad43cdcc5d..b199ddb5f6 100644 --- a/services/surfaceflinger/tests/AndroidTest.xml +++ b/services/surfaceflinger/tests/AndroidTest.xml @@ -14,6 +14,11 @@ limitations under the License. --> <configuration description="Config for SurfaceFlinger_test"> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="throw-if-cmd-fail" value="true" /> + <option name="run-command" value="mkdir -p /data/local/tmp/SurfaceFlinger_test_screenshots" /> + <option name="teardown-command" value="rm -fr /data/local/tmp/SurfaceFlinger_test_screenshots"/> + </target_preparer> <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> <option name="push" value="SurfaceFlinger_test->/data/local/tmp/SurfaceFlinger_test" /> @@ -27,4 +32,8 @@ <option name="native-test-device-path" value="/data/local/tmp" /> <option name="module-name" value="SurfaceFlinger_test" /> </test> -</configuration> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name = "pull-pattern-keys" value = ".*png" /> + <option name = "directory-keys" value = "/data/local/tmp/SurfaceFlinger_test_screenshots" /> + </metrics_collector> +</configuration>
\ No newline at end of file diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp index 151611cad2..ada9862f23 100644 --- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp @@ -662,6 +662,93 @@ TEST_P(LayerTypeAndRenderTypeTransactionTest, ParentCornerRadiusPrecedenceClient } } +TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBorderSettings) { + sp<SurfaceControl> parent; + sp<SurfaceControl> child; + const uint32_t size = 64; + const uint32_t parentSize = size * 3; + ASSERT_NO_FATAL_FAILURE(parent = createLayer("parent", parentSize, parentSize)); + ASSERT_NO_FATAL_FAILURE(fillLayerColor(parent, Color::RED, parentSize, parentSize)); + ASSERT_NO_FATAL_FAILURE(child = createLayer("child", size, size)); + ASSERT_NO_FATAL_FAILURE(fillLayerColor(child, Color::GREEN, size, size)); + + gui::BorderSettings outline; + outline.strokeWidth = 3; + outline.color = 0xff0000ff; + Transaction() + .setCrop(parent, Rect(0, 0, parentSize, parentSize)) + .reparent(child, parent) + .setPosition(child, size, size) + .setCornerRadius(child, 20.0f) + .setBorderSettings(child, outline) + .apply(true); + + { + auto shot = getScreenCapture(); + + shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize), + "testdata/SetBorderSettings_Opaque.png"); + } + + { + Transaction().setAlpha(child, 0.5f).apply(true); + auto shot = getScreenCapture(); + + shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize), + "testdata/SetBorderSettings_HalfAlpha.png"); + } + + { + Transaction().setAlpha(child, 0.0f).apply(true); + + auto shot = getScreenCapture(); + + shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize), + "testdata/SetBorderSettings_ZeroAlpha.png"); + } + + { + Transaction() + .setAlpha(child, 1.0f) + .setCrop(parent, Rect(0, 0, parentSize / 2, parentSize)) + .apply(true); + + auto shot = getScreenCapture(); + + shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize), + "testdata/SetBorderSettings_Cropped.png"); + } + + { + outline.color = 0xff0000ff; + outline.strokeWidth = 1; + Transaction() + .setCrop(parent, Rect(0, 0, parentSize, parentSize)) + .setBorderSettings(child, outline) + .apply(true); + + auto shot = getScreenCapture(); + + shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize), + "testdata/SetBorderSettings_StrokeWidth1.png"); + } + + { + outline.color = 0x440000ff; + outline.strokeWidth = 3; + Transaction() + .setCrop(parent, Rect(0, 0, parentSize, parentSize)) + .setBorderSettings(child, outline) + .apply(true); + + auto shot = getScreenCapture(); + + shot->expectBufferMatchesImageFromFile(Rect(0, 0, parentSize, parentSize), + "testdata/" + "SetBorderSettings_StrokeColorWithAlpha.png"); + } +} + TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBackgroundBlurRadiusSimple) { if (!deviceSupportsBlurs()) GTEST_SKIP(); if (!deviceUsesSkiaRenderEngine()) GTEST_SKIP(); diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h index 82390ac6f3..1bee27be3f 100644 --- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h +++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h @@ -504,6 +504,17 @@ public: mLifecycleManager.applyTransactions(transactions); } + void setBorderSettings(uint32_t id, gui::BorderSettings settings) { + std::vector<QueuedTransactionState> transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + + transactions.back().states.front().state.what = layer_state_t::eBorderSettingsChanged; + transactions.back().states.front().layerId = id; + transactions.back().states.front().state.borderSettings = settings; + mLifecycleManager.applyTransactions(transactions); + } + void setTrustedOverlay(uint32_t id, gui::TrustedOverlay trustedOverlay) { std::vector<QueuedTransactionState> transactions; transactions.emplace_back(); diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.png Binary files differnew file mode 100644 index 0000000000..b52d517299 --- /dev/null +++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_Cropped.png diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.png Binary files differnew file mode 100644 index 0000000000..e1ab54b22a --- /dev/null +++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_HalfAlpha.png diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.png Binary files differnew file mode 100644 index 0000000000..bbaf0afa5c --- /dev/null +++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_Opaque.png diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.png Binary files differnew file mode 100644 index 0000000000..0fe2ed8a81 --- /dev/null +++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeColorWithAlpha.png diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.png Binary files differnew file mode 100644 index 0000000000..3ee5ac6c43 --- /dev/null +++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_StrokeWidth1.png diff --git a/services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.png b/services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.png Binary files differnew file mode 100644 index 0000000000..e5e8850518 --- /dev/null +++ b/services/surfaceflinger/tests/testdata/SetBorderSettings_ZeroAlpha.png diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 07356b9ec9..d045eb884d 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -1546,6 +1546,14 @@ TEST_F(LayerSnapshotTest, setShadowRadius) { EXPECT_EQ(getSnapshot(1)->shadowSettings.length, SHADOW_RADIUS); } +TEST_F(LayerSnapshotTest, setBorderSettings) { + gui::BorderSettings settings; + settings.strokeWidth = 5; + setBorderSettings(1, settings); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot(1)->borderSettings.strokeWidth, settings.strokeWidth); +} + TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) { hideLayer(1); setTrustedOverlay(1, gui::TrustedOverlay::ENABLED); diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h index 0bedcd174e..02c3ecd587 100644 --- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h +++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h @@ -15,15 +15,23 @@ */ #pragma once +#include <android-base/file.h> +#include <android/bitmap.h> +#include <android/data_space.h> +#include <android/imagedecoder.h> #include <gui/AidlUtil.h> #include <gui/SyncScreenCaptureListener.h> #include <private/gui/ComposerServiceAIDL.h> #include <ui/FenceResult.h> +#include <ui/PixelFormat.h> #include <ui/Rect.h> #include <utils/String8.h> #include <functional> #include "TransactionUtils.h" +#include <filesystem> +#include <fstream> + namespace android { using gui::aidl_utils::statusTFromBinderStatus; @@ -174,6 +182,146 @@ public: } } + static void writePng(const std::filesystem::path& path, const void* pixels, uint32_t width, + uint32_t height, uint32_t stride) { + AndroidBitmapInfo info{ + .width = width, + .height = height, + .stride = stride, + .format = ANDROID_BITMAP_FORMAT_RGBA_8888, + .flags = ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE, + }; + + std::ofstream file(path, std::ios::binary); + ASSERT_TRUE(file.is_open()); + + auto writeFunc = [](void* filePtr, const void* data, size_t size) -> bool { + auto file = reinterpret_cast<std::ofstream*>(filePtr); + file->write(reinterpret_cast<const char*>(data), size); + return file->good(); + }; + + int compressResult = AndroidBitmap_compress(&info, ADATASPACE_SRGB, pixels, + ANDROID_BITMAP_COMPRESS_FORMAT_PNG, + /*(ignored) quality=*/100, &file, writeFunc); + ASSERT_EQ(compressResult, ANDROID_BITMAP_RESULT_SUCCESS); + file.close(); + } + + static void readImage(const std::filesystem::path& filename, std::vector<uint8_t>& outBytes, + int& outWidth, int& outHeight) { + std::ifstream file(filename, std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()) << "Failed to open " << filename; + + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector<char> fileData(fileSize); + file.read(fileData.data(), fileSize); + file.close(); + + AImageDecoder* decoder = nullptr; + int createResult = AImageDecoder_createFromBuffer(fileData.data(), fileSize, &decoder); + + ASSERT_EQ(createResult, ANDROID_IMAGE_DECODER_SUCCESS); + + const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder); + outWidth = AImageDecoderHeaderInfo_getWidth(headerInfo); + outHeight = AImageDecoderHeaderInfo_getHeight(headerInfo); + int32_t format = AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo); + ASSERT_EQ(format, ANDROID_BITMAP_FORMAT_RGBA_8888); + + size_t stride = outWidth * 4; // Assuming RGBA format + size_t bufferSize = stride * outHeight; + + outBytes.resize(bufferSize); + int decodeResult = AImageDecoder_decodeImage(decoder, outBytes.data(), stride, bufferSize); + ASSERT_EQ(decodeResult, ANDROID_IMAGE_DECODER_SUCCESS); + AImageDecoder_delete(decoder); + } + + static void writeGraphicBufferToPng(const std::string& path, const sp<GraphicBuffer>& buffer) { + base::unique_fd fd{open(path.c_str(), O_WRONLY | O_CREAT, S_IWUSR)}; + ASSERT_GE(fd.get(), 0); + + void* pixels = nullptr; + int32_t stride = 0; + auto lockStatus = buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels, + nullptr /*outBytesPerPixel*/, &stride); + ASSERT_GE(lockStatus, 0); + + writePng(path, pixels, buffer->getWidth(), buffer->getHeight(), stride); + + auto unlockStatus = buffer->unlock(); + ASSERT_GE(unlockStatus, 0); + } + + // Tries to read an image from executable directory + // If the test fails, the screenshot is written to $TMPDIR + void expectBufferMatchesImageFromFile(const Rect& rect, + const std::filesystem::path& pathRelativeToExeDir) { + ASSERT_NE(nullptr, mOutBuffer); + ASSERT_EQ(HAL_PIXEL_FORMAT_RGBA_8888, mOutBuffer->getPixelFormat()); + + int bufferWidth = int32_t(mOutBuffer->getWidth()); + int bufferHeight = int32_t(mOutBuffer->getHeight()); + int bufferStride = mOutBuffer->getStride() * 4; + + std::vector<uint8_t> imagePixels; + int imageWidth; + int imageHeight; + readImage(android::base::GetExecutableDirectory() / pathRelativeToExeDir, imagePixels, + imageWidth, imageHeight); + int imageStride = 4 * imageWidth; + + ASSERT_TRUE(rect.isValid()); + + ASSERT_GE(rect.left, 0); + ASSERT_GE(rect.bottom, 0); + + ASSERT_LE(rect.right, bufferWidth); + ASSERT_LE(rect.bottom, bufferHeight); + + ASSERT_LE(rect.right, imageWidth); + ASSERT_LE(rect.bottom, imageHeight); + + int tolerance = 4; // arbitrary + for (int32_t y = rect.top; y < rect.bottom; y++) { + for (int32_t x = rect.left; x < rect.right; x++) { + const uint8_t* bufferPixel = mPixels + y * bufferStride + x * 4; + const uint8_t* imagePixel = + imagePixels.data() + (y - rect.top) * imageStride + (x - rect.left) * 4; + + int dr = bufferPixel[0] - imagePixel[0]; + int dg = bufferPixel[1] - imagePixel[1]; + int db = bufferPixel[2] - imagePixel[2]; + int da = bufferPixel[3] - imagePixel[3]; + int dist = std::abs(dr) + std::abs(dg) + std::abs(db) + std::abs(da); + + bool pixelMatches = dist < tolerance; + + if (!pixelMatches) { + std::filesystem::path outFilename = pathRelativeToExeDir.filename(); + outFilename.replace_extension(); + outFilename += "_actual.png"; + std::filesystem::path outPath = std::filesystem::temp_directory_path() / + "SurfaceFlinger_test_screenshots" / outFilename; + writeGraphicBufferToPng(outPath, mOutBuffer); + + ASSERT_TRUE(pixelMatches) + << String8::format("pixel @ (%3d, %3d): " + "expected [%3d, %3d, %3d, %3d], got [%3d, %3d, %3d, " + "%3d], " + "wrote screenshot to '%s'", + x, y, imagePixel[0], imagePixel[1], imagePixel[2], + imagePixel[3], bufferPixel[0], bufferPixel[1], + bufferPixel[2], bufferPixel[3], outPath.c_str()) + .c_str(); + return; + } + } + } + } + Color getPixelColor(uint32_t x, uint32_t y) { if (!mOutBuffer || mOutBuffer->getPixelFormat() != HAL_PIXEL_FORMAT_RGBA_8888) { return {0, 0, 0, 0}; |