diff options
18 files changed, 376 insertions, 91 deletions
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp index 22dd86698b..6dc01b916e 100644 --- a/libs/renderengine/gl/GLESRenderEngine.cpp +++ b/libs/renderengine/gl/GLESRenderEngine.cpp @@ -921,7 +921,8 @@ void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display, // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners // and the middle part without rounded corners. - const int32_t radius = ceil(layer.geometry.roundedCornersRadius); + const int32_t radius = ceil( + (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / 2.0); const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius); setScissor(topRect); drawMesh(mesh); @@ -1266,23 +1267,24 @@ void GLESRenderEngine::drawLayersInternal( const half3 solidColor = layer.source.solidColor; const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha); + const float radius = + (layer.geometry.roundedCornersRadius.x + layer.geometry.roundedCornersRadius.y) / + 2.0f; // Buffer sources will have a black solid color ignored in the shader, // so in that scenario the solid color passed here is arbitrary. - setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, - layer.geometry.roundedCornersRadius); + setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color, radius); if (layer.disableBlending) { glDisable(GL_BLEND); } setSourceDataSpace(layer.sourceDataspace); if (layer.shadow.length > 0.0f) { - handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius, - layer.shadow); + handleShadow(layer.geometry.boundaries, radius, layer.shadow); } // We only want to do a special handling for rounded corners when having rounded corners // is the only reason it needs to turn on blending, otherwise, we handle it like the // usual way since it needs to turn on blending anyway. - else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) { + else if (radius > 0.0 && color.a >= 1.0f && isOpaque) { handleRoundedCorners(display, layer, mesh); } else { drawMesh(mesh); diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h index 154e5269f0..b3a617c04b 100644 --- a/libs/renderengine/include/renderengine/LayerSettings.h +++ b/libs/renderengine/include/renderengine/LayerSettings.h @@ -87,7 +87,7 @@ struct Geometry { // rectangle to figure out how to apply the radius for this layer. The crop rectangle will be // in local layer coordinate space, so we have to take the layer transform into account when // walking up the tree. - float roundedCornersRadius = 0.0; + vec2 roundedCornersRadius = vec2(0.0f, 0.0f); // Rectangle within which corners will be rounded. FloatRect roundedCornersCrop = FloatRect(); @@ -258,7 +258,8 @@ static inline void PrintTo(const Geometry& settings, ::std::ostream* os) { PrintTo(settings.boundaries, os); *os << "\n .positionTransform = "; PrintMatrix(settings.positionTransform, os); - *os << "\n .roundedCornersRadius = " << settings.roundedCornersRadius; + *os << "\n .roundedCornersRadiusX = " << settings.roundedCornersRadius.x; + *os << "\n .roundedCornersRadiusY = " << settings.roundedCornersRadius.y; *os << "\n .roundedCornersCrop = "; PrintTo(settings.roundedCornersCrop, os); *os << "\n}"; diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp index f3064f3c69..c39f0a97fd 100644 --- a/libs/renderengine/skia/Cache.cpp +++ b/libs/renderengine/skia/Cache.cpp @@ -66,7 +66,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin Geometry{ .boundaries = rect, .roundedCornersCrop = rect, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, }, // drawShadow ignores alpha .shadow = @@ -87,7 +87,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin Geometry{ .boundaries = smallerRect, .roundedCornersCrop = rect, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, }, .source = PixelSource{ @@ -148,7 +148,7 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting // In reduced shader mode, all non-zero round rect radii get the same code path. for (float roundedCornersRadius : {0.0f, 50.0f}) { // roundedCornersCrop is always set, but the radius triggers the behavior - layer.geometry.roundedCornersRadius = roundedCornersRadius; + layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius}; for (bool isOpaque : {true, false}) { layer.source.buffer.isOpaque = isOpaque; for (auto alpha : {half(.2f), half(1.0f)}) { @@ -181,7 +181,7 @@ static void drawSolidLayers(SkiaRenderEngine* renderengine, const DisplaySetting for (auto transform : {mat4(), kScaleAndTranslate}) { layer.geometry.positionTransform = transform; for (float roundedCornersRadius : {0.0f, 50.f}) { - layer.geometry.roundedCornersRadius = roundedCornersRadius; + layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius}; auto layers = std::vector<LayerSettings>{layer}; renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd()); @@ -238,7 +238,7 @@ static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySetti .geometry = Geometry{ .boundaries = rect, - .roundedCornersRadius = 27, // larger than the 20 above. + .roundedCornersRadius = {27.f, 27.f}, .roundedCornersCrop = FloatRect(0, 0, displayRect.width(), displayRect.height()), }, @@ -275,9 +275,9 @@ static void drawPIPImageLayer(SkiaRenderEngine* renderengine, const DisplaySetti // larger than the layer bounds. .positionTransform = kFlip, .boundaries = rect, - .roundedCornersRadius = 94.2551, - .roundedCornersCrop = FloatRect( - -93.75, 0, displayRect.width() + 93.75, displayRect.height()), + .roundedCornersRadius = {94.2551f, 94.2551f}, + .roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75, + displayRect.height()), }, .source = PixelSource{.buffer = Buffer{ @@ -307,10 +307,11 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett // the boundaries have to be smaller than the rounded crop so that // clipRRect is used instead of drawRRect .boundaries = small, - .roundedCornersRadius = 50.f, + .roundedCornersRadius = {50.f, 50.f}, .roundedCornersCrop = rect, }, - .source = PixelSource{ + .source = + PixelSource{ .solidColor = half3(0.f, 0.f, 0.f), }, .sourceDataspace = kDestDataSpace, diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index 97271cb32d..0caa9f2fbd 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -1276,7 +1276,7 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) { * produce the insected roundRect. If false, the returned state of the radii param is undefined. */ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, - const SkRect& insetCrop, float cornerRadius, + const SkRect& insetCrop, const vec2& cornerRadius, SkVector radii[4]) { const bool leftEqual = bounds.fLeft == crop.fLeft; const bool topEqual = bounds.fTop == crop.fTop; @@ -1288,8 +1288,8 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, // In particular the round rect implementation will scale the value of all corner radii // if the sum of the radius along any edge is greater than the length of that edge. // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap - const bool requiredWidth = bounds.width() > (cornerRadius * 2); - const bool requiredHeight = bounds.height() > (cornerRadius * 2); + const bool requiredWidth = bounds.width() > (cornerRadius.x * 2); + const bool requiredHeight = bounds.height() > (cornerRadius.y * 2); if (!requiredWidth || !requiredHeight) { return false; } @@ -1298,7 +1298,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, // contained within the cropped shape and does not need rounded. // compute the UpperLeft corner radius if (leftEqual && topEqual) { - radii[0].set(cornerRadius, cornerRadius); + radii[0].set(cornerRadius.x, cornerRadius.y); } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fLeft >= insetCrop.fLeft)) { radii[0].set(0, 0); @@ -1307,7 +1307,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the UpperRight corner radius if (rightEqual && topEqual) { - radii[1].set(cornerRadius, cornerRadius); + radii[1].set(cornerRadius.x, cornerRadius.y); } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) || (topEqual && bounds.fRight <= insetCrop.fRight)) { radii[1].set(0, 0); @@ -1316,7 +1316,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the BottomRight corner radius if (rightEqual && bottomEqual) { - radii[2].set(cornerRadius, cornerRadius); + radii[2].set(cornerRadius.x, cornerRadius.y); } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fRight <= insetCrop.fRight)) { radii[2].set(0, 0); @@ -1325,7 +1325,7 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, } // compute the BottomLeft corner radius if (leftEqual && bottomEqual) { - radii[3].set(cornerRadius, cornerRadius); + radii[3].set(cornerRadius.x, cornerRadius.y); } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) || (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) { radii[3].set(0, 0); @@ -1338,22 +1338,22 @@ static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop, inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect, const FloatRect& cropRect, - const float cornerRadius) { + const vec2& cornerRadius) { const SkRect bounds = getSkRect(boundsRect); const SkRect crop = getSkRect(cropRect); SkRRect clip; - if (cornerRadius > 0) { + if (cornerRadius.x > 0 && cornerRadius.y > 0) { // it the crop and the bounds are equivalent or there is no crop then we don't need a clip if (bounds == crop || crop.isEmpty()) { - return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip}; + return {SkRRect::MakeRectXY(bounds, cornerRadius.x, cornerRadius.y), clip}; } // This makes an effort to speed up common, simple bounds + clip combinations by // converting them to a single RRect draw. It is possible there are other cases // that can be converted. if (crop.contains(bounds)) { - const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius); + const auto insetCrop = crop.makeInset(cornerRadius.x, cornerRadius.y); if (insetCrop.contains(bounds)) { return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required } @@ -1367,7 +1367,7 @@ inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const Fl } // we didn't hit any of our fast paths so set the clip to the cropRect - clip.setRectXY(crop, cornerRadius, cornerRadius); + clip.setRectXY(crop, cornerRadius.x, cornerRadius.y); } // if we hit this point then we either don't have rounded corners or we are going to rely diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index 5ef9944b14..68c336327b 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -90,7 +90,8 @@ private: inline SkRect getSkRect(const FloatRect& layer); inline SkRect getSkRect(const Rect& layer); inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds, - const FloatRect& crop, float cornerRadius); + const FloatRect& crop, + const vec2& cornerRadius); inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha); inline SkColor getSkColor(const vec4& color); inline SkM44 getSkM44(const mat4& matrix); diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 7c70a748b5..8889f76ccf 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -444,7 +444,9 @@ public: const ubyte4& backgroundColor) { const Rect casterRect(castingLayer.geometry.boundaries); Region casterRegion = Region(casterRect); - const float casterCornerRadius = castingLayer.geometry.roundedCornersRadius; + const float casterCornerRadius = (castingLayer.geometry.roundedCornersRadius.x + + castingLayer.geometry.roundedCornersRadius.y) / + 2.0; if (casterCornerRadius > 0.0f) { // ignore the corners if a corner radius is set Rect cornerRect(casterCornerRadius, casterCornerRadius); @@ -1129,7 +1131,7 @@ void RenderEngineTest::fillRedBufferWithRoundedCorners() { renderengine::LayerSettings layer; layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; layer.geometry.boundaries = fullscreenRect().toFloatRect(); - layer.geometry.roundedCornersRadius = 5.0f; + layer.geometry.roundedCornersRadius = {5.0f, 5.0f}; layer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this); layer.alpha = 1.0f; @@ -2131,7 +2133,7 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) { casterBounds.offsetBy(shadowLength + 1, shadowLength + 1); renderengine::LayerSettings castingLayer; castingLayer.geometry.boundaries = casterBounds.toFloatRect(); - castingLayer.geometry.roundedCornersRadius = 3.0f; + castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f}; castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect(); castingLayer.alpha = 1.0f; renderengine::ShadowSettings settings = @@ -2219,7 +2221,8 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - redLayer.geometry.roundedCornersRadius = 5.0f; + redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; + redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2231,7 +2234,7 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) { renderengine::LayerSettings greenLayer; greenLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; greenLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - greenLayer.geometry.roundedCornersRadius = 5.0f; + greenLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; // Bottom right corner is not going to be rounded. greenLayer.geometry.roundedCornersCrop = Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3, DEFAULT_DISPLAY_HEIGHT, @@ -2268,7 +2271,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); - redLayer.geometry.roundedCornersRadius = 5.0f; + redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f}; redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2313,7 +2316,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { renderengine::LayerSettings redLayer; redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32); - redLayer.geometry.roundedCornersRadius = 64; + redLayer.geometry.roundedCornersRadius = {64.0f, 64.0f}; redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128); // Red background. redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); @@ -2334,6 +2337,49 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) { expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255); } +TEST_P(RenderEngineTest, testRoundedCornersXY) { + if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) { + GTEST_SKIP(); + } + + initializeRenderEngine(); + + renderengine::DisplaySettings settings; + settings.physicalDisplay = fullscreenRect(); + settings.clip = fullscreenRect(); + settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR; + + std::vector<renderengine::LayerSettings> layers; + + renderengine::LayerSettings redLayer; + redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR; + redLayer.geometry.boundaries = fullscreenRect().toFloatRect(); + redLayer.geometry.roundedCornersRadius = {5.0f, 20.0f}; + redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect(); + // Red background. + redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f); + redLayer.alpha = 1.0f; + + layers.push_back(redLayer); + + invokeDraw(settings, layers); + + // Due to roundedCornersRadius, the corners are untouched. + expectBufferColor(Point(0, 0), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0); + expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0); + + // Y-axis draws a larger radius, check that its untouched as well + expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0); + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 5), 0, 0, 0, 0); + expectBufferColor(Point(0, 5), 0, 0, 0, 0); + + // middle should be red + expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255); +} + TEST_P(RenderEngineTest, testClear) { initializeRenderEngine(); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 41a8426e42..cc323ad42e 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -48,11 +48,8 @@ void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, s delete[] mSlots; mSlots = new Slot[slotCount]; -} -void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { - // Unfortunately there is no way to read the initial contents of the slots. - // So when we reset the accumulator, we must assume they are all zeroes. + mCurrentSlot = -1; if (mUsingSlotsProtocol) { // Query the driver for the current slot index and use it as the initial slot // before we start reading events from the device. It is possible that the @@ -64,24 +61,22 @@ void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { // This can cause the touch point to "jump", but at least there will be // no stuck touches. int32_t initialSlot; - status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); - if (status) { - ALOGD("Could not retrieve current multitouch slot index. status=%d", status); - initialSlot = -1; + if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); + status == OK) { + mCurrentSlot = initialSlot; + } else { + ALOGD("Could not retrieve current multi-touch slot index. status=%d", status); } - clearSlots(initialSlot); - } else { - clearSlots(-1); } } -void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { +void MultiTouchMotionAccumulator::resetSlots() { if (mSlots) { for (size_t i = 0; i < mSlotCount; i++) { mSlots[i].clear(); } } - mCurrentSlot = initialSlot; + mCurrentSlot = -1; } void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { @@ -169,7 +164,7 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { void MultiTouchMotionAccumulator::finishSync() { if (!mUsingSlotsProtocol) { - clearSlots(-1); + resetSlots(); } } @@ -230,10 +225,12 @@ MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) MultiTouchInputMapper::~MultiTouchInputMapper() {} void MultiTouchInputMapper::reset(nsecs_t when) { - mMultiTouchMotionAccumulator.reset(getDeviceContext()); - - mPointerIdBits.clear(); - + // The evdev multi-touch protocol does not allow userspace applications to query the initial or + // current state of the pointers at any time. This means if we clear our accumulated state when + // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. + // We can only wait for updates to all the pointers and axes. Rather than clearing the state and + // rebuilding the state from scratch, we work around this kernel API limitation by never + // fully clearing any state specific to the multi-touch protocol. TouchInputMapper::reset(when); } diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index b7c3457285..e7d935082a 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -71,7 +71,6 @@ public: ~MultiTouchMotionAccumulator(); void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); - void reset(InputDeviceContext& deviceContext); void process(const RawEvent* rawEvent); void finishSync(); bool hasStylus() const; @@ -86,7 +85,7 @@ private: bool mUsingSlotsProtocol; bool mHaveStylus; - void clearSlots(int32_t initialSlot); + void resetSlots(); void warnIfNotInUse(const RawEvent& event, const Slot& slot); }; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 2ddacef02d..ed3b0eccf0 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1473,6 +1473,10 @@ void TouchInputMapper::process(const RawEvent* rawEvent) { } void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) { + if (mDeviceMode == DeviceMode::DISABLED) { + // Only save the last pending state when the device is disabled. + mRawStatesPending.clear(); + } // Push a new state. mRawStatesPending.emplace_back(); @@ -3522,6 +3526,8 @@ void TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, uint32_ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, bool hovering) { + LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER, + "%s cannot be used when the device is not in POINTER mode.", __func__); int32_t metaState = getContext()->getGlobalMetaState(); if (down || hovering) { @@ -3644,6 +3650,10 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin if (down || hovering) { mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords); mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties); + mPointerSimple.displayId = displayId; + mPointerSimple.source = mSource; + mPointerSimple.lastCursorX = xCursorPosition; + mPointerSimple.lastCursorY = yCursorPosition; } else { mPointerSimple.reset(); } @@ -3653,7 +3663,23 @@ void TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32 mPointerSimple.currentCoords.clear(); mPointerSimple.currentProperties.clear(); - dispatchPointerSimple(when, readTime, policyFlags, false, false); + if (mPointerSimple.down || mPointerSimple.hovering) { + int32_t metaState = getContext()->getGlobalMetaState(); + NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), + mPointerSimple.source, mPointerSimple.displayId, policyFlags, + AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED, + metaState, mLastRawState.buttonState, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 1, &mPointerSimple.lastProperties, + &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision, + mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, + mPointerSimple.downTime, + /* videoFrames */ {}); + getListener().notifyMotion(&args); + if (mPointerController != nullptr) { + mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); + } + } + mPointerSimple.reset(); } void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index c948f565d9..2937bf88d4 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -327,6 +327,8 @@ protected: int32_t rawVScroll; int32_t rawHScroll; + explicit inline RawState() { clear(); } + void copyFrom(const RawState& other) { when = other.when; readTime = other.readTime; @@ -712,6 +714,12 @@ private: // Time the pointer last went down. nsecs_t downTime; + // Values reported for the last pointer event. + uint32_t source; + int32_t displayId; + float lastCursorX; + float lastCursorY; + void reset() { currentCoords.clear(); currentProperties.clear(); @@ -720,6 +728,10 @@ private: down = false; hovering = false; downTime = 0; + source = 0; + displayId = ADISPLAY_ID_NONE; + lastCursorX = 0.f; + lastCursorY = 0.f; } } mPointerSimple; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 3afa52cf2c..03fbf07b16 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -111,6 +111,13 @@ MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") { return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY(); } +MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") { + const auto argToolType = arg.pointerProperties[0].toolType; + *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got " + << motionToolTypeToString(argToolType); + return argToolType == toolType; +} + template<typename T> static inline T min(T a, T b) { return a < b ? a : b; @@ -6787,6 +6794,82 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0)); } +TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + prepareAxes(POSITION | PRESSURE); + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + NotifyMotionArgs motionArgs; + + // Set the initial state for the touch pointer. + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 100); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 200); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_PRESSURE, RAW_PRESSURE_MAX); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + + // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch + // state by reading the current axis values. + mapper.reset(ARBITRARY_TIME); + + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the recreated touch state to generate a down event. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(SingleTouchInputMapperTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) { + std::shared_ptr<FakePointerController> fakePointerController = + std::make_shared<FakePointerController>(); + fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); + fakePointerController->setPosition(100, 200); + fakePointerController->setButtonState(0); + mFakePolicy->setPointerController(fakePointerController); + + addConfigurationProperty("touch.deviceType", "pointer"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareButtons(); + mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); + prepareAxes(POSITION); + SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>(); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); + + // Start a stylus gesture. + processKey(mapper, BTN_TOOL_PEN, 1); + processDown(mapper, 100, 200); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_DOWN), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_MOVE), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus + // gesture should be disabled. + auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); + viewport->isActive = false; + mFakePolicy->updateViewport(*viewport); + configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + // TODO(b/257078296): Pointer mode generates extra event. + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( + AllOf(WithAction(AMOTION_EVENT_ACTION_CANCEL), + WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS), + WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS)))); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + TEST_F(SingleTouchInputMapperTest, Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) { addConfigurationProperty("touch.deviceType", "touchScreen"); @@ -6848,10 +6931,17 @@ TEST_F(SingleTouchInputMapperTest, // No events are generated while the viewport is inactive. processMove(mapper, 101, 201); processSync(mapper); - processDown(mapper, 102, 202); + processUp(mapper); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + // Start a new gesture while the viewport is still inactive. + processDown(mapper, 300, 400); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_X, 300); + mFakeEventHub->setAbsoluteAxisValue(EVENTHUB_ID, ABS_Y, 400); + mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1); + processSync(mapper); + // Make the viewport active again. The device should resume processing events. viewport->isActive = true; mFakePolicy->updateViewport(*viewport); @@ -6861,8 +6951,7 @@ TEST_F(SingleTouchInputMapperTest, ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled()); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); - // Start a new gesture. - processDown(mapper, 100, 200); + // In the next sync, the touch state that was recreated when the device was reset is reported. processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); @@ -9397,6 +9486,80 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) { ASSERT_EQ(uint32_t(1), motionArgs.pointerCount); } +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); + + NotifyMotionArgs motionArgs; + + // First finger down. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + + // Second finger down. + processSlot(mapper, SECOND_SLOT); + processId(mapper, SECOND_TRACKING_ID); + processPosition(mapper, 300, 400); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); + + // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be + // preserved. Resetting should not generate any events. + mapper.reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use + // the existing touch state to generate a down event. + processPosition(mapper, 301, 302); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action); + + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + +TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) { + addConfigurationProperty("touch.deviceType", "touchScreen"); + prepareDisplay(DISPLAY_ORIENTATION_0); + prepareAxes(POSITION | ID | SLOT | PRESSURE); + MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); + + NotifyMotionArgs motionArgs; + + // First finger touches down and releases. + processId(mapper, FIRST_TRACKING_ID); + processPosition(mapper, 100, 200); + processPressure(mapper, RAW_PRESSURE_MAX); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action); + processId(mapper, INVALID_TRACKING_ID); + processSync(mapper); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); + ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action); + + // Reset the mapper. When the mapper is reset, we expect it to restore the latest + // raw state where no pointers are down. + mapper.reset(ARBITRARY_TIME); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); + + // Send an empty sync frame. Since there are no pointers, no events are generated. + processSync(mapper); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled()); +} + // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index df721cdc89..428c19fe02 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -152,6 +152,8 @@ protected: virtual const compositionengine::CompositionEngine& getCompositionEngine() const = 0; virtual void dumpState(std::string& out) const = 0; + bool mustRecompose() const; + private: void dirtyEntireOutput(); compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const; @@ -170,6 +172,9 @@ private: std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache; std::unique_ptr<planner::Planner> mPlanner; std::unique_ptr<HwcAsyncWorker> mHwComposerAsyncWorker; + + // Whether the content must be recomposed this frame. + bool mMustRecompose = false; }; // This template factory function standardizes the implementation details of the diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 1ec6449ed0..163d9a3748 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -426,7 +426,7 @@ void Display::finishFrame(const compositionengine::CompositionRefreshArgs& refre // 1) It is being handled by hardware composer, which may need this to // keep its virtual display state machine in sync, or // 2) There is work to be done (the dirty region isn't empty) - if (GpuVirtualDisplayId::tryCast(mId) && getDirtyRegion().isEmpty()) { + if (GpuVirtualDisplayId::tryCast(mId) && !mustRecompose()) { ALOGV("Skipping display composition"); return; } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index b724daa8ce..d0c5803d42 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -977,17 +977,17 @@ void Output::beginFrame() { // frame, then nothing more until we get new layers. // - When a display is created with a private layer stack, we won't // emit any black frames until a layer is added to the layer stack. - const bool mustRecompose = dirty && !(empty && wasEmpty); + mMustRecompose = dirty && !(empty && wasEmpty); const char flagPrefix[] = {'-', '+'}; static_cast<void>(flagPrefix); - ALOGV_IF("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __FUNCTION__, - mustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty], - flagPrefix[empty], flagPrefix[wasEmpty]); + ALOGV("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __func__, + mMustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty], + flagPrefix[empty], flagPrefix[wasEmpty]); - mRenderSurface->beginFrame(mustRecompose); + mRenderSurface->beginFrame(mMustRecompose); - if (mustRecompose) { + if (mMustRecompose) { outputState.lastCompositionHadVisibleLayers = !empty; } } @@ -1590,5 +1590,9 @@ void Output::finishPrepareFrame() { mRenderSurface->prepareFrame(state.usesClientComposition, state.usesDeviceComposition); } +bool Output::mustRecompose() const { + return mMustRecompose; +} + } // namespace impl } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index 344fea3331..5369642a1b 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -971,16 +971,40 @@ TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) { // We expect no calls to queueBuffer if composition was skipped. EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0); + EXPECT_CALL(*renderSurface, beginFrame(false)); gpuDisplay->editState().isEnabled = true; gpuDisplay->editState().usesClientComposition = false; gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION; + gpuDisplay->editState().lastCompositionHadVisibleLayers = true; + gpuDisplay->beginFrame(); gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer)); } -TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) { +TEST_F(DisplayFinishFrameTest, skipsCompositionIfEmpty) { + auto args = getDisplayCreationArgsForGpuVirtualDisplay(); + std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args); + + mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>(); + gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface)); + + // We expect no calls to queueBuffer if composition was skipped. + EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0); + EXPECT_CALL(*renderSurface, beginFrame(false)); + + gpuDisplay->editState().isEnabled = true; + gpuDisplay->editState().usesClientComposition = false; + gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); + gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1)); + gpuDisplay->editState().lastCompositionHadVisibleLayers = false; + + gpuDisplay->beginFrame(); + gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer)); +} + +TEST_F(DisplayFinishFrameTest, performsCompositionIfDirtyAndNotEmpty) { auto args = getDisplayCreationArgsForGpuVirtualDisplay(); std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args); @@ -989,11 +1013,15 @@ TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) { // We expect a single call to queueBuffer when composition is not skipped. EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1); + EXPECT_CALL(*renderSurface, beginFrame(true)); gpuDisplay->editState().isEnabled = true; gpuDisplay->editState().usesClientComposition = false; gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1)); gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1)); + gpuDisplay->editState().lastCompositionHadVisibleLayers = true; + + gpuDisplay->beginFrame(); gpuDisplay->finishFrame({}, std::move(mResultWithBuffer)); } diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0c4b012fe8..c2b0a112d6 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -406,7 +406,7 @@ void Layer::prepareBasicGeometryCompositionState() { const auto& drawingState{getDrawingState()}; const auto alpha = static_cast<float>(getAlpha()); const bool opaque = isOpaque(drawingState); - const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; + const bool usesRoundedCorners = hasRoundedCorners(); auto blendMode = Hwc2::IComposerClient::BlendMode::NONE; if (!opaque || alpha != 1.0f) { @@ -482,7 +482,7 @@ void Layer::preparePerFrameCompositionState() { compositionState->hasProtectedContent = isProtected(); compositionState->dimmingEnabled = isDimmingEnabled(); - const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f; + const bool usesRoundedCorners = hasRoundedCorners(); compositionState->isOpaque = isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf; @@ -1917,27 +1917,22 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { const auto& parent = mDrawingParent.promote(); if (parent != nullptr) { parentSettings = parent->getRoundedCornerState(); - if (parentSettings.radius > 0) { + if (parentSettings.hasRoundedCorners()) { ui::Transform t = getActiveTransform(getDrawingState()); t = t.inverse(); parentSettings.cropRect = t.transform(parentSettings.cropRect); - // The rounded corners shader only accepts 1 corner radius for performance reasons, - // but a transform matrix can define horizontal and vertical scales. - // Let's take the average between both of them and pass into the shader, practically we - // never do this type of transformation on windows anyway. - auto scaleX = sqrtf(t[0][0] * t[0][0] + t[0][1] * t[0][1]); - auto scaleY = sqrtf(t[1][0] * t[1][0] + t[1][1] * t[1][1]); - parentSettings.radius *= (scaleX + scaleY) / 2.0f; + parentSettings.radius.x *= t.getScaleX(); + parentSettings.radius.y *= t.getScaleY(); } } // Get layer settings Rect layerCropRect = getCroppedBufferSize(getDrawingState()); - const float radius = getDrawingState().cornerRadius; + const vec2 radius(getDrawingState().cornerRadius, getDrawingState().cornerRadius); RoundedCornerState layerSettings(layerCropRect.toFloatRect(), radius); - const bool layerSettingsValid = layerSettings.radius > 0 && layerCropRect.isValid(); + const bool layerSettingsValid = layerSettings.hasRoundedCorners() && layerCropRect.isValid(); - if (layerSettingsValid && parentSettings.radius > 0) { + if (layerSettingsValid && parentSettings.hasRoundedCorners()) { // If the parent and the layer have rounded corner settings, use the parent settings if the // parent crop is entirely inside the layer crop. // This has limitations and cause rendering artifacts. See b/200300845 for correct fix. @@ -1951,7 +1946,7 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const { } } else if (layerSettingsValid) { return layerSettings; - } else if (parentSettings.radius > 0) { + } else if (parentSettings.hasRoundedCorners()) { return parentSettings; } return {}; @@ -2072,7 +2067,8 @@ void Layer::writeToProtoDrawingState(LayerProto* layerInfo) { layerInfo->set_effective_scaling_mode(getEffectiveScalingMode()); layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius); - layerInfo->set_corner_radius(getRoundedCornerState().radius); + layerInfo->set_corner_radius( + (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0); layerInfo->set_background_blur_radius(getBackgroundBlurRadius()); layerInfo->set_is_trusted_overlay(isTrustedOverlay()); LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform()); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index c547da06cb..200baf0ba1 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -134,13 +134,14 @@ public: struct RoundedCornerState { RoundedCornerState() = default; - RoundedCornerState(FloatRect cropRect, float radius) + RoundedCornerState(const FloatRect& cropRect, const vec2& radius) : cropRect(cropRect), radius(radius) {} // Rounded rectangle in local layer coordinate space. FloatRect cropRect = FloatRect(); // Radius of the rounded rectangle. - float radius = 0.0f; + vec2 radius; + bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; } }; using FrameRate = scheduler::LayerInfo::FrameRate; @@ -596,7 +597,7 @@ public: // corner crop does not intersect with its own rounded corner crop. virtual RoundedCornerState getRoundedCornerState() const; - bool hasRoundedCorners() const override { return getRoundedCornerState().radius > .0f; } + bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); } virtual PixelFormat getPixelFormat() const { return PIXEL_FORMAT_NONE; } /** diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index bbfedc7685..fbc532eb77 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -664,7 +664,8 @@ struct BaseLayerProperties { EXPECT_EQ(false, layer.source.buffer.isY410BT2020); EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha); EXPECT_EQ(false, layer.source.buffer.isOpaque); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; @@ -714,7 +715,8 @@ struct BaseLayerProperties { EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1], LayerProperties::COLOR[2]), layer.source.solidColor); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha); return resultFuture; @@ -792,7 +794,8 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties> const renderengine::LayerSettings layer = layerSettings.back(); EXPECT_THAT(layer.source.buffer.buffer, IsNull()); EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor); - EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x); + EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y); EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace); EXPECT_EQ(1.0f, layer.alpha); return resultFuture; |