diff options
author | 2022-02-28 21:35:57 +0800 | |
---|---|---|
committer | 2023-11-07 06:16:21 +0000 | |
commit | fdcf72cb9b13cd6410e57a52d7cc305cc0de4167 (patch) | |
tree | 7f93710fedfc1889a47ce6e3218e0de1cfb2119d /libs | |
parent | 731829eccff2890aa1157eae708a70904254c088 (diff) |
Add setBackdropRenderEffect for View and RenderNode.
support visual effects for backdrop contents of a View or RenderNode.
Test: added unit test & hwui_unit passes
Test: added BackdropBlurActivity in HwAccelerationTest, build & run it
Signed-off-by: Dongya Jiang <jiangdongya@coolpad.com>
Change-Id: If1ac1b8aee53667f175e8fa80ecfc7bdfa28173d
Merged-In: If1ac1b8aee53667f175e8fa80ecfc7bdfa28173d
Diffstat (limited to 'libs')
-rw-r--r-- | libs/hwui/Android.bp | 1 | ||||
-rw-r--r-- | libs/hwui/RenderNode.cpp | 6 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.h | 5 | ||||
-rw-r--r-- | libs/hwui/jni/android_graphics_RenderNode.cpp | 9 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp | 85 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/BackdropFilterDrawable.h | 67 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/RenderNodeDrawable.cpp | 9 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/RenderNodeDrawable.h | 7 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp | 9 | ||||
-rw-r--r-- | libs/hwui/tests/unit/RenderNodeDrawableTests.cpp | 79 |
11 files changed, 278 insertions, 6 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index db581471e2ca..b5e6f94af022 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -514,6 +514,7 @@ cc_defaults { "canvas/CanvasOpRasterizer.cpp", "effects/StretchEffect.cpp", "effects/GainmapRenderer.cpp", + "pipeline/skia/BackdropFilterDrawable.cpp", "pipeline/skia/HolePunch.cpp", "pipeline/skia/SkiaDisplayList.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 1c39db3a31bb..1dd22cf43c5c 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -260,6 +260,12 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu pushStagingDisplayListChanges(observer, info); } + // always damageSelf when filtering backdrop content, or else the BackdropFilterDrawable will + // get a wrong snapshot of previous content. + if (mProperties.layerProperties().getBackdropImageFilter()) { + damageSelf(info); + } + if (mDisplayList) { info.out.hasFunctors |= mDisplayList.hasFunctor(); mHasHolePunches = mDisplayList.hasHolePunches(); diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index 0589f136b666..c5371236b9cf 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -55,6 +55,12 @@ bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) { return true; } +bool LayerProperties::setBackdropImageFilter(SkImageFilter* imageFilter) { + if (mBackdropImageFilter.get() == imageFilter) return false; + mBackdropImageFilter = sk_ref_sp(imageFilter); + return true; +} + bool LayerProperties::setFromPaint(const SkPaint* paint) { bool changed = false; changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint))); @@ -70,6 +76,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) { setXferMode(other.xferMode()); setColorFilter(other.getColorFilter()); setImageFilter(other.getImageFilter()); + setBackdropImageFilter(other.getBackdropImageFilter()); mStretchEffect = other.mStretchEffect; return *this; } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 064ba7aee107..e358b57f6fe1 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -97,8 +97,12 @@ public: bool setImageFilter(SkImageFilter* imageFilter); + bool setBackdropImageFilter(SkImageFilter* imageFilter); + SkImageFilter* getImageFilter() const { return mImageFilter.get(); } + SkImageFilter* getBackdropImageFilter() const { return mBackdropImageFilter.get(); } + const StretchEffect& getStretchEffect() const { return mStretchEffect; } StretchEffect& mutableStretchEffect() { return mStretchEffect; } @@ -129,6 +133,7 @@ private: SkBlendMode mMode; sk_sp<SkColorFilter> mColorFilter; sk_sp<SkImageFilter> mImageFilter; + sk_sp<SkImageFilter> mBackdropImageFilter; StretchEffect mStretchEffect; }; diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 8c7b9a4b5e94..2a218a25913d 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -243,6 +243,13 @@ static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMM return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC); } +static jboolean android_view_RenderNode_setBackdropRenderEffect( + CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, jlong renderEffectPtr) { + SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr); + return SET_AND_DIRTY(mutateLayerProperties().setBackdropImageFilter, imageFilter, + RenderNode::GENERIC); +} + static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, bool hasOverlappingRendering) { return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, @@ -792,6 +799,8 @@ static const JNINativeMethod gMethods[] = { {"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha}, {"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect}, + {"nSetBackdropRenderEffect", "(JJ)Z", + (void*)android_view_RenderNode_setBackdropRenderEffect}, {"nSetHasOverlappingRendering", "(JZ)Z", (void*)android_view_RenderNode_setHasOverlappingRendering}, {"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint}, diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp new file mode 100644 index 000000000000..ffad69993fd8 --- /dev/null +++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include "BackdropFilterDrawable.h" + +#include <SkImage.h> +#include <SkSurface.h> + +#include "RenderNode.h" +#include "RenderNodeDrawable.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +BackdropFilterDrawable::~BackdropFilterDrawable() {} + +bool BackdropFilterDrawable::prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, + int backdropImageWidth, int backdropImageHeight) { + // the drawing bounds for blurred content. + mDstBounds.setWH(properties.getWidth(), properties.getHeight()); + + float alphaMultiplier = 1.0f; + RenderNodeDrawable::setViewProperties(properties, canvas, &alphaMultiplier, true); + + // get proper subset for previous content. + canvas->getTotalMatrix().mapRect(&mImageSubset, mDstBounds); + SkRect imageSubset(mImageSubset); + // ensure the subset is inside bounds of previous content. + if (!mImageSubset.intersect(SkRect::MakeWH(backdropImageWidth, backdropImageHeight))) { + return false; + } + + // correct the drawing bounds if subset was changed. + if (mImageSubset != imageSubset) { + SkMatrix inverse; + if (canvas->getTotalMatrix().invert(&inverse)) { + inverse.mapRect(&mDstBounds, mImageSubset); + } + } + + // follow the alpha from the target RenderNode. + mPaint.setAlpha(properties.layerProperties().alpha() * alphaMultiplier); + return true; +} + +void BackdropFilterDrawable::onDraw(SkCanvas* canvas) { + const RenderProperties& properties = mTargetRenderNode->properties(); + auto* backdropFilter = properties.layerProperties().getBackdropImageFilter(); + auto* surface = canvas->getSurface(); + if (!backdropFilter || !surface) { + return; + } + + auto backdropImage = surface->makeImageSnapshot(); + // sync necessary properties from target RenderNode. + if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) { + return; + } + + auto imageSubset = mImageSubset.roundOut(); + backdropImage = + backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset, + imageSubset, &mOutSubset, &mOutOffset); + canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds, + SkSamplingOptions(SkFilterMode::kLinear), &mPaint, + SkCanvas::kStrict_SrcRectConstraint); +} + +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.h b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h new file mode 100644 index 000000000000..9e35837675ae --- /dev/null +++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#pragma once + +#include <SkCanvas.h> +#include <SkDrawable.h> +#include <SkPaint.h> + +namespace android { +namespace uirenderer { + +class RenderNode; +class RenderProperties; + +namespace skiapipeline { + +/** + * This drawable captures it's backdrop content and render it with a + * image filter. + */ +class BackdropFilterDrawable : public SkDrawable { +public: + BackdropFilterDrawable(RenderNode* renderNode, SkCanvas* canvas) + : mTargetRenderNode(renderNode), mBounds(canvas->getLocalClipBounds()) {} + + ~BackdropFilterDrawable(); + +private: + RenderNode* mTargetRenderNode; + SkPaint mPaint; + + SkRect mDstBounds; + SkRect mImageSubset; + SkIRect mOutSubset; + SkIPoint mOutOffset; + + /** + * Check all necessary properties before actual drawing. + * Return true if ready to draw. + */ + bool prepareToDraw(SkCanvas* canvas, const RenderProperties& properties, int backdropImageWidth, + int backdropImageHeight); + +protected: + void onDraw(SkCanvas* canvas) override; + + virtual SkRect onGetBounds() override { return mBounds; } + const SkRect mBounds; +}; + +} // namespace skiapipeline +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index da4f66d45a70..9d72c2315198 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -362,7 +362,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { } void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, SkCanvas* canvas, - float* alphaMultiplier) { + float* alphaMultiplier, bool ignoreLayer) { if (properties.getLeft() != 0 || properties.getTop() != 0) { canvas->translate(properties.getLeft(), properties.getTop()); } @@ -378,7 +378,8 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S canvas->concat(*properties.getTransformMatrix()); } } - if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) { + if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale && + !ignoreLayer) { const StretchEffect& stretch = properties.layerProperties().getStretchEffect(); if (!stretch.isEmpty()) { canvas->concat( @@ -388,10 +389,10 @@ void RenderNodeDrawable::setViewProperties(const RenderProperties& properties, S const bool isLayer = properties.effectiveLayerType() != LayerType::None; int clipFlags = properties.getClippingFlags(); if (properties.getAlpha() < 1) { - if (isLayer) { + if (isLayer && !ignoreLayer) { clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer } - if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) { + if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering()) || ignoreLayer) { *alphaMultiplier = properties.getAlpha(); } else { // savelayer needed to create an offscreen buffer diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index c7582e734009..818ac45bf346 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -120,7 +120,7 @@ private: * Applies the rendering properties of a view onto a SkCanvas. */ static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas, - float* alphaMultiplier); + float* alphaMultiplier, bool ignoreLayer = false); /** * Stores transform on the canvas at time of recording and is used for @@ -149,6 +149,11 @@ private: * display list that is searched for any render nodes with getProjectBackwards==true */ SkiaDisplayList* mProjectedDisplayList = nullptr; + + /** + * Allow BackdropFilterDrawable to apply same render properties onto SkCanvas. + */ + friend class BackdropFilterDrawable; }; } // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 3ca7eeb37a89..58c14c1fabbd 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -37,6 +37,7 @@ #include "NinePatchUtils.h" #include "RenderNode.h" #include "pipeline/skia/AnimatedDrawables.h" +#include "pipeline/skia/BackdropFilterDrawable.h" #ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc. #include "pipeline/skia/GLFunctorDrawable.h" #include "pipeline/skia/VkFunctorDrawable.h" @@ -168,6 +169,14 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { // Put Vulkan WebViews with non-rectangular clips in a HW layer renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex()); } + + // draw backdrop filter drawable if needed. + if (renderNode->stagingProperties().layerProperties().getBackdropImageFilter()) { + auto* backdropFilterDrawable = + mDisplayList->allocateDrawable<BackdropFilterDrawable>(renderNode, asSkCanvas()); + drawDrawable(backdropFilterDrawable); + } + drawDrawable(&renderNodeDrawable); // use staging property, since recording on UI thread diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 596bd37e4cf5..f67042b21f8f 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -20,14 +20,17 @@ #include <SkBlendMode.h> #include <SkClipStack.h> #include <SkSurface_Base.h> +#include <include/effects/SkImageFilters.h> #include <string.h> + #include "AnimationContext.h" #include "DamageAccumulator.h" #include "FatalTestCanvas.h" #include "IContextFactory.h" -#include "hwui/Paint.h" #include "RecordingCanvas.h" #include "SkiaCanvas.h" +#include "hwui/Paint.h" +#include "pipeline/skia/BackdropFilterDrawable.h" #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaPipeline.h" @@ -1210,3 +1213,77 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) { canvas.drawDrawable(&drawable); EXPECT_EQ(2, canvas.mDrawCounter); } + +// Verify drawing logics for BackdropFilterDrawable +RENDERTHREAD_TEST(BackdropFilterDrawable, drawing) { + static const int CANVAS_WIDTH = 100; + static const int CANVAS_HEIGHT = 200; + class SimpleTestCanvas : public TestCanvasBase { + public: + SkRect mDstBounds; + SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + // did nothing. + } + + // called when BackdropFilterDrawable is drawn. + void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst, + const SkSamplingOptions&, const SkPaint*, + SrcRectConstraint) override { + mDrawCounter++; + mDstBounds = dst; + } + }; + class SimpleLayer : public SkSurface_Base { + public: + SimpleLayer() + : SkSurface_Base(SkImageInfo::MakeN32Premul(CANVAS_WIDTH, CANVAS_HEIGHT), nullptr) { + } + virtual sk_sp<SkImage> onNewImageSnapshot(const SkIRect* bounds) override { + SkBitmap bitmap; + bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT); + bitmap.setImmutable(); + return SkImage::MakeFromBitmap(bitmap); + } + SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); } + sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; } + bool onCopyOnWrite(ContentChangeMode) override { return true; } + void onWritePixels(const SkPixmap&, int x, int y) {} + }; + + auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + [](RenderProperties& props, SkiaRecordingCanvas& canvas) { + canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, + Paint()); + }); + + sk_sp<SkSurface> surface(new SimpleLayer()); + auto* canvas = reinterpret_cast<SimpleTestCanvas*>(surface->getCanvas()); + RenderNodeDrawable drawable(node.get(), canvas, true); + BackdropFilterDrawable backdropDrawable(node.get(), canvas); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // no backdrop filter, skip drawing. + EXPECT_EQ(0, canvas->mDrawCounter); + + sk_sp<SkImageFilter> filter(SkImageFilters::Blur(3, 3, nullptr)); + node->animatorProperties().mutateLayerProperties().setBackdropImageFilter(filter.get()); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // backdrop filter is set, ok to draw. + EXPECT_EQ(1, canvas->mDrawCounter); + EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), canvas->mDstBounds); + + canvas->translate(30, 30); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // the drawable is still visible, ok to draw. + EXPECT_EQ(2, canvas->mDrawCounter); + EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH - 30, CANVAS_HEIGHT - 30), canvas->mDstBounds); + + canvas->translate(CANVAS_WIDTH, CANVAS_HEIGHT); + canvas->drawDrawable(&drawable); + canvas->drawDrawable(&backdropDrawable); + // the drawable is invisible, skip drawing. + EXPECT_EQ(2, canvas->mDrawCounter); +} |